Skip to main content

03.11Rule

Git worktree isolation for parallel AI coding agents

How AR Labs isolates parallel AI coding agents with git worktrees: each task on its own branch and checkout, and a hook that blocks edits to main.

On this page
  1. The problem it prevents
  2. How it works
  3. Parallel agents get their own worktrees
  4. Enforced by a hook, not by documentation
  5. Adopting it

Worktree isolation is how AR Labs keeps AI coding agents (AI agents that write code, such as Claude Code or Codex) from landing edits on main by mistake, and lets several of them work in parallel without touching each other's files. Every project our agents work on is checked out in at least two places at once. A primary checkout stays on main as the clean integration point. Work happens in a separate git worktree on the task's own branch. Agents edit in the worktree, never in the primary, and a hook (a script run automatically around an agent's action that can block it) enforces that rule.

The problem it prevents

A git worktree is one repository checked out into more than one directory, each on a different branch. The directories cannot see each other's uncommitted files. An agent session usually starts inside the primary checkout, so the project's files are right there, visible and editable, and they are the wrong copy. An edit there lands on main with no branch, review or pull request, or sits unnoticed in the primary's working tree while the agent looks for it in the worktree. Nothing errors. The edit simply goes to the wrong place.

Reading has the same trap with no error at all. One analysis came out wrong because it read the primary's copies of tooling that existed only on a task branch. A task's newest files, including its spec, evidence and walkthrough, exist only in its worktree until the pull request merges.

How it works

The figure shows the only two directions edits can travel. Each agent edits its own task worktree, and work reaches main on the code host through a pull request a person merges. The primary checkout only pulls; an edit aimed at it is refused by the hook.

The primary checkout pulls from main; three agents edit three task worktrees on their own branches; the hook refuses an edit aimed at the primary; worktrees reach main by pull request. The code host holds main. The primary checkout stays on main as a clean integration point and only pulls. Each task gets its own git worktree on its own branch, cut from a freshly fetched main. Each agent edits only in its task's worktree. An agent that tries to edit the primary checkout is refused by a pre-edit hook, whose message names the correct worktree. Finished work goes from the worktree to a pull request, a person merges it to main, and the next task's branch is cut from the updated main. Worktree isolation: where edits go ONE PRIMARY CHECKOUT STAYS ON MAIN · EVERY TASK GETS ITS OWN WORKTREE AND BRANCH · A HOOK ENFORCES IT FIGURE WORKTREE ISOLATION DRAWN BY AR LABS SHEET 1 / 1 Code host: main [origin] Work lands here only through a merged pull request Primary checkout [always on main] Clean integration point. Pulls; never edited by an agent Agent A [one task] Worktree [task/app-42.3] Spec, code, tests, evidence, walkthrough Agent B [one task] Worktree [task/app-42.4] Spec, code, tests, evidence, walkthrough Agent C [one task] Worktree [task/app-43.1] Spec, code, tests, evidence, walkthrough AFTER THE MERGE git branch -d refuses to delete a branch whose work has not merged, so a clean deletion is git confirming the work is on main. edits edits edits refused by the hook pulls fetch, cut a branch per task from main pull request; a person merges
One primary checkout stays on main; each agent edits only its own task worktree, and the hook refuses the rest.
  1. Confirm the layout. git worktree list shows which directory holds which branch.
  2. Start a task. Fetch, then cut the task branch from the freshly fetched target branch. A worktree can never sit on main itself, because git will not check the same branch out twice and the primary already holds it.
  3. Do all the work there. The spec, the code, the evidence and the review all sit on one branch in one worktree.
  4. End the task. Once the pull request has merged, cut the next task's branch, then delete the finished one with the safe form of the command.
cd ~/worktrees/app/dev
git fetch --prune origin
git checkout -B task/app-42.4-export-csv origin/main
git branch -d task/app-42.3-persist-session

git branch -d refuses to delete a branch whose work has not merged, so the refusal is the check: a clean deletion is git confirming the work is on main. The lowercase form is used on purpose; the uppercase -D deletes regardless. The check relies on merge commits. With squash merges the branch's commits are not literally on main, so -d refuses even merged work; confirm the merge on the code host before forcing it with -D.

Parallel agents get their own worktrees

When the coding pipeline dispatches work unattended, each task gets its own worktree, on a branch named for the task, cut from the campaign's integration target (the branch its work lands on, main unless the campaign has its own). The first step creates it; every later step of the same task finds it by branch and reuses it, so the spec, code and review stay together. Reviewers get no worktree, because a review only reads.

A project with heavy dependencies can also keep one shared development worktree with its secrets and installed packages in place. A task takes the shared one only when it is clean and free: nothing uncommitted, no merge in progress, no other open task's branch checked out. Otherwise the task gets its own, and the shared worktree is left exactly as it was. A new worktree is prepared before the agent starts: listed ignored files such as local environment settings are copied in, never over an existing file, and the project's setup script installs dependencies.

Enforced by a hook, not by documentation

The rule began as a warning in one project's own docs. That warning sat uncommitted in a single checkout and was invisible from every other one, which is the failure the rule exists to prevent. We moved the rule into two places that do not depend on the target repository's state: a rule file loaded into every session, and a hook.

The hook runs before every file edit or write. It walks up from the target path to the nearest .git entry. A .git file means a linked worktree, and the edit is allowed. A .git directory means a primary checkout; if that repository is one the pipeline dispatches work into, the edit is denied, and the denial names the correct worktree path. The decision is made from files on disk, and once a guarded primary has been identified, any later error still denies, so a malfunction cannot open the gate.

Specs get no exemption. A spec rides the pull request with the code it describes, so it is written on the task branch in a worktree like everything else.

Adopting it

For a team running more than one agent, the minimum is: one worktree per concurrent task, branches cut from a freshly fetched remote reference, git branch -d as the cleanup check, and a pre-edit hook that refuses writes to the primary checkout. Instructions explain the rule; the hook makes sure it holds.