LangGraph/Core Model
Intermediate14 min

State: The Agent's Brain

State is the contract between your nodes. Get the shape wrong — unbounded lists, unserializable objects, missing reducers — and checkpointing, streaming, and debugging all break at once.

Quick Reference

  • MessagesState covers 90% of agents — use it unless you need extra fields
  • TypedDict is the default for custom state — no overhead, works with all LangGraph features
  • Pydantic BaseModel validates on every state update — use it only when you need runtime coercion or field defaults
  • Reducers (Annotated[list, operator.add]) control how node outputs merge — no reducer means last writer wins
  • add_messages deduplicates messages by ID — it replaces a message if the ID already exists
  • RemoveMessage surgically prunes state; REMOVE_ALL_MESSAGES clears the entire messages list
  • Overwrite (from langgraph.types) bypasses a reducer at runtime — wrap the value, not the annotation
  • Unserializable objects (file handles, lambdas, locks) silently break checkpointing — keep state JSON-serializable

When State Design Matters (and When It Doesn't)

Most agents don't need custom state. MessagesState is one import and handles message deduplication, tool call chains, and serialization out of the box. Only reach for custom state when nodes need to share information that isn't a message — a counter, a plan, a list of retrieved documents, a user-specific config.

What does your graph need?Chat messages only?(no extra fields)YESMessagesState1 import, doneNONeed validation ordefault values?YESPydanticBaseModelvalidates on writeNOPrefer class syntaxover dicts?YES@dataclassno overheadNOTypedDict (default)fastest, works with everything

Start with MessagesState. Add TypedDict fields when you need more. Only reach for Pydantic when you need validation.

Default to MessagesState

If you're adding a custom state class with only a `messages` field, you've recreated MessagesState with extra steps. Start from MessagesState and extend it: ```python from langgraph.graph import MessagesState class AgentState(MessagesState): plan: str step_count: int ```

State design has a direct production cost: every super-step serializes the full state to your checkpoint store. A state with 500 messages and a 10 MB embedding cache gets written on every node transition. Keep state minimal and intentional.