Introduction to Multi-Agent Systems with Claude
Multi-agent systems (MAS) represent the next evolution in AI, where multiple specialized AI instances collaborate to solve complex tasks. Unlike single-agent setups, MAS mimic human teams: a supervisor delegates work, agents execute roles like researcher or editor, and results iterate until completion.
Claude excels here due to its superior reasoning, tool use, and context handling in the Anthropic API. Models like Claude 3.5 Sonnet shine in role-playing and structured outputs, making them ideal for agent swarms.
This tutorial builds a Content Creation Pipeline: a supervisor oversees a Researcher, Writer, and Editor—all powered by Claude via the TypeScript SDK. We'll use sequential API calls with a stateful orchestration loop for collaboration.
Why Multi-Agent with Claude?
- Modularity: Break tasks into roles for better accuracy (e.g., Researcher focuses on facts).
- Scalability: Parallelize agents with async calls (future-proof for production).
- Reliability: Claude's constitutional AI reduces hallucinations in team settings.
- Cost-Effective: Use Haiku for simple agents, Opus for complex supervision.
Real-world use: Automate reports, code reviews, or marketing campaigns.
Prerequisites
- Node.js 18+
- Anthropic API key (free tier available at console.anthropic.com)
- Basic TypeScript knowledge
Step 1: Project Setup
Create a new directory and initialize:
mkdir claude-multi-agent
cd claude-multi-agent
npm init -y
npm install @anthropic-ai/sdk typescript ts-node @types/node
npx tsc --init
Update tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true
}
}
Create src/index.ts and .env for your API key:
npm install dotenv
npm install -D @types/dotenv
.env:
ANTHROPIC_API_KEY=your_key_here
Step 2: Core Components
We'll define:
- Agent Interface: Standardized inputs/outputs.
- Prompt Templates: Role-specific system prompts.
- Supervisor Logic: Decides next action.
Agent Types
import Anthropic from '@anthropic-ai/sdk';
import dotenv from 'dotenv';
dotenv.config();
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
type AgentMessage = {
role: string;
content: string;
task: string;
};
type AgentResponse = {
output: string;
nextAction: 'research' | 'write' | 'edit' | 'done';
feedback?: string;
};
interface ClaudeAgent {
name: string;
model: string;
systemPrompt: string;
invoke(messages: AgentMessage[]): Promise<AgentResponse>;
}
Step 3: Define Agents
Researcher Agent
const researcher: ClaudeAgent = {
name: 'Researcher',
model: 'claude-3-5-sonnet-20240620',
systemPrompt: `You are a meticulous Researcher. Given a topic, gather key facts, sources, and insights. Output structured bullet points. Suggest if writing or editing is next.`
};
function createAgentInvoke(agent: ClaudeAgent) {
return async (messages: AgentMessage[]): Promise<AgentResponse> => {
const response = await client.messages.create({
model: agent.model,
max_tokens: 2000,
system: agent.systemPrompt,
messages: messages.map(m => ({ role: 'user' as const, content: m.content })),
});
const output = response.content[0].text;
// Parse structured response (use XML or JSON tools in prod)
const parsed = parseAgentResponse(output); // Implement parser
return parsed;
};
}
researcher.invoke = createAgentInvoke(researcher);
Implement a simple parser (in production, use Claude's tool calling for JSON):
function parseAgentResponse(text: string): AgentResponse {
// Regex or simple split for tutorial
const nextMatch = text.match(/Next action: (\w+)/i);
return {
output: text,
nextAction: (nextMatch?.[1].toLowerCase() as any) || 'done',
};
}
Writer and Editor Agents
const writer: ClaudeAgent = {
name: 'Writer',
model: 'claude-3-5-sonnet-20240620',
systemPrompt: `You are a skilled Writer. Use research to draft engaging content. Output full draft. Decide if editing needed.`
};
writer.invoke = createAgentInvoke(writer);
const editor: ClaudeAgent = {
name: 'Editor',
model: 'claude-3-haiku-20240307', // Faster/cheaper
systemPrompt: `You are an Editor. Review draft for clarity, grammar, facts. Suggest revisions or approve.`
};
editor.invoke = createAgentInvoke(editor);
const agents = { researcher, writer, editor };
Step 4: Supervisor Agent
The brain: Oversees workflow, maintains state.
const supervisorSystem = `You are Supervisor. Manage agents for content pipeline.
State: {research, draft, edits}
Commands:
- research: Send topic to Researcher
- write: Send research to Writer
- edit: Send draft to Editor
- done: Output final
Respond with JSON: {"next_agent": "research|write|edit|done", "rationale": "...", "input": "..."}`;
type SupervisorDecision = {
next_agent: keyof typeof agents | 'done';
rationale: string;
input?: string;
};
async function getSupervisorDecision(state: any): Promise<SupervisorDecision> {
const msg = `Current state: ${JSON.stringify(state)}\
Decide next step.`;
const res = await client.messages.create({
model: 'claude-3-opus-20240229',
system: supervisorSystem,
max_tokens: 500,
messages: [{ role: 'user' as const, content: msg }],
});
return JSON.parse(res.content[0].text); // Use tools for robust parsing
}
Step 5: Orchestration Loop
async function runPipeline(topic: string) {
let state = {
topic,
research: '',
draft: '',
edits: '',
history: [],
};
while (true) {
const decision = await getSupervisorDecision(state);
state.history.push(decision);
if (decision.next_agent === 'done') {
console.log('Final Output:', state.draft);
break;
}
const currentAgent = agents[decision.next_agent as keyof typeof agents];
const taskMsg: AgentMessage = {
role: currentAgent.name,
content: decision.input || topic,
task: state.topic,
};
const response = await currentAgent.invoke([taskMsg]);
state.research = decision.next_agent === 'research' ? response.output : state.research;
state.draft = decision.next_agent === 'write' ? response.output : state.draft;
state.edits += response.feedback || '';
console.log(`${currentAgent.name}: ${response.output.slice(0, 100)}...`);
}
}
// Run
runPipeline('Best practices for Claude prompt engineering');
Step 6: Enhancements for Production
- Tool Calling: Use Claude's tools for structured outputs.
// Example tool tools: [{ name: 'publish', description: 'Finalize content', input_schema: { type: 'object', properties: { content: {type: 'string'} } } }] - Parallelism:
Promise.allfor independent agents. - Persistence: Redis/Stateful DB for long runs.
- Error Handling: Retry logic with exponential backoff.
- MCP Integration: Extend with Model Context Protocol servers for external tools.
- Costs: Monitor tokens; use Haiku for editors.
Full Code
Combine into src/index.ts. Run with npx ts-node src/index.ts.
Expected Output:
- Supervisor delegates: research → write → edit → done.
- Produces polished article on topic.
Best Practices & Limitations
Prompt Engineering Tips:
- Role specificity reduces drift.
- Chain-of-thought in system prompts.
- XML/JSON delimiters for parsing.
Claude-Specific:
- Leverage 200k context for long histories.
- Sonnet for balance of speed/quality.
- Avoid infinite loops with max iterations (e.g., 10).
Limitations:
- Sequential by default; async for true parallelism.
- Token costs scale with agents.
- No native state; manage externally.
Conclusion
You've built a scalable multi-agent swarm with Claude! Experiment by adding agents (e.g., Fact-Checker). For enterprise, integrate with n8n or Claude Code.
Fork on GitHub, share your variants. Next: AI Agents with Tools.
Word count: ~1450
Stay ahead of the AI curve
The most important updates, news, and content — delivered in one weekly newsletter.