· 6 min read
The Claude Agent SDK provides the building blocks for creating multi-agent systems where specialized agents collaborate to solve complex problems. This tutorial covers the core concepts, from defining individual agents to orchestrating teams that execute in sequence, parallel, or coordinated patterns. By the end, you will understand how to structure agent teams in TypeScript and integrate them into your applications.
A multi-agent system in the Claude Agent SDK consists of three core primitives:
The SDK does not impose a specific architecture. You can build sequential pipelines, parallel fan-outs, coordinator patterns, or any combination. The primitives are flexible enough to support any topology.
Multi-agent systems are the right choice when:
Multi-agent systems add complexity. If a single prompt can solve your problem well, use a single prompt. Reach for multi-agent architecture when the task genuinely benefits from decomposition.
An agent definition in the SDK is a configuration object that specifies the agent's role, input expectations, and output format:
import { defineAgent } from "@anthropic/agent-sdk";
const researchAgent = defineAgent({
name: "research-agent",
model: "claude-sonnet-4-20250514",
systemPrompt: `You are a Research Agent. Your role is to gather and
structure information on a given topic.
INPUT: A research topic and specific questions to answer.
OUTPUT: A structured research brief with:
- Key findings (5-10 bullet points with sources)
- Data points (quantitative facts with confidence levels)
- Knowledge gaps (what you could not find or verify)`,
outputFormat: "structured",
});
The systemPrompt is where you define the agent's expertise and behavior. Be specific about input expectations and output format -- this is the contract that other agents and your orchestration logic depend on.
A team groups agents together and specifies how they interact:
import { defineTeam } from "@anthropic/agent-sdk";
const analysisTeam = defineTeam({
name: "market-analysis",
agents: [researchAgent, analysisAgent, recommendationAgent],
orchestration: "sequential",
});
The orchestration field determines the execution pattern. The SDK supports three built-in patterns:
"sequential" -- Agents run in the order they are listed. Each agent receives the previous agent's output as its input."parallel" -- All agents run simultaneously on the same input. A merge step combines their outputs."coordinator" -- The first agent in the list acts as a coordinator, delegating tasks to the other agents based on its analysis of the input.Execute a team with a single call:
const result = await analysisTeam.run({
input: {
topic: "AI infrastructure market 2026",
questions: [
"What is the current market size?",
"Who are the top 5 players by revenue?",
"What are the growth projections for the next 3 years?",
],
},
});
console.log(result.output);
console.log(result.metadata.executionTime);
console.log(result.metadata.agentOutputs); // individual agent results
The result.metadata.agentOutputs field gives you access to each agent's individual output, which is useful for debugging and for cases where you want to present the breakdown alongside the final synthesis.
For teams where agents operate independently on the same input, use parallel orchestration:
const feedbackTeam = defineTeam({
name: "feedback-analysis",
agents: [sentimentAgent, featureRequestAgent, churnRiskAgent],
orchestration: "parallel",
merge: (outputs) => {
// Custom merge logic
return {
sentiment: outputs["sentiment-agent"],
features: outputs["feature-request-agent"],
churnRisk: outputs["churn-risk-agent"],
summary: synthesize(outputs),
};
},
});
The merge function is where you control how parallel outputs combine. Without a custom merge function, the SDK concatenates outputs in agent order. A custom merge function lets you structure the combined output, resolve conflicts between agents, and add synthesis logic.
The coordinator pattern is the most flexible. The first agent decides how to delegate:
const coordinatorAgent = defineAgent({
name: "coordinator",
model: "claude-sonnet-4-20250514",
systemPrompt: `You are a Coordinator Agent. Analyze the incoming request
and determine which specialist agents to invoke.
Available specialists: research, analysis, writing, strategy.
For each specialist you invoke, specify:
- The agent name
- The specific input to pass
- The expected output format
After receiving specialist outputs, synthesize them into a final response.`,
canDelegate: true,
});
const adaptiveTeam = defineTeam({
name: "adaptive-team",
agents: [coordinatorAgent, researchAgent, analysisAgent, writingAgent, strategyAgent],
orchestration: "coordinator",
});
With canDelegate: true, the coordinator agent can selectively invoke other agents based on its analysis of the input. This means the same team can handle different types of requests by activating different subsets of agents.
Multi-agent systems need robust error handling because a failure in one agent can cascade:
const resilientTeam = defineTeam({
name: "resilient-analysis",
agents: [researchAgent, analysisAgent, recommendationAgent],
orchestration: "sequential",
onAgentError: (agentName, error, previousOutputs) => {
if (agentName === "recommendation-agent") {
// Fall back to analysis output without recommendations
return { fallback: true, output: previousOutputs["analysis-agent"] };
}
throw error; // Re-throw for critical agents
},
});
The onAgentError handler gives you control over failure modes. You can fall back to partial results, retry with modified input, or fail fast depending on which agent encountered the error and how critical it is.
The SDK's multi-agent primitives are designed to support the kind of agent teams you build for real business problems. Here is how the SDK concepts map to practical team design:
Agent roles map to business functions. A "Research Agent" in the SDK corresponds to the research function in a market analysis team. Define each agent's system prompt around a specific business capability, not a generic technical function.
Teams map to workflows. A "Due Diligence Team" in the SDK is a team definition with agents covering financial analysis, legal review, and market assessment. The orchestration pattern (sequential, parallel, or coordinator) depends on the dependencies between these functions.
Output formats map to deliverables. The structured output from a team run should match the deliverable format your end users expect. If the business need is a report, the final merge step should produce report-formatted output, not raw agent outputs.
// A complete team definition for a practical use case
const dueDiligenceTeam = defineTeam({
name: "due-diligence",
agents: [
financialAnalysisAgent, // Analyzes financial statements
legalReviewAgent, // Reviews legal structure and risks
marketAssessmentAgent, // Evaluates market position
],
orchestration: "parallel",
merge: (outputs) => ({
executiveSummary: generateSummary(outputs),
financialAnalysis: outputs["financial-analysis"],
legalReview: outputs["legal-review"],
marketAssessment: outputs["market-assessment"],
riskMatrix: combineRisks(outputs),
recommendation: generateRecommendation(outputs),
}),
});
The key insight is that the SDK handles the infrastructure -- execution, data passing, error handling -- while you focus on defining what each agent does and how the outputs combine. The business logic lives in the agent prompts and the merge functions, not in orchestration boilerplate.
When designing your agent teams, start with the deliverable your users need, work backward to identify the analyses required, and map each analysis to an agent. The SDK patterns (sequential, parallel, coordinator) then follow naturally from the dependencies between those analyses.