Agent Architecture/System Design
Advanced18 min

State Design Patterns

How to structure LangGraph state for parallel execution, API safety, and long-running workflows — including schema separation, reducers, the Command API, state explosion mitigation, checkpointing, and debugging with time travel.

Quick Reference

  • Use MessagesState for simple chatbots — only build custom state when you have parallel nodes, private fields, or API-facing schemas
  • Any list field written by parallel nodes needs an Annotated reducer — without one, last-write-wins silently drops data
  • input_schema / output_schema are the only LangGraph-enforced way to separate public from internal state — the _ prefix convention enforces nothing
  • State explosion from unbounded list growth is the #1 production state bug — trim messages before you need to, not after
  • LangGraph has no built-in schema versioning — add new fields with defaults first, then make them required in a separate deploy
  • get_state_history() + invoke with checkpoint_id gives you deterministic replay from any prior checkpoint
  • Command(update={...}, goto='node') combines state mutation and routing in one return — use it instead of conditional edges when routing logic lives inside the node

When State Complexity Is Worth It

Most agents don't need custom state. If your graph has a single entry point, a single exit, and no parallel branches, MessagesState is almost always enough. The question to ask before adding state complexity is: what can't MessagesState do that I actually need?

ScenarioUse MessagesStateUse custom state
Chatbot / Q&A✓ — messages list is sufficient
Parallel sub-tasks accumulating results✓ — reducer needed to merge concurrent writes
API-exposed graph with internal bookkeeping✓ — input/output schema keeps private fields from leaking
Long-running workflow with retry tracking✓ — private retry counter, RemainingSteps
Multi-turn workflow with resume-on-failurePartial — messages checkpoint, but no custom recovery logic✓ — full checkpoint + state-based recovery
Start with MessagesState

from langgraph.graph import MessagesState class State(MessagesState): pass # add fields only when you need them MessagesState gives you messages with the add_messages reducer already wired. Extend it with class State(MessagesState): when you need additional fields.