Learn Claude Code
s04

Subagents

Planning & Coordination

Clean Context Per Subtask

288 LOC5 toolsTypeScriptSubagent spawn with isolated messages[]
Subagents use independent messages[], keeping the main conversation clean

s01 > s02 > s03 > [ s04 ] s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12

"Break big tasks down; each subtask gets a clean context" -- subagents use independent messages[], keeping the main conversation clean.

Harness layer: Context isolation -- protecting the model's clarity of thought.

Problem

As the agent works, its messages array grows. Every file read, every bash output stays in context permanently. "What testing framework does this project use?" might require reading 5 files, but the parent only needs the answer: "pytest."

Solution

Parent agent                     Subagent
+------------------+             +------------------+
| messages=[...]   |             | messages=[]      | <-- fresh
|                  |  dispatch   |                  |
| tool: task       | ----------> | while tool_use:  |
|   prompt="..."   |             |   call tools     |
|                  |  summary    |   append results |
|   result = "..." | <---------- | return last text |
+------------------+             +------------------+

Parent context stays clean. Subagent context is discarded.

How It Works

  1. The parent gets a task tool. The child gets all base tools except task (no recursive spawning).
const PARENT_TOOLS = [
  ...CHILD_TOOLS,
  {
    name: "task",
    description: "Spawn a subagent with fresh context.",
    input_schema: {
      type: "object",
      properties: {
        prompt: { type: "string" },
        description: { type: "string" },
      },
      required: ["prompt"],
    },
  },
];
  1. The subagent starts with messages=[] and runs its own loop. Only the final text returns to the parent.
async function runSubagent(prompt: string): Promise<string> {
  const subMessages: Message[] = [{ role: "user", content: prompt }];

  for (let attempt = 0; attempt < 30; attempt += 1) {
    const response = await client.messages.create({
      model: MODEL,
      system: SUBAGENT_SYSTEM,
      messages: subMessages,
      tools: CHILD_TOOLS,
      max_tokens: 8000,
    });

    subMessages.push({ role: "assistant", content: response.content });
    if (response.stop_reason !== "tool_use") {
      return response.content
        .filter((block) => block.type === "text")
        .map((block) => block.text)
        .join("") || "(no summary)";
    }

    const results = [];
    for (const block of response.content) {
      if (block.type !== "tool_use") continue;
      const handler = TOOL_HANDLERS[block.name as ToolUseName];
      const output = handler(block.input as Record<string, unknown>);
      results.push({
        type: "tool_result" as const,
        tool_use_id: block.id,
        content: String(output).slice(0, 50000),
      });
    }

    subMessages.push({ role: "user", content: results });
  }

  return "(no summary)";
}

The child's entire message history (possibly 30+ tool calls) is discarded. The parent receives a one-paragraph summary as a normal tool_result.

What Changed From s03

ComponentBefore (s03)After (s04)
Tools55 (base) + task (parent)
ContextSingle sharedParent + child isolation
SubagentNonerun_subagent() function
Return valueN/ASummary text only

Try It

cd learn-claude-code
cd agents-ts
npm install
npm run s04
  1. Use a subtask to find what testing framework this project uses
  2. Delegate: read all .ts files and summarize what each one does
  3. Use a task to create a new module, then verify it from here