# Claude Code Subagents: When They Save Context and When They Hurt

> When is a Claude Code subagent worth it? Startup cost and result size from the author's runs, what cuts startup context, model choice, and when subagents hurt.
> Author: Roman Belov · Published: 2026-10-21 · Source: https://futurecraft.pro/blog/claude-code-subagents/

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](https://code.claude.com/docs/en/sub-agents) (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](https://futurecraft.pro/blog/claude-code-skills-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](https://code.claude.com/docs/en/sub-agents), 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](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md), 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 `tools` affects 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.

```python
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](https://code.claude.com/docs/en/sub-agents), a subagent's model is resolved in this order:

1. The `model` parameter Claude passes in a specific Agent call.
2. The `model` field in the agent's frontmatter.
3. The `CLAUDE_CODE_SUBAGENT_MODEL` environment variable.
4. 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](https://futurecraft.pro/blog/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](https://futurecraft.pro/blog/claude-code-complete-guide/). Here's a read-only reviewer with the fields that affect cost and behavior:

```markdown
---
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](https://futurecraft.pro/blog/claude-code-skills-agents/).

## 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:

1. **Trigger first.** "Use after completing a logical unit of code" or "Use when…" followed by a list of situations.
2. **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 `description` or with invalid YAML, the agent doesn't load, and the reason goes to the debug log. A file without `name` is 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 a `does not recognize or apply` line.
- **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](https://futurecraft.pro/blog/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](https://futurecraft.pro/blog/claude-md-guide/), and the general logic of managing the window is in the [context engineering](https://futurecraft.pro/blog/context-engineering-guide/) guide.

## Where to start

1. Run the measurement script on your transcripts and find your subagent startup price. It determines the task length at which delegation pays off.
2. Don't delegate 1-3-call tasks or work that needs the conversation history. For the latter, use a fork or `/btw`.
3. Set `tools` on every custom agent. In the experiment on a heavy setup, it was the strongest lever: −78.5% startup context, plus permissions restricted by design.
4. Add `omitClaudeMd: true` to agents that don't need project rules, and remove unused MCP servers from the session.
5. 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_MODEL` doesn't override it without `_FORCE=1`.
