Fanning out was about 1.7x slower on work built to favour it
Subagent guides put the speed-up at 40 to 60 percent. This site had only measured fan-out on four-byte tasks, where spawning obviously dominates, and said so at the time. Given eight independent 40KB files, one agent each, delegating took 29.6 seconds against 16.8 inline. It took two earlier attempts and one documented field I had not read to measure that honestly.
The subagent guides are specific about speed. “Parallel execution cuts task completion time by 40-60% compared to sequential prompting”, and three to five concurrent subagents is named as the sweet spot.
This site measured fan-out before and found it slower, with a hole in the measurement stated plainly at the time: each agent wrote four bytes. At that size spawning is the whole cost. The published caveat named what would settle it: “A real fan-out over slow, genuinely independent work is where the wall-clock case lives, and this task cannot speak to it.”
So: files of 40KB each, one planted marker per file, report every marker. Independent per file, nothing shared, heavy enough that handling one is real work.
| Median wall clock | Fan-out | Inline | Ratio |
|---|---|---|---|
| 4 units | 20.0s | 11.7s | 1.72x slower |
| 8 units | 29.6s | 16.8s | 1.76x slower |
At both widths delegating cost about 1.7x the wall clock. The eight-unit gap is 12.8 seconds of real time.
Six runs per arm at each width, no exclusions, and the arms do not overlap on either. At eight units the fastest fan-out run took 28.7s against a slowest inline of 21.5s, a 7.2-second margin; at four units the margin is 4.0s.
Tokens go the same way: 1.82x at four units, 2.17x at eight, also separated.
Getting to a number I trusted took three attempts
The interesting part of this is not the ratio, it is what it took to be entitled to it.
The obvious worry with any fan-out measurement is that the parent does not really delegate. If it spawns agents and then does the work itself, you are comparing “delegation plus the whole job” against “the job”, and the answer means nothing. So you look at the parent’s tool calls.
The first attempt showed the fan-out parent’s stream carrying exactly N non-Agent file operations alongside N Agent spawns. Read as the parent doing the job itself, that is blatant contamination, and the corpus was discarded on that reading.
The second attempt added an explicit instruction not to inspect the files and a control to enforce it. That control voided a quarter of the fan-out runs. It was also blind: it tested whether the string report- appeared in a tool argument that had already been truncated to 120 characters, and a Read argument is an absolute path whose filename sits past that. It caught Bash greps, where the pattern lands early in the command, and never once looked at a Read. That corpus was discarded too.
Both attempts failed on the same unanswered question: when a non-Agent tool call shows up in the parent’s stream, whose call is it?
The field that answers it
Every assistant and user message in --output-format stream-json carries parent_tool_use_id. On a message from the main conversation it is null. On a message from a subagent it is the tool_use id of the Agent call that spawned it.
None of this was hidden. Anthropic documents it twice, once under Follow subagent messages in the headless docs and once under Detect subagent invocation in the SDK docs. Two corpora were built and thrown away without reading the page that describes the stream format they were parsing. That is the actual lesson here, and it is a more common failure than a hidden field would have been.
What the field changes is what the first attempt’s shape most likely meant. In this corpus the parent makes zero non-Agent calls and the subagents make exactly N, split between Read and Bash depending on how each one chose to find its marker. The first attempt showed exactly N non-Agent calls in that same Read/Bash mix, in all eight of its fan-out runs. That signature matches subagents doing what they were told, not a parent redoing the work.
It stops short of proof, and the stopping point is worth naming: that corpus was recorded before the harness read parent_tool_use_id, so its calls cannot be attributed now and never will be. The claim is that the discarded attempt was probably fine, not that it has been re-scored.
With attribution in place the picture is clean and boring, which is what a working experiment looks like:
| Per run | Parent calls | Subagent calls | Parent fixture operations |
|---|---|---|---|
| Fan-out, 8 units | 8 (the spawns) | 8 (one file operation each) | 0 |
| Inline, 8 units | 8 (the reads) | 0 | 8 |
Zero parent file operations in all twelve fan-out runs. Exactly N in all twelve inline runs, which is the positive half of the same control: a check that never fires is not a check, and this one fires precisely where it should.
Dollars, and a mechanism this cannot claim
Fan-out came out slightly cheaper: 0.92x at four units and 0.97x at eight, non-overlapping but small.
That is odd next to using roughly twice the tokens, and the arms do price differently: about $3.39 per million context tokens for fan-out against $7.55 for inline at eight units. The tempting explanation is that subagents run on a cheaper model. They do not. A separate probe of the per-model split found both arms overwhelmingly on Opus, with Haiku under a thousand tokens in each.
So the token mix differs rather than the model, and this corpus sums context without splitting fresh input from cache creation and cache read. It can show the price-per-token gap is real and cannot say what causes it. The cost result is reported and the mechanism is left open.
What this does not settle
The crossover is still not located, and that is now two corpora saying so. Four-byte units: slower. 40KB-file units: slower. Somewhere above this parallelism must win, because eight units that each block for a minute cannot be beaten by doing them in series. Nothing here says where that point is; the honest reading is that it sits above a 40KB file search, not that it does not exist.
The case the advice is probably describing is a unit that blocks for a long time on something that is not the model: a test suite, a build, a network fetch. Untested here, and a genuinely different shape.
Wall clock is a property of this machine. The ratio transfers, the seconds do not. Arms ran back to back inside each round and the running order alternated between rounds.
Controls
Delegation, by attribution rather than by guessing. Every tool call is published with a by field of parent or subagent, derived from parent_tool_use_id. The matcher was control-tested before these runs against four synthetic calls: a parent Read with a path long enough to defeat the old truncation, a subagent Read, a parent grep and a subagent grep. It fires on the first and third only. The previous version of this control was never tested that way, which is why it shipped blind.
Arrival, 24 of 24. Every planted marker had to come back, matched as an exact string.
No exclusions. Every run in this corpus is scored.
The fixture was measured on disk every run: 160.4 KB at four units, 320.7 KB at eight.
Isolation on every call, --setting-sources project, asserted onto the command line. Tokens from modelUsage, not .usage.
All 24 runs, with per-call attribution, are published in fanout-wall-clock.json.