Why Multi-Agent Systems?
Single-agent LLM systems hit a hard ceiling. Complex tasks require more context than fits in a single prompt, benefit from specialized sub-agents with different tools and instructions, and need checkpointing so long-running workflows can resume after failures. Multi-agent architectures address all three problems.
LangGraph is the best framework for building these systems. It models your agent workflow as a directed graph — nodes are LLM calls or tool invocations, edges define transitions, and a central state object flows between nodes. This makes complex orchestration explicit and debuggable rather than buried in nested function calls.
Core LangGraph Concepts
State Schema
Define your state schema first. Everything agents read and write lives here. Use TypedDict for type safety.
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator
class AgentState(TypedDict):
messages: Annotated[list, operator.add]
current_task: str
research_results: list[str]
draft: str
review_passed: bool
Nodes and Edges
Each node is a Python function that takes state and returns a partial state update. Conditional edges route execution based on state values.
def research_node(state: AgentState) -> AgentState:
results = search_tool.run(state["current_task"])
return {"research_results": [results]}
def writer_node(state: AgentState) -> AgentState:
prompt = f"Write based on: {state['research_results']}"
draft = llm.invoke(prompt).content
return {"draft": draft}
def should_revise(state: AgentState) -> str:
if state["review_passed"]:
return "end"
return "writer"
A Real-World Example: Research and Writing Agent
workflow = StateGraph(AgentState)
workflow.add_node("researcher", research_node)
workflow.add_node("writer", writer_node)
workflow.add_node("reviewer", reviewer_node)
workflow.set_entry_point("researcher")
workflow.add_edge("researcher", "writer")
workflow.add_edge("writer", "reviewer")
workflow.add_conditional_edges(
"reviewer",
should_revise,
{"writer": "writer", "end": END}
)
app = workflow.compile(checkpointer=MemorySaver())
The checkpointer parameter enables persistence. If the workflow fails mid-run, it resumes from the last checkpoint rather than starting over — critical for long-running tasks that make expensive API calls.
Supervisor Pattern for Complex Orchestration
The most powerful multi-agent pattern is the supervisor: one orchestrator agent that decides which specialist agent to invoke next, based on the task and current state.
def supervisor_node(state: AgentState) -> AgentState:
system = """You are a supervisor managing: researcher, coder, reviewer.
Based on the conversation, decide which agent should act next.
Respond with ONLY the agent name or FINISH."""
response = llm.invoke([SystemMessage(content=system)] + state["messages"])
return {"next_agent": response.content.strip()}
Each specialist has its own system prompt, toolset, and responsibility boundary. The supervisor coordinates without micromanaging — it decides the "who" and "when" but not the "how".
Tool Design for Agents
Tools are what make agents useful. Define them with clear docstrings — the model reads these to decide when and how to use each tool.
from langchain_core.tools import tool
@tool
def search_knowledge_base(query: str) -> str:
"""Search the internal knowledge base for relevant information.
Use this when you need factual information about our products or policies."""
return retriever.invoke(query)
@tool
def create_ticket(title: str, description: str, priority: str) -> str:
"""Create a support ticket in the ticketing system.
Priority must be: low, medium, high, or critical."""
return ticket_api.create(title, description, priority)
Debugging Multi-Agent Workflows
Complex agent graphs fail in non-obvious ways. Use LangSmith tracing to visualize every node execution and the state at each step. Without this, debugging agent loops feels like reading tea leaves.
- Enable LangSmith: set
LANGCHAIN_TRACING_V2=trueandLANGCHAIN_API_KEY - Add a maximum iteration limit to prevent infinite loops
- Log the state at entry and exit of each node during development
- Test each node in isolation before testing the full graph
- Use deterministic seed values and temperature=0 in tests for reproducible runs
Multi-agent systems are worth the complexity overhead for tasks that genuinely require specialization and long context. Start with a single-agent system and only add agents when you hit a concrete limitation — not because the architecture sounds impressive.