Workflow · last verified 2026-07-28
Parallel agents, one working tree: partition by operation, not by folder
When several agents write to one checkout at the same time, a lost write raises no error. Give each agent its own worktree if you can; when they must share a tree, partition the work by the operation each one performs, not by the folder it lives in — and put the method in every brief, not just the prohibition.
The location split is the intuitive one and it is where we started. It is necessary, and on its own it is not enough: a bulk in-place sweep claims files by pattern, not by path, so two agents can each have a scope nobody disputes and still both legitimately own the same page. No rule about where separates a sweep from a targeted edit over the same corpus, because neither is trespassing.
Every failure case below is dated and from this platform's own build — four collisions across three nights of running agent desks in parallel on one repository. It is part of the Engineering Workflow Library.
The job this workflow does
You are about to fan out several agents across one codebase, because the work is genuinely parallel and running it serially would take all night. The decision this page improves is the one you make in the ten minutes before you dispatch: how do you carve up the work so that nothing any of them does is silently undone?
That question has an obvious answer that is right and insufficient, which is why it is worth a page. The obvious answer is to give each agent a scope nobody else holds. We did that, with seven agents, and lost work anyway — because of how we drew the scope, not because we forgot to.
Why a lost write is silent
The reason this class is expensive is not that collisions are frequent. It is that they are unobservable by every mechanism you already have.
Version control can detect a conflicting concurrent edit because a three-way merge is handed a shared base. Git's own manual for the three-way file merge puts the condition exactly: "a conflict occurs if both <current> and <other> have changes in a common segment of lines" — both, measured against <base>. During a merge git materialises that base, recording the common-ancestor version as stage 1 of the index.
A filesystem write has no base. open(), truncate, write — the call carries no expected prior value, so there is nothing to compare against and no conflict to notice. The previous contents are gone and the operating system reports success, because the write did succeed. Nothing in that path is wrong.
Git refuses the same shape of unvalidated overwrite one level up, at the ref: a non-fast-forward push is rejected by default, and the documentation says why — it "does not allow an update that is not a fast-forward to prevent such loss of history." Its safer force variant, --force-with-lease, works by "requiring their current value to be the same as the remote-tracking branch we have for them." That is a compare-and-swap: I will overwrite only if what I read is still what is there. Nothing in a shared working tree does that for files.
So a clobbered edit is not a failure state. It is a normal, successful write, and it produces:
- no error, no non-zero exit, no stack trace;
- no failing test, unless a test happened to cover the exact lost line;
- no conflict marker;
- no entry in
git statusdistinguishable from the rest of the run's churn; - in a large diff, no visual signal at all — one page's wording inside a 110-file change looks exactly like 110 files that changed.
Concurrency control named this anomaly a long time ago. It is the lost update, catalogued as P4 in Berenson et al.'s 1995 critique of the ANSI SQL isolation levels — which introduced it precisely because the ANSI standard's own three phenomena did not cover it. Their formal history is the agent scenario one-for-one: T1 reads, T2 writes, T1 writes back from its earlier read. The paper's summary of the damage is the whole problem in eight words — "even if T2 commits, T2's update will be lost."
The usual fixes are pessimistic (lock it before you touch it) or optimistic. It is tempting to say a shared agent tree is doing the optimistic thing, and that is worth stating carefully, because it is almost right and the difference matters. Kung and Robinson's 1981 paper that named optimistic concurrency control requires a transaction to run "a read phase, a validation phase, and a possible write phase," and is explicit that "a transaction will have a write phase only if the preceding validation succeeds." Validation is not a refinement of the optimistic approach — it is the part that makes it correct, and it is a write-set intersection test.
So a shared agent tree is not optimistic concurrency control missing a step. It is optimistic execution with no concurrency control at all, which is why it collapses to last-writer-wins by default — the policy Dynamo's authors describe a store falling back on when it can "only use simple policies, such as 'last write wins'." In Dynamo that is a deliberate, documented choice. On a filesystem it is not a choice at all; it is what happens when nobody decided.
That also explains why more review does not help. A reviewer looking at the final tree sees a consistent file. The evidence that something was overwritten was destroyed by the overwrite.
The clearest demonstration that the silence is a property of the validation and not of the conflict is one system doing both. In PostgreSQL, the same UPDATE against a concurrently-modified row either proceeds quietly "using the updated version of the row" under Read Committed, or is rolled back with ERROR: could not serialize access due to concurrent update under Repeatable Read — documented side by side. Same conflict, same statement. Whether you get an error depends entirely on whether anything checked.
Four collisions from this build
1. Declared-disjoint scopes, and one page quietly went back to how it was
Seven agent desks, one repository, overnight, 2026-07-25. Each had a declared scope no other desk held. One desk still reverted another's work.
The scopes were split by directory and topic, and they were written from ticket text rather than from the code. That is the root cause, and it is worth stating plainly because it is less flattering than "we partitioned and it still broke": four of the seven exceeded their declared scope during the run — every time correctly, every time flagged loudly, because the scopes were under-specified rather than because the desks misbehaved.
Two of them ended up with a legitimate simultaneous claim on the same folder of HTML pages. One was making targeted copy edits across 66 of them. The other was doing a mechanical version-string bump across 110. The mechanical one ran a bulk in-place sed sweep, which reads each file, rewrites it, and knows nothing about the edit that landed moments earlier. One page lost its copy fix. A stray sed temp file left in public/ was the only visible trace, and it was visible only to someone who looked.
It was recovered because the desk that made the edit re-read its own 66 files afterwards instead of trusting that its sweep had exited cleanly. Nothing else in the system would have caught it: no test covers marketing copy, and the loss was one page's wording inside a 110-file diff.
The lesson is about the axis, not the enforcement. The scopes were split by location. What collides is the operation. A bulk sweep and a targeted edit over the same corpus cannot be separated by any rule about location, because both of them genuinely own it. A lock would not have helped either — the sweep was not trespassing.
2. A baseline measurement reverted four agents at once
2026-07-26. A desk needed a clean lint baseline to measure its own delta against, so it ran git stash. That reverted four siblings' uncommitted work simultaneously. A sibling then wrote a file inside the window, so git stash pop hit a conflict, and one desk's three files existed only inside the stash entry.
It was recovered by hand — diff both versions, build the union, verify all 18 files — and nothing was lost. But this is the first failure with a worse blast radius than a clobber: sed -i destroys one file, a stash destroys every concurrent writer at once, and it is a completely normal thing to reach for.
The root cause was not the command. Six briefs had told desks to "establish your own baseline" and none of them said how. One desk independently found the right method; another found the wrong one. That is a coin flip built into the dispatch, replicated six times.
Scope is not only about files. It is about the blast radius of the commands you allow. A rule listing file paths does not constrain git stash, git checkout -- ., git reset, or git clean — every one of which is tree-wide by definition.
3. A gate went red on a file another agent was mid-write on
2026-07-26. A desk ran the sitemap freshness check and got a genuine-looking stale result. A sibling desk was, at that moment, part-way through writing public/sitemap.xml.
The desk that hit it reconstructed the expected XML read-only, found it byte-identical, re-ran the check, and got green. Regenerating on that red would have clobbered the sibling's write.
This is the same silent-revert failure reproduced on a gate rather than an edit, and it is worse in one specific way: a red gate reads as an instruction to act. The tooling itself invites the clobber. Every instinct a good engineer has says fix the red.
4. The dispatcher is a writer too
2026-07-28. With a read-only reporting job mid-run, the dispatcher — me — rewrote two rows of the ticket ledger and a section of the roadmap. The job then quoted the ledger with pre-edit text, and one of its rules surfaced one ticket but not its sibling.
The cost was not a lost edit. It was that I can no longer tell whether that is a gap in the rule or an artifact of my write, because the evidence was the file I overwrote. A read-only job's entire value is that its report is a clean snapshot of a moment. Editing underneath it does not corrupt the file; it corrupts the observation.
What the tools actually do about this
Worth knowing before you design around it, because the answer is more uniform — and thinner — than you would expect.
Every major tool's answer is "give each agent a different checkout." Claude Code has a --worktree/-w flag and a per-subagent isolation: worktree option; Cursor documents worktrees for local agents and runs cloud agents in per-agent Firecracker microVMs; Codex offers Local, Worktree and Cloud modes; Devin boots a fresh VM per session and, for its parallel mode, one isolated VM per managed agent. Different implementations, one strategy.
No tool surveyed documents a lock that arbitrates two agents writing the same file in the same checkout. That is worth stating carefully, because the word "lock" does appear. Claude Code's agent teams use "file locking" — over the task queue, to stop two teammates claiming the same task. Its git worktree lock stops the cleanup sweep removing a worktree in use. Neither arbitrates a write. Where agents genuinely share a tree, the documented mechanism is partitioning by file ownership, stated as advice and enforced by nothing.
And the default is usually shared, not isolated. Anthropic's subagent documentation is explicit: a subagent "starts in the main conversation's current working directory," and isolation is the opt-in — "to give the subagent an isolated copy of the repository instead, set isolation: worktree." Most people assume the opposite. If you dispatch subagents today without setting that, they are in your tree.
Two caveats on the strategy everyone has converged on. Git's own manual still files multiple checkout under BUGS: "Multiple checkout in general is still experimental, and the support for submodules is incomplete." And isolation moves the problem rather than removing it — as recent work on multi-agent state management puts it, workspace isolation "defers conflict resolution to a post-hoc merge step where recovery is expensive." That is still a much better place to have the problem, because a merge reports the conflict. It is not the same as not having one.
What nobody has measured. We looked for a study that runs several agents against one shared tree and reports how many edits were silently lost, and did not find one. The empirical work measures conflicts at merge time between branches — precisely the conflicts git can see. A useful adjacent number does exist: a July 2026 study of agent-authored pull requests on GitHub found textual conflict in 41.7% of cross-agent PR pairs against 19.8% for pairs from the same agent. That is branch-level and not our failure mode, but the direction is the point: the conflicts we can count are the ones that surfaced.
The workflow
1. First ask whether they need to share a tree at all
Every rule below is mitigation for a constraint you may not have. If the agents can each work on their own checkout, the collision class disappears rather than being managed, and you should take that trade unless something forces you not to.
This is also the vendor's own first recommendation. Anthropic's guidance on running agents in parallel opens with "isolate the work with worktrees," and notes that subagents and sessions you launch yourself "can each use a separate worktree" — while agent teams do not get that isolation. Read the rest of this page as what to do in the case the same paragraph goes on to describe: the one where they share.
Take the cheap win first, though. If you are dispatching subagents, isolation is one field: isolation: worktree on the subagent. There is no reason to run the discipline below for agents that could simply not be in the same tree.
git worktree is the cheap version of this: one clone, several working directories, all sharing a single object database. Verified in this repository on 2026-07-28:
git worktree add -b agent-a ../trees/agent-a
# the worktree's .git is a FILE, not a directory:
cat ../trees/agent-a/.git
# gitdir: /path/to/repo/.git/worktrees/agent-a
# that directory holds its OWN HEAD, index, refs, logs
# plus a `commondir` pointer back to the shared object store
ls .git/worktrees/agent-a
# HEAD ORIG_HEAD commondir gitdir index logs refs
The separate index is the part that matters here: staging in one worktree cannot disturb another, which is precisely the shared-state problem in the shared-tree model. Git also refuses to check the same branch out twice — attempting it on this repo returns fatal: 'main' is already used by worktree at ... — so each agent is structurally forced onto its own branch, and their work meets in a merge, where conflicts are detected rather than silently resolved by whoever wrote last.
The cost is real and worth stating: a worktree is a full checkout of the working files (1,154 files on this repo), so setup is seconds and disk rather than free, and the work has to be merged afterwards instead of simply being there. For a fan-out of mechanical, genuinely independent edits, that overhead can exceed the risk. For anything where two agents might reason about the same file, it does not.
2. Partition by operation, not by location
This is the correction the first collision bought, and it is worth being precise about what it corrects. The standard advice — including Anthropic's own, which says to "partition the work so each teammate owns a different set of files" — is right, and it is the first thing to do. Our finding is narrower: a location partition is necessary and not sufficient, because operation class cuts across it. Our own scopes were declared by directory rather than file-by-file, so we were doing the coarse version of that advice — but tightening it to a literal file list does not close the gap, because a bulk sweep's claim is defined by a pattern. Enumerating a sweep's files either defeats the point of running a sweep or silently under-covers it, and the moment it matches a file on another agent's list you are back here.
So sort the planned work by what kind of write it performs before you sort it by where:
- Bulk in-place sweeps — a regex or version-string pass over many files. These claim every file they match, no matter who else is in there.
- Targeted edits — a specific change to a specific region of a specific file.
- Whole-file authoring — writing or rewriting a file end to end.
- Read-only analysis — reports, audits, gates.
A bulk sweep and a targeted edit over the same corpus are not parallelisable at any granularity. Sequence them — sweep first, then targeted edits on the swept tree — or merge them into one agent. No file list separates them, because the sweep's claim is defined by a pattern, not by a path.
3. Write the scope into the brief, including the test files
Give every agent an explicit, disjoint file list, and tell it that unrelated churn in git status is a sibling's work rather than drift it should investigate or revert.
Then scope the test files separately and explicitly. A source-based split silently shares them: a test file imports from the modules it covers, so two agents given disjoint source scopes will both correctly conclude they own the same test file. We split two desks by source on 2026-07-26 and both wrote the same test file; nothing was lost only because their blocks happened not to overlap. A shared test file is the natural collision point of a source-based split, and it is the one file where a lost hunk shows up as a passing suite with a missing case.
4. Put the method in the brief, not just the ban
This is the single highest-value line in the whole workflow, and it is the one most likely to be omitted, because a prohibition feels like an instruction.
"Do not use git stash" does not tell an agent that needs a baseline what to do instead, so it will find something. Name the replacement:
# Required line in every brief:
To measure a baseline, read the pre-run content with:
git show HEAD:<path>
Never git stash, git checkout -- ., git reset, or git clean.
git show HEAD:<path> prints a committed blob to stdout and touches no working file. If an agent genuinely needs a stash object, git stash create builds one without modifying the working tree. The general rule: an agent may only run commands whose blast radius is its own file scope. Tree-wide commands belong to the dispatcher alone.
5. Any bulk sweep re-reads its own result
A sweep's exit code tells you the sweep ran. It does not tell you what is in the files now, and specifically it cannot tell you that a sibling's edit was overwritten mid-pass.
Require every bulk in-place pass to verify by grepping for its own intended result afterwards, across its own file set, and to paste that output back. In the first collision this was the only thing standing between a recovered edit and a lost one.
The same brief should require a pattern sweep whenever it names a specific line. A brief that says "fix the phrase at line 117" reliably gets a fix at line 117 and nowhere else — we shipped exactly that on 2026-07-26, leaving the identical phrase at line 99, twelve lines up. Name the line and require a sweep of the file for the same pattern, with the output pasted back.
6. A red gate on someone else's file is a report, not a task
Tell every agent explicitly: if a gate goes red on a file outside your scope, verify it read-only and report it. Do not fix it.
Reconstruct what the file should contain and compare; if it matches, the red is a sibling mid-write and it will clear. This has to be stated, because it runs against the correct instinct in every other context.
7. The dispatcher re-runs the gates, and queues its own writes
Each agent's green was measured against a tree that no longer exists by the time the last one reports. Re-run the end-to-end gates yourself after everyone lands — that is the only validation phase this architecture has, and it is the piece the optimistic-concurrency comparison says is missing.
Two dispatcher-specific rules, both learned the hard way:
- Do not edit a file while a read-only job you started is reading it. You are a writer too. Queue the writes until it lands, or you will destroy the snapshot the job exists to produce.
- Never let an agent bake a claim about another agent's file into a durable artifact. On 2026-07-26 one desk correctly reported that a reference page still carried a defect and helpfully wrote that into a skill file as a warning. A sibling fixed the page later in the same run, so the committed instruction then told every future reader the opposite of the truth. The premise was correct when written and was invalidated by a sibling, in parallel, by design. Report cross-scope observations to the dispatcher, who can reconcile after everyone lands — or timestamp them ("as of commit
abc123"), never as present-tense fact.
As built here
This platform runs both models side by side, which is why the trade-off in step 1 is stated as a trade rather than a recommendation.
Isolated: the long-running implementation loop gets its own checkout under .claude/worktrees/, one per branch. That buys real isolation and costs three real gotchas, all of which have bitten us. A worktree branches off origin/main, not local main, so unpushed local commits are simply not in it. Untracked files do not exist there at all. And because our worktrees are nested inside the repo, a test run from the repo root collects both trees and roughly doubles the totals — 3,545 tests from the root against 1,770 in the worktree, on one tree, on 2026-07-25. That third one is the dangerous one, because a doubled count does not look like an error. It looks like a result.
Shared, read-only: the weekly coordination report fans out four reporter agents through a parallel() call in .claude/workflows/huddle.js, then a synthesizer. Every reporter is told it is strictly read-only, and the synthesizer's brief names the report file as its single sanctioned write — "touch NO other file." Four concurrent agents, one writer, zero source-write collision surface. (Not literally zero filesystem writes: the reporters run the lint, typecheck and test commands, which touch build and test caches. The distinction is worth keeping — "read-only" in a shared tree almost always means read-only with respect to source.) That is step 2 applied at the strongest setting: the whole fan-out is one operation class.
Shared, writing: the desk fan-out, which is where all four collisions happened. The rules from those incidents are encoded as a numbered list in the repo's agent instructions rather than left as habit, and the gates the dispatcher re-runs afterwards are the ordinary shipping gates — scripts/build-sitemap.mjs --check among them, which is also the gate that went red on a sibling's mid-write in case 3.
The honest summary after three nights of this: isolation is better whenever you can afford the merge. The shared tree survives on discipline, and discipline degrades under exactly the conditions that make you want to parallelise in the first place.
Failure modes
| Failure | What it looks like | Control |
|---|---|---|
| Bulk sweep overwrites a targeted edit | A file quietly reverts. A stray temp file may be the only trace. | Partition by operation (step 2); sweep re-reads its own result (step 5) |
| Tree-wide command reverts every writer | Multiple agents' uncommitted work vanishes at once; stash pop conflicts | Name the method in the brief (step 4) |
| Gate red on a sibling's mid-write | A genuine-looking stale/failed result on a file you do not own | Read-only verify, report, do not fix (step 6) |
| Shared test file | Suite passes; a test case is missing | Scope test files explicitly (step 3) |
| Cross-scope claim committed as fact | An instruction or comment that was true when written and false when merged | Report to dispatcher; timestamp if durable (step 7) |
| Dispatcher writes under a read-only job | A report quoting stale text; an unfalsifiable finding | Queue dispatcher writes until the job lands (step 7) |
| Scope excludes the only file the fix can live in | Fix lands in the wrong layer to satisfy a file list | Agent widens scope, names the file and why, and says so — an out-of-scope file the fix requires is a report, not a violation |
Verification
How you know the partitioning worked, rather than assuming it from a clean run:
- Every bulk sweep pasted back a grep of its own result, over its own file set, after the sweep. Absent that output, treat the sweep as unverified regardless of exit code.
- The dispatcher's gate run is green on the merged tree, not just each agent's green on the tree it saw.
- No stray temp files —
git statusshows nothing unexpected. The first collision's only visible artifact was asedtemp file. - Every red gate that was reported but not fixed has a named owner and a read-only verification attached, so a genuine failure is not filed away as a sibling artifact.
- Where the stakes justify it, diff the merged tree against the union of what each agent said it changed. A file that changed and appears in nobody's report is the signature of this failure.
When not to use this workflow
- One agent. All of this is coordination overhead for a problem you do not have. Serial is simpler and usually fast enough.
- Read-only fan-out. Analysis, audits, and reporting agents do not collide. Parallelise them freely — but remember the dispatcher can still corrupt their snapshot by writing underneath them.
- When worktrees or containers are available and cheap. Then take step 1 and stop. Isolation removes the failure class; every other step here manages it.
- When the work is not actually independent. If two tasks reason about the same design, parallelising them produces two half-consistent answers and a reconciliation problem that costs more than the serial run. Partition the writes, not the thinking.
- All four collisions are from operating this repository: seven desks and the
sedsweep (2026-07-25); thegit stashrevert and the shared test file (2026-07-26); the sitemap gate red on a sibling's mid-write (2026-07-26); the dispatcher writing under a read-only job (2026-07-28); and the line-number-scoped fix (2026-07-26). Each is recorded in this repo's dated post-mortems and encoded as a numbered dispatch rule in its agent instructions. - The
git worktreemechanics — the.gitfile pointer, the per-worktreeHEAD/index/refs/logs, thecommondirback-pointer to the shared object store, and the refusal to check out one branch in two worktrees — were executed against this repository on 2026-07-28 and the output is quoted as observed. - Lost update (P4) and its formal history: Berenson, Bernstein, Gray, Melton, O'Neil and O'Neil, "A Critique of ANSI SQL Isolation Levels", Proc. ACM SIGMOD 95, pp. 1–10 — full text, read 2026-07-28. Note for anyone repeating the claim: ANSI SQL-92 does not define lost update. It defines three phenomena; this paper argues they are incomplete and introduces P4. We checked Gray et al. 1976 for an earlier use of the phrase and found none, so we do not credit it there.
- Optimistic concurrency control, its three phases, and validation as a write-set intersection test: H. T. Kung and John T. Robinson, "On Optimistic Methods for Concurrency Control", ACM TODS 6(2), June 1981, pp. 213–226 — author's copy, read 2026-07-28.
- Three-way merge conflict detection and the index's stage-1 common-ancestor version: the
git-merge-fileandgit-mergemanuals. Non-fast-forward rejection and the--force-with-leasecompare-and-swap: thegit-pushmanual. All checked 2026-07-28. - Last-writer-wins as a fallback policy: DeCandia et al., "Dynamo: Amazon's Highly Available Key-value Store", SOSP '07 — paper, checked 2026-07-28.
- The same statement silently succeeding or raising
could not serialize access due to concurrent updatedepending only on isolation level: PostgreSQL documentation §13.2, checked 2026-07-28. - The tooling survey: Anthropic's subagent documentation for the shared-working-directory default and the
isolation: worktreeopt-in; agent teams for the task-queue file locking; Cursor, Codex and Devin for their isolation models. All checked 2026-07-28. The claim that no surveyed tool documents a write lock between agents in one checkout is a negative from that survey, not a proof — we searched the documented behaviour of the major tools and several open-source harnesses and found only task-queue, state-file and anti-cleanup locks. - "Multiple checkout in general is still experimental": the BUGS section of the
git-worktreemanual, fetched and grepped 2026-07-28. - Isolation deferring conflict resolution to merge: arXiv:2605.20563 (multi-agent state management, May 2026), abstract. Cross-agent PR conflict rates: arXiv:2607.04697 (July 2026), abstract. We cite both from their abstracts and say so; neither is quoted beyond what the abstract states.
- Worktree isolation as the first recommendation, and the file-set partition for shared teams: Anthropic's Claude Code documentation, "Run agents in parallel", fetched and quoted 2026-07-28. We agree with it and are disagreeing with nothing — the claim on this page is that a file-set partition is necessary and not sufficient, which is a finding from operating one, not a correction to the guidance.
- Deliberately not claimed: that "disjoint write sets" is a named strategy with a citable owner (it is the intersection test inside OCC validation, and we present it that way); and that there is an established term for the unobservability of a lost write. We looked for one and did not find it, so this page describes the silence mechanically instead of coining a name for it. Also not claimed: that anyone has measured the silent-overwrite rate in a shared working tree (we found no such study, which is why that is stated as a gap); and we deliberately do not cite the one published first-hand account of two agents colliding on a migration filename, because those two agents were in separate worktrees — it is a namespace collision resolved wrongly at merge, not a shared-tree clobber, and using it here would be the reach.
The incidents and line-level quotations are fixed to the dates given; the files they live in are under active change. Re-read the source before relying on any snippet. Corrections: hello@aiarch.dev.
Changelog
- 2026-07-28 — First published. Four dated collisions, seven workflow steps, worktree mechanics verified first-hand against this repository, and a survey of what Claude Code, Cursor, Codex and Devin actually document about isolation — checked against their own docs on the publication date. Tool defaults in this area change; the survey is dated for that reason.
Learn to run agents as an engineering system.
aiArch teaches evaluation, gating, and production agent architecture by building — on a platform whose own agent org has clobbered itself four different ways, and publishes each one. The build is the curriculum.
See how aiArch helps senior engineers become AI-native, or compare Professional Membership pricing.
Free sample — no signup · every claim cited · full curriculum is waitlist-only