Running two coding agents at once sounds like it should be twice as fast. The first time you try it in one directory, it is slower than one agent, and the failure is confusing rather than obvious.
This page is about why, and what has to be separated before parallel is actually parallel.
The failure, concretely
Two agents, one directory. Agent A is adding a field to a user model. Agent B is refactoring the same file's imports.
- A reads
user.ts, thinks for twenty seconds, and writes back the version it read plus a field. - B reads
user.tsduring those twenty seconds, thinks, and writes back its version plus new imports. - A's write lands after B's. B's imports are gone.
Neither agent has any way to know. An agent does not re-read a file it just wrote, because it believes it knows what is in it. B carries on for another ten minutes against a file that no longer contains its work, and the first time anyone finds out is when the tests fail for a reason that matches nothing either agent did.
This is a plain lost update, and it is worse with agents than with people because the loop is faster, nobody is watching, and the agent will confidently explain the resulting mess.
Four things need isolating
1. Files
The big one. Each agent needs a working directory nothing else writes to.
The cheap wrong answers: telling the agents which files to touch, and hoping. That works until one of them needs to fix an import in a file it does not own, which is always.
The right answer for a git project is a worktree each. Separate directories, shared history, cheap to create and cheap to merge.
2. Ports
The second thing you hit, and it arrives fast. Every agent wants to run the dev server. They all try to bind 3000. The second one either fails, which is fine, or silently attaches to the first one's server, which is not, because now an agent is testing another agent's code and reporting on it.
Each agent needs its own ports, and they need to be stable, so a bookmark and a printed URL still mean something after a restart.
3. Processes
An agent starts a dev server, and the dev server outlives the agent. Kill the agent and the port stays held, by a process that nothing in your interface can point at any more.
The specific trap: a script like npm run dev forks the real server. Signalling the process you
launched reaches npm and leaves its child running. You have to signal the process group.
4. History
Isolation is not the goal, it is the cost. The goal is combining the work at the end, and that only stays cheap if the agents share one object database. Separate clones would mean pushing and pulling between them; separate branches in one repository means an ordinary merge.
What isolation does not buy you
Worktrees isolate files. They do not isolate:
- Your machine. Agents run with your privileges. An agent can write to your home directory or call an API whether or not it has its own checkout. That is a permission problem, not an isolation problem, and it needs a different tool: see permission models for coding agents.
- Shared external state. One database, one Stripe test account, one staging environment. Four agents running migrations against one dev database will produce exactly the mess you expect. Isolating that is your job, usually with per-agent database names driven by an environment variable.
- The merge. Three isolated agents produce three branches that all edit the same interface in three different ways. Isolation prevents silent corruption. It does not prevent disagreement, and disagreement is what review is for.
The coordination problem underneath
Once the mechanics are solved, a real question remains: who decided the three agents should not overlap in the first place?
You can answer it yourself, by writing three briefs and picking the seams. That works, and it is also a job. Or the coordination itself can be delegated, which is what an orchestrator is: one agent or a squad is about when that is worth it.
Where Fleet fits
Fleet gives each session a worktree, a deterministic block of ten ports in 3100 to 3999 exported as
FLEET_PORT and friends, and process-group teardown so a killed script does not orphan its server.
Merging happens in one repository, so it is a merge and not a patch transfer.