duet · open source · MIT

Two AI models build it.
The harness checks the rules.

A single agent grades its own homework and reports that it passed. duet puts a second model in the loop, runs your tests itself, and holds each workflow to a rule it checks against what actually happened — not what either model says happened.

curl -fsSL https://raw.githubusercontent.com/shubharya-os/duet/main/install.sh | sh

Or inside Claude Code, as a plugin: /plugin marketplace add shubharya-os/duet then /plugin install duet@duet.

Uses the Claude and ChatGPT plans you already have — no API keys. One subscription works too. GitHub →

Nine commands, each with a checked rule

Each one tells the pair its rule — and then checks it.

duet build "…"

Start from nothing. The gate is red before any code exists, and a gate already green on an empty directory is refused.

duet fix "…"

The tests are replayed against a snapshot of the original code. They must fail there and pass now.

duet add "…"

A test must fail against the code as it was before. A whitespace edit to a test file does not count.

duet refactor "…"

Every test that existed at the start must end byte-for-byte unchanged. Refused if the suite starts red.

duet plan "…"

Both argue it out into PLAN.md. Any other file changing clears both sign-offs.

duet review

A second model reads your diff, held read-only by its backend — not asked politely.

duet make "…" --like URL

Research the references from public sources, build, then score parity in the harness. It will not finish while the reference's name, domain or colours are still in the product.

duet jury

Simulated users, a paying client, an investor, judges and a skeptic. Every objection must quote the product, and the harness checks the quote is really there.

duet page check

A website's gate: renders the page in Chrome at desktop and phone widths and fails on what is wrong on screen.

An app from one sentence, then attacked

An expense-tracker web app — single-page UI, JSON API, SQLite, CSV export — from an empty directory.

34m 57sto a double sign-off
2,963lines across 22 files
51acceptance criteria, agreed first
10test files, on Python 3.9 and 3.12

Then attacked by hand — over HTTP, and in a real browser — not through its own tests:

attackresultdetail
SQL injection in a categorystored as textx'); DROP TABLE expenses;-- — table intact
stored XSS, loaded in a browserrendered as text0 injected elements; no external resources loaded
CSV formula injectiondefused=cmd|… exported as '=cmd|… — added unprompted
0.1 + 0.2exactly 0.30money kept as integer cents
30 simultaneous writes, then restartall persisted36 of 36 rows
10 kinds of malformed requestright code each400, 404, 411, 413, 415
bind address127.0.0.1stated in the spec as the first default

Why the tests are replayed

The first version of fix asked only whether the gate had ever gone red. The first live run beat it.

claude-sonnet-1 DONE  state 8e75764a        test and fix written in one turn
claude-sonnet-2 DONE  state 8e75764a
✗ both signed off, but the `fix` rule is not met
claude-sonnet-1 CONTINUE  state 0b73182d, gate FAILING   re-broke working code on purpose
...
BOTH AGENTS SIGNED OFF on 8e75764a              byte-identical to the rejected state

The rule was met by ceremony. So now the harness copies a snapshot of the original code, lays the finished tests over it, and runs them there.

history rulereplay
rounds63
vetoes of a correct fix10
turns spent re-breaking code10

It becomes the default

duet skill default — then the same bug report, word for word, in a fresh Claude Code session.

beforeafter
used duetnostarted duet fix on its own
tests for the bugnone added8 added; 6 fail on the original with the reported error
time~1 min6 min

Questions, exploration and one-line edits stay direct. Always-on cost: about 545 tokens of instructions per session.

Coming from ECC?

Both install with one command. The difference is what you carry afterwards, and what gets checked.

ECC 2.2.2, full installduet
to learn386 skills, 68 agents, hooks, profiles6 commands
every session~41,500 tokens (smaller profiles exist)~545 tokens
"test first"an instructionchecked by replay
reviewersame model, another rolea second model
domain skills~300 includeduse any you install
duet from-ecc                    # what you actually used, from your own logs
duet from-ecc --keep <name>      # keep it, licence intact
claude plugin uninstall ecc@ecc  # you run this
duet skill default

On the machine it was built on: two ECC agents in use, once each, out of 454 skills and agents.

Run against ECC

Same brief, same model, ECC installed with its own agents. One build, two plans, graded blind.

duetECC
build an app from one specfinished, 35 minunfinished at 82 min
security & validation attacksall heldall held
100 simultaneous writes, x3300/300218/300
planning pitfalls caught (blind)4/5 · 5/54/5 · 5/5
planning time, full debate (before v0.11)8–10 min3–4 min
planning, full debate, v0.11, side by side5/5, 3 min 43 s5/5, 2 min 56 s
planning, --quick, side by side4/5, 5 min 59 s4/5, 5 min 23 s

ECC won planning speed; the rest is in docs/QA.md, caveats included.

Where it runs

harness/duetdefault for real changes
Claude Code✓~/.claude/CLAUDE.md
Codex$duet~/.codex/AGENTS.md
Gemini CLI · Google Antigravity✓~/.gemini/GEMINI.md (shared)
OpenCode✓its AGENTS.md, or your CLAUDE.md
Cursor · any AGENTS.md reader—duet skill default --project

What it cannot do

Two models can agree and still be wrong — one session ended in consensus on a CLI that crashed on Python 3.9, because the gate ran on 3.12 and the criteria never named a version. The gate only proves what it runs. fix cannot tell that a failure on the original code is this bug, and says so when the only failure is an import. Every one of these is written down, with the session that found it, in docs/QA.md.