Files
magnus919_agent-skills/langgraph/references/multi-agent-supervisor.md
T
Magnus Hedemark 4a73657522 feat: add langgraph expert skill — multi-agent patterns, scaffolds, evals, and production guidance
Comprehensive LangGraph skill covering:
- Core architecture: Graph API, Functional API, state management, agent loops
- Three multi-agent patterns: supervisor (~94% accuracy), swarm (~40% fewer LLM calls),
  hierarchical teams (subgraphs with nested state)
- Persistence: checkpointers vs stores, per-invocation/per-thread/stateless modes
- Production: Agent Server deployment, LangSmith observability, 8 failure modes
- Evals: routing accuracy, resolution coverage, LLM-as-judge methodology
- Troubleshooting: symptom→cause→fix tables per pattern
- 3 Python scripts: supervisor scaffold, swarm scaffold, eval generator
- 3 runnable templates: supervisor, swarm, subgraph composition

Ships 8 reference files, 3 scripts, and 3 templates.
2026-07-08 14:54:53 -04:00

7.4 KiB
Raw Blame History

Supervisor Pattern — Centralized Multi-Agent Routing

The supervisor pattern uses a dedicated routing node to coordinate specialist agents. Every message passes through the supervisor, which classifies intent and routes to the appropriate specialist. After the specialist responds, control returns to the supervisor for the next routing decision.

This pattern is the right choice when accuracy matters more than latency, domain boundaries are ambiguous, and you need a centralized audit trail of every routing decision.

Architecture

[User] → [Supervisor] → [Billing Agent] → [Supervisor] → [Response]
                    ↘ [Tech Support] → ↗
                    ↘ [Account Mgmt] → ↗

The supervisor is a dedicated LLM node with structured output (with_structured_output(RoutingDecision)) — no regex, no string parsing, just typed routing decisions.

Comparative Metrics

Metric Supervisor Swarm
Avg latency (single-domain) ~4.2s ~2.8s
Avg latency (handoff required) ~9.1s ~5.4s
LLM calls (single-domain) 2 (route + specialist) 1 (specialist only)
LLM calls (handoff required) 4 (route×2 + specialist×2) 2 (specialist×2)
Avg tokens per request ~2,800 ~1,900
Routing accuracy ~94% ~91%

Implementation

1. Define State

from typing import Annotated, TypedDict
from langgraph.graph import MessagesState
import operator

class MultiAgentState(MessagesState):
    current_agent: str                           # which specialist is active
    resolution_notes: Annotated[list[str], operator.add]  # audit trail
    handoff_count: int                           # recursion guard

2. Define Routing Schema (Pydantic)

from pydantic import BaseModel, Field

class RoutingDecision(BaseModel):
    next_agent: str = Field(
        description="Next agent: 'billing', 'tech_support', 'account', or 'DONE'"
    )
    reasoning: str = Field(description="Why this agent was chosen")

3. Create the Supervisor Node

routing_llm = llm.with_structured_output(RoutingDecision)

def supervisor(state: MultiAgentState) -> dict:
    notes = "\n".join(state.get("resolution_notes", []))
    history_context = f"\n\nAlready resolved:\n{notes}" if notes else ""

    response = routing_llm.invoke([
        SystemMessage(
            content="You are a multi-agent supervisor. Analyze the conversation "
                    "and decide which specialist should handle the next step.\n\n"
                    "Available agents:\n"
                    "- billing: invoices, payments, discounts, pricing\n"
                    "- tech_support: technical issues, SSO, integrations, bugs\n"
                    "- account: plan changes, upgrades, account administration\n"
                    "- DONE: the customer's request has been fully addressed\n\n"
                    "Do NOT re-route to an agent that has already handled "
                    "its portion of the request." + history_context
        ),
        *state["messages"],
    ])
    return {"current_agent": response.next_agent}

4. Create Specialist Agents

Each specialist has a focused system prompt and domain-specific tools. Using create_agent from langchain.agents:

from langchain.agents import create_agent

billing_agent = create_agent(
    llm,
    tools=[lookup_billing_info, apply_discount],
    system_prompt="You are a billing specialist. Help customers with invoices, "
           "payments, discounts, and plan pricing. "
           "Customer ID is 'C-1042' unless otherwise specified.",
)

tech_support_agent = create_agent(
    llm,
    tools=[diagnose_sso, check_system_status],
    system_prompt="You are a technical support specialist. Help customers "
           "diagnose and resolve technical issues.",
)

account_agent = create_agent(
    llm,
    tools=[lookup_account_details, update_plan],
    system_prompt="You are an account management specialist. Help customers "
           "with plan changes, upgrades, and account administration.",
)

5. Specialist Node Wrappers

def billing_node(state: MultiAgentState) -> dict:
    result = billing_agent.invoke({"messages": state["messages"]})
    return {
        "messages": result["messages"][-1:],
        "resolution_notes": [
            f"Billing: {result['messages'][-1].content[:200]}"
        ],
    }

def tech_support_node(state: MultiAgentState) -> dict:
    result = tech_support_agent.invoke({"messages": state["messages"]})
    return {
        "messages": result["messages"][-1:],
        "resolution_notes": [
            f"Tech Support: {result['messages'][-1].content[:200]}"
        ],
    }

def account_node(state: MultiAgentState) -> dict:
    result = account_agent.invoke({"messages": state["messages"]})
    return {
        "messages": result["messages"][-1:],
        "resolution_notes": [
            f"Account: {result['messages'][-1].content[:200]}"
        ],
    }

6. Wire the Graph

from langgraph.graph import StateGraph, START, END

def route_to_agent(state: MultiAgentState) -> str:
    agent = state.get("current_agent", "DONE")
    if agent == "DONE":
        return "end"
    return agent

builder = StateGraph(MultiAgentState)

builder.add_node("supervisor", supervisor)
builder.add_node("billing", billing_node)
builder.add_node("tech_support", tech_support_node)
builder.add_node("account", account_node)

builder.add_edge(START, "supervisor")

builder.add_conditional_edges(
    "supervisor",
    route_to_agent,
    {
        "billing": "billing",
        "tech_support": "tech_support",
        "account": "account",
        "end": END,
    },
)

# Each specialist returns to supervisor
builder.add_edge("billing", "supervisor")
builder.add_edge("tech_support", "supervisor")
builder.add_edge("account", "supervisor")

supervisor_graph = builder.compile()

Failure Modes

Routing Loops

The supervisor routes to billing → billing responds → supervisor routes to billing again → repeats.

Fix: Include resolution notes in the supervisor's context so it can see what's already been addressed. The resolution_notes accumulator (with operator.add reducer) tracks what each specialist has done.

Supervisor Bottleneck

Every interaction requires a routing LLM call — even when the intent is obvious ("I need to change my password" doesn't need a routing decision).

Fix: Add a fast-path classifier before the supervisor:

FAST_PATH = {
    "password": "tech_support",
    "invoice": "billing",
    "upgrade": "account",
    "downgrade": "account",
}

def fast_path_or_supervisor(state: MultiAgentState) -> dict:
    last_msg = state["messages"][-1].content.lower()
    for keyword, agent in FAST_PATH.items():
        if keyword in last_msg:
            return {"current_agent": agent}
    return supervisor(state)

Token Waste on Re-routing

The supervisor pattern doubles token spend on routing calls compared to swarm.

Fix: Track total tokens per pattern across sessions in LangSmith. If token costs exceed the value of the routing accuracy gain, consider switching to swarm.

When to Use

  • Routing accuracy is the most important metric
  • Domain boundaries are ambiguous (billing vs account overlap)
  • You need a centralized audit trail of every decision
  • You're iterating on routing logic and want to change it in one place
  • You have fewer than 5 specialists and latency is acceptable