-
Notifications
You must be signed in to change notification settings - Fork 0
07 worktrees running agents in parallel
📖 This page is generated from
modules/07-worktrees-running-agents-in-parallel/README.md. Edit the source, not the wiki; edits here are overwritten on the next sync. Run the hands-on labs from the repo, linked inline.
⬅ Previous: Module 6: Branches as Sandboxes for Experiments
A branch lets one agent try something risky. A worktree lets two agents try two things at the same wall-clock time, in separate folders, on separate branches, without touching each other's files. This is the move that turns "I run an agent" into "I run agents."
-
Module 1: the
tasks-app. The running example continues here. -
Module 2: Version control. The
tasks-appis already a Git repo with commits, and you read a project's state fromgit status/git diff/git log. Each worktree has its own answer to those, which is the whole point. - Module 4: Getting the AI out of the browser. The agents in this module edit real files in a folder. You'll point an editor-integrated AI session at each worktree directory.
- Module 6: Branches. You can create a branch, switch to it, merge it back, and resolve a conflict. A worktree is the physical counterpart to the logical isolation a branch already gives you, so this module makes no sense without it.
If you parachuted in: you minimally need a Git repo with at least one commit and a working understanding of branches.
By the end of this module you can:
- Explain why a single working directory is the bottleneck the moment you want two agents running at once, and why branches alone don't fix it.
- Create, list, and remove linked worktrees (
git worktree add/list/remove), each on its own branch. - Run two independent AI edit sessions on the same project simultaneously without them colliding on files, branches, or app state.
- Merge parallel work back to
mainand clean up worktrees without leaving stale state behind. - State precisely what worktrees share (history/objects) and what they don't (working files, uncommitted changes, checked-out branch), and where that bites.
Module 6 gave you branches: spin one up, let the agent do something wild, keep it or throw it away
with zero risk to main. That's logical isolation: two lines of history that don't affect each
other.
But there's a physical fact branches don't change: a repo has exactly one working directory, and
only one branch can be checked out in it at a time. The files on disk are the files. When you
git switch other-branch, Git rewrites those same files in place to match the other branch. There's
one floor, and switching branches yanks it out and lays a different one down.
That's fine when you are the only one standing on the floor. It falls apart the instant you want two things happening at once. Watch it break:
# Agent A added a `wipe` command and committed it on its own branch:
git switch -c feature/wipe
# ...agent A edits the usage line in cli.py to add `wipe`...
git commit -am "Add wipe command"
# You start Agent B on a fresh branch off main; it begins editing the SAME
# usage line to add `remaining`, and hasn't committed:
git switch main
git switch -c feature/remaining
# ...agent B edits cli.py, hasn't committed...
# You try to hop the working directory back to Agent A's branch to check on it:
git switch feature/wipe
# error: Your local changes to the following files would be overwritten by checkout:
# cli.py
# Please commit your changes or stash them before you switch branches.Git stops you, and correctly so. Switching to feature/wipe would overwrite Agent B's uncommitted edits
to cli.py with Agent A's committed version of those same lines, so Git refuses rather than silently
destroy the work. But now you're stuck choosing between bad options:
-
Commit half-finished work just to get it out of the way (pollutes history, and Agent B's
remainingcommand isn't done). - Stash it (now Agent B's context lives in a stash you have to remember to pop, and Agent B, a long-running session that thinks its files are right there, is now editing files that silently changed under it).
-
Run both agents on the same branch in the same folder, and watch them overwrite each other's
edits, because they're both writing the same
cli.pywith no idea the other exists.
The branch was never the problem. The single working directory is. You need two floors.
git worktree gives you exactly that: additional working directories attached to the same
repository, each with its own checked-out branch. One repo, many checkouts.
$ cd ~/ai-workflow-course/tasks-app # your existing repo from Module 2
$ git worktree add ../tasks-app-remaining -b feature/remaining
Preparing worktree (new branch 'feature/remaining')
HEAD is now at a1b2c3d Add done commandThat command creates a brand-new folder, ~/ai-workflow-course/tasks-app-remaining, containing a full
checkout of your project on a new branch feature/remaining. Your original folder is untouched,
still on its own branch. You now have two real directories you can cd into, edit, and run
independently:
~/ai-workflow-course/
tasks-app/ ← the "main" worktree, on (say) main
tasks-app-remaining/ ← a "linked" worktree, on feature/remaining
Both are backed by one repository. There is a single .git: a single object store, a single
history, a single set of branches and tags. The linked worktree doesn't get its own copy of the
history; it gets its own copy of the files, and a pointer back to the shared .git. (If you peek,
the linked worktree has a tiny .git file, not a directory; it just points at the real one in
the main worktree.)
This is the distinction that makes the whole thing click:
A clone copies the history. A worktree copies the working files and shares the history.
A clone is a second repository: separate objects, separate .git, you sync between them with
pull/push (Module 8). A worktree is one repository checked out in two places. A commit you make in
one worktree is instantly an object in the shared store. No pushing, no pulling; it's just there,
because there's only one store.
Think of the shared object store as the project's single, settled past: every commit, on every
branch, in one place. Each worktree is a different present moment checked out of that past: this
folder is "the project as of feature/remaining," that folder is "the project as of main." They all
write to the same past (commits go to the shared store), but each lives in its own present (its own
files on disk).
That's why worktrees are the natural payoff of branches. A branch is a logical "what if." A worktree makes that "what if" a place you can stand: a folder you can open, run, and point an agent at, while every other "what if" stays open in its own folder at the same time.
git worktree add <path> -b <new-branch> # new folder + new branch, checked out there
git worktree add <path> <existing-branch> # new folder, checks out an existing branch
git worktree list # every worktree, its path, and its branch
git worktree remove <path> # delete a worktree (must be clean, or use --force)
git worktree prune # forget worktrees whose folders were deleted by handgit worktree list is your map:
$ git worktree list
~/ai-workflow-course/tasks-app a1b2c3d [main]
~/ai-workflow-course/tasks-app-remaining d4e5f6a [feature/remaining]
~/ai-workflow-course/tasks-app-wipe 7g8h9i0 [feature/wipe]Three folders, one repo, three branches checked out simultaneously. No stashing, no switching, no collisions.
Here's the payoff the module exists for. An AI agent isn't a quick command; it's a long-running session that holds a working directory and usually a running process (your app, your test runner, a watcher). Two such sessions in one folder is a guaranteed mess:
- They edit the same files; their changes interleave and clobber each other.
- One commits or switches branches and the floor moves under the other.
- Their app runs and test runs share state and step on each other's output.
Give each agent its own worktree and every one of those collisions disappears by construction:
-
Separate folders → separate files. Agent A literally cannot touch Agent B's
cli.py; it's a different file on disk. - Separate branches → separate history lines. Neither can move the other's branch.
- Shared object store → when both finish, merging their work back together is trivial; it's all already in one repo. No syncing between copies.
So "run two agents at once" stops being a coordination nightmare and becomes "open two folders." That's the local foundation; doing this at scale (many agents, split work, kept reviewable) is Module 26 (Orchestrating Multiple Agents). Worktrees are the primitive that module is built on. Learn the primitive here on two; the orchestration comes later.
Worktrees look like a niche convenience: a way to dodge git stash when you switch branches. For
AI-assisted work they're closer to essential, for a reason specific to how agents behave:
- An agent assumes its working directory is stable. It reads files, reasons about them, and writes them back over a session that can run for many minutes. If a second agent (or you, switching branches) rewrites those files underneath it, the first agent is now operating on a reality that silently changed. That's the worst kind of bug, because nothing errors; the work just comes out wrong. A worktree pins each agent to a directory nobody else will touch.
- Parallelism is the whole point of cheap agents. The model is fast and you can run several at once: a feature here, a bugfix there, a doc update in a third. The constraint was never the model; it was that they'd trip over one repo. Worktrees remove the constraint.
-
Each worktree is its own durable memory (Module 2). A fresh agent dropped into
tasks-app-remainingreadsgit status/git diff/git logand gets that branch's ground truth, not a blur of three agents' half-finished work. Per-agent isolation makes per-agent "where were we?" actually answerable. - It keeps parallel AI output reviewable. Each agent's work lands as its own branch with its own clean history, instead of a tangle of interleaved edits on one branch that no human could ever review. That reviewability is what later lets agents run with less supervision (Unit 5).
You don't reach for worktrees because you read about them. You reach for them the first time you try to run two agents and watch them overwrite each other's work.
Starting point (this lab is skip-friendly). You do not need to have done the earlier labs. To begin from a clean, known state, copy this module's snapshot into a fresh
tasks-appand make the first commit:mkdir -p ~/ai-workflow-course/tasks-app cp -r ~/ai-workflow-course/modules/07-worktrees-running-agents-in-parallel/lab/start/. ~/ai-workflow-course/tasks-app/ cd ~/ai-workflow-course/tasks-app && git init -b main && git add -A && git commit -m "start: module 7"Already carrying your
tasks-appfrom earlier modules? Keep using it and ignore this box. Lab language: shell (Git commands), plus two AI edit sessions on thetasks-app.
In this lab you'll run two AI sessions at the same time on the same project (one adding a
wipe command, one adding a remaining command), each in its own worktree, and watch them not
collide. Then you'll merge both back and clean up. (We use two commands your carried-forward
tasks-app doesn't have yet, so neither agent re-adds something that already exists: the lesson is
the parallel isolation, not the commands.)
You'll need:
- The
tasks-appGit repo from Module 2 (initialized, with a few commits). If you skipped ahead, rungit init -b mainand make one commit first; the-b mainmatches Module 2, so thegit switch mainsteps below resolve. - Git 2.5 or newer (worktrees landed in 2.5; any modern Git is fine, run
git --versionto check). - Two editor-integrated AI sessions you can run at once (Module 4): two editor windows, or two terminal AI sessions. If you only have a browser chat, you can still do the lab; just treat each worktree folder as a separate copy-paste context.
- The starter scripts and prompts in this module's
lab/folder, at~/ai-workflow-course/modules/07-worktrees-running-agents-in-parallel/lab/. As established in Module 4, the course's lab scripts live in the course repo whiletasks-appis a separate folder. Here the worktree git is the AI's job (the Module 4 pivot): you direct the coordinating session to run thegit worktreecommands, or hand itsetup-worktrees.sh/cleanup-worktrees.shto run, and you verify the result. You don't type the git by hand.
Before fixing it, reproduce the bottleneck from "Where branches alone run out." The wall only appears
when both branches touch the same line of cli.py (one committed, one not), so we make each
branch edit the usage line. (The sed … > tmp && mv is just a portable, copy-pasteable stand-in for
the edit an agent would make.) In your tasks-app:
cd ~/ai-workflow-course/tasks-app
# Agent A's branch: add `wipe` to the usage line and commit it.
git switch -c feature/wipe
sed 's/done <index>/done <index> | wipe/' cli.py > cli.tmp && mv cli.tmp cli.py
git commit -am "Add wipe command (demo)"
# Agent B's branch, off main: start adding `remaining` to the SAME line; leave it uncommitted.
git switch main
git switch -c feature/remaining
sed 's/done <index>/done <index> | remaining/' cli.py > cli.tmp && mv cli.tmp cli.py
# Try to hop the working directory back to Agent A's branch:
git switch feature/wipe
# error: Your local changes to the following files would be overwritten by checkout:
# cli.py
# Please commit your changes or stash them before you switch branches.(The sed matches done <index>, which is still in your usage line no matter how many commands
you've added since Module 1, and inserts a new one right after it, so both branches edit the same
line.) Git refuses: moving the one working directory to feature/wipe would overwrite Agent B's
uncommitted edit with feature/wipe's committed version of that line. That is the wall: one
directory can't hold two agents' in-progress work at once. These two branches existed only to feel
the collision, so clean them up before continuing:
git restore cli.py # drop Agent B's uncommitted edit
git switch main
git branch -D feature/wipe feature/remaining # throw away the demo branchesAn agent that lives inside a worktree can't create its own worktree, so the coordinating
session (the AI you already have pointed at tasks-app from Module 4) sets them up. That's Claude
Code in this example; sub your own agent. Tell it:
"From the
tasks-apprepo, create two linked worktrees as siblings of this folder: one at../tasks-app-wipeon a new branchfeature/wipe, and one at../tasks-app-remainingon a new branchfeature/remaining. Then show megit worktree list."
It runs the git worktree add calls for you. (If you'd rather it run a script than type the commands,
hand it lab/setup-worktrees.sh, which does exactly this.) Then verify by hand:
cd ~/ai-workflow-course/tasks-app
git worktree list # should show main + feature/wipe + feature/remainingThree folders backed by one repo, and you didn't type a git command. You directed, the agent did the git, you confirmed.
This is the part to actually do simultaneously, not one then the other.
-
Open
~/ai-workflow-course/tasks-app-wipein one editor/AI session. Give it the prompt inlab/agent-a-prompt.md: add awipecommand that removes all tasks. -
Open
~/ai-workflow-course/tasks-app-remainingin a second editor/AI session. Give it the prompt inlab/agent-b-prompt.md: add aremainingcommand that prints the number of pending tasks. -
Let both work at the same time. While they run, prove the isolation from a third terminal, but use commands that already exist. (
wipeandremainingdon't yet; the agents are still writing them.) Give each worktree its own task and list it:cd ~/ai-workflow-course/tasks-app-wipe && python3 cli.py add "from worktree A" && python3 cli.py list cd ~/ai-workflow-course/tasks-app-remaining && python3 cli.py add "from worktree B" && python3 cli.py list
Each
listshows only its own task: worktree A never sees "from worktree B" and vice versa. Each worktree has its owntasks.json(gitignored runtime state, not shared history), so the two running apps don't even share data. Separate files, separate state, while both agents work. -
Review each agent's diff, then have that worktree's own session commit its work on its branch. In the
tasks-app-wipesession, read the diff and tell the agent:"The diff looks right. Commit this on the branch with the message 'Add wipe command'."
Do the same in the
tasks-app-remainingsession (message 'Add remaining command'). Each agent stages and commits its own work; you verify each landed and left a clean tree:cd ~/ai-workflow-course/tasks-app-wipe && git status && git log --oneline -1 cd ~/ai-workflow-course/tasks-app-remaining && git status && git log --oneline -1
Two agents, two commits, two branches, and neither ever saw the other's files.
-
Now the new commands exist: run each in its own worktree to watch it work:
cd ~/ai-workflow-course/tasks-app-wipe && python3 cli.py wipe # agent A's new command cd ~/ai-workflow-course/tasks-app-remaining && python3 cli.py remaining # agent B's new command
remainingcounts a single pending task, the one you added to worktree B in step 3, because B'stasks.jsonis the only state it can see.
Both feature branches need to come home to main. Back in the coordinating session (the one on
tasks-app), direct the merges:
"On the
tasks-apprepo: switch tomain, then mergefeature/wipeandfeature/remaininginto it."
Both commits are already in the shared object store, so there's nothing to fetch; the merges are
local and instant. The second merge may hit a small conflict in cli.py if both agents added
their elif branch in the same spot. That's expected, and it's a merge-time event, not a
parallel-work collision. When it happens, direct the agent to resolve it with the same conflict skill
from Module 6:
"
cli.pyhas a merge conflict. I want the final file to keep BOTH thewipeandremainingcommands. Resolve it and complete the merge."
Then verify the result before you trust it, the same way you did in Module 6:
cd ~/ai-workflow-course/tasks-app
git diff # no conflict markers remain
python3 cli.py list # the app still runs
python3 cli.py wipe # both new commands work
python3 cli.py remainingNow tear down the worktrees. Direct the coordinating session:
"Remove the
tasks-app-wipeandtasks-app-remainingworktrees and prune any stale records."
It runs git worktree remove on both folders and git worktree prune. (Hand it
lab/cleanup-worktrees.sh if you'd rather it run the script.) The branches are already merged into
main, so the work is safe. Verify only the main worktree is left:
git worktree list # only the main worktree remainsWorktrees are sharp tools. The honest caveats:
-
You cannot check out the same branch in two worktrees. Git refuses
(
fatal: 'main' is already checked out at ...). This is a feature, not a bug; it's exactly what stops two agents from writing the same branch, but it surprises people. One branch, one worktree. -
Uncommitted work is not shared. Only commits go to the shared store. The edits sitting
modified-but-uncommitted in
tasks-app-remainingexist only in that folder. If yougit worktree removea dirty worktree, Git refuses unless you pass--force, and--forcethrows that uncommitted work away for good. Commit before you remove. -
Cleanup is a two-part chore. Deleting a worktree folder with
rm -rfdoes not tell Git it's gone; you'll have a stale entry ingit worktree listforever until you rungit worktree prune. Prefergit worktree remove <path>, which does both. (The cleanup script does this for you.) -
One shared object store means one shared fate. All worktrees depend on the main repo's
.git. Delete or move the main worktree and every linked worktree breaks; they're pointing at a.gitthat isn't there anymore. Worktrees are not independent backups; they're one repository. (The backup story is still Module 8: get the history off this one machine.) - Worktrees don't prevent merge conflicts; they defer them. Two agents editing the same lines will still conflict when you merge. What worktrees buy you is that the conflict happens once, on your terms, in one calm step (Module 6), instead of two live agents corrupting each other's files in real time. Isolation during work; resolution after.
- Each worktree is a full set of working files. Cheaper than a clone (the history is shared), but not free: a worktree per agent means a working tree per agent on disk, plus whatever each agent's running process consumes. Fine for two; something to plan for when Module 26 takes this to many.
- Tooling that hardcodes the repo root can get confused. Anything keyed to an absolute path, a per-checkout cache, or "the one working directory" may need per-worktree setup. The committed AI config from Module 5 travels with each worktree (it's a tracked file), which is exactly why committing it pays off here: every agent in every worktree inherits the same instructions.
You're done when:
-
git worktree listshowed three entries at once, and you ran thetasks-appfrom two different worktree folders, adding a different task in each and watching each keep its owntasks.json. - You ran two AI sessions in parallel, each in its own worktree on its own branch, and confirmed
neither touched the other's files (different folders, different
tasks.json, different branch). - You merged both feature branches back into
main(resolving a conflict if one appeared) and the app has both new commands. - You cleaned up so that
git worktree listshows only the main worktree and the stray folders are gone, with no stale entries left behind. - You can state, without looking, what a worktree shares with the repo (history, objects, branches, tags) and what it keeps to itself (working files, uncommitted changes, its one checked-out branch).
When "run two agents at once" feels like "open two folders" instead of "orchestrate a stash dance," you've got it. This is the primitive Module 26 scales up; for now, two is plenty.
Continue to: Module 8: Remotes and Hosting (GitHub, the Alternatives, and Owning Your Repo) ➡
Generated from the ai-workflow-course repo • the model is the cheap, swappable part; the workflow is the durable skill.
Unit 1: Get out of the chat window
- 1 · The Copy-Paste Problem
- 2 · Version Control as a Safety Net
- 3 · Version Control for Words, Not Just Code
- 4 · Getting the AI Out of the Browser
- 5 · Commit the AI's Config, Not Just the Code
- 6 · Branches as Sandboxes for Experiments
- 7 · Worktrees for Running Agents in Parallel
Unit 2: Make it shareable, reviewable, recoverable
- 8 · Remotes and Hosting (GitHub, the Alternatives, and Owning Your Repo)
- 9 · Issues and the Task Layer
- 10 · Reviewing Code You Didn't Write
- 11 · Collaboration: Humans and Agents on One Repo
- 12 · When It Goes Wrong: Revert, Reset, and Recovery
Unit 3: Automate the checking and shipping
- 13 · Testing in the AI Era
- 14 · Continuous Integration
- 15 · Security Scanning for AI-Generated Code
- 16 · Containers and Reproducible Environments
- 17 · Secrets, Config, and Environments
- 18 · Continuous Delivery and Deployment
- 19 · Runners, the Compute Behind the Automation
Unit 4: Extend the AI into your systems
- 20 · MCP Servers, Giving the AI Hands
- 21 · Skills: Teaching the AI Your Playbook
- 22 · Securing Third-Party MCP Servers and Skills
- 23 · Working with Existing Codebases
Unit 5: AI in the Loop
- 24 · Assistive Agents (AI Review and Issue Triage)
- 25 · Module 25. Autonomous Agents: Issue-to-PR and Self-Healing CI
- 26 · Orchestrating Multiple Agents
- 27 · Module 27. Evals: Trusting an Agent That Acts Without You
Finale