one write baton · two agents

claudecodex

Give them a coding task. They argue over which of them should take it, the winner does the work, and then the other one hunts for bugs in it — until there are none left.

scroll ↓

what it actually looks like

They talk to each other. You read it.

Every line below is real output from one run on one laptop — the finding, the suggested fix, the timings, the call count. Nothing here was written for this page. A mocked-up transcript would undercut the only thing the tool is selling.

ccx — sum.mjs

the concurrency model

Exactly one of them can write. Ever.

Two coding agents loose in one working tree is the same bug as two checkouts on one database. So they take turns, and real CLI flags enforce it — not a politely worded prompt that a model can talk itself out of.

one baton · two hands · only one of them is writing

the driver

Writable, because editing is the job.

claude --permission-mode acceptEdits
codex  -s workspace-write

the reviewer

Cannot write. Enforced by the CLI itself.

claude --permission-mode plan
       --disallowedTools Edit Write
codex  -s read-only

Nobody is asked whether they want the task. "Do you want this?" is a leading question and a model answers yes, which manufactures deadlock and turns the whole thing into expensive theatre over a regex. Each one picks between two named candidates and may name its opponent — and an agent choosing the other one is the strongest signal available.

the loop

Two models left to iterate on each other don't converge.

After the work, the baton keeps alternating — review, fix, review — until the reviewer finds nothing. Most of the engineering is the brakes.

stuck the same finding survives a fix round two models that disagree don't resolve by repeating themselves, they just bill
no progress the driver exits 0 having changed nothing it doesn't accept the finding, and nothing about round 7 will change that
disagreement every finding still open has been refused, with a reason both of them have dug in, so a human decides instead of a seventh round
scope collapse the diff shrinks by half the cheapest way to satisfy a reviewer is to delete what it complained about
reverted the delta is empty after a fix the driver backed its own change out, so check the task still got done
caps 6 rounds, 20 agent calls hardcoded, because a cap you can raise is a cap you raise at 2am

A leg that dies also ends the run, and says which one. A reviewer that crashed, hit a rate limit or has an expired login is never recorded as a clean pass — only a leg that exited 0 with a parseable verdict counts as evidence.

the part that took the longest to get right

The driver gets a memory. The reviewer stays blind.

Every leg is a fresh process, so by round three the driver has no memory of the approach it already tried or why it refused F2. A small docket fixes that — folded into the driver's next prompt, capped at 6,000 characters.

The reviewer never sees it, and that asymmetry is the whole design. A reviewer that can read "the driver says this is fine, because X" starts agreeing with X. Findings drop, the loop ends sooner, every number improves — and independent review is dead while looking exactly like success. So the reviewer forms its findings from the diff alone, and the reconciliation happens locally, in code. Independence costs no extra agent call.

// nothing throws when that breaks, so the test asserts the shape of the code
test('THE REVIEWER STAYS BLIND: review.mjs cannot even see the docket', () => {
  assert.ok(!/from\s+'\.\/docket\.mjs'/.test(src));
  assert.ok(!/\bdocket\b/.test(src));
});

Threading the docket into a review prompt breaks the suite. So does giving a read-only leg a write flag, or adding a real git commit. Each one was broken on purpose to check it fires — a tripwire that can't go off is worse than no test.

measured, not estimated

What it costs, honestly.

0k

Claude tokens before any work starts. claude -p reloads its context every call and no flag avoids it.

$0

Codex's leg, on a ChatGPT subscription.

0

Hard cap on agent calls per invocation — the conference plus six fix rounds, and no more.

0

Tests, including tripwires that were each broken on purpose to prove they fire.

A conference is ~8–12 seconds. After that a review leg took 9–20s and a headless fix round took ~54s, on one Mac in July 2026. The spread on the review leg is the model, not the wrapper. --fast exists for when you already know the answer.

install

Node, plus the two CLIs you already have.

git clone https://github.com/hangryclaude/claudecodex
cd claudecodex
mkdir -p ~/bin
ln -sfn "$PWD/bin/claudecodex.mjs" ~/bin/claudecodex
ln -sfn "$PWD/bin/claudecodex.mjs" ~/bin/ccx

No build step and no npm install — a fresh clone runs as it stands, and ~/bin only has to be somewhere on your PATH. Needs Node 18.17+, plus claude and codex each signed in to its own subscription. Zero runtime dependencies.

ccx "<task>"          confer, work, then review and fix until it's clean
ccx --fast "<task>"   router only. instant, $0, no conference
ccx --why "<task>"    print the decision and stop. spends nothing
ccx --robot "<task>"  headless, one json object on stdout
ccx --stats           per-domain record and router agreement

the limits

What it doesn't do.

  • Correctness only. A finding has to name inputs or state that produce a wrong result, a crash or data loss. Style and formatting opinions are dropped unread, so a fix round can never churn working code over a preference.
  • It reviews the driver's delta, not your repo. Breakage in a file the driver never touched is outside what the reviewer is shown.
  • It doesn't check the task got done. The loop only asks whether the diff has bugs — which is why a diff that shrinks by half is reported as scope collapse instead of scored as a win.
  • It doesn't settle disagreements. Once the driver has refused everything still open, the loop stops and hands both sides to you.
  • It never commits, branches or opens a PR. The work is left in your tree for you to read.