Multi-Agent Orchestration: Routing and Failure Recovery — Darko Trpevski
Multi-Agent Orchestration: Routing and Failure Recovery<br>Five agents talking to each other destroys context and costs money. Here's the pattern every major vendor converged on: one orchestrator, no loops.<br>You start with one agent. It works. Then you add more because one agent can't handle customer service + billing + technical support at the same time. Now you have five agents and they're destroying each other.
Agent A sends to Agent B. Agent B doesn't know the context, so it re-asks questions. Agent B sends to Agent C. Agent C thinks it's a new conversation and starts over. Meanwhile Agent D got the same message and is also working on it. Your cost just tripled and your customers are waiting for contradictory responses.
This is what happens when you bolt agents together without an orchestrator. In 2026, the teams that figured this out all converged on the same pattern: one orchestrator owns the conversation, spawns isolated agents for specific tasks, collects their results, and decides what to do next.
No peer-to-peer. No agent-to-agent handoffs. No infinite loops.
This post covers what actually works at scale.
Why Single Agents Don't Scale
A single agent with access to everything sounds good in theory. In practice:
Context window explosion — Every tool call, every attempted action, every failure eats tokens. A complex task that needs 20 steps burns through context fast.
Decision fatigue — The agent re-plans constantly. Should I use tool A or B? Should I retry or escalate? With 10 tools available, it thrashes.
Liability — Everything the agent does is one entity. If it makes a mistake, there's no clear separation of concerns. No audit trail of "who decided what."
Recovery is hard — When the agent fails partway through, where does it resume? You have to restart the whole thing.
Example of agent thrashing:
User: "I need a refund but also want to keep my subscription"
Agent thinks:<br>- Should I use get_refund tool?<br>- But the policy says refunds end subscription<br>- Should I ask the user to clarify?<br>- Should I escalate to billing?<br>- Actually, let me just try both
Result: Makes refund call AND tries to keep subscription<br>Outcome: Inconsistent state, confused customer, support ticket
With an orchestrator:
1. Orchestrator routes to "refund_agent" because user mentioned refund<br>2. Refund_agent is narrowly scoped: can only check policies and create refund<br>3. Refund_agent says "this ends subscription per policy"<br>4. Orchestrator decides: if customer still wants it, route to "retention_agent"<br>5. Retention_agent handles upgrade/alternative solutions<br>6. Clear sequence, clear responsibilities, clear audit trail
The Orchestrator Pattern (What Actually Works)
There's a reason every major vendor converged here. It's the only pattern that scales without chaos.
python
from typing import List, Dict, Any<br>from dataclasses import dataclass<br>from enum import Enum
class AgentType(Enum):<br>"""Types of agents available."""<br>SUPPORT = "support"<br>BILLING = "billing"<br>TECHNICAL = "technical"<br>ESCALATION = "escalation"
@dataclass<br>class Agent:<br>"""Agent definition."""<br>name: str<br>type: AgentType<br>system_prompt: str<br>tools: List[str] # Tools this agent can use<br>max_steps: int = 5 # Prevent runaway agents
@dataclass<br>class AgentResult:<br>"""Result from agent execution."""<br>agent_name: str<br>success: bool<br>output: str<br>used_steps: int<br>cost: float
class Orchestrator:<br>"""<br>Central orchestrator that owns conversation context.
Responsibilities:<br>- Route messages to appropriate agents<br>- Maintain conversation history<br>- Collect results and decide next action<br>- Handle failures and retries<br>- No agent talks to another agent<br>"""
def __init__(self):<br>self.agents = self._initialize_agents()<br>self.conversation_history = []<br>self.max_turns = 10<br>self.turn_count = 0
def _initialize_agents(self) -> Dict[str, Agent]:<br>"""Initialize available agents."""<br>return {<br>"support": Agent(<br>name="support",<br>type=AgentType.SUPPORT,<br>system_prompt="""You handle customer support queries.<br>You can only access: get_faq, create_ticket, get_order_status.<br>Do not make refunds. Do not change subscriptions.<br>If customer needs those, say so and stop.""",<br>tools=["get_faq", "create_ticket", "get_order_status"],<br>),<br>"billing": Agent(<br>name="billing",<br>type=AgentType.BILLING,<br>system_prompt="""You handle billing and refunds.<br>You can only access: process_refund, update_subscription, view_invoice.<br>Do not handle technical issues or general support.<br>If customer has those, say so and stop.""",<br>tools=["process_refund", "update_subscription", "view_invoice"],<br>),<br>"technical": Agent(<br>name="technical",<br>type=AgentType.TECHNICAL,<br>system_prompt="""You handle technical issues.<br>You can only access: run_diagnostic, restart_service, check_logs.<br>Do not handle billing or general support.<br>If customer needs those, say so and stop.""",<br>tools=["run_diagnostic", "restart_service", "check_logs"],<br>),
def process_message(self, user_message: str) ->...