Two or three coding agents at once looks like free speed. It only is free when the agents share nothing — not a working directory, not a branch, not a file. This guide covers the one primitive that makes that true, the exact failure mode you get without it, and the discipline that keeps it working past three agents.
Running two or three coding agents at once looks like an obvious win: three tasks finish in roughly the time of one. That math is real, but it holds only under a condition that's easy to skip past — the tasks have to be genuinely independent. Independent means something precise here, not just "different tickets": if you applied either agent's diff to the shared starting point with the other agent's diff completely absent, would the result be identical either way? If yes, the tasks are independent and parallel work is free. If no, you don't have two parallel tasks — you have one task with two writers, and the dependency you skipped past at launch time reappears later as a merge conflict, at a point when it's far more expensive to resolve than it would have been up front.
The clean case is common and looks like this: one agent building a new page, another fixing an unrelated bug in a different file, a third updating a config nothing else touches. None of the three diffs, applied together, would produce anything different than applying them one at a time in any order. That's what makes an agent's loop — prompt, act, observe, repeat — safe to run three times over without the copies coordinating with each other at all.
The fake case is subtler, because it still feels parallel while it's running. Say agent two's task assumes a schema field that agent one is in the middle of adding. Launching both "at once" doesn't remove that dependency — it just moves the point where it gets resolved from before the work starts (a five-second decision: sequence these two) to after both agents report done (a merge conflict, or worse, a silent logical mismatch that compiles cleanly and ships wrong). You don't save the time the dependency cost; you defer it to a moment when there's real code on both sides to reconcile instead of a task description to reorder. Writing tasks that are actually independent — narrow, verifiable, sized to one commit — is most of what spec discipline buys you before an agent ever starts typing.
The default git setup is one working directory per repository — files on disk, one staging area, one HEAD pointing at the currently active branch. The full mechanics of those three pieces live in /git; the fact that matters here is narrower and easy to miss: that working directory has no concept of whose edit is whose. It only ever reflects one state — whatever is on disk and staged the instant a git command runs. Two agents pointed at the same directory are, to git, indistinguishable from one operator typing unusually fast into the same terminal.
That would be harmless if every command failed loudly the moment two writers collided. It doesn't, because nothing about git's real invariants — content-addressed, immutable commits — gets violated by two agents sharing a directory. Every command each agent runs is individually correct. What breaks is something git was never asked to protect: which agent's edit happened to be sitting in the working tree at the exact moment the other agent's command executed. Three concrete versions of this, all common:
git switch (or checkout) rewrites every tracked file on disk to match the target branch's snapshot — but if your uncommitted edit wouldn't be overwritten by that swap, git keeps it rather than erroring. Another agent's half-finished edit rides along onto a branch it has nothing to do with, silently.git add -A stages every modified file currently on disk, regardless of who touched it. A broad-brush stage from one agent nets whatever the other left dirty.Play it out below: the same two tasks, first sharing one checkout, then given one worktree each.
Nothing in that collision was a git bug. It's git doing exactly what each command asked, one command at a time, blind to intent — because intent was never something git tracked. The fix isn't a smarter prompt telling agents to coordinate; a locking convention written in English is not a lock, and it will not survive contact with a process that reads instructions statistically rather than obeying them. The fix is structural: give every concurrent writer a working directory that nothing else touches.
A worktree is an additional working directory attached to an existing repository — its own files on disk, its own staging area, its own HEAD — pointed at the same object database as every other worktree of that repo: the same commit graph, the same file blobs, the same branch pointers. Nothing about the history is duplicated, only the checked-out files. That's different from a second clone, which copies the entire object database and needs its own push and pull to stay in sync with anything else. A worktree is created in under a second, costs disk space equal to one checkout's worth of files, and any commit made inside it is visible to every other worktree of the same repository immediately — there's nothing to sync, because there's only one object database underneath all of them.
One rule makes the isolation actually hold: a branch can be checked out in exactly one worktree at a time. That's not an arbitrary limit — it's what stops two worktrees from independently trying to move the same branch pointer and corrupting each other's view of where that branch actually is. The failure mode when this rule catches an unattended process — and the one-line fix for it — is covered in full in /git. The corollary for parallel agents is the rule to actually operate by: one branch per worktree per agent, always. Pointing two agents at worktrees checked out to the same branch doesn't buy isolation — it just relocates the shared-checkout problem one level up, because both worktrees are still racing to move one pointer.
What's genuinely safe to share across worktrees is the object database, precisely because every object in it is content-addressed and immutable (again, see /git for why). Two worktrees can commit at the same instant with zero coordination, because a commit never edits another commit — it only ever adds a new one and, as its last step, moves its own branch's pointer forward. That's the one piece of true concurrency git gives you for free. What is not shared — working tree files, the staging area, HEAD — is exactly what caused every failure in the section above. Give each writer its own copy of those three things and there is nothing left to collide on.
Worktrees solve the collision problem. They solve nothing else on their own — a worktree nobody is watching is just a directory that can go stale, and a worktree whose agent got killed mid-task is indistinguishable from one still working until someone checks. The isolation only pays off inside a stack of matching commitments, one for one:
git worktree list audit should read like a task board, not a puzzle.Naming and lifecycle follow directly once the branch and worktree share a name. Create one per task, work inside it exclusively, and remove it once the merge lands (next section):
git worktree add ../parallel-agents parallel-agents # create — one per task, branch name = folder name git worktree list # audit mid-day — should read like a task board git worktree remove ../parallel-agents # remove once the branch has merged
Worktrees are cheap enough to be disposable — there is no cost to creating one per task and deleting it the same day. The mistake is letting one outlive its task: an idle worktree still holds its branch checked out, which means it's still occupying the one slot that branch is allowed to have, quietly blocking whatever tries to check that branch out next.
Once several agents finish, you don't have one action left — you have that many merges, and both the order and the manner matter. Three rules cover it:
Integrate one branch at a time. Even when every merge is a trivial fast-forward, keep them separate rather than merging everything into main in one motion. A single branch's diff reads start to finish and reverts cleanly if it turns out wrong; three branches merged together produce one diff that tangles three unrelated intents, which is much harder to review and much harder to undo just one of.
Review before merge, unconditionally. Worktree isolation is about editing safety — it guarantees an agent's work wasn't corrupted by another agent's commands. It says nothing about whether the work is correct. An agent can finish in perfect isolation and still ship the wrong diff; the review step doesn't disappear because the collision problem did.
Order only matters where branches overlap. When every finished branch touches a disjoint set of files — the common case, if tasks were chosen well per the first section — merge order is arbitrary. Pick any order; every merge is clean, most are outright fast-forwards. When two branches touch the same file — a shared config, a shared index page, a schema both needed — the second one to land will not merge cleanly, because the commit it branched from no longer matches current main. The fix is the discipline any team already uses: the branch merging second rebases onto the now-updated main, replaying its own commits on top and resolving the overlap once, inside its own branch, before it ever touches shared history. Keeping tasks commit-sized (/specs, again) is what keeps that rebase small — usually one file, often one hunk — instead of a sprawling conflict nobody wants to untangle at 6pm.
Pick a merge order below and watch which pairs need the extra step.
None of this needs a human staring at a dashboard to work. It needs a sequence: land one branch, confirm it actually reached the shared branch — "agent says done" and "commits are on origin/main" are separate, independently failable claims — then let the next branch rebase onto that new baseline before it merges.
Put the whole stack together and a concurrent day looks like this. Three tasks start at once, each in its own worktree, its own branch, its own persistent session:
09:02 three tasks launch — worktrees parallel-agents, specs, sessions; one branch and one persistent session each 11:40 parallel-agents finishes first (smallest task). Diff reviewed, merged to main, worktree removed. 13:15 specs finishes. Touches specs.html plus one new line in index.html's guide list — nothing merged so far shares those files. Clean merge, worktree removed. 16:50 sessions finishes. Touches sessions.html plus the same line in index.html. Rebases onto current main (which now carries specs's nav line), resolves the one overlapping hunk by keeping both lines, pushes, merges clean. Worktree removed. 17:00 git worktree list shows only the primary checkout. Three tasks shipped; nothing stranded, nothing clobbered.
The total wall-clock for the day is roughly the length of the longest single task plus the few minutes the last rebase took — not the sum of all three, which is the entire promise from the first section, delivered because the conditions held: the tasks were genuinely disjoint at the file level (bar one small, expected, single-line overlap), each agent had its own worktree, branch, and session, and the merges landed one at a time in the order agents actually finished rather than a fixed schedule that would have blocked the fastest task behind the slowest.
TermRoam gives every task its own worktree and its own persistent session by construction — there's no manual step where an agent shares a directory with another task's in-flight work. The launch step refuses to double-book a repository and branch with two live agents at once, queuing additional work behind whatever is already running there instead of starting it beside it. When a task finishes, the merge step lands it, verifies the commits actually reached the shared branch rather than trusting the agent's own "done" claim, and rebases the next queued branch onto the new baseline automatically before its merge — the sequencing this guide describes, running without anyone watching a dashboard for it.