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:
- spec change — "if the spec ever mentions rate limiting, revisit this"
- code change — "if more than 3 places end up calling
withRetryinline, the middleware approach we rejected was actually right" - metric — "if this variant's performance score drops below 0.75, the simplicity tradeoff cost more than we thought"
- time — "re-check at every major version, or every 90 days anyway"
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:
| Status | Meaning | Set by |
|---|---|---|
active | Recorded; provisional resolution applied; waiting for triggers | flow-chavruta |
reactivated | A condition matched; needs attention | flow-dissent-monitor |
acknowledged | Reviewed; the tradeoff still holds; no code change | flow-dissent |
mitigated | Code changed to address it; triggers shouldn't match anymore | flow-dissent |
resolved | Formally retired — a spec change made it moot, or it was wrong | flow-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 #
- Append-only. You never edit a past position. If the disagreement evolves, you resolve the old dissent and raise a new one that cites it. The history is part of the value.
- Self-surfacing. Nobody has to remember to go back and re-read anything — the relevant past argument comes find you when conditions change.
- Disagreement is preserved, not resolved. A reactivated dissent isn't a veto. It's a flag that says "the assumption behind this old decision may no longer hold — take a fresh look."
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 assessments | Both reviewers produce positions |
| Scrum Master synthesizes one recommendation | Provisional 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 retro | Reactivation is automatic |
| Memory mostly dies at sprint close | Memory 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.