Loop protocol / How a change is proven
Gates first, then three reviewers, then a composed gate
The chain every change passes before it lands: the lane's gate, CodeRabbit, a DeepSeek Flash reviewer, an Opus reviewer when it matters, and one composed gate before the push.
A lane's claim that its change works is worth nothing until something else has checked it. Every change passes the same chain, and each step catches a different kind of mistake. The rules are section 5 and 6 of the contract; this page is why they look the way they do.
The chain
- The lane proves it red, then green. A worker writes a test that fails for the right reason, makes it pass, and runs the repository's gate (
just check) on its worktree. It hands back an uncommitted patch and a return naming the command it ran and its exit status. A real one, from a worker lane in In practice:
The root reads```lane-return {"v":3,"lane":"W2270","task":"<task id>","status":"complete", "base":"17c5bdd8...","worktree":"<repo>/codex/loop87/worktrees/loop87-W2270", "patch":"<repo>/codex/loop87/W2270/candidate.patch","patch_sha256":"986b5caf...", "check":"gaterun-slot W2270 -- gaterun --log <run dir>/W2270/gate3.log -- just check","exit":0, "tail":"gr-run: slot=4 base=17c5bdd8... kind=heavy\ngr-run: tree=20c14c04... 11 changed paths\ngr-run: cmd=just check\ngr-run: end=2026-10-21T01:38:00Z exit=0 result=pass", "coderabbit":{"ran":true,"major":0,"unreviewed":0}, "dsreview":{"verdict":"PASS","major":0,"fixed":0,"disputed":0}, "questions":["The root regenerates the two identity files; they are left out of the patch.", "CodeRabbit minor: wants different wording in one table row; I kept the brief's exact string."]} ```exit,tailand the review fields; a non-zero exit, a missinggr-run: ... result=passline or an unanswered question sends it back. - CodeRabbit, once. The lane runs
coderabbit review --agenton its final candidate and fixes every critical and major finding. - dsreview. The lane saves its brief and runs the DeepSeek Flash reviewer on the patch. It checks each major finding against the code: it fixes what holds and reruns once, or disputes the finding with evidence in its return.
- An Opus reviewer, when the tier calls for one. See the table below.
- A composed gate. One lander lane takes every queued candidate, rebases them onto current
main, regenerates shared generated code once and runs the gate on the composed tree. A candidate that conflicts or fails drops out and goes back to its lane; the rest land. - The root pushes, and CI runs. Only the root pushes. A red check on
mainstops the line: nothing new lands until the fix for that check does.
Which reviewer, by tier
A task is guarded when it touches auth, secrets, privacy or redaction, tenant scope, migrations, destructive SQL, IaC, CI workflows, release or deploy configuration, or makes a live change. Everything else is routine.
| Step | Routine | Guarded |
|---|---|---|
| Lane gate | Always | Always |
| CodeRabbit | Once per final candidate | After the reviewer |
| dsreview | In place of the Opus reviewer | As well as the reviewer |
| Opus reviewer | Only when dsreview gives no verdict, a lane disputes one of its majors, on a second repair round, or when CodeRabbit didn't run; runs after landing | Always, with a security brief, before landing |
| Composed gate | Always | Always |
A change after a verdict invalidates it, and only the delta is reviewed again. Reviewers report and never edit. Disagreements between the Opus reviewer and dsreview go in the run's decision log.
Why three reviewers
They miss different things. CodeRabbit is fast and good at local defects and convention, but it reads the diff without the task's intent. The two model reviewers get the brief, so they can say the change doesn't do what was asked.
Before dsreview replaced the routine Opus reviewer, I replayed 59 historical reviews whose exact patch was still on disk, graded each against the code with separate judge agents, and compared:
| Reviewer | Right verdict | Defective changes caught | False blocks |
|---|---|---|---|
| Opus reviewer lane | 56 of 59 | 8 of 11 | 0 of 48 |
| DeepSeek Flash, diff only | 49 of 59 | 5 of 11 | 4 of 48 |
| DeepSeek Flash with read tools | 56 of 59 | 8 of 11 | 0 of 48 |
The tool-using version tied the Opus lane at a median of about $0.05 a review against about $1.09, at similar wall time. The two missed different defects, and together they caught all eleven. That's the case for running dsreview on everything and keeping Opus for guarded work and disputes.
The limits: eleven defective changes, mostly from one repository, graded by Opus judges. Treat the tie as "comparable", not "equal". DeepSeek also raises far fewer minor findings and tends to call a real small issue major, which is why a lane may dispute a major with evidence.
In its first live loop, lanes ran dsreview on 104 candidates: 84 passed, 9 failed and 11 got no verdict, which sent them to an Opus reviewer. It raised 29 major findings. Lanes disputed 12 of them with evidence and fixed most of the rest; a lane can do both in one return, so the split is approximate.
Gating the composed tree
Every candidate passed its own gate against an older main, so the composed tree has to be gated again. Two things make that affordable.
In the repository from In practice, a landing goes like this. One lander lane takes everything queued, rebases it onto current main, regenerates the OpenAPI code, the sqlc database code and the code fingerprints once, and commits. Then it gates the composed tree with every leg the touched packages need:
just check, the whole local gate;- each integration shard the touched packages run in, out of fourteen (four API shards, API capacity, reporting, services, domain, runtime, the rest, two inventory and two jobs shards), each a set of Postgres-backed Go packages under
-racein containers; - the Playwright browser suite, about 150 tests, when the front end changed.
# from one committed compose worktree, every leg in the background at once
gaterun-slot L17-check -- gaterun --log L17/check.log -- just check &
gaterun-slot L17-api-1 -- gaterun --log L17/api-1.log -- just integration-shard-ci api-1 &
gaterun-slot L17-jobs-2 -- gaterun --log L17/jobs-2.log -- just integration-shard-ci jobs-2 &
# ... one line per shard the touched packages run in ...
gaterun-slot L17-browser -- gaterun --kind browser --log L17/browser.log -- \
bash -c 'just playwright-setup && just playwright-comprehensive' &
waitA candidate that conflicts or fails a leg drops out and goes back to its lane, and the rest land. The root pushes, and the next lander starts at once.
- Run the legs in parallel. A full gate on a large repository is the unit tests, a dozen integration shards and a browser suite. Run one after another, thirteen legs took about 90 minutes; run side by side, the remaining legs of the next landing took about 20.
- Only from a committed tree. The remote runner uploads
HEADplus uncommitted files, so parallel runs from a dirty worktree each gated a different partial tree, and a leg could pass without the change in it. Commit the candidates and the regeneration, checkgit status --porcelainis empty, and edit nothing until every leg exits.
CI runs the same recipes
After the push, CI proves the change again on clean runners, and it calls the same just recipes a lane ran, split across jobs: just check-go on a large arm64 runner, just check-gen, just check-web, the chart render, an image build, the integration shards as a matrix of just integration-shard-ci <shard>, and two Playwright suites. A first job classifies the changed paths so a docs-only push skips the heavy jobs, and a Renovate bump that only changes a pin takes a lighter proof route.
Two aggregator jobs, ci-success and integration-success, need every other job and are the only checks branch protection and LOOP.md's ci-required name. Adding a job never means editing the branch protection. The aggregators run even when a job failed, and a job may be skipped only when the path classifier said the change doesn't touch it; a skip anywhere else fails the check. The CI runners are pinned to an image that carries just, and the workflow fails if the image's version differs from the one it expects, so CI and the justfile never pin the same tool twice.
Traps
- CodeRabbit's exit 0 isn't a clean review. A run with no
completeline failed, andunreviewedFileCountabove zero is partial coverage. New files are invisible until staged. - dsreview through a Cloudflare AI Gateway: a DLP check on responses buffers the whole reply, so streaming doesn't stream, and a proxied custom domain cuts any call over 100 seconds. Check requests only.
- A gate that passes on its first run against unchanged code has probably asserted nothing. Make it fail for the right reason first.
- Never hide a
git pusherror. A push redirected to/dev/nullfailed silently twice in one loop; check the remote after every push.
Without my setup
The chain works with less. Keep the lane gate and the composed gate whatever else you drop. CodeRabbit is optional, and the lane template says how to remove it. dsreview needs a DeepSeek route; without one, the Opus reviewer takes its place on every routine change at roughly twenty times the cost. Remote gates become any machine with a slot limit, or CI on a pushed branch.
Get dsreview: tools/dsreview in the agent-workflows plugin. Rules: CodeRabbit is the review gate, gate runner.