Agents are state machines, and state needs a shape
A single LLM call is stateless. A useful agent is not — it loops, calls tools, waits for a human, and resumes later. LangGraph models that as a graph: nodes do work, edges decide what runs next, and a shared state object flows through. The thing that makes it production-grade is that every transition is checkpointed — serialized and saved — so an agent can pause, survive a crash, and pick up exactly where it left off.
Understanding the JSON underneath that state is what lets you debug agents instead of staring at them.
The typed state schema
State in LangGraph is a typed schema (a TypedDict, dataclass, or Pydantic model) that every node reads and updates. A minimal example:
from typing import Annotated, TypedDict
from operator import add
class AgentState(TypedDict):
messages: Annotated[list, add] # reducer: new messages append, not overwrite
step: int
done: boolThe Annotated[..., add] reducer matters: when a node returns {"messages": [new]}, LangGraph *appends* rather than replaces. Conceptually the state at any checkpoint is just JSON:
{
"messages": [
{ "role": "user", "content": "Book a table for two." },
{ "role": "assistant", "tool_calls": [{ "name": "find_restaurant", "args": { "seats": 2 } }] }
],
"step": 2,
"done": false
}Checkpoints: a recovery point, not a log entry
A checkpoint is a snapshot of state after a transition, written through a checkpointer (saver). LangGraph ships several: MemorySaver for development, and SQLite or Postgres savers for production. Because each transition is saved, you get for free what most frameworks bolt on later:
- Pause / resume — stop for human approval, resume days later from the stored state.
- Time-travel debugging — replay from any past checkpoint to see how the agent reached a decision.
- Failure recovery — a crashed or timed-out run restarts from the last good checkpoint instead of the beginning.
- Horizontal scaling — multiple workers share one checkpoint store, so any instance can continue any thread.
The serializer: JsonPlusSerializer
State often contains things plain JSON can't hold — datetimes, enums, and framework message objects. LangGraph's default JsonPlusSerializer handles this: it serializes with ormsgpack for speed and falls back to an extended JSON format that knows how to encode LangChain/LangGraph types, datetimes, and enums, then reconstruct them on load. So the on-disk form is compact binary, but the mental model — and what you inspect when debugging — is JSON.
Debugging agent state
When an agent misbehaves, the answer is almost always in a checkpoint. A practical loop:
- Dump the state at the failing checkpoint and format it — nested message histories are unreadable until pretty-printed.
- Diff two consecutive checkpoints to see exactly what a node changed — often a reducer appended where you expected a replace, or a tool wrote the wrong field.
- Lock your state shape with a JSON Schema so a node that adds an unexpected key gets caught in review, not in production.
- For long message arrays, explore the tree to navigate turns without losing your place.
Production checklist worth internalizing: a typed StateGraph schema, Postgres checkpointing (not MemorySaver), per-node tracing, and recursion limits so a loop can't run away. For related agent-state patterns see Multi-Agent JSON State and AI Agent Memory in JSON.