Building production agents with LangGraph: state, checkpoints, and control
A practical walkthrough of modelling an agent as an explicit graph — nodes, conditional edges, shared state, and checkpoints that make sessions resumable and debuggable.
The earlier LangGraph post made the case for modelling an agent as a graph. This one is the hands-on version: how the nodes, edges, state, and checkpoints actually fit together, and why that structure is what makes an agent safe to run in production.
Model the agent as a graph
Nodes are steps (an LLM call, a tool executor, a bit of logic). Edges are transitions. A shared state object is threaded through every node, so each one reads and updates a common memory. You build the graph, then compile it.
from langgraph.graph import StateGraph, END
g = StateGraph(AgentState)
g.add_node('agent', call_model)
g.add_node('tools', run_tools)
g.set_entry_point('agent')
def route(state): # conditional edge
return 'tools' if state['tool_calls'] else END
g.add_conditional_edges('agent', route)
g.add_edge('tools', 'agent') # loop back to reason again
app = g.compile(checkpointer=saver) # resumableConditional edges are where you keep control
The router function inspects the state and decides the next node — continue to tools, or finish. This is exactly where you cap the loop (a step counter in state), branch on results, and stop a runaway agent. The control flow is explicit and testable, not buried in a free-form 'think then act' prompt.
Checkpoints make sessions resumable
A checkpointer persists the state after every node against a thread id. That single feature buys you a lot: a conversation can pause for human approval and resume, a crashed run can continue instead of restarting, and you can inspect the exact state at any step when debugging.
# each conversation is a thread; state is saved at every step
cfg = {'configurable': {'thread_id': user_id}}
app.invoke({'messages': [msg]}, cfg)
# crashed or paused? invoke again with the same thread_id — it resumesWhy this beats a free-form loop
- Explicit — you can see and reason about every path the agent can take.
- Testable — nodes and routing are ordinary functions you can unit-test.
- Observable — pair it with tracing (see the LangSmith post) and nothing is a black box.
- Resumable — checkpointing turns a fragile long-running loop into a durable one.
A production agent isn't a clever loop — it's an explicit graph with saved state, so you always know where it is and can pick up exactly where it left off.