Start with the process you want to automate
Imagine that you need to build an AI agent that handles customer support emails. Your product team has given you these requirements:Step 1: Map out your workflow as discrete steps
Start by identifying the distinct steps in your process. Each step becomes a node (a function that does one specific thing). Then sketch how these steps connect. Do this before writing code.Command.
Now that the components are identified, clarify what each node needs to do:
Read Email: Extract and parse the email contentClassify Intent: Use an LLM to categorize urgency and topic, then route to search, bug tracking, or draftDoc Search: Query your knowledge base for relevant informationBug Track: Create or update an issue in a tracking systemDraft Reply: Generate an appropriate response, then route to human review or sendHuman Review: Escalate to a human agent for approval or handlingSend Reply: Dispatch the email response
Step 2: Identify what each step needs to do
For each node from Step 1, decide what kind of work it is and what context it needs. Use the categories below, then expand the email-agent examples for concrete fields and retry choices.LLM steps
Use when you need to understand, analyze, generate text, or make reasoning decisions
Data steps
Use when you need to retrieve information from external sources
Action steps
Use when you need to perform external actions
User input steps
Use when you need human intervention
LLM steps
When a step needs to understand, analyze, generate text, or make reasoning decisions:Classify intent
Classify intent
- Static context (prompt): Classification categories, urgency definitions, response format
- Dynamic context (from state): Email content, sender information
- Desired outcome: Structured classification that determines routing
Draft reply
Draft reply
- Static context (prompt): Tone guidelines, company policies, response templates
- Dynamic context (from state): Classification results, search results
- Desired outcome: Professional email response ready for review
Data steps
When a step needs to retrieve information from external sources:Document search
Document search
- Parameters: Query built from intent and topic
- Retry strategy: Yes, with exponential backoff for transient failures
- Caching: Could cache common queries to reduce API calls
Action steps
When a step needs to perform an external action:Send reply
Send reply
- When to execute: After approval (human or automated)
- Retry strategy: Yes, with exponential backoff for network issues
- Should not cache: Each send is a unique action
Bug track
Bug track
- When to execute: Always when intent is “bug”
- Retry strategy: Yes, critical to not lose bug reports
- Returns: Ticket ID to include in the response
User input steps
When a step needs human intervention:Human review
Human review
- Context for decision: Original email, draft response, urgency, classification
- Expected input format: Approval boolean plus optional edited response
- When triggered: High urgency, billing, or complex issues
- On rejection: End the run without sending (human handles the email outside the graph)
Step 3: Design your state
State is the shared memory accessible to all nodes in your agent. Think of it as the notebook your agent uses to keep track of everything it learns and decides as it works through the process.What belongs in state?
Ask yourself these questions about each piece of data:Include in state
Does it need to persist across steps? If yes, it goes in state.
Do not store
Can you derive it from other data? If yes, compute it when needed instead of storing it in state.
- The original email and sender info (cannot reconstruct these later)
- Classification results (needed by later nodes)
- Search results or ticket context (expensive to re-fetch)
- The draft response (needs to persist through review)
- Execution metadata (for debugging and recovery)
Keep state raw, format prompts on demand
That separation means:- Different nodes can format the same data differently
- You can change prompt templates without changing the state schema
- Debugging shows the data each node received, not a baked-in prompt string
Step 4: Build your nodes
A node is a function that takes the current state and returns updates to it. When the node also chooses the next step, return aCommand.
Handle errors appropriately
Before you fill in node bodies, consider how each node can fail. Attach retries on nodes that call external APIs. Useinterrupt() for human input. Use this table as a guide:
For retry policies, timeouts, compensation handlers, LLM-recoverable loops, when to let errors bubble up, and graph-wide defaults, see Fault tolerance.
For this example, attach a retry policy when you add a node that can hit transient failures:
Implementing the email agent nodes
Turn each unit of work into a node. Each node reads the shared state, does one piece of work, and returns updates. When the next step depends on the result, return aCommand with both the update and the destination. Human review is still a node: it calls interrupt() inside the function, not a separate graph primitive.
The stubs below show the signatures, state updates, and routing for the email agent. Replace the placeholder bodies with your real LLM calls and API integrations. Put these nodes in the same file as the state from Step 3.
read_email is a passthrough in this walkthrough. Keeping it as its own node still gives you a checkpoint before classification and a place to add real MIME parsing later.
Step 5: Wire it together
Connect the nodes into a graph. Because routing lives insideCommand nodes, you only need a few fixed edges. Compile with a checkpointer, which is required for interrupt() to pause and resume.
Command, pass ends to addNode so LangGraph knows the valid destinations.
Try out your agent
After the graph is compiled, invoke it with a sample email. With the placeholder classifier above, this urgent billing message is treated as high urgency, so the run drafts a reply, hitsinterrupt(), and you can practice resume. Use the same thread_id for the first invoke and the resume invoke.
interrupt(), saves state to the checkpointer, and waits. When you resume with Command(resume=...), the human_review node re-runs from the start, interrupt() returns your resume value, and the graph continues. The thread_id keeps this conversation’s state together.
Summary and next steps
Key insights
Building this email agent shows the Graph API way of thinking:Break into discrete steps
Each node does one thing well. Node boundaries are where the graph checkpoints, so failures and resumes redo less work.
State is shared memory
Store raw data, not formatted text. Different nodes can use the same information in different ways.
Nodes are functions
They take state, do work, and return updates. When they route, they return both the update and the next destination with
Command.Errors are part of the flow
Transient failures get node retry policies, user-fixable problems pause with
interrupt(), and unexpected errors fail the run.Human input is first-class
interrupt() pauses execution and saves state. On resume, the node re-runs from the start, so keep work before interrupt() idempotent.Graph structure stays minimal
You declare essential edges. Nodes that need dynamic routing return
Command, which keeps control flow explicit and traceable.Node granularity trade-offs
Most applications can use the breakdown above. Read this section when you are deciding whether to merge or split nodes. LangGraph checkpoints at node boundaries. When a run resumes after an interrupt or failure, it restarts from the beginning of the node where execution stopped. Smaller nodes mean more frequent checkpoints and less work to repeat. Why this breakdown fits the email agent:- Isolation of external services: Doc Search and Bug Track are separate nodes so retries stay scoped to those API calls.
- Visibility: Classification is its own node, so you can inspect the structured result before the next action.
- Different failure modes: LLM calls, lookups, and email sends get different retry policies.
- Testing: Smaller nodes are easier to unit test and reuse.
Read Email and Classify Intent into one node is valid. You lose a checkpoint between them and repeat both on failure. For most agents, the observability benefit of separate nodes is worth it.
Caching search results is an application-level choice inside your node functions. LangGraph does not prescribe a cache API for that. More nodes do not necessarily mean slower runs: by default, checkpoints write in the background (async durability mode).
Where to go from here
This walkthrough introduces Graph API design. Extend it with the how-tos and related guides:Build checklist
Decision guide for state, control flow, Send, Command, and nodes
Define and update state
Schemas, reducers, messages, and private channels
Control flow
Sequences, branches, loops, and recursion limits
Send and Command
Map-reduce fan-out and update-and-route in one step
Configure nodes
Retries, timeouts, runtime context, and caching
Thinking in the Functional API
The same email agent with entrypoints and tasks
Human-in-the-loop patterns
Tool approval, batch approval, and other interrupt patterns
Add memory
Short-term checkpointers and long-term stores
Fault tolerance
Retries, timeouts, error handlers, and graph defaults
Subgraphs
Nested graphs for complex multi-step operations
Streaming
Real-time progress updates for users
Connect these docs to your agent of choice via MCP for real-time answers.

