In The Ones I wrote down a table of imaginary agents. The top row was the boring one: an agent that asks what a thing is for, and cannot propose and cannot cut.
Yesterday I ran it by hand. No custom runner, no daemon, no theones binary. Two terminal sessions, two log files, and a real task on incus-compose.
It found real defects and the work landed. The interesting part is where the constraint came off, and that I did not notice at the time.
The setup
Multi-architecture image support, planned in work/multi-arch-images.md, on branch fix/arch_images.
Two sessions side by side:
- The coder —
8c2c9476, 1,115 records. - The boring one —
231004e5, 321 records.
The second session opened with a skill, .claude/commands/boring.md, in full:
## Read as your rules
- `CONTRIBUTING.md` — coding, architecture, testing, workflow rules
- `AGENTS.md` — org + project AI meta rules (rule hierarchy, Legal, Formatting)
- `AGENTS.local.md` — personal collaboration notes (treat as canonical for agent
behavior; do not copy content into public docs)
## What has been implemented
The plan $arguments
## Task
Read `git diff`. For each change, ask what it is for. One question per change.
Do not answer your own questions. If every change has an obvious purpose, say
nothing.
$arguments was work/multi-arch-images.md with Option A. No permission to edit, no proposals, no counter-designs. The constraint is Do not answer your own questions.
Pass 1: questions only
It opened with the part I would not have got from a reviewer:
Most of it has an obvious purpose — the
cacheAlias()re-key and its propagation tolockStore,materialize,deleteCached,ensureBuildandbuildImage; the digest pin inpullSource; […] and the mechanical test churn.These I cannot see the purpose of. One question each, no answers.
Then fifteen questions, each a location and a question, and not one of them proposing anything:
Exported
Platform()(client/resource_image.go:259) —rgfinds no caller in the tree, and it readsr.platformwithoutmuwhileresolveArchwrites it undermu. Who is it for?
platformArch(client/build.go:129) — a non-arm variant is dropped, solinux/amd64/v3andlinux/amd64produce the same cache alias suffix. What is folding them for, given the store now keys on that suffix?
The coder took the fifteen, removed what had no answer, and reworked the rest. Thirteen of them landed as changes.
Where the constraint came off
The second pass started with me typing: The coder is done with his changes. Do another review please.
That sentence has the word review in it and does not have the constraint. Here is how the boring one opened:
Five findings, most severe first.
Findings, ranked by severity. From that pass on it called the review tool, wrote recommendations — the switch needs to tell “these disagree” apart from “this does not parse” — and closed each report with a note that was not a finding. A good reviewer, and no longer the thing in the table.
Nothing failed here. It was never prevented from proposing. It was asked not to, once, in a prompt, and the ask lasted exactly as long as the prompt did.
Which is the argument of The Ones arriving as evidence against my own test rather than for it. A boundary that holds is better than a rule that has to be remembered, and what I had built was a rule.
What I should have typed
The sentence that broke it was mine, and it did not have to be that sentence. Do another review please carries a role noun and a verb that implies a shape for the output. Next pass carries neither — no role, no format, nothing to reach for except the rules already in context.
I think that would have held for all five passes. I have not run it, so it is a guess with a mechanism behind it rather than a result: one word did the damage, and the fix is not typing that word.
It is still a rule I have to remember, which is the same failure one rate down. A continuation the runner writes does not need remembering, and that is what the table was for.
What the passes found
- Pass 1 — 15 questions
- Pass 2 — 5 findings
- Pass 3 — 3
- Pass 4 — 2
- Pass 5 — 2
Twelve findings after the questions. Every one fixed with a test before the next pass began, most of them proved red first.
It did not converge to nothing. Pass 5 produced two new findings and I stopped there because the work was good enough to land, which it did, as 1a895c87.
Why reading the whole diff mattered
Each pass re-read the entire git diff rather than the delta since the last one.
Pass 2’s worst finding was an OCI reference that resolves straight to a manifest and gets its digest pinned with no platform check — an amd64 image landing in the shared cache under an arm64 key, breaking every later run in every project on that server until somebody deletes the alias by hand.
The fix for it produced the next one. Canonicalising the platform key collapsed arm/v5 and arm/v6 onto the same value, and the boring one named what that was:
This is the defect the CHANGELOG records as fixed for v6-against-v7, one variant further down.
The CHANGELOG was wrong again in pass 5, on the other path:
the CHANGELOG line says such a source “fails rather than caching the wrong architecture” — it does both.
Neither is visible in a patch-level review, because in the patch both of them are fixes. The coder’s own write-up gets to the same place from the other side:
The findings that mattered were defects I introduced, not gaps I left.
The thing I had to stop, after it had happened
On pass 2 it decided to check empirically. It ran just test-local, wrote client/zz_scratch_test.go into the worktree to print key outputs, read the log, and deleted the scratch file.
The coder was editing that worktree at the time.
I interrupted and said: you are both on the same tree, do not execute tests.
By then the file had been written and removed four tool calls earlier. I did not stop it. I read what it had done and told it not to do that again, which is the correct handling for a wrong opinion and the wrong handling for a write into somebody else’s tree.
That failure has a row of its own in the table, one that matches a short list, sits in front of an action rather than behind it, and never looks at what is being built.
The question it could not ask
Read the opening of pass 1 again, the part it waved through:
the
cacheAlias()re-key and its propagation tolockStore,materialize,deleteCached,ensureBuildandbuildImage
Fifteen questions, and the one worth asking was not among them.
That is the scope rather than a lapse. The prompt was read git diff, for each change ask what it is for, and lockStore was not a change — the re-key passed through it. A question aimed at a diff cannot reach what the diff is built on. Everything already in the tree is out of range by construction, and everything already in the tree is most of it.
Which is worth putting next to the finding it did make. Reading the whole diff every pass caught the defects the patches introduced. Nothing in five passes could reach a defect the patches inherited.
What it showed
The questions worked. Pass 1 is the only pass in which the boring one existed, and it is the pass that got code removed rather than corrected — a proposal that cannot answer the question falls over without anybody arguing against it. That is what the table claimed, and it is what happened.
Everything after pass 1 was a competent code review, and I would take it again. It is a different product, and what separates the two is one sentence I did not repeat.
So the honest result of testing an imaginary agent by hand is that I proved the part of the design I had not intended to test. The rule was in context the whole time. It stopped applying the moment I typed a word that implied something else.
By René Jochum. License: CC-BY-4.0.
