[SL_CORE]
< CD ..||6 min read

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][ai-agents][architecture][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:

  1. Limit agent count — every agent adds latency and cost. Four well-scoped agents beat ten vague ones.
  2. Use Temporal for durability — wrap the pipeline in a Temporal workflow so it survives crashes and rate limits. See Building Production AI Agents.
  3. 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.