Skip to content
Pere Villega
Go back

Running Parallel Agents Without Collisions

9 min read

Three agents editing one checkout are sharing a desk, moving each other’s papers and occasionally throwing them away.

Opening three terminals does not create isolation. Nor does giving each agent a branch name if all three processes can still modify the same working directory. Useful parallel development needs separate workspaces, but that is only the mechanical half of the problem. We must also split the work so that the results can meet again.

In the previous chapter, I argued that adding agents changes the coordination and review economics. This chapter is the practical version: how I would decompose a change, assign ownership, isolate it with Git and decide when the combined work is actually finished.

Begin with the dependency graph

Suppose a change adds a new account export format. It needs a serialisation library, a command-line entry point, documentation and an end-to-end test.

It is tempting to create four tasks and start four agents. Yet the command and documentation both depend on names and behaviour defined by the library. The end-to-end test depends on all of them. Four bullets are not four independent tasks.

A better schedule is:

  1. agree the format, public interface and error behaviour;
  2. implement the library while a second agent investigates fixtures and failure cases without editing shared files;
  3. once the interface is stable, implement the command and documentation in parallel, with separate file ownership;
  4. integrate those changes, then add or finish the end-to-end test against the combined result.

There is less simultaneous activity in this schedule. There is also less speculative code to discard.

This is the first rule: split work at stable interfaces, not at convenient nouns in a feature request. If the interface is still being discovered, either settle it first or run competing implementations as experiments. Be honest that experiments are alternatives, not additive progress.

Give each task a change surface

“Implement the CLI” describes an outcome but not ownership. A parallel task also needs to say what it may change and what it must leave alone.

A task brief could look like this:

task: Add the account-export command
base: 4d3c2b1
owns:
  - src/cli/export/**
  - tests/cli/export/**
may_read:
  - src/export/**
must_not_edit:
  - src/export/**
  - package-lock.json
acceptance:
  - the command writes the agreed CSV columns in order
  - invalid date ranges return exit code 2
checks:
  - npm test -- tests/cli/export
stop_if:
  - the agreed export interface cannot support the command
handoff:
  - commit SHA, changed paths, checks run, open concerns

The exact format is unimportant. The constraints are not. The base revision makes provenance explicit. owns reduces accidental overlap. must_not_edit stops an agent from “helpfully” changing the upstream contract. The stop condition turns a discovered dependency into a report instead of a surprise redesign.

Ownership should normally include the tests closest to the code. Assigning all production code to one worker and all tests to another creates a slow conversation about intent. A separate verification agent can still challenge the result, but the implementation task should be able to prove its own local behaviour.

Generated files, lockfiles, central registries and shared schemas deserve special treatment. They attract conflicts even when the surrounding modules are independent. Give each one a single owner for a phase, or defer regeneration until integration. Asking every worker to update the lockfile is an efficient way to manufacture meaningless conflicts.

Protect existing work before creating worktrees

Git worktrees let one repository have several linked working trees, each with its own checked-out branch while sharing the repository’s history. They are lighter than making a full clone per agent and, unlike several processes in one checkout, they isolate ordinary file edits.

Before creating any, establish what must be protected:

git status --short
git rev-parse HEAD
git worktree list

Do not tidy a dirty main checkout for the sake of automation. The uncommitted files may be somebody’s work. Record the intended base SHA and create new worktrees from that known revision; leave the existing checkout alone. The paths and SHA below are examples. They assume the agreed interface exists at that base; if a consumer needs a new library interface, integrate it first and use the resulting revision as the consumer’s base.

git worktree add ../project-agent-library -b agent/export-library 4d3c2b1
git worktree add ../project-agent-cli -b agent/export-cli 4d3c2b1

Each agent receives one path and one branch. It should not switch branches, enter a sibling worktree or modify the main checkout. If the task is abandoned, its branch and worktree remain an inspectable recovery point until a human explicitly decides to remove them.

Worktrees do not isolate everything. Git references and much of the repository configuration are shared, and an unrestricted process can still reach sibling directories. A worktree is not a security boundary. Workers may also share caches, containers, ports, databases and external test accounts. Two agents can pass when run alone and fail when both compete for port 3000. Allocate separate external resources where practical, or serialise the checks that cannot be isolated.

Ignored local files also need attention. A fresh worktree may not contain environment files, generated credentials or installed dependencies from the main checkout. Copying secrets into every workspace is rarely the right automatic fix. Prefer a documented bootstrap command and task-specific credentials with the minimum permissions needed.

Some agent products automate this setup. For example, Claude Code documents isolated worktree sessions and its own lifecycle rules. Those rules are implementation details of that tool. The Git principles remain the same: know the base, know the branch, preserve useful work and inspect before cleanup.

One branch, one reviewable result

A branch should correspond to a deliverable that can be accepted or rejected independently. “Agent 2’s branch” tells the integrator who ran it; agent/export-cli says what decision it contains.

At hand-off, ask for evidence rather than a declaration of success:

A focused test passing is evidence for that slice. It is not certification of the combined repository. Keeping PASS, FAIL and NOT-RUN distinct matters even more when several hand-offs are being summarised.

When local commits are authorised, committing the task result on its branch provides a stable hand-off and a recovery point; it does not mean the change is approved or should be pushed. If the owner wants uncommitted changes for review, preserve the workspace and record the diff, including any new files, instead. Keep the original result while integration is in progress so that conflict resolution cannot destroy its only copy.

Integrate in dependency order

Give integration its own worktree from the approved feature base. This keeps the main checkout and worker results intact. Replace the example paths, base SHA and uppercase commit placeholders before running these commands:

git worktree add -b integrate/account-export ../project-integration 4d3c2b1
git -C ../project-integration cherry-pick LIBRARY_COMMIT
git -C ../project-integration cherry-pick CLI_COMMIT
git -C ../project-integration cherry-pick DOCUMENTATION_COMMIT

Cherry-picking is not mandatory; merging or rebasing may suit the repository better. The important part is intentional order. Land the contract or foundational change before its consumers, then validate after each meaningful step. A sequence of small, attributable integrations is easier to diagnose and reverse than one synthetic mega-patch assembled from several working directories.

Before accepting a branch, compare it with its declared base:

git log --oneline 4d3c2b1..agent/export-cli
git diff --stat 4d3c2b1...agent/export-cli
git diff --check 4d3c2b1...agent/export-cli

The diff should match the task’s change surface. An unexpected edit is not automatically wrong, but it requires an explanation before integration. This catches a common failure mode: an agent solves a local build problem by editing shared configuration outside its assignment.

Treat conflicts as information

A textual conflict says that Git cannot combine two edits automatically. A clean merge says only that it can. Two branches can change different files and still disagree about a protocol, data invariant or error model.

When integration conflicts, stop and classify the cause:

I would reject the tempting alternative of telling an agent to resolve every conflict and continue. It optimises for a green Git state, not a coherent system. Conflict resolution is part of implementation and deserves the same acceptance criteria as the original change.

At very large scale, different economics may apply. Cursor’s multi-agent research deliberately accepted some overlapping edits and temporary breakage, relying on continuous ownership. The report proposes a final green-branch pass before release. It also says that a shared coordination file reduced 20 agents to the throughput of one to three. That experiment shows both sides of the trade-off: some turbulence can be cheaper than elaborate synchronisation, but unmanaged contention can erase the parallelism. A normal product repository should not assume it has Cursor’s harness, workload or appetite for rework.

Define two terminal predicates

Each worker needs a local definition of done, and the integrator needs a broader one.

For a worker, completion means:

For integration, completion means:

These predicates prevent a familiar misunderstanding. Five workers can all be locally PASS while the feature is globally FAIL. Conversely, one experimental branch may be FAIL as an implementation and still provide useful evidence that changes the design. Status belongs to a stated scope.

Not every idle agent needs work

Parallel execution is a scheduling choice, not a target state. Keep a task serial when it changes a shared contract that others cannot safely assume, when its tests require a single external environment, when the likely change surface is still unknown, or when the review queue is already full.

The cleanest parallel plan often contains pauses: decide, fan out, integrate, decide again. Those barriers are not wasted time. They stop uncertainty at one boundary from being copied into several branches.

Worktrees prevent agents from overwriting each other’s files. Ownership and terminal predicates prevent something more expensive: several agents producing changes that cannot be trusted together. Once that foundation is in place, we can choose whether the workers should remain independent, report to a lead or form a more elaborate topology. That is the question for the next chapter.


Share this post on:

Previous Post
How to Organise a Team of Agents
Next Post
Agentic reflective practice