Subagents in DeepAgents: Delegation That Keeps Context Clean
How the task tool works, when to delegate, and the config that decides whether subagents help or hurt
A subagent is a context firewall. That is the one idea to hold onto. When the main agent has to do something noisy, like ten web searches or a pile of file reads, it can delegate that work to a subagent. The subagent runs in its own fresh context, does the messy part, and hands back a single clean result. The main agent never sees the ten searches, only the answer.
This is part two of my series on building with DeepAgents. The context post argued that the framework's whole design is about keeping the working set small. Subagents are the sharpest tool for that. They are also the easiest to misuse, so this post is as much about when not to delegate as how to wire it up.
Why subagents exist#
The problem they solve is context bloat. Tools with large outputs fill the window fast with intermediate junk you do not need after the fact. Subagents quarantine that work. The main agent stays focused on coordination and gets back summaries instead of raw data.
Use a subagent when:
- The task is multi-step and would clutter the main agent's context
- The work needs specialized instructions or a different tool set
- A different model would do the job better (cheaper, bigger context, stronger at math)
- You want the main agent to stay at the coordination level
Do not use one when:
- The task is a single step
- You actually need the intermediate context in the main thread
- The overhead of spinning up a subagent is more than the work itself
That last point is real. A subagent is not free. There is a round trip, a fresh prompt, and a model call. For a one-shot lookup it is pure overhead.
How delegation works#
When at least one synchronous subagent exists, DeepAgents attaches the subagent middleware and gives the main agent a task tool. The main agent calls task(name, task) to delegate. The subagent runs autonomously to completion and returns one final report.
DeepAgents also adds a general-purpose subagent automatically, unless you provide your own subagent with that name. It has the filesystem tools, uses the same model, and inherits the main agent's skills. It exists for exactly the case where you want context isolation without any specialized behavior:
Instead of the main agent making 10 web searches and filling its context with results, it delegates:
task(name="general-purpose", task="Research quantum computing trends"). The subagent does the searches internally and returns only a summary.
If you want to run with no delegation at all, you disable the general-purpose subagent on the harness profile (enabled: false) and pass no synchronous subagents. One caveat from the docs that will save you a confusing error: do not try to remove the subagent middleware via excludedMiddleware. It is required scaffolding and listing it throws. The enabled flag is the supported path.
Defining a custom subagent#
Most of the time you define subagents as plain objects matching the SubAgent spec. The fields that matter:
| Field | Notes |
|---|---|
name | Required. The main agent uses this in the task call. Also tags messages for streaming. |
description | Required. The main agent reads this to decide when to delegate. Be specific. |
systemPrompt | Required. Does not inherit from the main agent. Put tool guidance and output format here. |
tools | Optional. Inherits from the main agent; when set, replaces them entirely. Keep it minimal. |
model | Optional. Defaults to the main agent's model. Accepts a "provider:model" string or a model object. |
skills | Optional. Custom subagents do NOT inherit skills; pass paths to give them their own. |
interruptOn | Optional. Per-subagent human-in-the-loop config. Overrides the main agent's. |
responseFormat | Optional. A schema for structured output, so the parent gets JSON instead of free text. |
Here is a researcher subagent with a focused tool set and an explicit output contract:
import { createDeepAgent, type SubAgent } from "deepagents";
const researchSubagent: SubAgent = {
name: "research-agent",
description: "Conducts in-depth research using web search and synthesizes findings",
systemPrompt: `You are a thorough researcher. Your job is to:
1. Break down the research question into searchable queries
2. Use internet_search to find relevant information
3. Synthesize findings into a comprehensive but concise summary
4. Cite sources when making claims
Output format:
- Summary (2-3 paragraphs)
- Key findings (bullet points)
- Sources (with URLs)
Keep your response under 500 words to maintain clean context.`,
tools: [internetSearch],
};
const agent = await createDeepAgent({
model: "google_genai:gemini-3.5-flash",
systemPrompt: "You coordinate research. Delegate topic research to the research-agent.",
subagents: [researchSubagent],
});For genuinely complex workflows you can pass a prebuilt LangGraph graph as a CompiledSubAgent instead. It takes a name, a description, and a runnable (the compiled graph). Reach for this when the subagent is itself a multi-node state machine, not just an LLM with tools.
The four things that make subagents work#
The config is easy. Getting value out of it comes down to four habits.
Write descriptions the router can use. The main agent picks a subagent purely from its description. "Analyzes financial data and generates investment insights with confidence scores" is useful. "Does finance stuff" is not. If the wrong subagent keeps getting called, this is almost always why.
Keep the system prompt detailed. Subagents do not inherit the main prompt, so a thin prompt gives you a vague agent. Spell out the steps, the tool usage, and the output format.
Minimize the tool set. Give a subagent only what it needs. An email subagent gets sendEmail and validateEmail, not web search and database access. Fewer tools means better focus and a smaller attack surface.
Return concise results. This is the one that actually protects context. Tell the subagent, in its prompt, to return insights and not raw data:
const dataAnalyst = {
name: "data-analyst",
description: "Analyzes data and returns key insights with a confidence score",
systemPrompt: `Analyze the data and return:
1. Key insights (3-5 bullet points)
2. Overall confidence score
3. Recommended next actions
Do NOT include:
- Raw data
- Intermediate calculations
- Detailed tool outputs
Keep response under 300 words.`,
};If you skip this, the subagent dumps everything back into the parent and you have moved the bloat, not removed it.
Choosing models per subagent#
One underrated benefit: each subagent can run a different model. Match the model to the job.
const subagents = [
{
name: "contract-reviewer",
description: "Reviews legal documents and contracts",
systemPrompt: "You are an expert legal reviewer...",
tools: [readDocument, analyzeContract],
model: "google_genai:gemini-3.5-flash", // large context for long documents
},
{
name: "financial-analyst",
description: "Analyzes financial data and market trends",
systemPrompt: "You are an expert financial analyst...",
tools: [getStockPrice, analyzeFundamentals],
model: "gpt-5.4", // stronger on numerical work
},
];A long-document reviewer wants a big context window. A numerical analyst wants a model that is good at math. You do not have to pay for your most expensive model on every step.
A common multi-subagent pattern#
A clean pipeline is three specialized subagents the main agent coordinates:
const subagents = [
{
name: "data-collector",
description: "Gathers raw data from various sources",
systemPrompt: "Collect comprehensive data on the topic",
tools: [webSearch, apiCall, databaseQuery],
},
{
name: "data-analyzer",
description: "Analyzes collected data for insights",
systemPrompt: "Analyze data and extract key insights",
tools: [statisticalAnalysis],
},
{
name: "report-writer",
description: "Writes polished reports from analysis",
systemPrompt: "Create professional reports from insights",
tools: [formatDocument],
},
];The main agent plans, sends collection to the collector, passes results to the analyzer, sends insights to the writer, and assembles the output. Each subagent works in clean context scoped to its job. This is the supervisor shape; if you are deciding between it and peer-to-peer handoffs, I compared them in handoffs versus the supervisor pattern.
Context, skills, and a gotcha#
Runtime context propagates to subagents automatically. Invoke the parent with context and every subagent run receives the same context. You set the user or the API key once.
Skills are the opposite. The general-purpose subagent inherits the main agent's skills, but custom subagents do not. If a custom subagent needs skills, pass them explicitly:
const researchSubagent: SubAgent = {
name: "researcher",
description: "Research assistant with specialized skills",
systemPrompt: "You are a researcher.",
tools: [webSearch],
skills: ["/skills/research/", "/skills/web-search/"], // its own skills
};
const agent = await createDeepAgent({
model: "google_genai:gemini-3.5-flash",
skills: ["/skills/main/"], // main agent and general-purpose subagent get these
subagents: [researchSubagent], // gets only its own two skill sets
});Skill state is fully isolated in both directions. The parent's skills are invisible to the child, and the child's loaded skills do not propagate back. More on that in the skills post.
When context still bloats#
If you delegate and the main context still grows, the usual culprit is subagents returning too much. Tighten their output instructions, or have them write large results to files and return just the path. The filesystem is there for exactly this. If the wrong subagent keeps getting selected, sharpen the descriptions until they stop overlapping. And if a subagent is never called, check that the main agent's prompt actually tells it to delegate, and that the subagent's description matches the kind of task you are sending.
Next in the series: memory in DeepAgents, how anything the agent learns survives past a single conversation.

Folarin Akinloye is an AI Engineer based in London, UK. He builds production-ready agentic AI systems, multi-agent architectures, and sophisticated RAG implementations, and writes about the engineering decisions behind them.