KetanShukla.dev
A2A7 min read

One agent found 0 of 36. A crew found 36 of 36, for half the tokens.

A 240-item workload measured both ways. The single agent does not fail because it does more work — it fails because it re-sends everything it has already read on every iteration. Including the part where my first measurement was wrong.

A single agent loop is a very good cook working alone. Give it a job with sixty pieces and it is perfect. Give it four times that and it runs out of room somewhere in the middle, having achieved nothing at all — and having spent 440,000 tokens getting nowhere.

So I added one tool, spawn_agent, whose implementation is the agent loop calling itself. Then I measured it, because "delegation helps" is the kind of claim that sounds obviously true and is only sometimes true.

The numbers first

one agenta crewverdict
60 jars100% · ~99k tokens100% · ~83k tokensa wash — same answer, comparable cost
240 jars0 of 36 found · 439k tokens36 of 36 · 248k tokenscrew wins: correct and roughly half the price

0 / 36

single agent, 240 jars

439k

tokens spent to find nothing

36 / 36

crew, same workload

248k

tokens spent to find all of it

The headline is not "delegation is faster". It is that past a certain job size, delegation was the difference between an answer and 440,000 tokens of nothing.

Why the single agent fails, and why it is not the reason you would guess

The intuition is that the big job fails because there is more work in it. That is wrong, and the wrongness is the whole point.

The single agent fails expensively because it re-sends everything it has already read on every iteration. All 240 inspection reports ride along in the conversation, growing each trip around the loop, until it hits the iteration cap with nothing to show for it.

Three workers each carry 80 reports and finish. The orchestrator only ever sees three paragraphs.

That reframing is what makes delegation predictable rather than magical. You are not buying more intelligence. You are buying context isolation: several small tapes instead of one enormous one, with only the summaries crossing between them.

My first version of this table was wrong

The original table said the crew cost 1.7× more at 60 jars, and I wrote a tidy conclusion under it: delegation is pure overhead at this size, and a trade at larger sizes.

That number came from dividing one mode's total token spend by the other's — while the two modes had run different numbers of runs. I was comparing a sum against a sum without normalising by the denominator underneath each one.

Corrected, the crew is marginally cheaper even at 60 jars. The honest summary is not the tidy trade-off line the README originally carried. It is this: for this job shape, delegating never cost more, and past a certain size it was the difference between an answer and nothing.

I am leaving that here rather than quietly fixing it because the failure mode is extremely easy to repeat. Aggregate-over-aggregate is not a ratio. If you are benchmarking agent architectures, normalise per run before you divide, and be suspicious of any comparison that produces a satisfyingly quotable multiple on the first attempt.

A sub-agent is a tool that happens to think

There is no framework here. spawn_agent is one tool, and its implementation is runAgentLoop — the same function that is calling it. The sub-agent gets:

  • a fresh, empty conversation — its own head
  • the same toolbox — the same kitchen
  • one instruction, from its parent — the ticket
  • no spawn_agent of its own — cooks do not hire cooks

It hands back one paragraph of text. To the orchestrator, that paragraph is indistinguishable from any other tool result. It never learns that an agent produced it.

That last property is what keeps the design small. The orchestrator has no special code path for delegation; it has a tool that is slow and returns prose.

Recursion that cannot run away

The obvious failure of "a tool that starts an agent" is an agent that starts an agent that starts an agent. The obvious fix is a depth counter, and the obvious bug is getting the depth counter wrong.

There is no depth counter in this code. A sub-agent is started with delegate: false, so spawn_agent is not in its tool list at all.

lib/crew.ts
// The child is built from the same loop, minus one tool.
const child = runAgentLoop({
  prompt: task,
  toolbox,
  delegate: false,   // ← spawn_agent is never advertised to this agent
  depth: childDepth,
  // ...
});

It cannot recurse for the same reason you cannot dial a phone number that was never printed. The tree is exactly two levels deep, structurally, and no arithmetic can be off by one.

Three caps, because one cap leaks

Every seatbelt in the single-agent version was per-loop. Recursion multiplies per-loop limits instead of adding them: an orchestrator allowed ten iterations that spawns three workers allowed ten each has quietly authorised forty API calls.

So there are three limits, and they are deliberately different kinds of limit:

capvaluekind
MAX_CONCURRENT3a rate limit — how many run at once
MAX_SPAWNS8a quantity limit — how many run in total
MAX_TREE_TOKENSbudgeteda money limit — what the whole tree spends

Any one alone leaks. Concurrency of 3 with no total means sixty workers, three at a time. A total with no budget means eight workers that each burn their entire iteration allowance. The three only work together, and the reason is that they constrain three genuinely different resources: time, count, and money.

Five agents, one human

The hardest part was not spawning. It was approvals.

The single-agent approval model has an assumption buried in it so obviously true that it is invisible: the agent that hit the gate is the agent you are talking to. The loop stops, the request ends, the browser shows a card, you click, a new request restarts that same loop.

Delegation breaks it. The agent that hits the gate is two levels down, running inside a Promise.all, alongside two siblings that are still working. Nobody is talking to it. It has no browser tab.

The rule the implementation follows:

  • Every approval belongs to the human at the top. There is one queue, however deep the tree.
  • The child's reasoning travels up with it. The card says which worker asked and why, or the human is approving an anonymous call.
  • Finished siblings are kept, not re-run. Partial results are persisted, so resuming after an approval does not repeat completed work — which would be both expensive and, for any non-idempotent tool, wrong.

That third one is where the real engineering is. Pausing a tree is easy; resuming one without redoing the parts that already succeeded is what makes the pattern usable.

The question to ask about your own workload

Delegation is not free and it is not universally better. The measurement that decides it is cheap to run, and it is the one I would run before adopting any of this:

Does your job's context grow with the number of items processed? If each item's output has to stay in the conversation for the final answer to be correct, you have the 240-jar shape and delegation will help. If the loop reads one thing, acts, and discards it, you have the 60-jar shape and delegation is overhead you do not need.

The code is lib/crew.ts, and the comparison harness that produced the table is scripts/08-compare.ts. Run it against your own job shape rather than trusting my table.

sub-agentsagent-loopllm-costevalsmcpdelegation

Read next