Multi-Agent Systems in Production: Beyond Single Agents
# Multi-agent systems split complex work across specialized AI agents that collaborate. Learn the orchestration patterns, failure modes, and communication protocols that make them reliable in production.
Multi-agent systems solve what single agents can't: tasks too large for one context window, work that benefits from parallel execution, and problems where different expertise is needed at different stages. A research agent, a writing agent, and a fact-checker working in parallel produces better output than one generalist agent doing everything sequentially.
When Multi-Agent Makes Sense
Don't reach for multi-agent by default — it adds complexity. Use it when:
- Context overflow: The full task exceeds a single model's context window
- Parallelism: Independent subtasks can run simultaneously (3× speedup)
- Specialization: Different parts need different system prompts, tools, or models
- Quality checks: One agent's output should be validated by another
For simple, linear tasks, a single agent with good tools is faster and cheaper.
The Four Orchestration Patterns
1. Sequential Pipeline
Each agent hands its output to the next. Good for multi-stage transformation.
Researcher → Analyst → Writer → Editor
async function sequentialPipeline(topic: string) {
const research = await researchAgent.run(`Research: ${topic}`);
const analysis = await analystAgent.run(`Analyze this research: ${research}`);
const draft = await writerAgent.run(`Write an article based on: ${analysis}`);
const final = await editorAgent.run(`Edit this draft: ${draft}`);
return final;
}
2. Parallel Fan-Out / Fan-In
Multiple agents work simultaneously; an orchestrator merges results. Best for large-scale data processing.
┌── SubAgent A ──┐
Orchestrator ─┼── SubAgent B ──┼──▶ Merger ──▶ Result
└── SubAgent C ──┘
async function parallelFanOut(documents: string[]) {
const summaries = await Promise.all(
documents.map((doc) => summaryAgent.run(`Summarize: ${doc}`))
);
return mergerAgent.run(`Combine these summaries:\n${summaries.join("\n\n")}`);
}
3. Hierarchical Delegation
An orchestrator agent plans and delegates subtasks to specialist agents. The orchestrator never does the work directly.
const ORCHESTRATOR_SYSTEM = `You are a task orchestrator.
Available agents: researcher, coder, analyst, writer.
Break tasks into subtasks and delegate. Return JSON:
{"subtasks": [{"agent": "coder", "task": "..."}]}`;
async function hierarchicalOrchestrator(goal: string) {
const plan = await orchestratorLLM.plan(goal);
const results: Record<string, string> = {};
for (const subtask of plan.subtasks) {
results[subtask.agent] = await agents[subtask.agent].run(subtask.task);
}
return orchestratorLLM.synthesize(goal, results);
}
4. Critic-Actor Loop
One agent proposes solutions; another critiques them. Iterate until the critic approves or max rounds reached.
async function criticActorLoop(task: string, maxRounds = 3) {
let solution = await actorAgent.run(task);
for (let round = 0; round < maxRounds; round++) {
const critique = await criticAgent.run(
`Critique this solution. Respond with JSON {"approved": bool, "issues": [...]}.\n\nSolution: ${solution}`
);
if (critique.approved) break;
solution = await actorAgent.run(
`Revise based on this critique: ${critique.issues.join(", ")}\n\nOriginal: ${solution}`
);
}
return solution;
}
State Management Between Agents
Agents need shared state. Three options:
| Approach | Latency | Complexity | Best for | |----------|---------|------------|----------| | Pass context in messages | Low | Low | Sequential pipelines | | Shared KV store (Redis) | ~1ms | Medium | Parallel agents | | Event bus (Kafka/NATS) | ~5ms | High | Long-running workflows |
For most use cases, passing context in messages is enough:
interface AgentContext {
taskId: string;
goal: string;
completedSteps: Array<{ agent: string; output: string }>;
artifacts: Record<string, string>;
}
function buildAgentMessage(ctx: AgentContext, instruction: string): string {
return `Task: ${ctx.goal}
Completed work so far:
${ctx.completedSteps.map((s) => `[${s.agent}]: ${s.output}`).join("\n")}
Your task: ${instruction}`;
}
Failure Handling in Multi-Agent Systems
Single-agent failure is simple to handle. Multi-agent failure is a cascade problem.
async function resilientSubtask(
agent: Agent,
task: string,
retries = 2
): Promise<string> {
for (let i = 0; i <= retries; i++) {
try {
return await Promise.race([
agent.run(task),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error("Agent timeout")), 30_000)
),
]);
} catch (err) {
if (i === retries) throw err;
console.warn(`Agent ${agent.name} failed attempt ${i + 1}: ${err}`);
await sleep(1000 * 2 ** i); // exponential backoff
}
}
throw new Error("unreachable");
}
Always set per-agent timeouts. A hung subagent blocks the entire pipeline otherwise.
Cost Control in Multi-Agent Systems
Costs multiply with agents. Track per-agent spend:
class CostTracker {
private costs: Record<string, number> = {};
private budget: number;
constructor(budgetUSD: number) {
this.budget = budgetUSD;
}
record(agent: string, inputTokens: number, outputTokens: number, modelTier: "fast" | "standard" | "powerful") {
const prices = { fast: [0.00025, 0.00125], standard: [0.003, 0.015], powerful: [0.015, 0.075] };
const [iPrice, oPrice] = prices[modelTier];
const cost = inputTokens * iPrice / 1000 + outputTokens * oPrice / 1000;
this.costs[agent] = (this.costs[agent] ?? 0) + cost;
const total = Object.values(this.costs).reduce((a, b) => a + b, 0);
if (total > this.budget) throw new Error(`Budget exceeded: $${total.toFixed(4)}`);
}
}
What Works at Shahriar Labs
The QuantumSketch video generation pipeline is a 4-agent system: a script writer, a scene planner, a Manim code generator, and a quality validator. Each runs in sequence with the critic-actor pattern on the code generation step (ensures renderable Manim code).
The key lessons:
- Limit agent count — every agent adds latency and cost. Four well-scoped agents beat ten vague ones.
- Use Temporal for durability — wrap the pipeline in a Temporal workflow so it survives crashes and rate limits. See Building Production AI Agents.
- Specialize system prompts aggressively — each agent gets a focused persona, not a generic "helpful assistant."
FAQ
How many agents is too many? More than 6–8 agents in one workflow usually indicates scope creep. If you're at 10+, break the workflow into two separate pipelines that exchange results via a queue.
Do agents need to use the same model? No — mix models. Use cheap models for simple subtasks (summarization, classification) and powerful models for complex ones (code generation, analysis).
How do I debug a multi-agent system?
Assign a unique trace_id to each workflow run. Log every agent invocation with the trace ID. Use this to reconstruct what each agent received and produced. See AI Observability for a full tracing setup.
Can agents call other agents recursively? Yes, but set a max recursion depth (typically 3–5). Unbounded recursion is the most common cause of runaway costs in multi-agent systems.
What's the latency impact? Sequential agents add their latency in series. Parallel agents add only the slowest agent's latency. For a 4-agent sequential pipeline at 5 seconds each = 20 seconds. Parallelize where possible.