Parallel Subagents or One Agent in Sequence? Check Whether the Subtasks Share a Write
Fanning a task out to five workers looks five times faster, until two of them update the same record and the last write quietly wins. One question about shared writes settles most parallel-vs-sequential calls.
Splitting a job across parallel subagents is the most tempting move in multi-agent design, because the speedup is obvious and the risk isn't. The risk sits in one place: whether two workers end up writing to the same thing. If they don't, fan out. If they do, a supervisor that launches them side by side has built a race condition that no prompt will fix.
Most parallel-vs-sequential debates get stuck on latency, token cost and context size. Those matter, but they only come into play once you've answered the question about writes.
🔍 The failure, step by step
Take an ops agent that cleans up a customer accounts table after a messy import. The supervisor breaks the job into three subtasks and hands each to a worker:
- Dedupe worker: find accounts that look duplicated and merge them.
- Address worker: normalise postal addresses.
- Tier worker: recompute each account's billing tier from its order history.
On paper the three jobs are separate. At runtime:
- The address worker reads account
A-1042, fixes its address, and plans to write it back. - Meanwhile the dedupe worker merges
A-1042intoA-0987and deletesA-1042. - The address worker's write lands on a record that no longer exists. Depending on the tool, that either errors (the good outcome) or upserts a new
A-1042, bringing the duplicate back. - The tier worker read
A-0987's order history before the merge, so its new tier ignores half the customer's orders.
Each worker reports success, and each report is accurate about what that worker saw. The supervisor merges three "done" messages and ends up with a table in worse shape than it started. No single trace shows anything wrong, since the problem lives between the traces.
The deciding question: do the write sets overlap?
Before fanning out, write down what each subtask reads and what it writes, at the level of actual resources: rows, files, tickets, calendar entries, not "the database" in general.
subtask reads writes
dedupe accounts.* accounts (merge, delete)
address accounts.address accounts.address
tier accounts.id, orders.* accounts.tier
Then apply three checks, in this order:
- Write–write overlap. Do two subtasks write to the same resource?
dedupeandaddressboth write to account rows, anddedupedeletes them. That overlap alone rules out parallel. - Read–write overlap. Does one subtask read something another writes?
tierreads account identity, whichdedupechanges. Running them in parallel letstieract on a stale view. - No overlap. If both checks come back clean, the subtasks really are independent, and parallel is the right call.
The table takes about two minutes to write, and it's the most useful artefact in the whole design. If you can't fill it in because you don't know what a worker will touch, you aren't ready to run workers in parallel.
✅ When parallel is clearly right
Fan out without guilt when the subtasks are read-only, or write to disjoint, pre-assigned outputs:
- A research agent sends five workers to summarise five different sources, and each writes only its own summary.
- A support agent drafts replies to twenty separate tickets, one worker per ticket, and each worker can only update its own ticket.
- A data agent profiles eight unrelated tables and reports column statistics without changing anything.
What these share is that the supervisor handed out ownership before the work started. No worker has to find out mid-task what another worker is doing.
❌ When sequential is clearly right
Run in order when a later step depends on the result of an earlier one, or when steps change the same entities:
- Dedupe first, then normalise addresses on the rows that survive, then recompute tiers against the merged identities.
- Rename a function across a codebase first, then update the tests that call it.
- Move a meeting first, then send the notice that mentions the new time.
The order is information. A sequential pipeline gives each step the settled state left by the step before it, and parallel execution throws that guarantee away.
The middle ground: partition, then fan out
Often the honest answer is "sequential across phases, parallel inside each phase." For the accounts cleanup:
- Phase 1, sequential: dedupe the whole table. It changes identities, so nothing runs alongside it.
- Phase 2, parallel by partition: split the surviving account IDs into disjoint ranges, and give each worker one range for address normalisation. Workers can't collide because their write sets don't intersect.
- Phase 3, parallel by partition: same ranges, recomputing tiers.
The partitioning is the supervisor's job, and it has to be enforced, not just described in the prompt. A worker told "only touch accounts 0–999" will usually comply. A worker whose update tool rejects any ID outside 0–999 always complies. Put the boundary in the tool:
def make_update_tool(allowed_ids: set[str]):
def update_account(account_id: str, fields: dict):
if account_id not in allowed_ids:
return {"error": f"{account_id} is outside this worker's partition"}
return db.update("accounts", account_id, fields)
return update_account
A returned error lets the worker recover and report the conflict. A silent cross-partition write can only be found later, by someone reading a diff.
⚠️ Signs you picked parallel when you shouldn't have
These symptoms point back to overlapping writes:
- Results change between runs even with the same inputs and a fixed seed, because scheduling order decides which write lands last.
- Records you deleted come back.
- Every worker reports success, but a simple check afterwards (row counts, a uniqueness constraint) fails.
- Workers' final reports mention entities that belong to another worker's slice.
If you see any of these, don't reach for retries or a stricter prompt. Redraw the read/write table.
Rules of thumb
- Latency is the reason to fan out; the write table is the permission. Get the permission first.
- Read-only fan-out is almost always safe. Most useful parallelism in agent systems is gathering and summarising, not changing things.
- Shared writes mean a phase boundary. Put the steps that change shared state in order, and parallelise only within a phase.
- Enforce partitions in tools, not prompts. An instruction is a request; a tool check is a guarantee.
Parallel subagents don't fail because models are unreliable. They fail because the supervisor never decided who owns which part of the world. Make that decision on paper before any worker starts.