# Multi-Agent: Handoff Pattern

Ref:

- https://docs.langchain.com/oss/python/langchain/multi-agent
- https://docs.langchain.com/oss/python/langgraph/use-subgraphs

The handoff pattern allows agents to directly pass control to each other.

**Characteristics:**

- Decentralized control flow
- Active agent changes during conversation
- Good for multi-domain conversations with specialists

**Example:** Router hands off to either a math agent or a translation agent based on the request.


## Setup

Configure `.env` before running. See `.env.sample`.



```python
import rich
from dotenv import load_dotenv

load_dotenv()
```




    True



## Define State and Agents

Create specialized agents that can hand off to each other using `Command`.



```python
from typing import Annotated

from langchain_anthropic import ChatAnthropic
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
from langgraph.types import Command
from pydantic import BaseModel


class State(BaseModel):
    """Shared state between agents."""

    messages: Annotated[list[BaseMessage], add_messages]
    current_agent: str = "router"


model = ChatAnthropic(model="claude-sonnet-4-5-20250929")
```

## Create Router Agent

The router determines which specialist should handle the request.



```python
def router_agent(state: State) -> Command[str]:
    """Route to the appropriate specialist based on the query."""
    messages = state.messages
    if not messages:
        return Command(goto=END)

    last_message = messages[-1]

    # If last message is from an agent (AIMessage), we're done
    if isinstance(last_message, AIMessage):
        return Command(goto=END)

    # Ask the model to decide which agent should handle this
    response = model.invoke(
        [
            {
                "role": "system",
                "content": """You are a router. Based on the user's message, decide which specialist should handle it.
Reply with ONLY one word: 'math', 'translate', or 'done'.

- math: Calculations, arithmetic, math problems
- translate: Translation between languages
- done: Conversation is complete or greeting/goodbye""",
            },
            {"role": "user", "content": str(last_message.content)},
        ]
    )

    decision = str(response.content).strip().lower()
    rich.print(f"[dim]Router decision: {decision}[/dim]")

    if "math" in decision:
        return Command(goto="math_agent", update={"current_agent": "math"})
    elif "translate" in decision:
        return Command(goto="translate_agent", update={"current_agent": "translate"})
    else:
        return Command(goto=END)
```

## Create Specialist Agents

Each specialist handles their domain and can hand back to the router.



```python
def _format_messages(messages: list[BaseMessage]) -> list[dict[str, str]]:
    """Format messages for model invocation."""
    return [
        {"role": "user" if isinstance(m, HumanMessage) else "assistant", "content": str(m.content)} for m in messages
    ]


def math_agent(state: State) -> Command[str]:
    """Handle math calculations."""
    response = model.invoke(
        [
            {
                "role": "system",
                "content": """You are a math specialist. Solve calculations and math problems.
Show your work step by step, then give the final answer.""",
            },
            *_format_messages(state.messages),
        ]
    )

    return Command(
        goto="router",
        update={"messages": [AIMessage(content=str(response.content))], "current_agent": "router"},
    )


def translate_agent(state: State) -> Command[str]:
    """Handle translations."""
    response = model.invoke(
        [
            {
                "role": "system",
                "content": """You are a translation specialist.
Translate text between languages. If the target language is not specified, translate to English.
Provide the translation directly.""",
            },
            *_format_messages(state.messages),
        ]
    )

    return Command(
        goto="router",
        update={"messages": [AIMessage(content=str(response.content))], "current_agent": "router"},
    )
```

## Build the Graph

Connect the agents in a graph structure.



```python
builder = StateGraph(State)

# Add nodes
builder.add_node("router", router_agent)
builder.add_node("math_agent", math_agent)
builder.add_node("translate_agent", translate_agent)

# Start with router
builder.add_edge(START, "router")

# Compile
graph = builder.compile()

rich.print("Graph compiled with nodes: router, math_agent, translate_agent")
```


    Graph compiled with nodes: router, math_agent, translate_agent



## Test: Math Question



```python
result = graph.invoke({"messages": [HumanMessage(content="What is 25 * 4 + 15?")]})  # type: ignore[arg-type]

rich.print("Final response:")
rich.print(result["messages"][-1].content)
```


    Router decision: math




    Final response:




    I'll solve this step by step, following the order of operations (PEMDAS/BODMAS).
    
    **Step 1:** First, perform the multiplication
    25 × 4 = 100
    
    **Step 2:** Then, perform the addition
    100 + 15 = 115
    
    **Final Answer:** 115



## Test: Translation Question



```python
query = "Translate 'Hello, how are you?' to Japanese"
result = graph.invoke({"messages": [HumanMessage(content=query)]})  # type: ignore[arg-type]

rich.print("Final response:")
rich.print(result["messages"][-1].content)
```


    Router decision: translate




    Final response:




    こんにちは、お元気ですか？



## Stream Execution

Stream to see the handoff flow between agents.



```python
for chunk in graph.stream({"messages": [HumanMessage(content="What is 100 divided by 8?")]}):  # type: ignore[arg-type]
    rich.print("chunk =", chunk)
```


    Router decision: math




    chunk =
    {'router': {'current_agent': 'math'}}




    chunk =
    {
        'math_agent': {
            'messages': [
                AIMessage(
                    content='I need to calculate 100 divided by 8.\n\n**Step-by-step calculation:**\n\n100 ÷ 8\n\nI can
    work this out by finding how many times 8 goes into 100:\n- 8 × 10 = 80\n- 8 × 12 = 96\n- 8 × 12.5 = 100\n\nSo 8 
    goes into 100 exactly 12.5 times.\n\nAlternatively, I can express this as:\n100 ÷ 8 = 100/8 = 25/2 = 12.5\n\nOr as 
    a mixed number:\n100 ÷ 8 = 12 remainder 4 = 12 4/8 = 12 1/2\n\n**Final answer: 12.5** (or 12½ or 25/2)',
                    additional_kwargs={},
                    response_metadata={},
                    id='a258a5eb-8e7d-4ca1-9eda-e50382249bbd'
                )
            ],
            'current_agent': 'router'
        }
    }




    chunk =
    {'router': None}


