Multi-Agent Pipeline
Orchestrate subagents with agents{} and lifecycle hooks.
Exercise 1: Define Subagents
Define a planner and coder agent using the agents{} config. Each AgentDefinition specifies the model, available tools, and turn limits.
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.
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.
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?
}
}