Skip to main content
P9 Advanced

Multi-Agent Pipeline

Orchestrate subagents with agents{} and lifecycle hooks.

SDK APIs agents{} AgentDefinition maxTurns hooks.SubagentStart/Stop

Exercise 1: Define Subagents

Define a planner and coder agent using the agents{} config. Each AgentDefinition specifies the model, available tools, and turn limits.

Starter Code exercise-1.ts
import { query } from "@anthropic-ai/claude-agent-sdk";

// Define subagents using the standard agents{} config
const response = query({
  prompt: "Refactor the utils/ folder to use ES modules instead of CommonJS",
  options: {
    agents: {
      "planner": {
        description: "Breaks tasks into clear implementation steps",
        prompt: "You are a planning agent. Break tasks into numbered steps. Do not modify files.",
        model: "sonnet",
        tools: ["Read", "Glob", "Grep"],
        maxTurns: 5
      },
      "coder": {
        description: "Implements code changes based on plans",
        prompt: "You are a coding agent. Implement the plan step by step.",
        model: "sonnet",
        tools: ["Read", "Write", "Edit", "Bash", "Glob"],
        maxTurns: 15
      }
    }
  }
});

// Stream and collect results
const agentOutputs: Record<string, string[]> = {};
let currentAgent = "";

for await (const message of response) {
  if (message.type === "system" && message.subtype === "agent_start") {
    currentAgent = message.agent_name;
    agentOutputs[currentAgent] = [];
    console.log(`\n--- ${currentAgent} started ---`);
  }
  if (message.type === "assistant" && currentAgent) {
    agentOutputs[currentAgent].push(message.content);
  }
  if (message.type === "result") {
    console.log("\nPipeline complete:");
    for (const [agent, outputs] of Object.entries(agentOutputs)) {
      console.log(`\n[${agent}] ${outputs.join("").slice(0, 200)}...`);
    }
  }
}

Your task: Run this pipeline. Observe how the planner's output flows into the coder. Try adjusting maxTurns on the planner to 2 — does the plan quality change?

Exercise 2: Lifecycle Hooks

Add SubagentStart and SubagentStop hooks to track when each subagent runs. Use the Stop hook for cleanup.

Exercise Code exercise-2.ts
import { query } from "@anthropic-ai/claude-agent-sdk";

const timeline: Array<{ agent: string; event: string; time: number }> = [];
const startTime = Date.now();

const response = query({
  prompt: "Add input validation to all API route handlers",
  options: {
    agents: {
      "planner": {
        description: "Breaks tasks into numbered implementation steps",
        prompt: "Break the task into numbered steps. Be specific about what to change.",
        model: "sonnet",
        tools: ["Read", "Glob"],
        maxTurns: 5
      },
      "coder": {
        description: "Implements code changes based on plans",
        prompt: "Implement each step from the plan. Write clean, tested code.",
        model: "sonnet",
        tools: ["Read", "Write", "Edit", "Bash"],
        maxTurns: 10
      }
    },
    hooks: {
      SubagentStart: async (input) => {
        // Record the start event in timeline[]
        timeline.push({
          agent: input.agentName,
          event: "start",
          time: Date.now() - startTime
        });
        console.log(`[>] ${input.agentName} started at +${Date.now() - startTime}ms`);
      },
      SubagentStop: async (input) => {
        // Record the stop event
        timeline.push({
          agent: input.agentName,
          event: "stop",
          time: Date.now() - startTime
        });
        console.log(`[x] ${input.agentName} stopped at +${Date.now() - startTime}ms`);
      },
      Stop: async () => {
        // Print the full timeline on completion
        console.log("\n=== Pipeline Timeline ===");
        for (const entry of timeline) {
          console.log(`  +${entry.time}ms  ${entry.agent}  ${entry.event}`);
        }
        console.log(`\nTotal time: ${Date.now() - startTime}ms`);
      }
    }
  }
});

// Stream messages
for await (const message of response) {
  if (message.type === "result") {
    console.log("\n[Complete]", message.content?.slice(0, 200));
  }
}

Your task: Fill in the TODOs so the timeline tracks every subagent transition. Add a turnsUsed counter inside each stop event. What happens if a subagent hits its maxTurns limit?

Exercise 3: Write → Review → Revise Pipeline

Build a 3-agent pipeline where a writer generates code, a reviewer critiques it, and the writer revises. Track the full lifecycle.

Challenge exercise-3.ts
import { query } from "@anthropic-ai/claude-agent-sdk";

// Challenge: Build a Write - Review - Revise pipeline
//
// TODO 1: Define all three agents in an agents{} config
const agentTimings: Record<string, { start: number; end?: number }> = {};
const startTime = Date.now();

const response = query({
  prompt: "Create a retry utility with exponential backoff and jitter",
  options: {
    agents: {
      "writer": {
        description: "Writes clean, well-documented TypeScript code",
        prompt: "Write clean, well-documented TypeScript code. Include JSDoc comments.",
        model: "sonnet",
        tools: ["Read", "Write", "Glob"],
        maxTurns: 10
      },
      "reviewer": {
        description: "Reviews code for bugs, style issues, and edge cases",
        prompt: "Review code for bugs, style issues, and edge cases. Output a numbered list of issues.",
        model: "haiku",  // Use faster model for review
        tools: ["Read", "Glob", "Grep"],
        maxTurns: 5
      },
      "reviser": {
        description: "Applies review feedback and fixes all issues",
        prompt: "Apply each review comment. Fix every issue found by the reviewer.",
        model: "sonnet",
        tools: ["Read", "Write", "Edit", "Bash"],
        maxTurns: 10
      }
    },

    // TODO 2: Set up lifecycle hooks
    hooks: {
      SubagentStart: async (input) => {
        agentTimings[input.agentName] = { start: Date.now() - startTime };
        console.log(`[>] ${input.agentName} started`);
      },
      SubagentStop: async (input) => {
        if (agentTimings[input.agentName]) {
          agentTimings[input.agentName].end = Date.now() - startTime;
        }
        console.log(`[x] ${input.agentName} finished`);
      },
      Stop: async () => {
        console.log("\n=== Final Report ===");
        for (const [agent, timing] of Object.entries(agentTimings)) {
          const duration = (timing.end ?? Date.now() - startTime) - timing.start;
          console.log(`  ${agent}: ${duration}ms`);
        }
        console.log(`\nTotal: ${Date.now() - startTime}ms`);
      }
    }
  }
});

// TODO 3: Stream and analyze results
for await (const message of response) {
  if (message.type === "result") {
    console.log("\n[Pipeline Complete]");
    // TODO 4: Compare writer's original output vs reviser's final output
    //   - Did the reviewer catch real issues?
    //   - Did the reviser address all of them?
  }
}
← Back to Exercises