द्वन्द्व · dvandva — a compound of two equal, opposing members

Dvandva is retired and archived

Dvandva was a valuable learning experiment in how governed agent loops, subagent delegation, independent review, stateful handoffs, and two-agent coordination work in practice.

Agent harnesses and models have evolved substantially. Task state, subagent dispatch, review loops, verification gates, and cross-agent coordination are increasingly native capabilities that work with much less ceremony; continuing Dvandva would add complexity, execution time, token usage, and cost without enough practical benefit.

This page is a preserved technical record of the final implementation. The repository remains available as a historical research and learning artifact, but Dvandva is no longer maintained, supported, or recommended for new work.

retired and archived final crate release: 3.4.2 historical research and learning artifact no longer maintained, supported, or recommended
↓ the preserved technical record, with citations

why it existed

Different models had different blind spots. Dvandva explored how to expose them.

Decorrelated review

The reviewer is never the same role that produced the work — the engine locks two role identities, vadi and prativadi. Running those roles on different model vendors is a recommended decorrelation policy that reduces correlated blind spots, not a structural guarantee the engine enforces.

Proof, not prose

A run cannot end because an agent says it's done. Termination demands both role-bound approvals, artifacts that exist on disk, and a verification matrix fresher than the last code change.

Bounded arguments

Every review loop is capped — a cycle can't spin forever. When the pair keeps disagreeing past the cap, that repeated edge drops; what's left is either escalation to you or the run's legal next step forward.

the loop

One baton, one writer — thesis and antithesis, emergent

Thesis and antithesis are not documents anywhere in the schema. They're emergent: positions that accrue on the baton itself — questions, findings, counters, artifacts — checkpoint by checkpoint, as vadi proposes and prativadi presses back. Whoever the baton assigns acts; the other blocks on dvandva wait. A turn ends by proposing the entire next baton, and the engine validates it against the declared workflow before installing it. Wrong owner, stale checkpoint, missing evidence, dropped peer work, illegal edge: rejected with a machine-readable reason.

resolve · preflightfind the run, sanity-check brieffresh-context pack do the workTDD, subagents, review nextscaffold a legal candidate writegates → atomic install waitblock until your turn / pause the other role wakes with the checkpointed baton — same loop, opposite color, one position sharper than before reject = exit 23/24 + reason · hook guards the installed baton, not the candidate
Every full circuit checkpoints the baton — that's the mechanism the dialectic rides on: nothing resets, everything accumulates.

the proof

Receipts first, then the mechanism that earns them

Every row below is something the engine actually enforces at dvandva write — not a design intention. Citations are file:line in rust/dvandva/src/ on the current tree.

The headline receipt

dvandva 3.2.0 & 3.3.0 — both published on crates.io 206 / 400 commits carry a Dvandva-Checkpoint: trailer pinned @ main@36f134c, 2026-07-11
git log 36f134c --format=%B | grep -c '^Dvandva-Checkpoint:'   # → 206, as of 36f134c (2026-07-11)
git rev-list --count 36f134c                                     # → 400, as of 36f134c (2026-07-11)

The ratio grows with every merge, so the claim is stated as-of a fixed commit plus the exact command that reproduces it — a reader re-running this today gets a higher count, and that's expected, not a contradiction.

What the engine enforces

GuaranteeEnforcementCitation
Atomic install, monotonic checkpointsEvery candidate is validated, then installed with a single atomic rename — a corrupt or partial install can't happen mid-write. Checkpoints only ever advance by exactly +1; a candidate that repeats or skips one is rejected (exit 27, stale_checkpoint) before the transition table is even consulted. A post-install snapshot failure surfaces its own distinct exit code, never silently swallowed.write.rs:2681-2685, 2702-2707, 1992-2005
Role-bound final approvalsFinal approvals check DVANDVA_ROLE — a role can raise only its own final-approval bit, and only while entering termination_review.write.rs:1799-1829
Role-bound commitsThe pre-commit hook delegates to the commit gate, which blocks a commit unless the committing role is the baton's assignee or listed in active_roles.commit_gate.rs:125-137, hooks.rs:100-121
A capped loop bounds one edge — it doesn't force every disagreement to youEach loop edge is counted; at the cap, the engine removes that repeated edge from the legal-transitions set. Human escalation remains legal, but the engine only bounds the named edge — it doesn't mandate escalation as the sole exit.write.rs:4303-4327
Custom workflow graphs are checked at approval; presets are pre-clearedA hand-authored graph runs through five invariant checks before it's approved — one that lets done be reached without crossing a review gate FAILS validation outright (ReviewGateBypass). Built-in presets are pre-validated once, so that same runtime check is a no-op for them — both families satisfy the same contract, by different means.write.rs:4622-4629, workflow/invariants.rs (ReviewGateBypass)
Dual-approval done, every modedone is legal only from termination_review, and only once both vadi_final_approval and prativadi_final_approval are true — checked independently of mode or profile.write.rs:2079-2083
No coordination daemonNo long-lived controller owns the loop. Every subcommand is a synchronous, one-shot call; the optional watchdog scan never blocks — "a monitor, not a gate." The baton plus a fencing lock is the sole coordination primitive.main.rs:74-118, watchdog.rs:1-12, lock.rs:1-9
Two role identities are engine-locked; vendor pairing is policy, not a lockdone requires two ROLE identities' independent approvals — engine-enforced. Running those two roles on two different model vendors is the recommended decorrelation policy, not something the engine checks: which engine wrote a checkpoint is recorded as trace data only, never compared for gating.composes write.rs:1799-1829 + 2079-2083 + 2854

Advisory-only — read the edges precisely

  • Single-writer is not a blanket "the peer can't write on your turn" rule. Only outcome-deciding fields — approvals, review ownership, workflow sign-off — plus commits are role-locked. Most fields on a candidate baton are writable by whoever holds the lock.
  • disagreement_cap is a self-declared ceiling, read straight off the candidate's own field — not an immutability-guarded constant like total_phases.

State this precision as a feature, not a hedge — a technical reader trusts a pitch more, not less, for naming its own edges.

On the numbers

A naive git log --grep='Dvandva-Checkpoint' overcounts the receipt above by one (207) — it also matches a commit that mentions the phrase in prose but carries no real trailer line. The line-anchored grep -c '^Dvandva-Checkpoint:' command above avoids that trap.

This page's own research went through several rounds of adversarial review before acceptance — a live instance of the same loop this page describes, surfacing and closing real findings. The exact round-by-round counts live only in gitignored working files and aren't independently reader-verifiable, so they're kept qualitative here rather than cited as a number.

mechanics, for the curious

How the default routes actually run — regenerated from source

Most runs ride one of two declarable presets and never think about the rest. The graph a run lives under is itself a peer-reviewed, invariant-checked artifact — a custom, unapproved workflow must pass through a workflow_declaring → workflow_review loop before it can reach spec drafting; a built-in preset is pre-approved and enters spec drafting directly. Every path, regardless of mode or profile, first clears a mandatory clarifying-questions floor (at least five questions combined, both roles represented) — there is no bypass edge.

DEFAULT COMPACT ROUTES (clarifying-questions floor precedes both, off-diagram) STANDARD research ⟲ vadi ⇄ prativadi spec ⟲ review vadi drafts LOCK implementing phase_review ⟲ ⟲ phase_fixing, counted & capped termination_review team-owned done review_of_review ⇄ counter_review — rare, capped safety valve; absent from FAST entirely FAST research ⟲ implementing no spec ceremony, no safety valve — straight to compact review/fix/done (same shape as STANDARD from here)
Both routes converge on the identical review/fix/terminal tail; only the plan-and-lock stage differs.

FAST skips spec planning entirely and enters compact implementation directly from an accepted research package; STANDARD keeps the locked-plan stage. Both converge on the identical phase_review ⟲ phase_fixing → termination_review → done tail. Three more presets exist beyond these two — FULL (both roles implement concurrently and cross-review each other) and dedicated RESEARCH/REVIEW-mode graphs — five total, plus custom, peer-declared graphs; all five are validated against the same five invariants sketched above.

Full transition tables, every edge, every trigger: plugins/dvandva/references/state-transition-table.md ↗

delta re-verification

Re-laps reuse narrow test evidence, never final confidence

The first full-profile pass executes every gate. After phase_fixing or cross_fixing, a mechanical test_creation track may carry only when its declared covers_chunks and carry_reason resolve to the same engine-derived tracked-file closure, its same-id origin lies on the current phase-cycle ancestry, and git-covers-diff-v1 finds no covered change. Any ambiguity, drift, untracked file, symlink, global scope, or unbounded input fails closed and creates a new test track id for rerun.

fixing re-lapchanged closure known test_creation gateunchanged mechanical track: carrychanged / ambiguous / unbounded: rerunsame id only for accepted carry review depthcross + deep + risk anglesnever carryalways execute this cycle terminal done gateevery matrix row freshfull review completecorrectness boundary unchanged
Carry removes only demonstrably redundant mechanical test work; terminal evidence remains complete, current-cycle, and independent.

Carry is a best-effort optimization, not a cryptographic or terminal guarantee: every verification_matrix row still reruns. Legacy batons without carry fields follow the original full-rerun behavior.

archive status

Preserved for learning, closed to new work

The repository, final crate release 3.4.2, and this explainer preserve the protocol's implementation and lessons as historical research. No further protocol or product development is planned.

Installation and onboarding commands have been removed from this page because Dvandva is no longer maintained, supported, or recommended for new work. The technical sections above remain in their original explanatory voice as a record of how the final system worked.

DVANDVA · RETIRED AND ARCHIVED · historical technical record · final crate release 3.4.2 · 2026-07-14