Many Agents, One Rule

Running a second agent is not twice one agent. Two things change, and only one of them is about coordination.

The rule

Whoever reviews a piece of work must not be whoever produced it.

That is the whole rule. It is worth being precise about the reason, because the usual phrasing is wrong and a careful reader will notice.

It is not about trust. Nobody thinks the author is dishonest. And it is not "a second pair of eyes", which suggests the value is in the eyes being additional.

What review buys you is a reader who did not build the thing — and therefore does not know what it was supposed to do, and so reads what it actually says.

An author cannot do this for themselves at any level of diligence, because they cannot un-know the intent. When you read your own work you see what you meant; the reviewer sees what is there. Those differ exactly where the bugs are.

What that looks like when it works

The chapter before this one shipped with a false claim in it. Its centrepiece was a command returning nothing, and I had personally run that command before writing it down.

A reviewer on another machine ran the same command and got two lines of output. The cause was not the platform: my shell had a grep function shadowing the real one, routing to a different tool with ignore binary files set. The command I typed was not the command that ran.

No amount of care on my side would have found that, because the thing I would have checked more carefully is the thing that was lying. It took someone whose environment was not mine.

That is the rule earning its keep, on the previous page, on the day this one was written.

Where this comes from, and why we do not treat it as bureaucracy

The same logic runs through everything in this handbook: pre-registration exists because you cannot audit your own memory of what you predicted; the measurement audit in the beginner track exists because you cannot audit a number that agrees with you. Reviewer ≠ author is that idea applied to code.

We hold to it even when it is inconvenient — an agent that fixes a conflict on someone else's branch becomes a co-author of it and can no longer merge it. That costs a round trip fairly often. It is still cheaper than the alternative.

What actually breaks with two agents

Here is the failure everyone warns about. It is true — but for a reason most people get wrong, including the first draft of this chapter.

The folk version: a second agent runs git checkout in the same directory and destroys your uncommitted work.

git checkout is five commands wearing one name, and only one of them is the branch switch. Measured on git 2.50.1, one file edited and uncommitted, each command run as a second agent would run it in the same directory:

git checkout feature-b        rc 0   edit survives
git checkout .                rc 0   edit GONE   "Updated 1 path from the index"
git checkout -- f.txt         rc 0   edit GONE   (prints nothing at all)
git checkout main -- f.txt    rc 0   edit GONE   (prints nothing at all)
git checkout -f feature-b     rc 0   edit GONE   "Switched to branch 'feature-b'"

Four of the five destroy the work and exit 0 while doing it. Three of those four say nothing that would tell you — and the fourth, checkout ., prints Updated 1 path from the index, which is technically a notification and reads like success. The one that is guarded is the branch switch — which is, unhelpfully, the form everyone reaches for when demonstrating the problem:

# A is mid-edit, B switches branch in the same checkout
$ git checkout other
error: Your local changes to the following files would be overwritten by checkout:
	f.txt
Please commit your changes or stash them before you switch branches.
Aborting
$ echo $?
1

⚠ Row three is not a theoretical worry. git checkout <ref> -- <path> is what a reviewing agent runs to look at someone else's version of a file — it is the most ordinary thing a second agent does, and it is unguarded. That is the concrete shape of the incident this chapter is about.

And there is a second failure underneath the first, which no amount of care about checkout spellings will prevent: HEAD.

HEAD — which branch the checkout is on — is a property of the directory, not of the agent. There is one of it, and either agent can move it.

# A is working on main
$ git branch --show-current
main

# B, same directory, switches to their own branch. Harmless-looking.
$ git checkout feature-b

# A, unaware, finishes and commits
$ git add . && git commit -m "A: finish the thing"

$ git log main --oneline
fbd2cda base                       # A's commit is NOT here

$ git log feature-b --oneline
e8f25f9 A: finish the thing        # it is here
fbd2cda base

A believed they were committing to main. The commit is on B's branch.

Nothing errored. Nothing warned. A's own git log looks perfectly consistent to A — it shows their commit on the branch they are on, which is true and not what they meant.

That is the shape of every multi-agent bug worth worrying about: not destruction, which is loud, but a silent redirection that leaves every local view internally consistent. You find it later, from somewhere else, wondering why a merge is missing work that was definitely finished.

The fix is one command

Give each agent its own working tree:

git worktree add ../agent-b feature-b

Same repository, same history, same objects — separate directory, separate index, separate HEAD.

# A in repo/, B in agent-b/, B switches branches freely
$ git branch --show-current        # A's checkout
main                               # unmoved

$ git commit -m "A: finish"        # lands on main. Correct.

And the property that makes it isolation rather than convenience: A's uncommitted work is not visible to B at all. It is not merely safe from B — it does not exist in B's directory until A commits it. There is nothing for B to trip over.

The cost, since it is not free

Each worktree is a full checkout on disk. For a large repository with a build directory, that is real space, and running builds in several at once will compete for the same cores.

Clean them up (git worktree remove <path>, git worktree prune) or they accumulate. Each one carries its own build output, which is the part that surprises people: a single abandoned worktree of ours held 38 GB of compiled artifacts. It sat for fifteen hours after its session finished, filled the disk to 100%, and the first symptom anyone saw was an unrelated database refusing to write — on a different machine, to an agent who had never touched that worktree. Nothing swept it because the rule was "every session cleans up after itself", which works until one does not.

Walk it, including the part you will actually need

The command above is the easy half. The half that matters is what happens when you are already in the shared state, holding uncommitted work — which is where you will be when you first read this, because nobody sets up worktrees before the problem appears.

Real transcript, git 2.50.1, four commands:

# A is mid-edit in the shared checkout, nothing committed
$ git status --porcelain
 M app.txt

# Create B's tree WITHOUT committing, stashing, or cleaning first
$ git worktree add ../agent-b feature-b
$ echo $?
0
$ cat app.txt
A's uncommitted work            # still there, untouched

You do not have to stash to get out of this, and you should not. A stash is a shared stack — in a repository where a second agent might also stash or pop, reaching for it to solve an isolation problem creates a worse one. git worktree add does not touch your working tree at all.

Then the three properties. Order matters here — property 2 has to be tested while B is still holding the branch, which is why it comes before B moves off it:

# 1. Is A's uncommitted work visible to B?
$ cat ../agent-b/app.txt
v1                              # B sees the committed file. A's edit is not here.

# 2. Can A take the branch B is HOLDING?
$ git checkout feature-b
fatal: 'feature-b' is already used by worktree at '/private/tmp/wt-real/agent-b'
$ echo $?
128
$ git branch --show-current
main                            # A did not move

# 3. Now B moves to its own branch. Does A's HEAD follow?
$ git -C ../agent-b checkout -b feature-c
Switched to a new branch 'feature-c'
$ git branch --show-current     # A's tree
main                            # unmoved
$ cat app.txt
A's uncommitted work            # untouched

Property 2 is worth the most and is the one nobody mentions. Git refuses. Not a warning, not a race you have to be careful about — rc 128, and A stays where it was. One branch, one worktree, enforced by the tool.

That is a different kind of guarantee from "we agreed each agent works on its own branch." Agreements need everyone to remember. This one holds when somebody does not.

This section shipped with a transcript that could not have happened, twice

The first version tested property 2 after B had already moved to feature-c. So nobody was holding feature-b, A took it cleanly, and the transcript read Switched to branch 'feature-b' — which could have been written up as "git allows this, be careful."

I caught that, wrote this box about catching it, ran the corrected test in a separate scratch repo — and then pasted the corrected output back into the original broken ordering. The published transcript still had step 2 moving B away before step 3 claimed the lock. The output was real; the sequence that produced it was not the sequence printed.

An adversarial fact-check caught it by doing the only thing that finds this class: running the printed commands in the printed order. That is a different act from running the commands. I had done the second and believed I had done the first.

So: a test that does not construct the condition it names will confirm whatever you already believed — and a transcript is a claim about a sequence, not just about outputs. Re-ordered above so the printed order is the order that works.

Clean up when the work is done:

git worktree remove ../agent-b     # --force if it has uncommitted junk
git worktree prune                 # drops registrations whose directories are gone
git worktree list                  # what you actually have

Where the stop button is

Before you run several agents, know how to stop them — not after.

The mechanism matters less than that it is one action, reachable without thinking, and that everything checks it. Ours is a single record on the board: while it exists, no agent claims new work or merges anything. Any agent can create it; clearing it requires evidence that the thing which caused it is actually fixed.

Two properties are worth copying whatever you build:

Cheap to pull, deliberate to clear. If stopping is expensive, nobody stops in time. If clearing is cheap, the halt gets cleared before the problem is fixed.

It fails closed. If the check cannot tell whether the stop is active, it treats the fleet as stopped. An unreadable stop signal is not evidence that everything is fine.

Where to go next

  • Why It Is All Arrow — the substrate argument, and why an agent's board wants to be resident rather than parsed
  • The review model — spec 07, CC-BY-4.0, the full version of the rule at the top of this page

That spec is written for people already inside our system and uses vocabulary this handbook does not teach. The rule above stands without it.