How to Build a Multi-Agent System with Claude
A multi-agent system with Claude is an architecture where several independent Claude-powered agents, each with a narrow role and its own prompt, collaborate to complete a task that a single agent would handle poorly. You build one by defining distinct agent roles, giving each a focused system prompt and tool set, and writing an orchestration layer that routes messages, aggregates results, and decides when the task is done.
This article walks through the actual design decisions: how to split responsibilities between agents, how they communicate, how to avoid runaway loops and token costs, and what the orchestration code looks like in practice.
Why use multiple agents instead of one
A single Claude call with a long system prompt can handle a lot, but it starts to break down when a task requires:
- Conflicting priorities — e.g. one role needs to be creative, another needs to be strict and rule-following.
- Different tool access — a research agent needs web/search tools, a writer agent doesn't, and giving every tool to one agent increases the chance of misuse.
- Long context isolation — a planning agent's internal reasoning doesn't need to pollute the context of the agent that writes the final answer.
- Parallelism — several sub-tasks (research, drafting, fact-checking) can run concurrently instead of sequentially in one giant prompt.
If your task is a single well-defined transformation (summarize, classify, extract), skip multi-agent — it adds latency and cost for no benefit. Multi-agent systems earn their complexity when the task has distinct sub-tasks with different skills or tool requirements.
Core architecture patterns
1. Orchestrator-worker (hub and spoke)
One "orchestrator" agent receives the user request, breaks it into sub-tasks, and dispatches each to a specialized worker agent. Workers return results to the orchestrator, which synthesizes the final answer.
User → Orchestrator → Worker A (research)
→ Worker B (code)
→ Worker C (review)
← Orchestrator synthesizes → User
This is the easiest pattern to debug because all control flow goes through one place.
2. Pipeline
Agents run in a fixed sequence, each one consuming the previous agent's output. Good for tasks with a natural linear flow: draft → critique → revise → finalize.
3. Peer-to-peer debate / critique
Two or more agents exchange messages directly (often moderated by a thin controller) to refine an answer — e.g. a "generator" and a "critic" agent that iterate until the critic approves.
For most production use cases, orchestrator-worker is the right default. It's the easiest to reason about, log, and bill accurately per sub-task.
Defining agent roles
Each agent should have:
- A narrow system prompt describing exactly what it does and doesn't do
- A restricted tool set (don't give the summarizer agent a code execution tool)
- A clear output contract — ideally structured JSON so the orchestrator can parse it reliably
Example role definitions:
const AGENTS = {
researcher: {
system: "You research topics and return factual bullet points with sources. Do not write prose.",
tools: ["web_search"],
},
writer: {
system: "You turn research bullet points into a clear, well-structured article section.",
tools: [],
},
reviewer: {
system: "You check text for factual consistency against the provided research. Return APPROVE or a list of issues.",
tools: [],
},
};
Keeping roles narrow makes failures easier to diagnose — if the output is factually wrong, you know it's the researcher or the reviewer, not a tangled single prompt.
Orchestration code
The orchestrator is just a loop: call an agent, parse its structured output, decide the next step. Here's a simplified example using Claude via SubToAPI, which exposes Claude through a standard HTTPS API so each agent call is just a request with its own API key and model config:
async function callAgent(role, userMessage) {
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-opus-4",
system: AGENTS[role].system,
messages: [{ role: "user", content: userMessage }],
max_tokens: 1024,
}),
});
const data = await res.json();
return data.content[0].text;
}
async function orchestrate(topic) {
const research = await callAgent("researcher", `Research: ${topic}`);
const draft = await callAgent("writer", research);
const review = await callAgent("reviewer", `Research:\n${research}\n\nDraft:\n${draft}`);
if (review.trim().startsWith("APPROVE")) return draft;
// one revision pass using reviewer feedback
return callAgent("writer", `${research}\n\nRevise based on:\n${review}`);
}
This is deliberately simple — no framework, no hidden abstractions. For production you'll want to add retries, timeouts, and logging around each callAgent call, plus a hard cap on revision loops so a disagreeing writer/reviewer pair can't run forever.
Handling cost, auth, and observability
Multi-agent systems multiply your API calls, so a few operational details matter more than in single-agent setups:
- Per-agent API keys. Issuing a separate key per agent role lets you see exactly which agent is consuming tokens and lets you revoke or rate-limit one role without affecting others. SubToAPI's dashboard supports scoped
sub_live_...keys per team member or service, which works well for mapping one key per agent role. See /docs/quickstart to generate keys. - Streaming for long-running agents. If an agent produces a long response (e.g. the writer), stream it so the orchestrator — or the end user — sees progress instead of waiting on the full completion. See /docs/streaming.
- Tool use isolation. If an agent needs external tools (search, code execution, database lookups), define tools per-agent rather than globally. /docs/tools covers the request/response shape for tool calls.
- Usage metadata. Each response includes token usage, which you should log per agent role so you can see which stage of the pipeline is actually driving cost — often it's not the one you'd guess.
If you're running this across a team building the same system, /pricing has Team and Scale tiers with per-seat API keys, which maps cleanly onto "one key per developer working on one agent role."
A practical starting checklist
- Write down the sub-tasks in plain English before writing any code.
- Assign one agent per sub-task, with the narrowest possible system prompt.
- Define a strict output format (JSON or a clear sentinel like
APPROVE) for every agent so the orchestrator never has to guess. - Cap loops — pipelines and debate patterns need a max iteration count.
- Log token usage and latency per agent from day one, not after costs surprise you.
Questions
Do I need a framework like LangGraph or CrewAI to build a multi-agent system with Claude? No. For orchestrator-worker and pipeline patterns, a plain loop calling the Claude API per agent is often easier to debug than a framework's abstraction layer. Reach for a framework only if you need built-in state persistence or complex branching you don't want to hand-roll.
How many agents should a multi-agent system have? Start with the minimum that maps to distinct skills or tool needs — often 2-4. Each additional agent adds latency and failure points, so only split further if a single agent is genuinely struggling with conflicting instructions.
How do I prevent agents from looping forever on disagreement? Set a hard max iteration count (2-3 revision rounds is usually enough) and have the orchestrator force-accept the last draft if the cap is hit, logging it as a flagged case for human review.