The first time I ran three AI coding agents on the same repo, they all edited src/main.rs within 30 seconds of each other. Two hours of merge conflict resolution later, I discovered a git feature I'd never used: worktrees.
They solve the multi-agent conflict problem completely, and they've been in git since 2015.
The Problem
If you've ever run more than one AI coding agent on the same project — two Claude Code sessions, or a mix of Claude Code, Codex, and Aider — you've hit this wall:
Agent A is editing src/auth.rs. Agent B is also editing src/auth.rs. Someone loses.
It doesn't matter how smart the agents are. If two processes write to the same file at the same time, one overwrites the other. You end up with broken code, lost work, or a merge conflict that takes longer to resolve than the task itself.
The naive fix is telling agents to work on different files. But you can't always guarantee that. A refactoring that touches the module interface will affect every file that imports it. An agent adding a new API endpoint might need to update the same router file as another agent adding a different endpoint.
You need filesystem-level isolation. Each agent needs its own complete copy of the repo, on its own branch, where it can read, write, and run tests without affecting anything else.
That's exactly what git worktrees are.
What Git Worktrees Are
A git worktree is a linked working copy of your repository. Same .git directory, different filesystem path, different branch.
# Your main repo
~/my-project/ # on branch: main
# Create a worktree for agent-1
git worktree add ./agent-1 -b agent-1/task-42
# Now you have:
~/my-project/ # on branch: main
~/my-project/agent-1/ # on branch: agent-1/task-42
agent-1/ is a complete working copy. It has every file, every directory, the full project structure. But it's on its own branch. Changes in agent-1/ don't appear in the main directory, and vice versa.
The key insight: worktrees share the .git directory. There's no cloning, no duplicate object database, no extra remote connections. Git just checks out a different branch into a different directory. It's fast, and it's cheap on disk.
Basic Commands
Everything you need fits in four commands:
# Create a worktree on a new branch
git worktree add ./agent-1 -b agent-1/task-42
# Create a worktree on an existing branch
git worktree add ./agent-2 feature/api-endpoints
# List all worktrees
git worktree list
# /Users/me/my-project abc1234 [main]
# /Users/me/my-project/agent-1 def5678 [agent-1/task-42]
# /Users/me/my-project/agent-2 ghi9012 [feature/api-endpoints]
# Remove a worktree when you're done
git worktree remove ./agent-1
Each worktree is independent. You can cd into it, run your editor, run tests, make commits — all without touching the other worktrees or the main directory.
Setting Up Worktrees for Multiple Agents
Here's the setup I use for three parallel agents:
#!/bin/bash
# setup-agents.sh — create isolated worktrees for parallel agents
PROJECT_DIR=$(pwd)
WORKTREE_DIR="$PROJECT_DIR/.agents"
mkdir -p "$WORKTREE_DIR"
for i in 1 2 3; do
BRANCH="eng-$i/task-ready"
# Create worktree if it doesn't exist
if [ ! -d "$WORKTREE_DIR/eng-$i" ]; then
git worktree add "$WORKTREE_DIR/eng-$i" -b "$BRANCH"
echo "Created worktree: eng-$i on branch $BRANCH"
else
echo "Worktree eng-$i already exists"
fi
done
echo ""
git worktree list
After running this, your project looks like:
my-project/
├── src/ # main branch
├── .agents/
│ ├── eng-1/ # eng-1/task-ready branch
│ │ └── src/ # complete copy
│ ├── eng-2/ # eng-2/task-ready branch
│ │ └── src/ # complete copy
│ └── eng-3/ # eng-3/task-ready branch
│ └── src/ # complete copy
└── .git/ # shared by all worktrees
Point each agent at its worktree directory. Agent 1 works in .agents/eng-1/, Agent 2 in .agents/eng-2/, Agent 3 in .agents/eng-3/. They can all edit src/main.rs simultaneously — they're editing different copies on different branches.
The Performance Trick Nobody Mentions
The obvious approach is to create a fresh worktree for every task and destroy it when the task is done:
# The obvious (slow) approach
git worktree add ./agent-1 -b agent-1/task-42
# ... agent works ...
git worktree remove ./agent-1
# Next task
git worktree add ./agent-1 -b agent-1/task-43
# ... agent works ...
git worktree remove ./agent-1
Don't do this. Worktree creation copies the entire working tree. On a large repo with node_modules/ or target/ directories, this is slow and wastes disk space. And destruction loses any build cache, compiled artifacts, or debugging context.
Instead: keep persistent worktrees per agent and rotate branches.
# One-time setup (do this once)
git worktree add .agents/eng-1 -b eng-1/ready
# Between tasks (fast — just a branch switch)
cd .agents/eng-1
git checkout main && git pull
git checkout -b eng-1/task-43
The branch switch is near-instant regardless of repo size. The filesystem stays predictable. Build caches survive between tasks. And if something goes wrong, the worktree is still there for debugging.
This is the pattern Batty uses internally — persistent worktrees at .batty/worktrees/<agent-name>, with branch rotation between tasks.
Running Tests in Isolation
The biggest benefit of worktree isolation isn't preventing file conflicts — it's enabling reliable test runs.
When three agents share a working directory, running cargo test tells you the combined state works. But you don't know which agent's changes broke which test. With worktrees, each agent's tests reflect only that agent's changes against a clean baseline.
# Test each agent's work independently
cd .agents/eng-1 && cargo test
cd .agents/eng-2 && cargo test
cd .agents/eng-3 && cargo test
If eng-2's tests fail, you know it's eng-2's code. Send the failure output back to that agent. The other agents keep working, unaffected.
# Quick test gate script
test_agent() {
local agent_dir="$1"
local agent_name=$(basename "$agent_dir")
echo "Testing $agent_name..."
output=$(cd "$agent_dir" && cargo test 2>&1)
if [ $? -eq 0 ]; then
echo "$agent_name: PASS"
return 0
else
echo "$agent_name: FAIL"
# Send last 50 lines back to the agent
echo "$output" | tail -50 > "$agent_dir/.test-failure"
return 1
fi
}
# Test all agents in parallel
test_agent .agents/eng-1 &
test_agent .agents/eng-2 &
test_agent .agents/eng-3 &
wait
Each agent's tests run in parallel, in isolation. No interference.
The Merge Problem (and How to Solve It)
Worktrees prevent conflicts during active work. But eventually, each agent's branch needs to merge back to main. If three agents finish at the same time, you need to sequence the merges.
The wrong approach: merge all three simultaneously. You'll hit conflicts because Agent 2's merge doesn't account for Agent 1's changes that just landed.
The right approach: serialize merges.
#!/bin/bash
# merge-sequential.sh — merge agent branches one at a time
LOCK_FILE=".git/merge.lock"
merge_agent() {
local branch="$1"
local timeout=60
# Acquire lock (wait up to 60 seconds)
local start=$SECONDS
while ! mkdir "$LOCK_FILE" 2>/dev/null; do
if (( SECONDS - start >= timeout )); then
echo "Timeout waiting for merge lock"
return 1
fi
sleep 1
done
echo "Merging $branch..."
# Rebase on latest main, then merge
git checkout "$branch"
git rebase main
if [ $? -ne 0 ]; then
echo "Rebase conflict on $branch — needs manual resolution"
git rebase --abort
rmdir "$LOCK_FILE"
return 1
fi
git checkout main
git merge --no-ff "$branch"
# Release lock
rmdir "$LOCK_FILE"
echo "Merged $branch successfully"
}
merge_agent "eng-1/task-42"
merge_agent "eng-2/task-43"
merge_agent "eng-3/task-44"
The pattern:
- Agent 1 finishes → acquire lock → rebase on main → merge → release lock
- Agent 2 finishes → acquire lock → rebase on updated main (includes Agent 1's work) → merge → release lock
- Agent 3 finishes → same, on main that now includes both previous merges
Each agent rebases on the latest main before merging. This means each merge accounts for everything that came before it. Conflicts are still possible, but they're normal git conflicts at merge time — manageable and expected — instead of chaotic file overwrites during active work.
When Worktrees Aren't Enough
I want to be honest about the limits. Worktrees solve file-level isolation, but they don't solve everything:
Tasks with real-time dependencies. If Agent A is building the API and Agent B is building the frontend that calls it — and B needs to know A's endpoint signatures while working — worktree isolation won't help. B needs coordination with A, not isolation from it. These tasks need to be sequenced, not parallelized.
Shared configuration files. If every feature branch needs to modify Cargo.toml to add a dependency, you'll get merge conflicts on that file regardless of worktree isolation. The fix: decompose tasks so that shared config changes happen in a separate, first step.
Very large repos. While worktrees share the .git directory, each one still has its own working tree. A repo with a 2GB node_modules/ directory means each worktree adds 2GB. For large repos, consider using .gitignore patterns to exclude build artifacts from the worktree copies, or sharing dependency caches via symlinks.
Agent coordination. Worktrees isolate agents. If you need agents to communicate — "I changed this API interface, update your code" — you need a coordination layer on top. A markdown kanban board or message passing system. Worktrees provide the foundation, not the whole solution.
The Complete Setup
Putting it all together — worktrees, test gating, and sequential merge:
# 1. Create persistent worktrees (one-time)
for i in 1 2 3; do
git worktree add .agents/eng-$i -b eng-$i/ready
done
# 2. Assign a task (per task)
cd .agents/eng-1
git checkout main && git pull
git checkout -b eng-1/task-42
# Point your AI agent at this directory
# 3. Test gate (when agent reports done)
cd .agents/eng-1
cargo test # or npm test, pytest, etc.
# Exit 0 → ready to merge
# Exit 1 → send output back to agent
# 4. Sequential merge (after tests pass)
# Acquire lock → rebase on main → merge → release lock
This is the workflow I ran manually for months before automating it. It's the right workflow even without any tooling — just bash scripts and git commands.
If you want it automated — persistent worktrees, test gates, merge serialization, all running in tmux panes — Batty wraps this pattern into a single command:
cargo install batty-cli
batty init --template simple # architect + manager + 3 engineers
batty start --attach
Each engineer gets a persistent worktree at .batty/worktrees/<name>. Tests run automatically when an engineer reports done. Merges serialize with a 60-second lock timeout. You watch it all in tmux.
But the pattern is what matters. Worktrees are a git primitive available to everyone. You don't need a framework to use them.
Quick Reference
# Create worktree on new branch
git worktree add <path> -b <branch>
# Create worktree on existing branch
git worktree add <path> <branch>
# List worktrees
git worktree list
# Remove worktree
git worktree remove <path>
# Prune stale worktree references
git worktree prune
# Rotate branch (for persistent worktrees)
cd <worktree-path>
git checkout main && git pull && git checkout -b <new-branch>
Have you tried worktrees for multi-agent work? I'm curious about edge cases people have hit that I haven't covered. And if you have a different solution for agent isolation, I'd love to hear it.
Batty is open source, built in Rust, and published on crates.io. GitHub: github.com/battysh/batty
Top comments (0)