Most people using Claude Code or Codex or Cursor are running one agent in one chat, watching it work, correcting it when it drifts. That works. It also caps out fast, because you are the bottleneck: one agent can only be as parallel as your attention.
Going from one agent to many is not mostly a prompting problem. It is a setup problem, a topology problem, and a verification problem, roughly in that order. Here is how I run it, what the shapes actually are, and the failure modes that cost me real time. The failures are the useful part.
Get the setup right before the fleet is worth anything
Everything I run sits on a small set of always-on rules plus a much larger set of lazy-loaded skills.
The always-on layer is deliberately tiny. My global config file is 182 lines and it is an index, not a manual. Alongside it live six rule files that load into every session no matter what:
$ wc -l CLAUDE.md rules/*.md
182 CLAUDE.md
60 rules/api-over-browser.md
17 rules/bash-write-verification.md
22 rules/known-issues-protocol.md
82 rules/session-continuity.md
31 rules/ship-sequence.md
26 rules/worktree-isolation.md
420 total
420 lines of always-on context, total. Everything else is loaded on demand.
Everything else is a skill: a directory whose one-line description is the only thing in context until the skill actually fires. There are 27 of them. My Linear skill is 1,463 lines across ten files, and the 64-line SKILL.md is what gets read when it triggers. The other 1,399 lines are pulled in only when a specific sub-task needs them.
$ ls -1 | wc -l 27 $ wc -l linear/*.md 379 linear/agent-sdk.md 115 linear/awaiting-chris.md 228 linear/features-underused.md 172 linear/hygiene.md 78 linear/mcp-pacing.md 77 linear/personal-hq-github-sync.md 112 linear/readability.md 129 linear/scan-before-creating.md 64 linear/SKILL.md 109 linear/workflow-states.md 1463 total
One skill, ten files, 1,463 lines. Only the highlighted 64 load when it triggers.
That split is the whole game. Context is the scarcest resource in a fleet, and it gets scarcer the more agents you run, so almost everything should be lazy. But there is one rule about which things are allowed to be lazy, and I learned it by getting it wrong twice:
A guardrail has to load BEFORE the mistake it prevents. If it is lazy-loaded, it never fires in time.
I had written a rule about how worktree isolation actually behaves, and filed it inside a skill about machine infrastructure. That skill's description covers launchd agents, MCP scoping and dead daemons. It says nothing about git. So the skill never auto-triggered at session start, my main config file still carried the stale instruction, and an agent following that instruction reproduced the exact wedge the rule had been written to prevent.
Same shape a second time, with a rule about not chaining PR creation, merge and worktree cleanup into one shell command. Filed in a lazy skill. Never loaded at the moment it was needed.
Both are always-on now. The test I use: if the failure mode is silent or destructive, the rule is always-on. If it is reference material you look up once you already know you need it, it is a skill.
Fan-out, and why agents should look at their own output
The most common shape is the simplest one. Take N independent pieces of work, hand each to its own agent, merge. The part most people skip is the small loop inside each branch.
Last week I needed a LinkedIn banner. Instead of iterating with one agent, I ran four in parallel, each asked to produce two distinct concepts, render its own output headless, look at the resulting image, and critique it before reporting back. Eight designs, one pass, and this is genuinely what came out:

Two of them found real bugs in their own work, and both are worth describing because of what kind of bugs they are.
The first had added a dither layer to kill banding across a wide gradient. It rendered the banner, looked at the PNG, and noticed the whole canvas had gone dull. The dither was a flat mid-grey texture composited on multiply, so it was not adding noise, it was multiplying everything by about 0.5 and desaturating the entire image. Its fix, and the comment it left behind in the file:
/* Zero-mean dither: kills banding across the wide ramp, shifts no colour */ .bnr-warm-a .dither{ position:absolute; inset:-10%; opacity:.13; mix-blend-mode:overlay; pointer-events:none; }
The second was trying to put a cool blue accent on a warm ivory ground and could not get the blue to read as blue. It worked out why:
/* The whisper of cool: a pale blue tint, added as light so it cools without muddying.
Multiplying blue over warm ivory computes to dead neutral grey, so this sits normal. */
.bnr-warm-b .cool{
position:absolute; inset:0;
background:
radial-gradient(800px 600px at -3% 26%,
rgba(199,216,246,0.36) 0%,
...
Look at what those two have in common. Neither is visible in the source. The CSS is valid, the intent is legible, and a reviewer reading the diff would sign off on both. They exist only in the pixels.
An agent that renders and looks at its own output catches a class of error that an agent reading its own code structurally cannot.
That generalises well past design. An agent that runs the test suite catches what an agent that reads the test file does not. An agent that curls the endpoint catches what an agent that reads the route handler does not. Make "look at the artifact" part of the agent's own job rather than a step you do afterwards, and each branch of the fan-out gets a lot more useful.
Pipeline beats barrier almost every time
Once each unit of work has more than one stage (write, then check / research, then verify / draft, then edit) you have a choice that most people get wrong by default.
The default is a barrier. Run stage one on all N items, wait for all of them, run stage two on all N. It reads naturally and it is what a Promise.all in a loop gives you. It is also slow, because every stage waits on its slowest member, and the slow member is a different item in each stage.
The alternative is a pipeline: each item flows through its own stages without waiting for its siblings. Item three can be getting adversarially checked while item one is still being drafted.
With variable durations, which is always, the pipeline wins and it never loses. In practice the call takes the item list plus one function per stage and threads each item through:
// Each topic flows through analyze then challenge on its own.
// No item waits for a sibling.
const done = await pipeline(TOPICS,
(t) => agent(`Research ${t.title}. Write ${W}/${t.key}.md.`,
{ label: `analyze:${t.key}`, effort: 'high', schema: ANALYSIS }),
(res, t) => agent(`Adversarially check ${W}/${t.key}.md. Is any number
unsupported? Is the conclusion too strong? Rewrite it in place.`,
{ label: `challenge:${t.key}`, effort: 'high', schema: CHECK })
.then(v => ({ key: t.key, res, check: v })))
The barrier is genuinely required in three cases, and it is worth knowing them so you can tell when you actually need one:
- Dedup across the full set. You cannot dedupe an item against results that do not exist yet.
- Early exit on zero. If stage one finds nothing, you want to skip stage two entirely rather than pay per item to discover that.
- Cross-item comparison. A judge that ranks all eight, picks a winner, or diffs one against another needs all eight in hand.
Everything else, pipeline it.
Verification has to be adversarial, and diverse
Asking an agent "is this correct?" gets you "yes" far more often than it should. Agreement is cheap. So the verify step is prompted to refute, and the claim dies on majority refute rather than surviving on majority confirm.
The upgrade on plain N-refuters is perspective diversity: give each verifier a different lens instead of the same instruction N times. N identical skeptics correlate their errors, which is exactly the failure you were buying insurance against. Different lenses do not.
The reason I run it this hard: a research agent once came back CONFIRMED on a biographical claim, with a real, working citation URL. The claim was wrong. The page at that URL said something else. It had cited a genuine source and confabulated its contents, which is close to the worst possible failure because every surface signal looks right. Since then "verify" in my prompts means pull the document and quote the line, never tell me whether it checks out.
Loop until dry, do not guess a count
For discovery work where you do not know the size of the answer (how many matching files, how many candidates, how many broken links) do not pass a count. Loop: harvest a batch, dedupe against everything you already hold, and stop when a full pass adds zero new items.
A fixed count is a guess that is wrong in both directions. Too low and you silently truncate, which is the dangerous one because a truncated result looks exactly like a complete one. Too high and you burn agents on empty passes. The dry test is cheap, self-correcting, and gives you a real stopping condition you can log.
The failure modes, which are the actual content
Everything above is the happy path. These are the things that cost me hours.
01Eight concurrent agents tripped a burst limit and all returned null in 33 seconds
Not a usage cap, not a quota. An account-level burst limit on concurrent starts. The tell is the shape of the failure rather than the error text: real work does not fail in 33 seconds, and it does not fail uniformly. Eight identical nulls means infrastructure, not eight bad prompts. The fix was three sequential waves of two, which cost about a minute of wall clock and made the whole thing reliable. Above roughly four wide, wave it.
02{...null} spreads to {}, and {} is truthy
This is the bug that made the burst limit expensive instead of merely annoying. The result-collection code ran .filter(Boolean) over the agent results, which looks correct and is completely useless here, because spreading a null result into an object literal produces an empty object that sails through the filter. So I got an array of the right length full of nothing, and the synthesis stage downstream cheerfully wrote a confident document out of eight empty objects. Null-guard inside the .then, at the point the result arrives, not in a filter afterwards.
03A shared scratch directory made one agent screenshot another agent's work
Four agents, one tmp directory, each told to render its design and verify the image. They picked overlapping filenames. One agent's verification pass loaded, inspected and approved a PNG a sibling had just written. It reported a clean self-review, honestly, because the image it looked at genuinely was fine. It just was not its image. Every parallel agent gets a scratch directory keyed to its own label, always.
04Subagents do not start in the parent's worktree
They start in the primary project directory. So an agent handed a relative path edits a file in a different checkout of the same repo, the parent greps its own worktree, finds nothing, and you burn ten minutes working out which of you is hallucinating. Pass REPO_ROOT=$(git rev-parse --show-toplevel) explicitly and require absolute paths everywhere. This gets worse the more checkouts you keep alive, and running a fleet means keeping a lot alive:
$ git worktree list
/Users/chris/Developer/schoolwise ef2d7322 [fix-migration-version-collision]
/Users/chris/Developer/schoolwise-rc 95b3e848 [main]
/Users/chris/.../issue-sch-460-june22-fix be202c9e [fix/sch-463-prompt-june11-authoritative]
/Users/chris/.../sch-734-stale-001-determinism 7477af75 [sch-734-stale-001-determinism]
/Users/chris/.../sch-data-completeness2 63a4b515 [sch-data-completeness2]
/Users/chris/.../sch485-live-eval a0a0b5a7 [sch485-live-eval]
/Users/chris/.../sch485-verify 6558d700 [sch485-verify]
/Users/chris/.../sch557-wa-debounce cf805bfa [fix/sch557-wa-debounce]
05An unconditional success message after a && chain will lie to you
A write like cd <dir> && cat >> file <<EOF ... EOF with echo "done"; tail -3 file on the next line: if the cd fails, the && short-circuits, the heredoc never runs, and the echo and the tail both run anyway, printing a confident success and tailing a different copy of the file. In a fleet the working directory is not what you assume, so this fires far more often than it does interactively. Absolute paths for writes, confirmation inside the chain, and verify a write by grepping for the new text rather than tailing.
06Do not chain PR creation, merge and cleanup into one command
Each is network-bound and can take 10 to 60 seconds. Together they blow a two-minute timeout partway through with no signal about where. The damage is worse than a plain failure: if the trailing cleanup ran but the create did not, the branch and the worktree are gone while no PR was ever opened, so the work looks like it evaporated. Separate calls, and a timeout tells you exactly where you stopped.
07A fleet you cannot tell apart is a fleet you stop watching
Past about four concurrent sessions the bottleneck stops being the agents and becomes finding the right window. Terminals make this harder than it sounds, because most of them derive the window title from whatever tab is active, so the moment an agent updates its own title your carefully named window is gone. In WezTerm even wezterm cli set-window-title does not hold: the next title the tab emits recomputes it. What works is persisting the label per window id and returning it from a format-window-title handler, which always wins, then colour-coding the title bar per project on top. That turns "which one of these was schoolwise" into a glance. The forty lines are here.
If you are running one agent today
Three things, in this order.
Split your config into always-on rules and lazy skills, and be honest about which failures are silent. Silent and destructive go always-on, everything else is a skill. A fleet magnifies whatever your setup already does, gaps included.
Make every agent verify its own artifact before it reports. Render it, run it, curl it, read it back. This is the change that pays most, because it moves verification out of a serial step at the end and into work that happens in parallel for free.
Pipeline instead of barrier, and wave your fan-out at about four wide. Then read your failures carefully. A fleet fails in ways a single agent does not, mostly around shared state, uniform errors, and results that are the right shape but empty.
Some of the pieces I use for this are open source, if you want to see the skill format up close:
If you are doing this inside a team and want a hand, email me.