Skip to content
All articles
AI & ML

Multi-Agent AI Systems: Orchestrating Multiple LLMs to Solve Hard Problems

March 1, 2026 9 min readBy Daniyal Alam
Multi-Agent AI Systems: Orchestrating Multiple LLMs to Solve Hard Problems — article by Daniyal Alam

Multi-agent systems are architectures where multiple LLM-powered agents collaborate — each with a defined role, its own context window, and sometimes its own tools — to solve problems that a single prompt cannot reliably handle. If you have hit the ceiling of what a well-crafted prompt can do, or found that a single model collapses under the weight of a long, branching task, multi-agent design is the next lever to pull.

When a Single Prompt Is Not Enough

A single LLM call works well for bounded tasks: summarize this document, classify this ticket, draft this email. The cracks appear when:

  • The task requires long chains of reasoning where early errors compound downstream.
  • You need parallel subtasks that are logically independent — a single model context cannot easily branch and merge.
  • Different subtasks call for different capabilities or models (a cheap fast model for routing, a powerful model for synthesis).
  • You need independent verification — having the same model check its own output is unreliable.

In production I've found that most "my prompt isn't working" problems are actually architecture problems. The task is simply too large and too stateful for one call.

The Planner / Worker / Critic Pattern

The most durable pattern I keep returning to at DanixSoft is a three-role split:

Planner — receives the high-level goal and decomposes it into a structured plan: an ordered list of subtasks with typed inputs and expected outputs. The planner does not execute anything.

Worker(s) — each worker receives exactly one subtask, the relevant slice of context, and any tools it needs. Workers are isolated; they do not read each other's scratchpads.

Critic — reviews each worker output against the original plan's success criteria before it is accepted. The critic can return a structured rejection with a reason, triggering a retry.

This mirrors how a well-run engineering team operates. The planner is the tech lead, workers are engineers, the critic is code review. Separation of concerns makes each role easier to prompt, easier to test, and easier to replace with a different model.

Orchestration Topologies

Sequential Pipeline

The simplest topology. Output of agent N becomes input to agent N+1. Easy to reason about, easy to log, easy to debug.

Use it when tasks have strict data dependencies — you cannot summarize before you retrieve, cannot validate before you generate.

The cost is latency: every step adds a round-trip.

Parallel Fan-Out

The orchestrator dispatches multiple worker tasks simultaneously and waits for all to complete before merging results. This is the right shape for research tasks (query five sources in parallel, then synthesize), for scoring (evaluate a response on five dimensions simultaneously), or for generating multiple candidate outputs and then ranking them.

Fan-out dramatically reduces wall-clock time but increases peak token spend and requires a merge step that can itself be complex.

Hierarchical

A top-level orchestrator delegates to sub-orchestrators, each of which runs its own pipeline or fan-out. This is how large agentic systems scale. The top orchestrator stays ignorant of implementation details; it only sees sub-task results.

In practice, I recommend capping hierarchy at two levels until you genuinely need three. Each level adds indirection that makes failures harder to trace.

Passing State and Memory Between Agents

Context management is where most teams get burned. Every agent call starts with a blank context window — you have to decide deliberately what each agent sees.

Short-term state — pass as structured JSON in the system or user message. Keep it minimal: only the fields the agent needs for its current task. Sending the entire conversation history to every agent is wasteful and confusing.

Shared memory store — use a key-value store (Redis, a simple dict for prototypes) that agents can read from and write to. The orchestrator controls what gets written and when.

Scratchpad vs. output — distinguish between an agent's internal reasoning (cheap to discard) and its committed output (the artifact that flows downstream). Some frameworks expose chain-of-thought as a separate field; treat it as ephemeral.

Compression — for long pipelines, have a dedicated summarizer agent compress accumulated context before passing it to the next stage. This keeps token counts predictable.

A Concrete Orchestration Example

Below is a TypeScript sketch using a generic LLM client interface. The pattern applies equally to Anthropic's SDK, OpenAI's, or any provider that supports structured outputs.

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

interface SubTask {
  id: string;
  instruction: string;
  context: string;
}

interface TaskResult {
  taskId: string;
  output: string;
  approved: boolean;
}

async function callModel(
  systemPrompt: string,
  userMessage: string
): Promise<string> {
  const response = await client.messages.create({
    model: "claude-opus-4-5",
    max_tokens: 1024,
    system: systemPrompt,
    messages: [{ role: "user", content: userMessage }],
  });
  const block = response.content[0];
  return block.type === "text" ? block.text : "";
}

async function planner(goal: string): Promise<SubTask[]> {
  const raw = await callModel(
    "You are a task planner. Decompose the goal into 2-4 concrete subtasks. " +
      "Return JSON: [{id, instruction, context}].",
    goal
  );
  return JSON.parse(raw) as SubTask[];
}

async function worker(task: SubTask): Promise<string> {
  return callModel(
    "You are a specialist worker. Complete the assigned subtask precisely. " +
      "Return only the result, no commentary.",
    `Task: ${task.instruction}\nContext: ${task.context}`
  );
}

async function critic(
  task: SubTask,
  output: string
): Promise<{ approved: boolean; reason: string }> {
  const raw = await callModel(
    "You are a quality reviewer. Return JSON: {approved: boolean, reason: string}.",
    `Original task: ${task.instruction}\nProduced output: ${output}`
  );
  return JSON.parse(raw);
}

async function runPipeline(goal: string): Promise<TaskResult[]> {
  const tasks = await planner(goal);
  const results: TaskResult[] = [];

  // Fan-out: run workers in parallel
  const workerOutputs = await Promise.all(
    tasks.map(async (task) => ({ task, output: await worker(task) }))
  );

  // Sequential critic pass with one retry
  for (const { task, output } of workerOutputs) {
    let result = output;
    let review = await critic(task, result);

    if (!review.approved) {
      // Single retry with the critic's feedback embedded
      result = await worker({
        ...task,
        context: `${task.context}\nPrevious attempt was rejected: ${review.reason}. Correct it.`,
      });
      review = await critic(task, result);
    }

    results.push({ taskId: task.id, output: result, approved: review.approved });
  }

  return results;
}

This pattern — plan, fan-out workers, critic-gate with one retry — handles a large fraction of real agentic workloads. The key discipline is that the orchestrator (runPipeline) owns all control flow; individual agents only return data.

Reliability: Validation, Retries, and Loop Termination

Reliability is the hardest part of multi-agent engineering. Things that go wrong in production:

  • Schema violations — an agent returns malformed JSON. Always validate structured outputs before parsing. Use a schema library (Zod, Pydantic) or ask the model to self-correct once before you raise an error.
  • Infinite loops — an agent loop where the critic always rejects and retries run forever. Enforce a hard maximum iteration count (MAX_RETRIES = 2 in most cases) and surface a structured error when exceeded rather than silently failing.
  • Context drift — as context accumulates across retries, instructions get buried and the model starts ignoring them. Keep retry prompts focused; do not append the entire prior conversation.
  • Tool call failures — when agents use external tools (search, code execution, APIs), those tools fail. Wrap every tool call with a timeout and a fallback path the agent can follow when the tool is unavailable.

At DanixSoft, we also enforce output guardrails at the orchestrator level, separate from the critic agent. The critic checks quality; the guardrail checks safety and policy compliance. These are different concerns and should not live in the same prompt.

Cost and Latency Trade-offs

Multi-agent systems spend more tokens than a single prompt. That is a deliberate trade for reliability and capability. Some rules of thumb I apply:

  • Route by capability, not habit. Use a cheap, fast model (haiku-class) for planning, routing, and critic checks on simple tasks. Reserve expensive models for the generation steps that actually need them.
  • Parallelize aggressively. Wall-clock latency for a fan-out of five workers equals one worker's latency, not five. Total token cost is additive, but user-perceived speed is not.
  • Cache planner output. For recurring workflows with similar goals, the decomposition is often stable. Cache the plan and skip re-planning.
  • Set token budgets per agent. A worker that needs 300 tokens of output should not have a 4096-token max_tokens ceiling. Tight budgets prevent runaway generation and reduce cost.

Common Failure Modes to Avoid

  1. Giving every agent the full conversation history. Agents should receive only what they need. Overstuffed context degrades instruction-following and increases cost.
  2. No structured output contracts. Free-form agent outputs that the next agent has to interpret are fragile. Define JSON schemas for inter-agent communication and validate them.
  3. Critic and worker sharing a system prompt. This defeats the purpose of independent review. Keep roles fully separate, even if it means more prompt files to maintain.
  4. Skipping observability. In a single-prompt system you can read the one response. In a multi-agent system, log every agent call: model, input token count, output, latency, and whether the critic approved. Without this, debugging production failures is close to impossible.
  5. Building hierarchy before you need it. Start flat. Add layers only when a single orchestrator genuinely cannot coordinate the number of agents involved.

Key Takeaways

  • Multi-agent systems are the right tool when a task is too large, too branching, or too stateful for a single prompt.
  • The planner / worker / critic split maps cleanly to real-world engineering workflows and is a reliable starting point.
  • Control flow belongs in the orchestrator, not in individual agents. Agents return data; the orchestrator decides what happens next.
  • Enforce strict iteration limits and validate structured outputs at every boundary — this is non-negotiable in production.
  • Route model selection by the difficulty of each subtask, not by default. Cost and latency are manageable if you are deliberate about them.
  • Add observability from day one. Logging every agent call is not optional; it is how you debug, optimize, and build confidence in a system you cannot inspect in a single response.

Multi-agent design adds complexity, but it is the complexity of a well-structured system rather than a brittle monolith. Get the roles clean, the contracts typed, the control flow explicit, and you will find these systems are surprisingly maintainable — and far more capable than any single prompt can be.