Skip to main content
December 2025 Update: Production patterns for multi-agent orchestration including ReAct, hierarchical decomposition, and event-driven architectures.

Why Multi-Agent Systems?

Single agents have limitations:

Pattern 1: ReAct (Reason + Act Loop)

The foundational pattern for autonomous agents:

Implementation


Pattern 2: Hierarchical Task Decomposition

Break complex tasks into subtasks with specialized agents:

Implementation


Pattern 3: Event-Driven Agents

Agents that respond to events and can run for extended periods:

Implementation


Pattern 4: Debate and Consensus

Multiple agents debate to reach better conclusions:

Pattern 5: Supervisor Pattern

A supervisor agent manages and monitors worker agents:

Key Takeaways

ReAct for Autonomy

Use Reason+Act loops for agents that need to work independently

Hierarchy for Complexity

Break complex tasks into specialized subtasks

Events for Scale

Event-driven patterns for long-running, distributed systems

Debate for Quality

Multiple perspectives improve decision quality

What’s Next

Multimodal AI

Learn to build AI systems that work with vision, audio, and real-time voice

Interview Deep-Dive

Strong Answer:
  • ReAct (Reason + Act) is a single-agent loop: the agent thinks about what to do, takes an action (calls a tool), observes the result, and repeats until the task is done. It is ideal for tasks that are inherently sequential and exploratory — where you do not know upfront what steps are needed. A research agent that searches the web, reads results, decides what to search next based on what it found, and iterates until it has a complete answer is a perfect ReAct use case. The agent discovers the plan as it executes.
  • Hierarchical task decomposition is a multi-agent pattern where an orchestrator breaks a complex objective into subtasks, assigns each to a specialized agent, manages dependencies between subtasks, and synthesizes the results. It is ideal for tasks that can be planned upfront — where you know the general structure of the work even if you do not know the specifics. “Write a market analysis report” naturally decomposes into research, data analysis, and writing — three distinct phases with clear handoffs.
  • The practical distinction is about predictability. If you can describe the workflow as a DAG (directed acyclic graph) of subtasks before execution starts, use hierarchical decomposition. If the workflow is reactive and depends on intermediate results, use ReAct. Many real systems use both: the orchestrator decomposes the high-level task into subtasks, and each subtask is executed by a ReAct agent that has its own tool set.
  • The failure modes differ too. ReAct agents can get stuck in loops — repeatedly trying the same failing approach because the reasoning step is not sophisticated enough to learn from failure. I always set a maximum iteration count (typically 10-15) and build in explicit loop detection: if the last 3 actions were identical, force a different approach or escalate to a human. Hierarchical decomposition fails when the task decomposition is wrong — the orchestrator breaks the task into the wrong subtasks, or the dependencies are modeled incorrectly. The decomposition step itself is an LLM call, so it can hallucinate subtasks that do not make sense. I validate decompositions against a schema that enforces: each subtask must specify an agent type, each dependency must reference an existing subtask ID, and there must be no circular dependencies.
Follow-up: How do you handle the situation where a worker agent in a hierarchical system fails on its subtask? What is your error recovery strategy?There are three levels of error recovery. First, local retry: the worker agent retries its subtask with a modified approach (rephrased query, different tool, adjusted parameters). I give each worker 2-3 retry attempts before escalating. Second, substitution: the orchestrator reassigns the failed subtask to a different worker agent or a different model. If the research agent failed because the web search returned no results, maybe the analysis agent can derive the needed information from existing context. Third, replanning: the orchestrator re-decomposes the original objective, explicitly noting what failed and why. This produces a new plan that routes around the failure. The critical implementation detail is that each worker must return structured error information — not just “failed” but “failed because: search API returned 0 results for query ‘Q3 2024 EV sales data Europe.’” This context lets the orchestrator make intelligent recovery decisions rather than blindly retrying.
Strong Answer:
  • There are three fundamental communication patterns for multi-agent systems, and the choice depends on your coordination requirements. The first is direct message passing: Agent A sends a message directly to Agent B. This is simple but creates tight coupling — A must know about B. I use this for fixed pipelines where the agent topology is known at design time, like a three-stage pipeline of retrieval, analysis, and writing.
  • The second is a shared blackboard (or shared state): all agents read from and write to a common context object. The orchestrator updates the blackboard after each agent completes its task, and the next agent reads the relevant portions. This decouples agents from each other — they only need to know the blackboard schema, not the other agents. The downside is that the blackboard can grow unbounded. After 10 agent executions, the accumulated context might exceed the next agent’s context window. I manage this by structuring the blackboard as a typed dictionary with size limits per field: {"research_results": "...(max 2000 tokens)...", "analysis": "...(max 1500 tokens)...", "decisions": [...]}.
  • The third is event-driven communication via an event bus. Agents subscribe to event types and react when relevant events are published. This is the most flexible and scalable pattern — you can add new agents without modifying existing ones, agents can run concurrently, and the system naturally supports long-running workflows. The trade-off is complexity: debugging event-driven systems is harder because the execution flow is non-linear, and you need careful design of event types and payloads.
  • For context sharing specifically, the key challenge is that each agent has a limited context window and does not need all the information from all other agents. I build a context builder function per agent that selects and summarizes the relevant portions of shared state. The research agent’s output might be 5,000 tokens, but the writing agent only needs a 500-token summary of the key findings. This selective context injection keeps each agent’s prompt focused and within token limits.
Follow-up: In the event-driven pattern, how do you handle ordering guarantees and prevent race conditions when multiple agents process events concurrently?The honest answer is that you often do not need strict ordering guarantees in multi-agent systems, because the LLM’s reasoning step handles out-of-order information gracefully — it is not like a database transaction where ordering is critical for correctness. But when you do need ordering — for example, the analysis agent must not start until the research agent has finished — I use two mechanisms. First, explicit dependency barriers: the event bus tracks which events have been emitted and only delivers an event to a subscriber if all prerequisite events have been processed. This is essentially a lightweight workflow engine embedded in the event bus. Second, for true concurrency safety on shared state, I use optimistic concurrency control: each agent reads the blackboard with a version number, does its work, and writes back with a compare-and-swap. If the version has changed (another agent wrote in between), the write fails and the agent re-reads and retries. In practice, conflicts are rare because agents typically write to different fields of the blackboard, but the mechanism prevents corruption when they do overlap.
Strong Answer:
  • The first unique failure mode is cascading failures. In a single-agent system, if the agent fails, the task fails. In a multi-agent system, if the research agent produces bad output (hallucinated facts), the analysis agent builds analysis on those hallucinated facts, and the writing agent produces a confident, well-written report full of nonsense. Each agent did its individual job well, but the system produced garbage because errors amplified through the pipeline. The mitigation is inter-agent validation: the analysis agent should not blindly trust the research agent’s output. I add a “quality gate” step between agents where a lightweight model checks for internal consistency, unsupported claims, and obvious errors before passing results forward.
  • The second failure mode is coordination deadlock. Agent A is waiting for Agent B’s output, but Agent B is waiting for Agent A’s output due to a circular dependency. This is rare in well-designed systems but happens when the task decomposition step produces an invalid dependency graph. I enforce DAG validation on every decomposition and add a timeout on all inter-agent waits. If an agent has been waiting for more than 30 seconds, the orchestrator intervenes.
  • The third failure mode is context drift. In a long-running multi-agent workflow, the accumulated context gradually diverges from the original objective. Each agent adds its own interpretation, and by agent 5, the system is solving a slightly different problem than what was asked. I mitigate this by including the original objective in every agent’s prompt, not just the orchestrator’s. Every agent sees “Original objective: X. Your specific task: Y.” This anchors each agent to the user’s intent.
  • The fourth failure mode is cost explosion. A ReAct agent running inside a hierarchical system can enter a loop, consuming 50+ LLM calls before the iteration limit kicks in. Multiply that by 5 worker agents and a decomposition that produces 8 subtasks, and a single user request costs 5insteadof5 instead of 0.50. I enforce per-request cost budgets: each worker gets a maximum token budget, and the orchestrator tracks cumulative spending. If the budget is 80% consumed, the remaining agents are forced to use cheaper models or shorter responses.
  • The fifth and most subtle failure mode is inconsistency between agents. The research agent finds that Product X launched in 2023, but the writing agent (using its own knowledge) states it launched in 2024. Multi-agent systems can produce internally contradictory outputs because each agent has its own context and model instance. A final synthesis step that explicitly checks for contradictions across agent outputs catches most of these.
Follow-up: How do you debug a multi-agent system when the final output is wrong but you do not know which agent introduced the error?This is where observability design pays off. Every agent call must produce a trace that includes: the agent name, its input context (what it received from the blackboard or previous agents), its full prompt, the raw LLM response, and its output (what it wrote to the blackboard). I store these traces in a structured format that lets me reconstruct the full execution graph. When the final output is wrong, I work backward: I check the writing agent’s input — was the analysis correct? I check the analysis agent’s input — was the research accurate? At some point I find the agent where the input was correct but the output was wrong, and that is where the bug is. It is essentially a bisection search through the agent graph. The tooling I build for this is a “replay” capability: I can take any agent’s input trace and re-run it in isolation to see if the error is deterministic or stochastic. If it is deterministic, the bug is in the prompt or the tool. If it is stochastic, I need to add validation or retry logic at that stage.