Multi-Agent Coding Orchestration: Patterns for Running Parallel AI Agents Without Chaos
One coding agent is easy to reason about. Five running in parallel on the same repo is not โ here's how to structure worktrees, task queues, and merge gates so they don't destroy each other's work.
Teams that adopted a single coding agent (Claude Code, Cursor, Aider) in 2025 are now running several at once โ one per ticket, one per test suite, one babysitting CI. The GitHub trending charts this month are full of orchestration tools built for exactly this: coordinating multiple coding agents against the same codebase without them stepping on each other. If you've tried this yourself, you already know the failure mode: two agents touch the same file, one's changes silently overwrite the other's, and you spend longer untangling the mess than either agent saved you.
This post covers the patterns that actually hold up in production โ isolation, task decomposition, and merge gating โ with working examples you can adapt today.
Why "just run them in parallel" breaks
A single coding agent working alone is safe because it has exclusive access to the working tree. The moment you add a second agent to the same directory, you've introduced a race condition indistinguishable from two engineers editing the same file without pulling first. The naive fixes people try, and why they fail:
- Same directory, different terminals. Agent A writes a file mid-edit while Agent B reads it for context. Agent B now reasons about a half-written file and produces a plausible-looking patch against code that never stably existed.
- One agent, sequential tasks. Safe, but you've thrown away the parallelism โ you're back to single-agent throughput with extra orchestration overhead.
- Branch per agent, no merge discipline. Each agent works in isolation, which solves the read/write race, but you've pushed the conflict downstream to merge time, where a human now has to resolve five agents' worth of overlapping changes with none of the context for why each change was made.
The pattern that actually works separates three concerns that the naive approaches conflate: isolation (agents can't corrupt each other's working state), decomposition (tasks are scoped so agents rarely touch the same files), and gating (nothing lands on the shared branch without a checkpoint).
Pattern 1: Git worktrees for isolation
git worktree gives each agent its own checked-out directory sharing the same .git object store โ no clone overhead, no risk of one agent seeing another's uncommitted edits.
# Set up one worktree per agent, each on its own branch
git worktree add ../agent-auth-fix -b agent/auth-fix
git worktree add ../agent-rate-limit -b agent/rate-limit
git worktree add ../agent-flaky-test -b agent/flaky-test
# Each agent runs in its own worktree directory, completely isolated
cd ../agent-auth-fix && claude -p "Fix the session expiry bug in auth.py"
This is the mechanism behind most of the orchestration frameworks trending right now โ they're largely worktree management plus a scheduler on top. You don't need the framework to get the benefit; the primitive is git worktree itself, and it's worth understanding before you adopt a wrapper around it.
A minimal orchestration script that fans out tasks to worktrees:
import subprocess
from dataclasses import dataclass
from pathlib import Path
@dataclass
class AgentTask:
task_id: str
branch: str
prompt: str
scope: list[str] # file/directory globs this task is allowed to touch
def spawn_worktree(task: AgentTask, base_dir: Path) -> Path:
worktree_path = base_dir / task.task_id
subprocess.run(
["git", "worktree", "add", str(worktree_path), "-b", task.branch],
check=True,
)
return worktree_path
def run_agent(task: AgentTask, worktree_path: Path) -> subprocess.CompletedProcess:
scoped_prompt = (
f"{task.prompt}\n\n"
f"You may only modify files matching: {', '.join(task.scope)}. "
f"Do not touch any other files."
)
return subprocess.run(
["claude", "-p", scoped_prompt],
cwd=worktree_path,
capture_output=True,
text=True,
timeout=1800,
)
tasks = [
AgentTask("auth-fix", "agent/auth-fix", "Fix session expiry bug", ["src/auth/**"]),
AgentTask("rate-limit", "agent/rate-limit", "Add rate limiting to /api/search", ["src/api/search.py"]),
]
base = Path("../worktrees")
for task in tasks:
wt = spawn_worktree(task, base)
result = run_agent(task, wt)
print(f"{task.task_id}: exit={result.returncode}")
The scope field is doing real work here โ it's a prompt-level constraint, not an enforced sandbox, but combined with decomposition (below) it's usually enough to keep agents out of each other's files even without hard file-system permissions.
Pattern 2: Decompose by ownership boundary, not by ticket
The instinct is to split work by Jira ticket. That's wrong more often than it's right, because tickets don't respect module boundaries โ "fix auth bug" and "add rate limiting" might both need to touch middleware.py. Decompose instead along the codebase's actual ownership boundaries: modules, services, or files that one task can own exclusively for the duration of the run.
| Decomposition axis | Conflict risk | When to use |
|---|---|---|
| By ticket/task | High โ tickets ignore file boundaries | Only for tiny, well-isolated fixes |
| By file/module ownership | Low โ each agent has an exclusive write set | Default choice for parallel runs |
| By layer (frontend/backend/infra) | Medium โ shared contracts (API schemas, types) still collide | Good for larger, cross-cutting features |
| By test suite | Low for the fix, high if the underlying code overlaps | Best for fixing flaky/failing tests specifically |
If two tasks genuinely need to touch the same file, don't run them in parallel โ queue them sequentially against the same worktree, or assign one agent the merged task. A shared file is a signal that the tasks aren't actually independent, and trying to parallelize independent-looking-but-coupled work is where most orchestration setups quietly degrade into merge-conflict factories.
Pattern 3: Gate merges with a review agent, not a cron job
The biggest mistake teams make once isolation and decomposition are in place: auto-merging every agent branch that passes CI. CI catches whether the code runs โ it doesn't catch whether the change was scoped correctly, whether it duplicated logic that already exists elsewhere, or whether it silently changed a public API another agent's branch also assumes is stable.
Put a checkpoint between "agent finished" and "branch lands on main":
def review_and_merge(task: AgentTask, worktree_path: Path) -> bool:
diff = subprocess.run(
["git", "diff", "main", task.branch],
cwd=worktree_path, capture_output=True, text=True
).stdout
# scope check: did the agent stay inside its declared file boundary?
changed_files = subprocess.run(
["git", "diff", "--name-only", "main", task.branch],
cwd=worktree_path, capture_output=True, text=True
).stdout.splitlines()
import fnmatch
out_of_scope = [
f for f in changed_files
if not any(fnmatch.fnmatch(f, pattern) for pattern in task.scope)
]
if out_of_scope:
print(f"REJECTED {task.task_id}: touched out-of-scope files {out_of_scope}")
return False
tests_pass = subprocess.run(
["pytest", "-x"], cwd=worktree_path
).returncode == 0
if not tests_pass:
print(f"REJECTED {task.task_id}: tests failed")
return False
subprocess.run(["git", "checkout", "main"], cwd=worktree_path, check=True)
subprocess.run(["git", "merge", "--no-ff", task.branch], cwd=worktree_path, check=True)
return True
The scope check matters more than it looks โ it's the single cheapest signal that an agent went off-task, and it catches the failure mode where an agent "helpfully" refactors an adjacent file while fixing the thing you asked for. That kind of drive-by refactor is exactly what causes two agents' branches to conflict even when their assigned tasks didn't overlap.
Common pitfalls
- Sharing a Python venv or
node_modulesacross worktrees without realizing it's stale. Worktrees share.gitbut not your dependency install โ if Agent A adds a package, Agent B's worktree won't see it until you reinstall there too. Run setup per worktree, or symlink shared, version-locked dependency dirs deliberately. - No timeout on agent runs. An agent that gets stuck in a retry loop against a flaky test will burn tokens indefinitely if you don't cap wall-clock time per task.
- Merging in an arbitrary order. Merge order matters when two branches both modify a shared file that passed the scope check (e.g., both add an entry to the same config file). Merge one at a time and re-run tests after each merge, not all at once at the end.
- Treating agent-authored commit messages as ground truth. Agents describe what they intended to do, which isn't always what the diff shows. Read the diff before trusting the message, same as you would with a human PR.
- Skipping the worktree cleanup.
git worktree removeafter merging โ orphaned worktrees pointing at deleted branches accumulate and confusegit worktree listfast.
Next steps
Start with two agents and one shared boundary rule before reaching for a full orchestration framework โ you'll learn where your codebase's real coupling lives faster than any tool will tell you. Once you've run this manually a few times, the frameworks (Orca, herdr, and similar tools currently trending) start to make sense as thin wrappers around exactly this loop, and you'll be able to tell which parts of their abstraction you actually need.
If you want a structured, hands-on path through agent orchestration, tool design, and production agent patterns like this one, Joinloop's AI engineering cohort covers it in depth with real codebases, not toy examples.
Sources: Top 10 GitHub Repositories Trending in July 2026, GitHub Trending Radar: AI Builder Repositories, July 2026
Want to build this yourself?
Join a live cohort and go from reading about AI to shipping it, with practitioners who do it daily.