| name | langchain-multi-agent |
| description | Design and implement LangChain multi-agent systems using subagents, handoffs, skills, and router patterns. Use when building supervisor/worker agents, state-driven handoffs between agents, on-demand skill loading, or routing pipelines. Covers pattern selection, context engineering across agents, sync/async execution, and tool-based coordination. |
Documentation Index
Fetch the complete documentation index at: https://docs.langchain.com/llms.txt
Use this file to discover all available pages before exploring further.
LangChain Multi-Agent Patterns
First ask: Does this really need multiple agents? A single agent with dynamic tools and a good prompt often achieves the same result with less complexity.
Use multi-agent when: Too many tools overwhelm the model, tasks need specialized context, or you need parallel execution or sequential constraints.
Pattern Selection
| Pattern | Best for | Avoid when |
|---|
| Subagents | Parallel execution, large-context domains, centralized control | Simple single-domain tasks |
| Handoffs | Sequential constraints, direct user interaction across states | Parallel tasks |
| Skills | Single agent + many specializations, team distribution | Need strict isolation |
| Router | Stateless classification + parallel dispatch | Repeat requests (re-routes every time) |
Performance at a glance (model calls per request):
| One-shot | Repeat | Multi-domain |
|---|
| Subagents | 4 | 4+4 | 5, ~9K tokens |
| Handoffs | 3 | 3+2 ✅ | 7+, ~14K tokens |
| Skills | 3 | 3+2 ✅ | 3, ~15K tokens |
| Router | 3 | 3+3 | 5, ~9K tokens |
Subagents
A main agent (supervisor) calls subagents as tools. Subagents are stateless — clean context window per invocation.
Basic pattern
from langchain.tools import tool
from langchain.agents import create_agent
subagent = create_agent(model="...", tools=[...])
@tool("research", description="Research a topic and return findings")
def call_research_agent(query: str) -> str:
result = subagent.invoke({"messages": [{"role": "user", "content": query}]})
return result["messages"][-1].content
main_agent = create_agent(model="...", tools=[call_research_agent])
Single dispatch tool (for many/dynamic agents)
One parameterized tool + agent registry. Agents developed by different teams.
from enum import Enum
class AgentName(str, Enum):
RESEARCH = "research"
WRITER = "writer"
SUBAGENTS = {"research": research_agent, "writer": writer_agent}
@tool
def task(agent_name: AgentName, description: str) -> str:
"""Launch an ephemeral subagent for a task."""
result = SUBAGENTS[agent_name].invoke({"messages": [{"role": "user", "content": description}]})
return result["messages"][-1].content
For large/dynamic registries, add a list_agents discovery tool instead of enum.
Async subagents (long-running tasks)
Three-tool pattern when the user shouldn't wait:
@tool
def start_job(agent_name: str, task: str) -> str:
"""Start a background job, returns job_id."""
job_id = job_system.submit(agent_name, task)
return f"Started job {job_id}"
@tool
def check_status(job_id: str) -> str:
"""Check job status: pending/running/completed/failed."""
return job_system.status(job_id)
@tool
def get_result(job_id: str) -> str:
"""Get completed job result."""
return job_system.result(job_id)
Context engineering for subagents
Inputs — customize what the subagent receives:
@tool("subagent", description="...")
def call_subagent(query: str, runtime: ToolRuntime[None, CustomState]) -> str:
subagent_input = transform(query, runtime.state["messages"])
result = subagent.invoke({"messages": subagent_input})
return result["messages"][-1].content
Outputs — pass state back to supervisor via Command:
from langchain.tools import InjectedToolCallId
from langgraph.types import Command
@tool("subagent", description="...")
def call_subagent(query: str, tool_call_id: Annotated[str, InjectedToolCallId]) -> Command:
result = subagent.invoke({"messages": [{"role": "user", "content": query}]})
return Command(update={
"some_state_key": result["some_state_key"],
"messages": [ToolMessage(content=result["messages"][-1].content, tool_call_id=tool_call_id)]
})
Handoffs
Behavior changes dynamically based on a state variable. Tools update state to trigger transitions.
Single agent with middleware (preferred)
from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from langchain.tools import tool, ToolRuntime
from langchain.messages import ToolMessage
from langgraph.types import Command
from typing import Callable
class SupportState(AgentState):
current_step: str = "triage"
warranty_status: str | None = None
@tool
def record_warranty_status(status: str, runtime: ToolRuntime[None, SupportState]) -> Command:
"""Record warranty status and move to specialist step."""
return Command(update={
"messages": [ToolMessage(content=f"Status: {status}", tool_call_id=runtime.tool_call_id)],
"warranty_status": status,
"current_step": "specialist",
})
@wrap_model_call
def apply_step_config(request: ModelRequest, handler: Callable) -> ModelResponse:
step = request.state.get("current_step", "triage")
configs = {
"triage": {"prompt": "Collect warranty info.", "tools": [record_warranty_status]},
"specialist": {"prompt": "Resolve issue. Warranty: {warranty_status}", "tools": [provide_solution]},
}
cfg = configs[step]
request = request.override(
system_prompt=cfg["prompt"].format(**request.state),
tools=cfg["tools"],
)
return handler(request)
agent = create_agent(
model, tools=[...], state_schema=SupportState,
middleware=[apply_step_config], checkpointer=InMemorySaver()
)
Multiple agent subgraphs
Use only when agents need bespoke graph implementations. Handoff tools use Command.PARENT.
Critical: always pass both the AIMessage (tool call) AND a ToolMessage (response) or the receiving agent sees malformed history.
@tool
def transfer_to_sales(runtime: ToolRuntime) -> Command:
"""Transfer to the sales agent."""
last_ai_msg = next(m for m in reversed(runtime.state["messages"]) if isinstance(m, AIMessage))
return Command(
goto="sales_agent",
update={
"active_agent": "sales_agent",
"messages": [last_ai_msg, ToolMessage(content="Transferred", tool_call_id=runtime.tool_call_id)],
},
graph=Command.PARENT,
)
Routing after each agent node: end when last message is AIMessage with no tool calls.
def route_after_agent(state) -> Literal["sales_agent", "support_agent", "__end__"]:
last = state["messages"][-1]
if isinstance(last, AIMessage) and not last.tool_calls:
return "__end__"
return state.get("active_agent", "sales_agent")
Skills
A single agent loads specialized prompts on-demand. Lighter than subagents — no separate context window.
from langchain.tools import tool
from langchain.agents import create_agent
SKILLS = {
"write_sql": "You are a SQL expert. Write efficient, safe queries...",
"review_legal": "You are a legal document reviewer. Check for...",
}
@tool
def load_skill(skill_name: str) -> str:
"""Load a specialized skill.
Available: write_sql, review_legal
"""
return SKILLS[skill_name]
agent = create_agent(
model="gpt-5.4",
tools=[load_skill],
system_prompt="You have access to skills: write_sql, review_legal. Use load_skill before specialized tasks.",
)
Extensions:
- Dynamic tool registration: loading a skill also registers additional tools via state update
- Hierarchical skills: a skill's prompt lists sub-skills the agent can load next
- Reference awareness: skill prompts point to file paths the agent should read when relevant
Key Rules
- Subagent stateless by design — main agent holds all conversation memory; subagents start fresh each call
- Always pair AIMessage + ToolMessage in subgraph handoffs or conversation history breaks
- Prefer single-agent middleware for handoffs; only use subgraph handoffs for complex bespoke agents
- Skills accumulate context — token cost grows with loaded skills; use subagents for large-context isolation
- Patterns are composable — a subagents supervisor can use the skills pattern internally; a router can dispatch to handoffs agents
Command.PARENT in tools navigates the parent graph, not the subagent's own graph
Related Resources
- Context engineering:
/oss/python/langchain/context-engineering
- Middleware:
/oss/python/langchain/middleware
- Deep Agents (built-in multi-agent):
/oss/python/deepagents/overview
- Interrupts / human-in-the-loop:
/oss/python/langgraph/interrupts
- Subgraph persistence:
/oss/python/langgraph/use-subgraphs#subgraph-persistence