The most useful thing I built this year is not an agent. It is a loop with two of them: one writes, one reads and refuses to ship. The refusal is the product.
The shape
coder : reads the task, writes the code, RUNS it, reports real output
reviewer: read-only, same repo, must end with VERDICT: SHIP | BLOCK
The reviewer has no write access — proven with fingerprints before and after, not promised. It reads the diff, the tests, and the output of the tests. It is allowed to be pedantic about three things and nothing else: does it run, does it do what was asked, is there a test that would fail if the code were wrong.
Why two and not one
A single agent that both writes and judges will pass its own work, because it has already decided what “passing” means. The value comes from the information barrier, not from the second model being smarter — you can run the same weights on both sides and still get the benefit.
Exit codes, not adjectives
for round in 1 2 3; do
coder_round "$round"
verdict=$(reviewer_round "$round")
[ "$verdict" = "SHIP" ] && exit 0
done
exit 1
The loop exits zero only on SHIP. Anything else — three rounds of blocking, a crashed reviewer, a timeout — is a failure you can see in CI, which is the only place a verdict should ever be read.
What it caught
On a 2,400-line application it found a GET handler that ignored its own error parameter, a cookie marked Secure only behind a proxy header, and a password stash with no expiry. All three were mine. None of them were found by asking a model to “review this code”.