Subagents in the Claude Agent SDK: isolation and parallelism
AI · Aug 2026 · 17 min read
Delegation buys you a clean context window, real parallelism, and per-agent tool restrictions. It costs you a full context rebuild each time — which is the whole trade.
A subagent is a separate agent instance your main agent can spawn. It runs its own conversation, with its own system prompt and its own tool set, and returns a single final message to the parent.
That last detail is the point. Everything the subagent read, every dead end it explored, every 400-line file it opened — none of it lands in the parent's context. The parent gets the conclusion.
Step 1: define one
Subagents are declared in the agents option as a name-to-AgentDefinition mapping. description decides when Claude reaches for it; prompt decides how it behaves once invoked.
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
async def main():
async for message in query(
prompt='Review the authentication module for security issues.',
options=ClaudeAgentOptions(
allowed_tools=['Read', 'Grep', 'Glob', 'Agent'],
agents={
'security-reviewer': AgentDefinition(
description=(
'Security review specialist. Use for auth, input '
'handling, and anything touching credentials.'
),
prompt='You review code for security defects. Report each '
'finding with file, line, and concrete impact.',
tools=['Read', 'Grep', 'Glob'], # read-only
model='sonnet',
),
},
),
):
if hasattr(message, 'result'):
print(message.result)
asyncio.run(main())
Agent must be in allowed_tools: Claude invokes subagents through the Agent tool. Leave it out of allowed_tools and every delegation falls through to your permission handler — or, in dontAsk mode, is denied outright. The usual symptom is an agent that mysteriously refuses to delegate; this is the first thing to check.
Step 2: know exactly what crosses the boundary
The most common subagent bug is assuming shared context. The subagent's window starts fresh. The only thing your parent passes across is the prompt string in the Agent tool call — so file paths, error text, and prior decisions have to be written into that prompt explicitly.
what a subagent starts with Receives Does not receive Its own AgentDefinition.prompt The parent's conversation history The Agent tool's prompt string The parent's tool results Project CLAUDE.md (via setting sources) The parent's system prompt Its tools — inherited, or the subset in tools= Preloaded skills, unless listed in skills=
In practice this means a delegating prompt that reads like a briefing to someone who just walked in. "Review the auth module" is not enough; "Review src/auth/session.py and src/auth/tokens.py, focusing on the token refresh path — we already ruled out the login handler" is.
Step 3: restrict tools per agent
Each subagent gets its own tool list, and this is the cleanest privilege boundary the SDK offers. A tool you omit is not in that subagent's session at all — no permission prompt, no error, it simply does not exist from the subagent's point of view.
useful tool sets Role Tools Cannot Analysis / review Read, Grep, Glob Modify or execute anything Test runner Bash, Read, Grep Edit source files Refactorer Read, Edit, Write, Grep, Glob Run commands General omit tools= — inherits everything
A reviewer that physically cannot write is worth more than a reviewer instructed not to. The first is a property of the system; the second is a sentence in a prompt.
Step 4: parallelism, and where the money goes
Independent subagents run concurrently, so three reviews take the time of the slowest rather than the sum. That is the headline benefit and it is real — but it is worth being precise about the cost, because delegation is not free.
Every subagent rebuilds context from nothing: it re-reads the files it needs, re-derives what the parent already knew, and writes a report the parent then reads. For a task the parent could finish in three tool calls, that overhead dominates and you have made things slower and more expensive.
The rule that holds up: Delegate when the work is genuinely independent and sizeable — a wide multi-file investigation, several unrelated modules. Do not delegate work you could finish yourself in a handful of tool calls, and do not delegate verification: checking your own work belongs in the main loop, where the context already is.
Step 5: confirm it actually delegated
Printing only the final result tells you nothing about how it was produced. Watch for Agent tool calls in the stream if you want to know whether your definitions are earning their keep.
from claude_agent_sdk import ToolUseBlock
async for message in query(prompt=..., options=options):
for block in getattr(message, 'content', None) or []:
# 'Task' was renamed to 'Agent'; match both for compatibility
if isinstance(block, ToolUseBlock) and block.name in ('Task', 'Agent'):
print('delegated to', block.input.get('subagent_type'))
# messages produced inside a subagent carry the parent's tool id
if getattr(message, 'parent_tool_use_id', None):
print(' (from inside a subagent)')
If nothing delegates, the description is usually the culprit rather than the model. Descriptions are matched on intent, so name the situations that should trigger it, not the agent's job title.
Step 6: the failure modes worth knowing
Subagents run in the background by default in recent versions. If you need the result before continuing, that is run_in_background: false on the invocation — not something you can assume. Subagents can spawn their own subagents, several layers deep. Fine when intended, surprising when not; the depth is configurable via environment variable. An API error that kills a subagent is not delivered as a normal result. Partial text comes back with a note; a subagent that produced nothing returns an explicit early-termination error. The parent may summarise the subagent's final message rather than passing it through. If you need it verbatim, say so in the parent's prompt.
Subagent output is not trusted input: A subagent's final message passes through an instruction-shaped-pattern scan before the parent reads it — control tags get neutralised, turn markers get escaped. That exists because a subagent reading attacker-controlled files could otherwise write text that reads to the parent as an instruction. Treat anything a subagent returns as data.
When not to use them
Subagents are a context-management tool that happens to give you parallelism. If your context is comfortable and the work is sequential, they add latency, tokens, and a second place for things to go wrong, in exchange for nothing.
The honest test is whether you can name what the isolation buys. "This exploration would put 40k tokens of files into the main window" is a reason. "It seems more organised" is not — and at scale, that instinct produces a system that costs several times what the direct version would.
References
Takeaways
A subagent's context starts empty — the Agent tool's prompt string is the only thing you pass it. Per-agent tools= is the cleanest privilege boundary available; an omitted tool simply does not exist there. Include Agent in allowed_tools or delegation is denied, which looks like a model that won't delegate. Delegate independent, sizeable work — not verification, and not anything a few tool calls would finish.
All notes · Shehzad Aslam