flow · preserved dissent (P6)

The chavruta model

Two reviewers argue opposite sides, and the disagreement is preserved as a first-class artifact that resurfaces itself when the world changes — instead of being resolved and forgotten.

Where the name comes from #

Chavruta is the traditional Jewish method of studying texts in pairs — two people who deliberately argue opposite sides to sharpen understanding. The point isn't to win; it's that the friction surfaces things neither person would see alone. The Talmud goes a step further: it writes down the losing arguments alongside the winning ruling, because an argument that loses today might be the correct one under different circumstances tomorrow.

flow borrows both ideas — the adversarial pairing, and the preservation of the minority position.

The problem it solves #

Normal code review drives toward agreement. Two reviewers debate, one concedes, the discussion gets resolved and forgotten. The chavruta model's claim is that this throws away information. When two competent reviewers genuinely disagree, the disagreement itself is a signal about a hard tradeoff in the problem — not noise to be cleaned up.

Human teams can't really hold onto that. The reasoning lives in a Slack thread or a sprint retro, and six months later nobody remembers it. An agent system, though, can record it precisely and watch for the moment it becomes relevant again.

How it works in flow #

1. A pair reviews, with opposite biases baked in

At a key checkpoint — when variants are converging, when a big spec change lands, or when something's about to ship — two reviewer agents (flow-chavruta-pair) look at the work. One is wired to argue for stability ("don't break things, this is risky"). The other argues for velocity ("ship it, the caution is overblown"). They aren't neutral; the opposing stance is the whole design.

2. They exit on disagreement, not consensus

This is the unusual part. The review doesn't end when they agree — it ends when they've clearly documented where they disagree and why. Both positions get written down as if each were correct, plus a note on which one provisionally won this round.

3. The disagreement becomes a stored object — a "dissent"

Each dissent is a small record: the minority position, the majority position, who won for now, and — critically — reactivation conditions: the specific future circumstances under which the losing side would become right.

flowchart LR
    R["flow-chavruta-pair
stability vs velocity"] --> RA["raised"] RA --> AC["active
provisional resolution +
reactivation conditions"] AC -->|"monitor matches
a trigger"| RE["reactivated"] RE --> ACK["acknowledged
tradeoff still valid"] RE --> MIT["mitigated
code changed"] RE --> RES["resolved
moot or was wrong"] classDef start fill:#f3e8fd,stroke:#a142f4,color:#1a1a1a; classDef live fill:#e6f4ea,stroke:#34a853,color:#1a1a1a; classDef alert fill:#fce8e6,stroke:#ea4335,color:#1a1a1a; classDef done fill:#fff4e5,stroke:#f59e0b,color:#1a1a1a; class R start; class RA,AC live; class RE alert; class ACK,MIT,RES done;

The dissent lifecycle. Status is monotonic — it only moves forward; a resolved dissent that matters again becomes a new dissent citing the old one.

4. The conditions are machine-checkable

Instead of "we'll remember this," a dissent states its triggers concretely:

5. A monitor watches, and the dissent surfaces itself

A separate watcher (flow-dissent-monitor) checks those conditions automatically — on every spec bump, every commit, every eval run. When one matches, the dissent flips to reactivated and resurfaces for a decision. A human or the orchestrator then either acknowledges it (tradeoff still fine), mitigates it (changes the code), or resolves it (it's genuinely moot now).

The dissent record #

Dissents live in an append-only registry (dissents-active.yaml) scoped to the effort. Each carries both positions, the provisional resolution, its reactivation conditions, and a running history. The lifecycle states:

StatusMeaningSet by
activeRecorded; provisional resolution applied; waiting for triggersflow-chavruta
reactivatedA condition matched; needs attentionflow-dissent-monitor
acknowledgedReviewed; the tradeoff still holds; no code changeflow-dissent
mitigatedCode changed to address it; triggers shouldn't match anymoreflow-dissent
resolvedFormally retired — a spec change made it moot, or it was wrongflow-dissent

Only flow-chavruta-pair (and, for methodology concerns, flow-evaluator) may raise dissents. Generators never do — they implement, they don't second-guess; an ambiguous spec gets a HITL flag, not a dissent. Dissents are about decisions, not specifications.

Key properties #

Guardrails: the pair has a quota (default 2 dissents per checkpoint, the most material ones) to prevent dissent inflation, and an acknowledged-noisy status suppresses dissents whose triggers fire too often (>5 times) without resolution.

Why it's there #

It's flow's mechanism for institutional memory. The contrast with delivery-team is sharp:

delivery-team (Conservative + Aggressive PM)flow (chavruta-pair + dissent)
Both PMs produce assessmentsBoth reviewers produce positions
Scrum Master synthesizes one recommendationProvisional resolution recorded with reactivation conditions
Divergence archived in sprint memory (narrative)Dissent is an append-only, queryable registry (structured)
Reactivation needs a human to re-read the retroReactivation is automatic
Memory mostly dies at sprint closeMemory persists across generations

The chavruta pattern is the feature most likely to compound an advantage over time — but its value is invisible until the first reactivation actually fires. That delayed payoff is exactly why it has to be automated: a human team would never sustain the bookkeeping long enough to collect.