Claude Code Subagents: When They Save Context and When They Hurt
What is a subagent in Claude Code?
A Claude Code subagent is a separate Claude instance with its own context window, system prompt, and tool set that the main conversation hands a task to. It doesn't see the conversation history and returns only its final answer. That pays off when the intermediate work is large and the result is short, and it doesn't when the task is short or depends on the current conversation.
TL;DR
- -Across the author's 535 completed subagent runs, the median subagent peaks at ~117K tokens of context and returns ~1,500 characters to the main conversation. That gap is what isolation buys.
- -Every launch has a price: in the same setup, the median subagent starts with ~58K tokens of context. Tasks with 1-3 tool calls don't pay off: ~55K tokens at startup for a ~1,100-character answer.
- -In a controlled experiment (Claude Code 2.1.318, n=3 per variant), a narrow tools list cut startup context by 78.5%, omitClaudeMd by 12.8%, and both together by 91.8%, down to ~4.7K tokens. Result size didn't change.
- -Subagent model resolution order: the invocation parameter, the model field in frontmatter, CLAUDE_CODE_SUBAGENT_MODEL, then the main conversation's model. The environment variable overrides frontmatter only with CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1.
- -89% of the author's runs go to the built-in general-purpose agent. An agent file is for pinning tool restrictions, a model, and repeatable behavior, not for delegation as such.
On the author’s machine, the median Claude Code subagent peaks at about 117K tokens of context and returns about 1,500 characters to the main conversation. A subagent is a separate Claude instance with its own context window, system prompt, and tools: it does the work off to the side and hands back only the result. The gap between that peak and the result is why you launch one.
The same median has a second number. Before its first action, the subagent already holds about 58K tokens. That’s the launch price, and on a short task it eats the whole benefit. The Claude Code docs describe subagent mechanics in detail, but they don’t say what startup costs or when it pays off.
The goal here is to decide whether a specific task needs a subagent. The sources: the subagent docs (checked against Claude Code 2.1.318), aggregates from 648 of the author’s subagent transcripts, and a controlled experiment with 18 runs. The sample comes from one user with a heavy setup, so the numbers come with their limits and a script to repeat the measurement on your own machine. A reference for every agent file field already exists in Claude Code Skills and Agents, so it isn’t repeated here.
What a subagent isolates
A regular (non-fork) subagent starts with a fresh context window. It doesn’t continue the conversation. It gets a task written by the main agent and gathers the context it needs on its own.
What a subagent gets at startup and what it can’t see
| Gets | Doesn’t get |
|---|---|
| Its own system prompt (the agent file body) | The main conversation history |
| The task message from the main agent | Files the main agent already read |
User, project, and local CLAUDE.md (except Explore and Plan; turned off with omitClaudeMd) | Skills invoked in the main conversation |
| A git status snapshot (except Explore and Plan) | The main conversation’s auto memory |
Skills from the skills field (full content) | The main session’s output style |
| Schemas of allowed tools, MCP included |
The right column holds the main limitation. Anything the main agent knows from the conversation reaches the subagent only if it made it into the task text. Per the docs, the built-in Explore and Plan agents start without CLAUDE.md and git status so exploration stays fast and cheap.
What comes back to the main conversation
Only the subagent’s final answer comes back. Intermediate tool calls, the files it read, and its reasoning stay in its transcript. Since version 2.1.277, the result arrives indented, under a header that marks it as subagent output. According to the CHANGELOG, this keeps text from the result from passing itself off as session instructions.
The docs spell out two caveats. First, results pile up in the main context, and many subagents with detailed answers take up noticeable space. Second, each subagent sends its own requests, and they count toward the same usage limits as the main conversation.
Subagent vs. fork vs. skill
| Skill | Subagent | Fork | |
|---|---|---|---|
| Context | The whole current conversation | Fresh, just the task | Copy of the whole conversation |
| System prompt and tools | Main session’s | From the agent file | Main session’s |
| Model | Main session’s or from the skill field | From the resolution chain (see below) | Main session’s |
| Prompt cache | Shared | Separate | Shared with the main session |
| What lands in the main context | Everything | Only the final answer | Only the final answer |
A fork lets you hand off a side task without retelling the context. It sees what the main session sees, but its tool calls don’t land in the main conversation. You can start a fork manually with /subtask <task>. Fork mode is on by default since version 2.1.302 and turns off with CLAUDE_CODE_FORK_SUBAGENT=0. A fork can’t spawn other forks. Its system prompt and tools match the main session’s, so its first request uses the main session’s cache, and the docs call a fork the cheaper option for tasks that need the same context. A skill with context: fork also runs in a separate subagent.
What the measurements showed
The numbers below come from two sources. The first is aggregates over the author’s real subagent transcripts for the whole period they’re kept. The second is experiment E2, which ran the same trivial task across different agent configurations. Task contents weren’t analyzed or published, only sizes.
Method and sample
Claude Code writes each subagent’s transcript to ~/.claude/projects/<project>/<session>/subagents/agent-<id>.jsonl. Every assistant message has a usage field. Context size for a turn is input_tokens + cache_read_input_tokens + cache_creation_input_tokens.
- Startup context: size on the first turn (system prompt, CLAUDE.md, tool schemas, descriptions, task).
- Peak: the maximum size across turns.
- Result: the length of the subagent’s last text message in characters.
Recount on November 6, 2026, covering transcripts created before 15:00: 648 transcripts, 535 of them completed with an answer. The sample keeps growing: by that evening, new heavy runs had moved the median startup context to ~62K, so rerunning the script below will give slightly different numbers. Records with the <synthetic> model (stubs written on API errors) were excluded. By type (all 648 transcripts): general-purpose 577 runs (89%), custom agents 44 (~7%), a plugin agent that hands tasks to an external CLI 19 (3%), Explore 8 (1%).
Summing tokens across turns doesn’t work as a metric: the same 58K of startup context gets counted again on every turn. About 91% of input tokens in these transcripts are cache reads, so the dollar cost of a turn is well below its nominal size. What’s measured here is context size, not the bill.
The benefit: peak vs. result
| Metric (535 subagents) | Median | p10 | p90 |
|---|---|---|---|
| Startup context, tokens | ~58K | ~17K | ~82K |
| Peak context, tokens | ~117K | ~44K | ~238K |
| Result, characters | ~1,500 | ~100 | ~6,300 |
| Tool calls | 18 | 4 | 55 |
The median subagent peaks at ~117K tokens, and the answer that reaches the main conversation is ~1,500 characters, a few hundred tokens. The gap is in the hundreds of times. Without a subagent, most of that volume (file contents, command output) would land in the main context and stay there until compaction.
That gives a quick check for any task. If the answer is about as big as the work behind it, isolation bought nothing.
The cost: startup context
Median startup is ~58K tokens, p10 ~17K. That’s the context a subagent holds before its first action. In the author’s setup it includes a large global CLAUDE.md with rules, tool schemas from several dozen MCP servers, agent and skill descriptions, and git status.
The distribution is uneven: about a fifth of runs start at 10-20K tokens, most at 40-90K. That makes the startup median sensitive to filtering. If you don’t exclude the <synthetic> stubs, it climbs to ~65K, while peak and result barely move. When you repeat the measurement, drop those records explicitly.
Short tasks don’t pay off
| Group | Runs | Startup context | Peak | Result |
|---|---|---|---|---|
| ≤3 tool calls | 34 | ~55K | ~55K | ~1,100 characters |
| ≥15 tool calls | ~315 | ~66K | ~156K | ~1,900 characters |
In short tasks, context barely grows after startup: the subagent loads ~55K tokens for a ~1,100-character answer. The main conversation would have made the same 1-3 calls in context it already has. Long tasks flip the ratio, and ~156K tokens of work fold into ~1,900 characters. Where the line falls depends on the startup price in a given setup, and you can lower that price.
What lowers the startup price
Experiment E2 tested three levers: the omitClaudeMd field, a narrow tools list, and running the session without MCP servers. Conditions: Claude Code 2.1.318, the sonnet model (claude-sonnet-5-5 in transcripts), a temporary project without its own CLAUDE.md, four project agents, and the task “read a one-line file and return the line”. Each variant ran 3 times via claude -p. The metric was the subagent’s startup context from the first turn of its transcript.
| Agent variant | Startup context, median | Change |
|---|---|---|
| No restrictions (baseline) | 56,882 | baseline |
omitClaudeMd: true | 49,594 | −12.8% |
tools: Read, Grep | 12,232 | −78.5% |
tools: Read, Grep + omitClaudeMd: true | 4,667 | −91.8% |
| Baseline, session without MCP servers | 41,639 | −26.8% |
tools + omitClaudeMd, session without MCP servers | 4,667 | 0% vs. the same variant with MCP |
The tools field has the biggest effect. The allowlist drops the schemas of every other tool, MCP included, from the subagent’s context, and in this setup that’s ~44.6K tokens. omitClaudeMd saved ~7.3K: the test directory had only the global CLAUDE.md, and with a project file the savings would be larger. The levers add up almost linearly. The remaining ~4.7K tokens can be treated as the floor for this version: the subagent system prompt, two tool schemas, and the agent body.
Running the session without MCP (--strict-mcp-config with an empty config) cut the main session’s startup context by 22.6% (69,767 → 53,993) and the unrestricted subagent’s by 26.8%. For the agent with a narrow tools list, turning off MCP added nothing, since its MCP schemas were already cut. In every variant, the subagent returned the same 39-character answer. The levers change the launch price, not what reaches the main conversation.
Limits of experiment E2
Keep these conditions in mind before applying the numbers to your setup:
- One machine, one user, one Claude Code version, one model, one day.
- A very heavy setup: about 65 MCP servers (436 tools in the session), ~7K tokens of global rules, about 50 agents, and a long list of skills. A 57K-token baseline is unusual for a lightweight setup, and your absolute savings will be smaller. Carry over the ratios between levers, not the numbers.
- A trivial task, n=3 per variant. Spread within a variant is small (up to ~2%), but these are order-of-magnitude estimates, not confidence intervals.
- Not measured: result quality on real tasks, context growth across turns, time, and how
toolsaffects an agent’s ability to finish the task. An agent that needs Bash or Edit won’t work with a two-tool allowlist.
How to repeat the measurement
The script reads only the usage fields and answer lengths. It doesn’t print transcript contents. The jsonl format is undocumented and can change between versions.
import glob, json, os, statistics as st
KEYS = ("input_tokens", "cache_read_input_tokens", "cache_creation_input_tokens")
rows = []
for path in glob.glob(os.path.expanduser("~/.claude/projects/*/*/subagents/agent-*.jsonl")):
first = peak = None
result, tools = "", 0
for line in open(path, errors="ignore"):
try:
rec = json.loads(line)
except ValueError:
continue
msg = rec.get("message")
if rec.get("type") != "assistant" or not isinstance(msg, dict) or msg.get("model") == "<synthetic>":
continue # service records and API error stubs
ctx = sum((msg.get("usage") or {}).get(k) or 0 for k in KEYS)
first = ctx if first is None else first
peak = max(peak or 0, ctx)
for c in msg.get("content") or []:
tools += c.get("type") == "tool_use"
if c.get("type") == "text" and c.get("text", "").strip():
result = c["text"]
if peak and result:
rows.append((first, peak, len(result), tools))
med = lambda i, rs=rows: st.median(r[i] for r in rs)
print(f"n={len(rows)} start={med(0):.0f} peak={med(1):.0f} result={med(2):.0f} chars")
short = [r for r in rows if r[3] <= 3]
print(f"<=3 calls: n={len(short)} start={med(0, short):.0f} result={med(2, short):.0f} chars")
If your startup context comes out well below 58K, your break-even point shifts in favor of subagents. Claude Code deletes subagent transcripts after the cleanupPeriodDays period (30 days by default), so unless you change the setting, the sample covers roughly the last month.
When you need a subagent
The docs recommend a subagent for tasks with bulky output you won’t need again, for enforced tool and permission restrictions, and for self-contained work that returns a summary. The measurements add a threshold: the task should take more than a few calls.
Bulk search and reading
The typical case: find every place a function is used, read through logs, go over dozens of files, and return a short list. For codebase search there’s the built-in Explore agent. It only reads (Write and Edit are denied), supports quick, medium, and very thorough levels, and starts without CLAUDE.md. If the search turns into action, general-purpose fits.
A sign the task fits: you can describe the answer up front in a few lines (“a list of files and lines”, “three causes of the error”), while getting there takes dozens of calls.
Restrictions by design
A subagent with tools: Read, Grep, Glob physically can’t change anything, and that’s more reliable than asking in the prompt. A review agent without Write and Edit, or a researcher limited to reading and web search, wins twice: permissions are restricted, and startup context is smaller thanks to the short tool list. The disallowedTools field works as a denylist and applies before tools. Some tools are always missing from subagents, even if you list them: AskUserQuestion, EnterPlanMode, ExitPlanMode (except with permissionMode: plan), ScheduleWakeup, WaitForMcpServers, Workflow, EndConversation, and, at the nesting limit, Agent itself.
Independent tasks in parallel
The docs allow up to 20 subagents running at once in one session (CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS changes the limit). Subagents launched from the main conversation with no extra configuration start in the background: the main agent keeps working, and the result arrives later as a message. Parallelism makes sense when tasks don’t depend on each other, like three independent research tasks or checks of different modules. Each launch pays its own startup price, so ten small parallel tasks cost more than they look.
Isolating edits in a worktree
isolation: worktree runs the subagent in a temporary git worktree, so its edits don’t touch your working copy. The docs point out a detail that’s easy to miss: by default the worktree branches from the repository’s default branch, not from the parent session’s current HEAD. If the subagent needs to work on top of uncommitted or fresh changes, plan for that.
When a subagent gets in the way
Short, targeted tasks
Fix a typo, read one file, check a config value. The measurement above shows that in a heavy setup such tasks cost ~55K tokens of startup for an answer of ~1,000 characters. The docs name the main conversation as the right place for quick targeted edits and latency-sensitive tasks, because a new subagent needs time to gather context.
Work that needs the conversation history
If a task builds on what you discussed over the last half hour (decisions, rejected options, clarifications), a regular subagent starts from scratch. Retelling it in the task text loses details. You have three options: continue in the main conversation, launch a fork that sees the whole history, or use /btw for a question about something already discussed. The docs describe /btw as having no tool access, and it doesn’t add the answer to history.
Iterative work with frequent clarifications also fits a subagent poorly. Each round of edits goes through a retelling instead of shared context.
When cheap indexed search is available
If the project has a code index connected (for example, a symbol graph via an MCP tool), the question “where is this defined and who calls it” takes one or two index queries. A subagent that searches for the same thing by reading files adds the startup price and dozens of calls. The author’s global rules say this outright for such projects: code exploration happens in the main conversation through the index, not through Explore. There are no measurements for this comparison. It’s a working rule, not an experimental result.
Piling-up results and retelling errors
A subagent’s answer is a summary, and summaries can be wrong: miss a file, generalize badly, state something unchecked with confidence. The main agent sees only the result and can’t check it without redoing the work. For high-stakes decisions, ask for file and line references in the answer so you can spot-check them.
The second effect: with many launches, results pile up in the main context. Ten subagents at 6K characters of answer each (p90 from the measurements) already take noticeable space in the window, and the task prompts add to that.
Background runs and nesting
Background subagents run with a trimmed set of built-in tools, while MCP tools stay. Permission prompts surface in the main session with the name of the subagent that’s asking, and with several parallel runs that gets distracting. Since version 2.1.318, a background subagent’s shell command moves to the background on its own after 3 minutes so the prompt cache doesn’t expire.
Subagents can launch their own subagents up to three levels below the main conversation (CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH; a value of 1 turns nesting off). Each level pays its own startup price and retells the task, so deep nesting multiplies both cost and the risk of distortion.
Decision tree: subagent, fork, or main conversation
| Situation | Where to run it | Why |
|---|---|---|
| 1-3 calls, a targeted edit | Main conversation | Subagent startup costs more than the work |
| Iterations with clarifications, large shared context | Main conversation | A subagent doesn’t see the history |
| A question about something already discussed | /btw | No tools, doesn’t clutter history |
| A side task that needs the full context | Fork (/subtask) | Sees the history, shares the cache, returns only the result |
| Dozens of reads and searches, short answer | Explore or general-purpose | Isolation pays off |
| Strict permission limits needed | Custom agent with tools | Restricted by design, smaller startup |
| Edits that must not touch the working copy | Agent with isolation: worktree | Separate branch and directory |
| Several independent tasks | Parallel subagents | Up to 20 at once, each with its own window |
| Long work that doesn’t fit in one window | Separate sessions, background agents, or agent teams | The docs recommend this |
| A code index is available | Main conversation with the index | Search is cheaper than a subagent reading files |
Two questions settle most cases. The first is how many tool calls the task needs. With one to three, a subagent probably won’t pay off. The second is whether the task needs the conversation history. If it does, you’re left with a fork or the main conversation.
How a subagent’s model is chosen
Resolution order
Per the docs, a subagent’s model is resolved in this order:
- The
modelparameter Claude passes in a specific Agent call. - The
modelfield in the agent’s frontmatter. - The
CLAUDE_CODE_SUBAGENT_MODELenvironment variable. - The main conversation’s model.
Allowed field values: sonnet, opus, haiku, fable, a full ID (for example, claude-opus-5-5), or inherit, which means the main conversation’s model. When you connect to the Anthropic API, the opus, sonnet, and haiku aliases point to Opus 5.5, Sonnet 5.5, and Haiku 5.5, and fable points to Fable 5.1. On cloud providers the mapping may differ.
A common mistake is assuming CLAUDE_CODE_SUBAGENT_MODEL overrides everything. That was true in older versions. Now it’s a default, and frontmatter or the call parameter overrides it. To put one model on every subagent, add CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1 (since version 2.1.257). Built-in agents have their own rules. Plan inherits the main conversation’s model, and so does Explore, but if the main conversation runs on Fable and you connect through a Claude subscription, a Console account, or an LLM gateway, Explore switches to the model behind the opus alias.
Model policy and what the measurements don’t show
The author’s rules set agent models by policy: search and information gathering on Sonnet; analysis, planning, and decisions on Opus; no Haiku for agents. That’s a working convention, not a conclusion from the measurements.
The transcripts can’t support such a conclusion anyway. They mix models from several generations, the reason behind each choice (frontmatter, call parameter, variable, inheritance) can’t be recovered from a transcript, and answer quality wasn’t measured. There were no paired comparisons of one task on two models, so this data can’t back the claim that Sonnet handles the same tasks as well as Opus. For pricing and model differences, see Claude Opus vs Sonnet.
Effort and thinking
Subagents inherit the main conversation’s extended thinking setting. The effort level comes from the effort field in frontmatter (low, medium, high, xhigh, max), the call parameter, or the CLAUDE_CODE_SUBAGENT_EFFORT variable, which applies when nothing else sets the level. For a research agent with a narrow task, it makes sense to start lower and raise the level if the answers come out shallow.
Agent file format: fields that affect cost
An agent is a markdown file with YAML frontmatter in .claude/agents/ (project) or ~/.claude/agents/ (user). For basic Claude Code setup, see the complete guide. Here’s a read-only reviewer with the fields that affect cost and behavior:
---
name: code-reviewer
description: Reviews a diff for bugs and security issues. Use after a feature is implemented. Read-only; for writing tests use test-writer.
tools: Read, Grep, Glob
model: sonnet
maxTurns: 20
---
Review the changes you are pointed to. Report issues by priority
with file:line references. Do not propose rewrites of unrelated code.
| Field | What it does | When to set it |
|---|---|---|
tools | Tool allowlist; without it, the agent inherits all tools | Almost always: it covers both permissions and startup context |
model | Agent model (see above) | When the agent shouldn’t run on the session model |
maxTurns | Turn limit; when reached, the result is marked partial | Auditors and researchers that tend to wander |
omitClaudeMd | Start without CLAUDE.md (since 2.1.271); managed policy still loads | Agents that don’t need project rules |
isolation | worktree: a temporary git worktree | Agents that edit code in parallel with you |
autoCompactWindow | Early compaction for the subagent, 100,000-1,000,000 tokens | Agents that read long logs |
Write autoCompactWindow as an integer. The docs warn that 200k in frontmatter is read as 200 and ignored as out of range. The other fields (permissionMode, memory, skills, mcpServers, hooks, background, color) are covered in the skills and agents reference.
The agent description as a router
Claude decides whom to delegate to based on the description field. The descriptions of all agents sit in the main conversation’s context permanently. Per the docs, if their total size (excluding built-ins) exceeds 15,000 tokens, Claude Code shows a warning at startup.
What works
Two patterns in the author’s catalog make agent selection easier:
- Trigger first. “Use after completing a logical unit of code” or “Use when…” followed by a list of situations.
- Boundary with a neighbor at the end. A phrase like “for reference patterns, use the /mcp-server command” or “for test code, there’s the test-writer agent” removes the selection conflict between similar agents, commands, and skills.
The docs say adding “use proactively” raises the chance of automatic delegation.
Anti-patterns
- Long descriptions with examples. The catalog has agents with 900-1,000-character descriptions that contain “Example: User says… use this agent” blocks. You pay for such a description on every request, even when the agent isn’t needed.
- Bilingual descriptions. They work, but they lengthen text that sits in context permanently.
- Outdated names in
tools. An agent written for an older version may still list tools that no longer exist or that subagents never get (AskUserQuestion). The author hasn’t checked in a live run what Claude Code does with partially unresolved names, so compare the list against the current tool set.
Why an agent doesn’t get called
- The file was skipped. Without
descriptionor with invalid YAML, the agent doesn’t load, and the reason goes to the debug log. A file withoutnameis treated as documentation. - A typo in a field. Unknown fields are silently ignored. In
claude --debug(since 2.1.296), such fields show up in adoes not recognize or applyline. - The description doesn’t set the agent apart. If two agents have similar descriptions, Claude may pick the neighbor or fall back to general-purpose.
- You need a guaranteed run. Mentioning
@agent-<name>in the request, or picking the agent from the@list, launches that specific agent. Claude still writes the task wording for it.
When names collide, the definition with the higher priority wins: managed settings, then the --agents flag, then the project’s .claude/agents/, the user’s ~/.claude/agents/, and finally plugin agents.
How many agents you need
The author’s user catalog has 33 agent files. About 10 of them were written by the author, and the rest came with a third-party package. Yet 89% of all runs go to the built-in general-purpose agent, and all custom agents together account for ~7%. The most frequently launched ones are a research agent limited to read-only and web search tools and a read-only reviewer.
The number of agent files says nothing about their value. An agent file is worth having when it pins something down: a narrow tool set, a model, a turn limit, an answer format. For one-off delegation, general-purpose with a well-written task is enough. A sensible start is 2-3 agents for recurring roles with clear boundaries between them, not a catalog “for every occasion” whose descriptions permanently occupy context.
Hooks and observability
To control subagents, there are SubagentStart and SubagentStop events with a matcher on the agent name. SubagentStop receives the path to the subagent’s transcript and its last message, which is enough to log runs or check the answer format. You can also declare hooks in the agent’s frontmatter. They run while the agent is active, and in that case Stop turns into SubagentStop. The Claude Code hooks article goes through the events and the hook format in detail.
/tasks shows the model of each running subagent. For after-the-fact analysis, you have the transcripts in subagents/ and the .meta.json files next to them, which record the agent type.
CLAUDE.md is part of a subagent’s startup context, so its size also affects the cost of delegation. How to keep CLAUDE.md short is covered in the CLAUDE.md guide, and the general logic of managing the window is in the context engineering guide.
Where to start
- Run the measurement script on your transcripts and find your subagent startup price. It determines the task length at which delegation pays off.
- Don’t delegate 1-3-call tasks or work that needs the conversation history. For the latter, use a fork or
/btw. - Set
toolson every custom agent. In the experiment on a heavy setup, it was the strongest lever: −78.5% startup context, plus permissions restricted by design. - Add
omitClaudeMd: trueto agents that don’t need project rules, and remove unused MCP servers from the session. - Keep descriptions short: trigger first, boundary with neighbors at the end, no example blocks. Set the model explicitly in frontmatter, and remember that
CLAUDE_CODE_SUBAGENT_MODELdoesn’t override it without_FORCE=1.