Course · Lesson 19 of 30
Parallel Agents
Expert · 6 min read
Once agents are processes (lesson 18), running ten at once is a shell loop. The parallelism itself is genuinely the easy part — embarrassingly parallel work (lint every package, migrate every file, review every PR) fans out like any batch job. What's new is the failure surface: parallel agents fail in ways parallel code rarely does, because each worker improvises, writes files, and reports in prose. This lesson is the hazard catalog: three ways concurrent agents silently corrupt results — every one invisible at N=1, which is exactly the configuration where everything gets tested — plus the operational craft that keeps a fleet affordable and debuggable.
Hazard one: shared paths — last writer wins
Two workers write the same file: a scratch path both happened to choose, a shared output, the same branch. No error, no conflict marker. The second write lands on the first; both workers report success; one worker's result has vanished — or, nastier, worker A read worker B's intermediate state and computed a perfectly plausible answer from the wrong input. Lesson 13's corruption story, multiplied by N.
Prevention is structural, never behavioral — you cannot prompt your way to path safety across N improvising workers:
- Territory before launch. A worktree per agent (lesson 13) for
source; a per-worker output directory (
out/worker-07/) for results. The dispatcher assigns territory; workers never choose their own paths. - Pre-flight collision check. Before dispatch, assert no two workers' territories coincide. Before, because afterward there is nothing to see — the overwritten file looks exactly like a file.
- Containment enforcement. A hook denying writes outside the worker's own territory (lesson 15) turns the guarantee from "N prompts followed N times" into a property of the environment. And lesson 13's path lesson applies with teeth: containment logic must resolve from the invocation's directory and be tested with two lanes, or it will pass every single-lane test and fail the day the fleet arrives.
Hazard two: the association shuffle
You dispatch workers for items 1–30 and collect results as they finish. Results arrive in completion order, not dispatch order — and if the collector pairs results to items by arrival, item 4's analysis gets filed under item 9. Every individual result is correct. The mapping is scrambled. Nothing fails, nothing warns, and the pipeline that does this looks maximally robust because everything completes cleanly.
The rule: identity travels with the work. The worker's brief includes its item id; the worker's output repeats that id inside the result (this is half of lesson 11's return contract); the collector joins on the id — never on position, never on timing. Two testing notes from the field: don't validate the collector against an in-order backend only, where "join by id" and "join by arrival" behave identically — shuffle completion order once, deliberately, and watch; and treat any worker output missing its id as a failed result, not a guessable one.
Hazard three: partial success dressed as success
Thirty dispatched; twenty-eight return; the collector aggregates twenty-eight results into one tidy report. Every row is right. The report is wrong — two sources were never examined — and nothing in the output says so, because absence leaves no row to look wrong. Whoever reads the report reads it as complete. That's what reports look like.
The rule: completeness is computed from what was dispatched, never
from what returned. The dispatcher knows N; the collector must
report 28 of 30 — workers 12 and 23 did not return, failures named.
Downstream steps that require completeness should refuse a partial
set outright rather than pass it along — a loud refusal is visible; a
silently-partial dataset is a time bomb with a long fuse (the same
fail-closed instinct as lesson 15).
And the companion distinction, same territory, opposite direction: a worker that ran and legitimately found nothing is a success with zero findings, not a failure. Conflate "returned empty" with "never returned" and empty categories start looking like outages. Keep them as different facts — because they are — and make the worker say explicitly which one it's reporting.
Fleet craft: the operational side
- Cap concurrency. API rate limits, memory, and disk all vote. A work queue with 5–10 in flight plus retry-for-stragglers beats 400 simultaneous processes on every axis that matters, including finishing sooner.
- Budget the batch, not the item (lesson 5): per-item estimate × N × retry factor, computed and approved before launch. Refuse up front if it exceeds the budget — discovering the multiplication on the invoice is the canonical fleet failure.
- Sample before you fleet. Run 3 items; read the outputs carefully — not skim, read; fix the prompt; then run 400. A prompt flaw costs three items' worth of embarrassment in the sample and four hundred times that after dispatch. This is the cheapest QA in the entire course.
- Per-worker logs, always. When worker 23 fails, you want its transcript alone — not thirty interleaved streams. One log file per lane, named by the id that travels with the work.
- Same brief, same model, same territory shape for every worker. Uniformity is what makes fleet failures diagnosable: when lanes differ only by input, a lane that misbehaves indicts its input, not a configuration lottery.
The meta-lesson
All three hazards share a signature: invisible at N=1. One worker can't collide with itself, can't shuffle against itself, can't be partial against itself — so the exact configuration in which everything gets developed and tested is the one configuration in which the bugs cannot appear. "Worked fine in testing" is, precisely, the problem statement. Build territory, identity, and completeness in while the fan-out is small and boring; lesson 22 adds the last pieces — a planner that decides what to fan out and a merge that assembles honest answers — on top of exactly these three guarantees.
A personal teaching project, in development. The registry it builds is a teaching artifact and is not an assurance opinion about any issuer. Not affiliated with or endorsed by any employer, or by any vendor whose tools it describes. Where a unit depends on a specific flag or path, it names the version it was verified against. All units.