Sub-agent isolation as cost and pollution control
What it is
A sub-agent is a separate LLM invocation with its own context window, given a scoped task, whose result (not whose working context) is returned to the caller. The parent agent sees the answer; it never sees the sub-agent's intermediate reasoning, tool calls or tool results.
That last clause is the entire technique. Isolation is about what does not come back.
WITHOUT isolation (single agent):
context: [system][tools][history][search call][2,400 tokens of results]
[another search][3,100 tokens][read file][8,900 tokens of code]
[failed call][error][retry][1,200 tokens]... -> 40,000 tokens,
most of it debris, all of it re-sent on every subsequent turn
WITH isolation:
sub-agent context: the same 40,000 tokens, in its own window, discarded after
parent context: [system][tools][history]["Found: the retry logic in
handler.py:88 does not reset the backoff timer"] -> 180 tokens
The sub-agent burns 40,000 tokens once; the parent carries 180 forever. That asymmetry is why the pattern exists.
What it is confused with: multi-agent systems in the "team of collaborating personas" sense. Those are usually a different and weaker idea: several agents with different system prompts talking to each other, which multiplies cost and coordination failure without a clear mechanism for improvement. Sub-agent isolation is narrower and more defensible: a context management technique that happens to be implemented as a second LLM call.
The problem it solves
Two problems, and they compound.
Context pollution. An agent that searches, reads files, calls APIs and retries failures accumulates all of that in its window. By the time it answers, most of its context is intermediate work that is no longer relevant, and every subsequent turn re-sends it. This is the mechanism behind context rot, and the polluting content is exactly the kind that produces contradictions: a failed call and its retry, a search that returned nothing, a file read that turned out to be the wrong file.
Quadratic cost. In a single-agent loop, every tool result stays in the context for the rest of the session:
Agent does 12 tool calls, averaging 2,000 tokens of results each.
Without isolation, cumulative input tokens across the loop:
call 1: 3,000 (system + tools + task)
call 2: 5,000
call 3: 7,000
...
call 12: 27,000
TOTAL: ~180,000 input tokens for 24,000 tokens of actual results
With isolation (4 sub-agents, 3 calls each, returning ~200 tokens):
sub-agents: 4 x ~15,000 = 60,000
parent: 4 turns x ~3,500 = 14,000
TOTAL: ~74,000 input tokens
Roughly 2.4x cheaper, and the parent's final context is 3,500 tokens instead of 27,000, so its answer quality is better for the reasons on the context-rot page.
The saving grows with the number of tool calls, because the single-agent version is
O(n^2) in tool results and the isolated version is O(n).
Mechanics
The pattern
@dataclass
class SubAgentResult:
summary: str # what the parent needs, and ONLY that
artifacts: dict[str, str] # optional structured findings
tokens_used: int # for accounting
succeeded: bool
def run_subagent(task: str, tools: list[Tool], budget: int) -> SubAgentResult:
ctx = [SUBAGENT_SYSTEM, format_tools(tools), task]
used = 0
for _ in range(MAX_STEPS):
response = model(ctx)
used += response.total_tokens
if used > budget:
return SubAgentResult("budget exhausted", {}, used, succeeded=False)
if response.is_final:
return SubAgentResult(response.text, response.artifacts, used, True)
result = execute_tool(response.tool_call)
ctx += [response, result] # grows HERE, and is discarded after
return SubAgentResult("step limit reached", {}, used, succeeded=False)
The parent treats it as one tool call:
# From the parent's perspective, a sub-agent is just an expensive tool
# that returns a short string.
parent_tools = [
Tool("investigate_codebase",
description="Search and read code to answer a specific question. "
"Returns a summary. Give it ONE focused question.",
handler=lambda q: run_subagent(q, CODE_TOOLS, budget=50_000).summary),
]
The description telling the caller to give one focused question is load-bearing. A sub-agent given a vague task ("look into the auth system") returns a vague summary and burns its budget. Given a specific one ("does the token refresh path handle a 401 from the IdP?") it returns something actionable.
The return contract is the whole design
What comes back determines whether this works. Three levels of discipline:
# Weak: return the sub-agent's last message.
return response.text # may be 3,000 tokens of narration
# Better: constrain the summary length.
return summarise(response.text, max_tokens=300)
# Best: a structured schema, so the return is bounded AND parseable.
class InvestigationResult(BaseModel):
answer: str = Field(max_length=800)
evidence: list[str] = Field(max_length=5) # file:line references
confidence: Literal["high", "medium", "low"]
unresolved: list[str] = Field(max_length=3)
The structured version uses constrained decoding (see structured output) so the return is guaranteed bounded. An unbounded sub-agent return defeats the purpose: if the sub-agent hands back 4,000 tokens, you have paid for isolation and not received it.
The unresolved field matters more than it looks. A sub-agent that hits its budget or
cannot determine something must be able to say so, or the parent proceeds on a confident
non-answer.
When to isolate, and when not to
Isolate when:
- The task produces large intermediate output the parent does not need: searching, file reading, log analysis, running a query.
- The task is independent and can run in parallel with others.
- The task might fail messily, and you want the failure contained rather than polluting the parent's context with errors and retries.
- You want a different tool set or a different model for that work.
Do not isolate when:
- The parent needs the intermediate reasoning to make its decision. A sub-agent's summary is lossy by construction, and if the loss is the information the parent needs, you have broken the task.
- The task is small. A sub-agent costs a full model call plus its own system prompt and tool definitions, typically 1,500 to 3,000 tokens of overhead. Isolating a task whose results are 400 tokens costs more than it saves.
- The task needs conversation context. Sub-agents start fresh, so anything they need must be in their task description, and reconstructing that can be more expensive than the isolation saves.
The rough threshold: isolate when the intermediate work exceeds roughly 5x the overhead of spawning, so results above about 8,000 to 15,000 tokens.
Parallel sub-agents
Independent sub-agents run concurrently, which is a latency win the single-agent loop cannot have:
async def investigate(questions: list[str]) -> list[SubAgentResult]:
return await asyncio.gather(*[
run_subagent_async(q, CODE_TOOLS, budget=40_000) for q in questions
])
Sequential single agent, 4 investigations: ~48s
4 parallel sub-agents: ~14s (bounded by the slowest)
The cost is unchanged and the latency is a quarter. This is the second argument for the pattern and it is often the one that decides it for interactive products.
The constraint is that the parent must be able to decompose the task into genuinely independent questions before seeing any answers. If question 2 depends on the answer to question 1, you are sequential regardless.
Budget accounting, which is where this goes wrong
@dataclass
class TokenBudget:
total: int
spent: int = 0
def child(self, allocation: int) -> "TokenBudget":
if self.spent + allocation > self.total:
raise BudgetExceeded(f"cannot allocate {allocation}")
self.spent += allocation
return TokenBudget(total=allocation)
Without a budget hierarchy, sub-agents are how a single request costs $40. A parent spawning 5 sub-agents, each of which spawns 3 more, each running 10 steps on a large context, is a plausible accident. The parent must allocate from a bounded pool, and recursion depth must be capped explicitly.
A worked example: a code-review agent that cost $12 a review
An agent reviewing pull requests: reads the diff, searches the codebase for related code, checks tests, and produces review comments.
Original: a single agent with all tools.
mean tool calls per review: 23
mean final context: 84,000 tokens
mean total input tokens: 610,000 (the quadratic accumulation)
cost per review: $12.40
p50 latency: 4m 20s
review quality (human rating): 3.2/5
Two problems and one surprise. The cost and latency were the reported issues. The surprise was quality: reviews of large PRs were noticeably worse than reviews of small ones, and the team had assumed that was inherent difficulty.
Auditing the final context on a large-PR review:
system + tools: 3,100 tokens (4%)
the diff itself: 6,800 tokens (8%)
grep results (11 calls): 31,200 tokens (37%)
file reads (7 calls): 28,400 tokens (34%)
failed calls and retries: 9,900 tokens (12%)
test run output: 4,600 tokens (5%)
Eighty-three percent of the context was intermediate work, and 12 percent was failures and retries. The model was writing its review from a window in which the diff, the thing being reviewed, was 8 percent of the content and sat in the positional trough.
The redesign: three isolated investigators plus a synthesiser.
async def review(pr: PullRequest) -> Review:
budget = TokenBudget(total=250_000)
# Three INDEPENDENT investigations, run in parallel.
findings = await asyncio.gather(
run_subagent_async(
f"Does this diff break any existing caller? Diff:\n{pr.diff}",
tools=[grep, read_file], budget=budget.child(60_000)),
run_subagent_async(
f"Are the tests adequate for this change? Diff:\n{pr.diff}",
tools=[grep, read_file, run_tests], budget=budget.child(60_000)),
run_subagent_async(
f"Does this follow the conventions in this codebase? Diff:\n{pr.diff}",
tools=[grep, read_file], budget=budget.child(60_000)),
)
# The synthesiser sees the DIFF and three SHORT findings. Nothing else.
return synthesise(pr.diff, [f.summary for f in findings])
class Finding(BaseModel):
verdict: Literal["ok", "concern", "blocker"]
summary: str = Field(max_length=600)
evidence: list[str] = Field(max_length=4) # "src/api.py:214"
unresolved: list[str] = Field(max_length=2)
Measured:
single agent isolated
mean total input tokens 610,000 178,000 (-71%)
cost per review $12.40 $3.10 (-75%)
p50 latency 4m 20s 1m 10s (-73%)
synthesiser final context 84,000 9,400
of which the diff 8% 72%
review quality (human) 3.2/5 4.1/5
quality on LARGE PRs 2.4/5 4.0/5 (was the worst case)
Cost down 75 percent, latency down 73 percent, and quality up, most dramatically on the large PRs that had been worst. The quality gain is the interesting one: the synthesiser now writes its review from a context that is 72 percent the diff instead of 8 percent.
Two things went wrong on the way, and both are the standard failure modes.
First attempt, unbounded returns. The sub-agents returned their full final messages, averaging 2,900 tokens each:
synthesiser context: 9,400 -> 31,200 tokens
cost per review: $3.10 -> $6.80
quality: 4.1/5 -> 3.6/5
Most of the benefit disappeared, because the sub-agents were narrating their process
rather than reporting conclusions. Adding the Finding schema with a max_length fixed
it. An unbounded sub-agent return is isolation you paid for and did not get.
Second, a recursion accident. The "conventions" sub-agent was given access to the investigate tool by a copy-paste, so it could spawn its own sub-agents. One review spawned 19 sub-agents across three levels and cost $71 before the step limit stopped it.
# The fix: depth is explicit and tools are scoped per level.
def tools_for_depth(depth: int) -> list[Tool]:
base = [grep, read_file]
if depth == 0:
return base + [investigate_tool] # only the parent may spawn
return base # sub-agents get leaf tools only
Sub-agents get leaf tools only. Recursive spawning is almost never what you want and it is trivially easy to enable by accident.
The lesson: isolation's value is entirely in the return contract. The topology (a parent and three children) is the easy part and is not where the benefit comes from. The benefit comes from the parent receiving 600 tokens instead of 30,000, and that is a schema decision.
Production evidence
Anthropic's Claude Code uses sub-agents with exactly this contract: a scoped task, its own context, and only the result returned. Their published guidance frames it as a context management technique and recommends it for tasks producing large intermediate output such as broad searches.
Anthropic's multi-agent research system write-up reports that a lead agent spawning parallel sub-agents for independent sub-questions substantially outperformed a single agent on research tasks, and is explicit that the token cost is higher in total while the parent's context stays clean. They also report that most of the performance difference came from parallelism and context isolation rather than from any "collaboration" between agents.
OpenAI's Swarm and Agents SDK implement handoffs, where control transfers to another agent with a fresh context. That is a related pattern with a different topology: handoff replaces the agent, isolation subordinates one.
LangGraph's subgraph support allows a node to run its own graph with separate state, and the documented motivation is state isolation rather than agent collaboration, which is the same framing.
The "agents as tools" pattern appears across frameworks (CrewAI, AutoGen, LlamaIndex),
and the consistent finding in production write-ups is that the return contract determines
whether it helps. Frameworks that return full agent transcripts by default produce the
$6.80 outcome from the worked example.
The debate
Is this multi-agent architecture? Only in a narrow sense, and the distinction matters because "multi-agent" has accumulated a lot of unsupported enthusiasm. Sub-agent isolation is a context management technique implemented as a second LLM call. It is not several personas debating, not a simulated org chart, and it does not claim that specialised prompts produce specialised expertise. My position: isolation is well-motivated and measurable; "collaborating agent teams" mostly is not, and conflating them attaches a defensible technique to an indefensible one.
Does it improve quality or only cost? Both, through one mechanism: the parent's context stays small and relevant, so it is not subject to the positional and pollution effects on the context rot page. In the worked example quality on large PRs went from 2.4 to 4.0 out of 5, and the cause is visible in the numbers: the diff went from 8 percent to 72 percent of the synthesiser's context. The quality gain is a context-engineering gain, not an emergent property of having several agents.
When is a single agent better? When the parent needs the intermediate reasoning, when the task is small enough that spawning overhead dominates, and when debuggability matters more than efficiency. A single agent has one trace; a parent with four sub-agents has five, and correlating them requires deliberate instrumentation. For a task with fewer than about five tool calls, a single agent is simpler and cheaper.
How do you bound the cost? A budget hierarchy with explicit allocation, and a hard depth cap enforced by scoping tools per level rather than by instruction. The recursion accident in the worked example ($71 for one review) happened because a sub-agent had the spawn tool, and no prompt instruction reliably prevents that. Give sub-agents leaf tools only.
Is parallelism the real win? Frequently, yes, for interactive products: four sub-agents in parallel took latency from 4m20s to 1m10s in the worked example, and that is often worth more than the cost saving. The constraint is that the decomposition must be genuinely independent, which requires the parent to formulate all the questions before seeing any answers. If the questions are sequentially dependent, you get the context benefit and not the latency benefit, and it is worth knowing which one you are buying.
Follow-up Q&A
"What is a sub-agent and what makes it worth doing?"
A separate LLM invocation with its own context window and a scoped task, whose result returns to the caller while its working context is discarded. The value is entirely in what does not come back: the sub-agent may burn 40,000 tokens on searches and file reads, and the parent carries 200 tokens of conclusion forever. In a single-agent loop, every tool result stays in context for the rest of the session, so cost is quadratic in tool calls and the final context is mostly debris.
"When would you not use one?"
When the parent needs the intermediate reasoning, because the summary is lossy by construction and if the loss is what the parent needed you have broken the task. When the task is small, because spawning costs 1,500 to 3,000 tokens of system prompt and tool definitions, so isolating work that produces 400 tokens costs more than it saves. And when debuggability matters more than efficiency, since one agent has one trace and a parent with four children has five.
"What determines whether it actually helps?"
The return contract, and nothing else comes close. In one case sub-agents returning their full final messages averaged 2,900 tokens each, which took the parent's context from 9,400 to 31,200 and erased most of the cost and quality benefit. A structured schema with an explicit length bound, enforced by constrained decoding, is what makes the isolation real. Include a field for what the sub-agent could not determine, or the parent proceeds on a confident non-answer.
"How do you stop the cost from running away?"
A budget hierarchy where the parent allocates from a bounded pool and each child gets an explicit allocation, plus a hard depth cap enforced by scoping tools per level. A sub-agent should get leaf tools only, never the spawn tool. In one case a copy-paste gave a sub-agent the ability to spawn, and a single review produced 19 sub-agents across three levels and cost $71 before the step limit stopped it. No prompt instruction reliably prevents that; tool scoping does.
"Is this the same as a multi-agent system?"
Not in the sense that phrase usually carries. This is a context management technique implemented as a second LLM call, and its benefit is measurable and mechanical: the parent's context stays small and relevant. It is not several personas collaborating, and I would be cautious about claims that specialised system prompts produce specialised expertise. The published results that hold up attribute the gains to parallelism and context isolation rather than to collaboration.
"How does this interact with latency?"
Independent sub-agents run concurrently, which a single-agent loop cannot do: four investigations went from 4m20s sequential to 1m10s parallel at the same cost. That requires the parent to decompose the task into genuinely independent questions before seeing any answers. If question 2 depends on the answer to question 1 you remain sequential, and you get the context benefit without the latency benefit, which is worth knowing when you are choosing the pattern for a specific reason.
Common misconceptions
"Sub-agents are for specialisation." They are for context isolation. A "security expert" system prompt does not make a model a security expert; what the pattern reliably delivers is a clean parent context and parallelism.
"More agents means better results." Each agent costs a full call plus its overhead, and the benefit comes from what is excluded from the parent's context. Five sub-agents returning verbose summaries is worse than one agent, because you paid five overheads and still polluted the parent.
"The sub-agent should report what it did." It should report what it found. Narration of the process is exactly the content isolation exists to discard, and it is the default behaviour of an unconstrained return.
"Isolation costs more tokens in total." Often it costs fewer, because the single-agent alternative is quadratic in tool results. In the worked example total input tokens fell 71 percent. Where it does cost more in total, the parent's context quality and the parallelism are usually still worth it.
"You can let sub-agents spawn sub-agents." You can, and the recursion is very hard to bound by instruction. Scope tools per depth so leaf agents cannot spawn.
Interview delivery note
Say this verbatim: "A sub-agent's value is entirely in what does not come back. It might burn 40,000 tokens on searches, and the parent carries 200 tokens of conclusion. In a single-agent loop every tool result stays in the context for the rest of the session, so cost is quadratic in tool calls and the final answer is written from a window that is mostly debris." The mechanism and the cost argument in one breath.
The senior-versus-staff separator is the return contract as the load-bearing part. A senior engineer describes the topology (a parent spawning children) correctly. A staff engineer says the topology is the easy part and the benefit lives entirely in the schema, that an unbounded return is isolation you paid for and did not get, and can name the measurement: sub-agents returning full transcripts took the parent's context from 9,400 to 31,200 tokens and erased most of the benefit.
The second signal is scoping tools by depth rather than instructing sub-agents not to spawn. It shows you have watched a recursion accident happen and know that prompt instructions are not a control surface for this.
Further reading
- Anthropic's engineering post on building a multi-agent research system, for the parallel-sub-agent architecture and the honest accounting of its token cost.
- Anthropic's Claude Code documentation on sub-agents, for the scoped-task and result-only contract in a shipped product.
- LangGraph documentation on subgraphs, for state isolation as the stated motivation.
- The context-rot and compaction pages in this chapter, for why a clean parent context produces the quality gain.