Subagents and Delegation
A subagent gets a fresh context, a narrow job, and a report back. Use it to keep the parent window small. Isolate blast radius.
- agents
- subagents
- delegation
Exploring a 4k-file repo inside the parent thread is how you blow the window and still miss the bug. Subagents exist to stop that. You spawn a child with a fresh context, a narrow brief, a tight tool list, and a timeout. It returns a structured report. The parent never sees the raw file dumps. That is delegation, not a committee.
A handoff takes the microphone. A subagent sends a memo. If the user should keep talking to the same policy, spawn. If a different department should speak, hand off. Most “multi-agent” demos wanted the memo and built the committee.
Key takeaways
- A subagent starts empty: new messages, narrow goal, constrained tools, hard timeout.
- The parent consumes a typed report, not a transcript of grep.
- Isolation is for tokens and blast radius. A search child does not get
deploy.- Bound the fan-out. Five children on one user click is a fork bomb with an API key.
- Fail closed: child timeout or schema miss becomes an error the parent can explain.
Subagents get a fresh window
The parent is already carrying the user, the policy, and the plan. If it also carries every read_file of src/**, the question falls into the middle and the next tool call gets worse. Spawn when the work is:
- Wide search (“find the tax calculator”)
- Throwaway exploration (you will discard the traces)
- A different permission set (read-only auditor)
Do not spawn for “format this string.” That is a function.
import { z } from 'zod';
const Report = z.object({
summary: z.string().max(1_500),
files: z.array(z.object({ path: z.string(), reason: z.string() })).max(12),
quotes: z.array(z.object({ path: z.string(), excerpt: z.string().max(500) })).max(8),
unresolved: z.array(z.string()).max(8),
});
type Report = z.infer<typeof Report>;
type ChildSpec = {
id: string;
goal: string;
tools: Array<'glob' | 'grep' | 'read'>;
timeoutMs: number;
maxSteps: number;
};
async function runChild(
spec: ChildSpec,
opts: { cwd: string; reportSchema: typeof Report },
): Promise<unknown> {
const ac = new AbortController();
const timer = setTimeout(() => ac.abort(), spec.timeoutMs);
try {
return await completeJson({
system: `You may use: ${spec.tools.join(', ')}. Return JSON that matches the report schema. No edits.`,
user: spec.goal,
cwd: opts.cwd,
tools: spec.tools,
maxSteps: spec.maxSteps,
schema: opts.reportSchema,
signal: ac.signal,
});
} finally {
clearTimeout(timer);
}
}
export async function delegateSearch(parentGoal: string, repoRoot: string): Promise<Report> {
const spec: ChildSpec = {
id: crypto.randomUUID(),
goal: `Find files and quotes that answer: ${parentGoal}. Do not edit. Do not run the shell.`,
tools: ['glob', 'grep', 'read'],
timeoutMs: 45_000,
maxSteps: 8,
};
const raw = await runChild(spec, { cwd: repoRoot, reportSchema: Report });
const parsed = Report.safeParse(raw);
if (!parsed.success) {
return {
summary: 'child returned invalid report',
files: [],
quotes: [],
unresolved: [parsed.error.message],
};
}
return parsed.data;
}
The parent then reasons over files and quotes. It may read one path itself. It does not ingest the child’s 40-step diary. That diary belongs in tracing, not in the next prompt.
Spawn in a fixed order so you can debug it:
- Write the child’s goal as a sentence the parent could show a user.
- Copy only the ids and quotes the child needs. No parent system prompt dump.
- Attach the allowlist and
maxStepsin code. - Run with a timeout. On abort, return
unresolved. - Parse the report with a schema. Invalid JSON is a failed child, not prose to “interpret.”
- Merge into the parent as one user or tool message labeled with
spec.id.
If step 2 copies the whole parent transcript, you did not isolate anything. You opened a second bill with the same haystack.
Constrain the child
Tools are the blast radius. A research child with bash is a junior with prod creds. Encode the allowlist in the runtime, not in the child’s personality paragraph.
| Child | Tools | Must not |
|---|---|---|
| Search | glob, grep, read | write, network, deploy |
| Test | run_tests, read |
patch outside src/ and tests/ |
| Review | read, diff | merge, push |
Claude Code’s CLI lets you define custom agents with their own prompt, tool list, and model. The useful part of that API is the constraint, not the extra name. Copy the constraint even if you roll a 40-line loop.
Timeouts are part of the contract. Use AbortController on the child’s HTTP and shell. A hung grep should not hang the parent turn. See AbortController. If the child times out, the parent gets unresolved: ['timeout'] and can ask the user, not retry forever.
Fan-out without a fork bomb
Parallel children are tempting: one per directory, one per service. Bound them.
- Max children per user request (start with 1–3).
- Shared step budget:
parentSteps + sum(childSteps) <= cap. - Dedup goals. Two children grepping the same string is a wasted window.
- Merge reports in code (
Mapby path). Do not ask a fourth model to “synthesize the team.”
Context engineering on the parent is now easy: pin the user goal, attach two reports, drop the rest. Multi-agent systems that skip this packing step are just several bills.
The child sandbox should be stricter than the parent. If you already built a coding-agent loop, run children inside it with a subset of tools. Do not give the child the parent’s memory store. Isolated context means isolated writes.
Interview answer (out loud)
“A subagent is a spawned loop with a fresh context, a narrow goal, a tight tool allowlist, and a timeout. It returns a structured report. The parent never sees raw file dumps, which keeps the window small and the blast radius small. I bound fan-out and fail closed on schema errors. I hand off only when a different speaker should own the user conversation.”
Further reading (primary sources)
Related on this site
Related guides
- Agent Failure ModesAgents loop, lie about tool results, skip confirmation, and get stuck apologizing. Catalog the failures and the circuit breakers.
- Agent HandoffsA handoff passes the conversation to another agent with a slimmer prompt and different tools. What to copy, what to drop, and how to avoid ping-pong.
- Agent MemoryAgents forget when the context window ends. Working memory, session summaries, and long-term stores are three different systems. Do not mix them.
- Agent System Design InterviewDesign a support or coding agent in a loop: tools, approvals, traces, evals, and cost. The frontend surfaces matter as much as the model.
- Coding AgentsA coding agent reads, edits, and runs tests in a repo. Permissions, diffs, and why "it compiles" is not the acceptance test.