Git Worktrees for Agents: Parallel Loops Without Chaos
TL;DR
- The moment a loop runs more than one agent, files collide. Worktrees remove the mechanical collision (each agent gets its own working directory); the maker/checker split removes the quality collision (no agent merges its own work).
- Use a three-role fleet: explorer, implementer, verifier. Parallelize independent findings; serialize anything that shares state. Each parallel lane is its own worktree with auto-cleanup.
- Your review bandwidth, not the tool, is the ceiling. Rule of thumb: if you can honestly review N PRs a day, run at most N lanes.
📊 Result-Proof: A real two-finding run isolated each finding in its own git worktree on its own branch, a deterministic maker produced a one-file diff per lane, and a real
pytestchecker returned exit 0 for both. Both findings merged fromopentopassedin one state file, and the dead-lane worktrees were reaped on cleanup. Mechanism proof, not a graded autonomous fleet; nothing was instrumented, so there are no token figures to report (2026-06-15).
You fanned your loop out to two agents to finish faster, and they both edited the same file in the same working directory. One write stomped the other, the run produced a broken merge, and you spent the morning untangling it instead of shipping. The fix is isolation: git worktrees agents cannot share, one working directory per lane. You will give each parallel agent its own worktree, wire a three-role fleet with a separate checker, run a real two-finding triage that merges into one state file, and learn the honest ceiling on how many lanes you should run.
Prerequisites:
- You have skimmed Part 3 (stop conditions) and Part 4 (the state file) of this series. Part 5 builds directly on that state file.
- You know git worktrees, or will skim our worktrees walkthrough first. This post does not re-teach the mechanic.
- You can run a subagent from a loop and read a unified diff.
The frame for the whole series stays the same:
A loop is a recursive goal: you define a purpose and a verifiable stop condition, and the system iterates agents against it until the condition holds — without you prompting each turn.
Why do two agents in one working directory collide?
Two agents in one working directory collide for the same reason two engineers pushing to one branch with no standup collide: there is exactly one set of files on disk and no protocol for who touches what. The breakage comes in two flavors, and they need two different fixes. Name them before you fix them.
The first is the mechanical collision. Both agents share a working directory, so agent A’s edit to routes.ts and agent B’s edit to the same file interleave, or one agent’s git add . stages the other’s half-finished work, or two git checkout calls fight over HEAD. The output is a corrupted tree that belongs to neither agent. This is the collision you hit first, and it has nothing to do with the quality of either agent’s work.
The second is the quality collision. Suppose the two agents never touch the same file. An agent that writes a change and merges its own change has no second pair of eyes. Plausible-but-wrong work lands unreviewed, because the only reviewer was the author. Running more agents makes this worse, not better: you now have N authors each approving themselves. If you are new to running several agents at once, our multi-agent primer covers the intro; this post is the discipline that keeps a fleet from turning into N unreviewed authors.
The rest of this post fixes both. Worktrees fix the mechanical collision (next section). The maker/checker split fixes the quality collision (the section after). You need both, because solving one does nothing for the other.
| Mechanical collision | Quality collision | |
|---|---|---|
| Symptom | Corrupted tree, broken merge, lost edits | Plausible-but-wrong change lands unreviewed |
| Cause | One working directory, racing writes | Author is also the only reviewer |
| Fixed by | Worktree per lane (Beat 2) | Maker/checker split (Beat 3) |
Key insight: Two agents in one directory is two engineers sharing one keyboard. The problem was never the agents, it was the missing isolation and the missing review.
How does worktree isolation work inside a loop?
You give each agent its own git worktree, so each lane has a private working directory on a private branch and nothing one lane does can stomp another. The creation mechanic, the built-in support, and the manual git worktree add path all live in our worktrees walkthrough, so assume it from here. What changes inside a loop is who provisions the worktree and who cleans it up when no human is watching.
In an interactive session you create a worktree by hand and remember to remove it later. In a loop you declare isolation on the subagent and let the orchestrator do both. Marking a subagent with isolation: worktree tells the orchestrator to provision a fresh worktree per lane before the agent runs, and to reap it when the agent finishes or dies:
# fleet config: each implementer lane runs isolated- role: implementer isolation: worktree # orchestrator provisions a private worktree + branch per lane on_exit: auto-cleanup # finished or failed, the dead lane's worktree is removedThe auto-cleanup of dead lanes is the part that matters in an unattended run. A loop that iterates every night and never reaps its worktrees accumulates orphan directories until disk pressure or a stale-lock error takes the whole run down. In an interactive session you would notice and run git worktree prune. In a loop nobody is there, so cleanup has to be automatic: a lane that finished merging and a lane that crashed both get removed, and the next iteration starts from a clean list.
Isolation is also what lets the checker trust the diff it reviews. Because each lane is a separate checkout, a lane only ever contains its own change. In the run below, the lane fixing one file still held the original, untouched version of the other lane’s file, which is exactly the property you want: no lane can secretly depend on another lane’s in-flight work.
Key insight: In an interactive session you create a worktree; in a loop you declare one and let the orchestrator provision and reap it. The difference is who cleans up when no one is watching.
What fleet shapes work, and when do you parallelize vs. serialize?
The workhorse fleet is three roles, and the rule is one sentence: parallelize across independent findings, serialize within a single finding’s pipeline. The three roles are the practical payoff of this post, three small agent files you can reuse:
- Explorer. Finds work and emits a list of independent findings. It does not fix anything; it produces the work list the fleet fans out over.
- Implementer (the maker). Takes one finding and produces a diff in its own worktree. One implementer per lane, one finding per implementer.
- Verifier (the checker). Reviews that diff against a verifiable stop condition and returns accept or reject. The implementer never grades its own work.
That maker/checker split is not a house style; it is the evaluator-optimizer pattern from Anthropic’s Building Effective AI Agents, where a generator and a separate evaluator pass work back and forth until the evaluator is satisfied. The split is what turns the quality collision from Beat 1 into a controlled handoff.
When do you parallelize and when do you serialize? Run the lanes in parallel only when the findings are genuinely independent: different files, no shared state, order does not matter. Serialize the moment any of these is true:
- A stage depends on another stage’s output. The explorer must finish before implementers can fan out, so explore-then-implement is always serial.
- Two lanes would touch the same file. Independence is a property of the findings, not a wish; if two findings edit
routes.ts, they are one serial lane, not two parallel ones. - A later finding depends on an earlier finding’s merge. If fixing B only makes sense after A lands, B waits.
This is not the “worktrees vs. a single session” question, which the worktrees walkthrough already answers. This is about role topology and stage dependencies inside one loop. The open-source fleet runners converge on exactly this shape: claude-squad runs N agents each in its own worktree and merges the winners, claude_code_agent_farm coordinates 20+ agents against a shared problem list, and continuous-claude exposes a --worktree flag with an optional reviewer per iteration. Different implementations, same two primitives: isolate per agent, then check before merging.
explorer ──► [ implementer A ──► verifier A ] ─┐ └─► [ implementer B ──► verifier B ] ─┴─► merge into one state file (parallel: independent findings) (serial within each lane)| Situation | Decision |
|---|---|
| Findings touch different files, no shared state | Parallelize |
| Stage B needs stage A’s output | Serialize |
| Two findings edit the same file | Serialize (one lane) |
| Later finding depends on an earlier merge | Serialize |
Key insight: Parallelism is for independent findings; the pipeline within a finding is always serial. An implementer that grades its own diff is not a fleet, it is one agent wearing two hats.
Hands-on: a two-finding triage run across parallel worktrees
Here is a real run of the mechanism end to end. An explorer surfaced two independent findings, each got its own worktree on its own branch, a maker produced a one-file diff per lane, a real checker ran the tests, and both results merged into a single state file. Read the honesty note first: this is a mechanism proof, not a Haiku-graded autonomous fleet. The maker step here is a deterministic edit rather than an LLM agent turn, and nothing was instrumented, so there is no token or cost figure to report. What it proves is that the orchestration plumbing (isolate, make, check, merge, reap) works as described. The full verbatim trace lives in the draft’s run-trace file.
The two findings the explorer recorded:
- Finding A:
mathutil.add(a, b)returnsa - binstead ofa + b. Failing test:test_mathutil.py::test_add. - Finding B:
strutil.shout(s)returnss.lower()instead ofs.upper(). Failing test:test_strutil.py::test_shout.
They share no files and no state, so by the Beat 3 rule they are eligible to run in parallel.
Isolate. One worktree per finding, each on its own branch:
$ git worktree add ../wt-finding-a -b fix/finding-a$ git worktree add ../wt-finding-b -b fix/finding-b$ git worktree list/home/you/p5-fleet 25ee424 [master]/home/you/wt-finding-a 25ee424 [fix/finding-a]/home/you/wt-finding-b 25ee424 [fix/finding-b]Make. Each maker edits only its own lane’s file. Lane A’s commit touches mathutil.py and nothing else:
$ git diff HEAD~1 HEAD # in wt-finding-adiff --git a/mathutil.py b/mathutil.py@@ -1,3 +1,3 @@ def add(a, b):- # BUG (Finding A): returns difference instead of sum- return a - b+ # FIX (Finding A): correct sum+ return a + bLane B’s commit touches strutil.py and nothing else:
$ git diff HEAD~1 HEAD # in wt-finding-bdiff --git a/strutil.py b/strutil.py@@ -1,3 +1,3 @@ def shout(s):- # BUG (Finding B): lowercases instead of uppercasing- return s.lower()+ # FIX (Finding B): correct uppercase+ return s.upper()The isolation is real, not asserted. Inside lane A, the other lane’s file is still the buggy seed, because lane A never touched it:
$ git show HEAD:strutil.py # in wt-finding-adef shout(s): # BUG (Finding B): lowercases instead of uppercasing return s.lower()Check. A real pytest runs in each lane. The verdict is whatever the exit code says, not a decision the maker gets to make:
$ pytest -q test_mathutil.py # in wt-finding-a1 passed in 0.00s # exit 0
$ pytest -q test_strutil.py # in wt-finding-b1 passed in 0.00s # exit 0Merge. Both lanes’ real verdicts land in one state file, reusing the Part 4 tried/passed/open/blocked schema. Before the run, both findings sit in open:
## tried
## passed
## open- [finding-a] mathutil.add() returns a-b instead of a+b (fails test_mathutil.py::test_add)- [finding-b] strutil.shout() lowercases instead of uppercasing (fails test_strutil.py::test_shout)
## blockedAfter the run, both moved to passed, because both checkers returned exit 0:
## tried- [finding-a] worktree wt-finding-a / branch fix/finding-a: maker edited mathutil.py (a-b to a+b)- [finding-b] worktree wt-finding-b / branch fix/finding-b: maker edited strutil.py (lower to upper)
## passed- [finding-a] test_mathutil.py::test_add green in wt-finding-a (checker pytest exit 0)- [finding-b] test_strutil.py::test_shout green in wt-finding-b (checker pytest exit 0)
## open
## blockedBoth lanes passed in this run, so nothing landed in blocked. That is the honest outcome; the schema simply has somewhere to put a failure when one happens. Had a lane’s pytest returned non-zero, the merge step would have left that finding in open (retry next run) or moved it to blocked (needs a human), and the next iteration would pick it up from there rather than re-deriving it.
Reap. Cleanup removes both dead lanes, leaving only the root tree:
$ git worktree remove ../wt-finding-a$ git worktree remove ../wt-finding-b$ git worktree list/home/you/p5-fleet 25ee424 [master]That is the full loop: isolate, make, check, merge, reap. Two independent findings went through two isolated lanes and landed in one file the next run can read.
Key insight: The merge is the moment of truth. Two isolated lanes are only useful if their results land in one place the next run can read, and that place is the state file from Part 4.
What is the orchestration tax, and what is the honest ceiling on fleet size?
Every lane you add costs tokens and, more bindingly, costs you review attention. The honest ceiling on fleet size is not the tool’s concurrency limit; it is your review bandwidth. The rule of thumb is blunt: if you can genuinely review N pull requests a day, run at most N lanes. Treat that as a heuristic, not a measured constant; it is the article’s own framing, calibrate it to your team.
There are two taxes. The first is the token tax. N lanes is roughly N times the per-agent context and turn cost, plus the orchestrator’s own overhead in coordinating them. Anthropic is explicit in Building Effective AI Agents that orchestrating multiple agents multiplies cost and is not free; more agents buys wall-clock and breadth, and you pay for it in tokens. That tax is real but it is also the one you can see on a bill, so it tends to self-correct.
The second is the review tax, and it is the binding one. The checker raises the floor: it filters the obvious failures so they never reach you. But a human still merges, and un-reviewed agent output is the quality collision from Beat 1 reappearing at fleet scale. The checker decides what is plausibly correct; you still decide what is actually correct for your codebase. That decision does not parallelize. Ten lanes producing ten green diffs is ten diffs you have to read before they land, and your reading speed is fixed.
So the checker raises the floor and your review sets the cap. Worktrees and the maker/checker split let you run more agents safely; they do not let you review faster. The tool’s ceiling and your ceiling are different numbers, and yours is lower. Size the fleet to the lower one.
| Per lane you add | Effect |
|---|---|
| Token cost | Up (≈ N× per-agent cost + orchestration overhead) |
| Wall-clock to results | Down (lanes run concurrently) |
| Your review load | Up (every green diff still needs a human merge) |
Key insight: Worktrees and checkers let you run more agents. Your review bandwidth decides how many you should. The tool’s ceiling and your ceiling are not the same number, and yours is lower.
What you have, and what is next
You now have the full mechanism: a three-role fleet (explorer, implementer, verifier), worktree-isolated lanes with auto-cleanup, a maker/checker split so no agent merges its own work, and a two-finding run that merged into one state file. Three things to watch in production: lanes that secretly share state (serialize them), orphan worktrees if auto-cleanup is off (reap them), and a fleet sized to the tool instead of your review bandwidth (cap it). And remember that a checker which rubber-stamps is just one agent wearing two hats again, so hold it to a real verifiable stop.
You can run this fleet on demand. But a loop earns its name when it runs on a cadence you did not trigger: scheduled discovery, a heartbeat, and runs that archive themselves. That is loop-scheduled-automations-heartbeat, next in the series.
FAQ
How many parallel agents can I actually run at once?
Technically as many as your tool’s concurrency cap allows; honestly, as many as you can review. The rule of thumb: if you can review N PRs a day, run at most N lanes. The tool’s ceiling and your review ceiling are different numbers, and yours is lower.
Do subagents get their own worktree automatically?
Only if you declare it. Set isolation: worktree on the subagent and the orchestrator provisions a private working directory per lane and reaps it on completion or failure. Without that, every lane shares one directory, which is the collision you are trying to avoid.
How is a worktree different from a branch for parallel agents?
A branch is a pointer in history; a worktree is a separate checked-out directory for a branch. Two agents can be on two branches but still fight over one working directory. Worktrees give each agent its own files on disk, which is what actually prevents the racing-write collision.
When should I serialize the fleet instead of parallelizing?
Serialize whenever a stage depends on another stage’s output (the explorer must finish before implementers fan out), whenever two lanes would touch the same file, or whenever a later finding depends on an earlier merge. Parallelize only genuinely independent findings.
What happens to a worktree when an agent fails mid-run?
With auto-cleanup on, a failed lane’s worktree is removed so the loop does not accumulate orphan directories across iterations. The finding stays open (or moves to blocked) in the state file, so the next run can retry it without re-deriving what already happened.
What to Read Next
- Git worktrees for parallel work: the worktree mechanic this post assumes (built-in support and the manual path).
- Multi-agent AI coding: the intro to running more than one agent, if Beat 1 moved too fast.
- Stop conditions and verification: what the verifier role enforces; the checker is only as good as its stop condition.
- The state file pattern: the tried/passed/open/blocked file the fleet merges into; read this first if you skipped Part 4.