Skip to content
MCP Five

The CrewProject #429 / 51

A sub-agent is a tool that happens to think

The model gets one extra tool, and its implementation is the function that is calling it.

"Multi-agent orchestration" sounds like a framework. A message bus, a scheduler, a supervisor tree, a state machine, probably a YAML file and a diagram with swimlanes.

Here is the entire mechanism.

Part 1 ยท One extra tool

The model gets one extra tool. Its implementation is runAgentLoop โ€” the function that is calling it.

lib/crew.ts โ€” with the bookkeeping removed
ts
async run(args, context) {
  for await (const event of runAgentLoop({
    messages: [{ role: "user", content: briefing(args.task) }],
    toolbox: context.toolbox,   // same kitchen
    gate: true,                 // same handbrake
    delegate: false,            // โ† cooks don't hire cooks
    budget: context.budget,     // same wallet
  })) {
    context.emit(event);                       // forward its trace upward
    if (event.type === "done") return event.finalText;   // its report
  }
}

That is it. Eight lines of substance.

Why it is this small

Look at the signature of the loop. It takes messages in, and yields events out.

A sub-agent is a thing you give a task to, which then gets on with it and reports back.

Those are the same sentence. There was never anything to build here โ€” only something to notice.

Read the five arguments again, because four of them are the design:

ArgumentWhy
messagesa fresh conversation โ€” this is the whole point, see the ceiling
toolboxthe same servers. A worker is not sandboxed differently.
gate: truethe same approval rules. A worker cannot do what the boss could not.
delegate: falsecooks don't hire cooks โ€” this is the recursion seatbelt, and it works by absence
budgetthe same wallet, by reference, so MAX_TREE_TOKENS stays true

Part 2 ยท What the orchestrator actually receives

This is the part that makes the whole thing compose.

A worker's entire forty-second investigation of eighty cookie jars โ€” twenty tool calls, three iterations, thousands of tokens of inspection reports โ€” arrives in the orchestrator's conversation as one paragraph of text, in an ordinary tool_result block.

json
{
  "type": "tool_result",
  "tool_use_id": "toolu_01ABCโ€ฆ",
  "content": "Inspected jars 1-80. Found 12 tampered: 4, 11, 19, 23, 31, 38, 44, 52, 57, 63, 71, 78. Seal broken on 4 and 38; the rest are weight anomalies with no AUTHORIZED note."
}

The orchestrator reads it exactly like it reads a dice roll. It has no idea an agent produced it.

Part 3 ยท The two sentences in the briefing that do the work

The briefing handed to a worker is short. Two clauses in it are load-bearing, and both are about context economics rather than correctness.

"Reply with a short report, not a transcript."

A worker that returns its whole trace hands the context problem straight back to the orchestrator โ€” which is the exact thing delegation was supposed to fix.

Without this sentence you have paid for three extra agents, three extra system prompts, three extra tool lists, and moved the ceiling nowhere.

"If you run out of room, report what you DID establish."

Turns a partial failure into partial data.

A worker that hits its iteration cap and says nothing is a hole in the answer. One that says "I inspected jars 1 through 54 and found three tampered; 55โ€“80 were not reached" is a result the orchestrator can act on โ€” and a human can see the gap.

npm run crew -- --boss
  โ”‚  ๐Ÿ‘ท hired #1 "jars 1-20"
โ”‚  ๐Ÿ‘ท hired #2 "jars 21-40"
โ”‚  ๐Ÿ‘ท hired #3 "jars 41-60"
โ”‚  โœ… #1 "jars 1-20" โ€” 3 iters, 14,654 tokens, end_turn

Note that last line: each worker reports its own iteration count and token bill, because each worker is a real run. That is what makes the comparison in the measurement trap possible at all.

Part 4 ยท The honest question

Sub-agents are impressive to watch. Three lanes light up, tool calls scroll past, reports come back, and the whole thing feels like an upgrade.

Feeling like an upgrade is not being one.

Count what delegation costs, every single run:

CostPaid
each worker re-reads the system promptร— 3
each worker re-reads the tool definitionsร— 3
the orchestrator's own loopร— 1
a summarising trip homeร— 1

The cost of delegation is real, immediate, and paid on every single run. The benefit is conditional on the job being big enough.

So measure it

scripts/08-compare.ts runs the same eval cases twice โ€” once with spawn_agent withheld, once with it offered. Same model, same prompt, same toolbox, same gate, same seatbelts. One flag differs.

60 jars โ€” the demo size

one agent: 100% ยท ~99k tokens

a crew: 100% ยท ~83k tokens

A wash. Same answer, and the cost difference is inside run-to-run variance.

240 jars โ€” past the ceiling

one agent: 0 of 36 ยท 439k tokens

a crew: 36 of 36 ยท 248k tokens

Correct and roughly half the price.

Part 5 ยท Which raises the thing that cannot live on a server

Look again at what spawn_agent needs: context.toolbox, context.budget, context.emit.

None of that survives a trip over HTTP. The budget in particular is a shared mutable object three other agents are drawing from concurrently โ€” serialise it and you have a copy, and copies diverge.

So the loop has to learn about a kind of tool it has never had before, and that turns out to be a security boundary as well as a plumbing detail.

Local tools โ†’

What you now know

  • A sub-agent is one extra tool whose implementation is the loop that is calling it.
  • The orchestrator receives its report as ordinary text in a tool_result โ€” which is why the gate, the trace, the budget and replay all work on it for free.
  • Two English sentences in the briefing carry the architecture: report, don't transcribe, and report partial findings.
  • Delegation costs are paid every run; benefits are conditional on size. Measure your own crossover.
  • spawn_agent cannot be a server tool, because shared mutable state cannot cross a network boundary.