Course Content
LangGraph Agents
7 sections · 49 lessons
How do you design a state schema for a production agent (minimal vs rich state)?
What you need to know
Minimal state
Python
from langgraph.graph import MessagesState # just: messages with add_messagesRight when the transcript already holds everything: a chat assistant or a simple tool loop.
Rich state
Python
1import operator2from typing import Annotated, Literal, TypedDict3from langgraph.graph.message import add_messages45class RefundState(TypedDict):6 messages: Annotated[list, add_messages] # conversation7 order_id: str # input8 order: dict # artifact: looked-up order9 amount_inr: int # artifact10 route: Literal["auto", "needs_approval", "reject"] # decision11 approved_by: str | None # audit12 attempts: int # loop budget13 error: str | None # recovery signal14 audit: Annotated[list[str], operator.add] # append-only logEach key has a reader. route is read by an edge; audit is read by compliance; order by the refund node.
Rules for a healthy schema
- Every key has a reader. If nothing reads it, delete it.
- Store references, not payloads. A 4 MB PDF becomes
document_ids: list[str]. - Nothing that cannot be serialised. Database connections, HTTP clients, open files: pass them in the runtime context.
- No secrets or tokens. State is written to the checkpoint table and often shows up in traces.
- Typed decisions.
Literal[...]for routes makes a typo a type error, not a silent wrong branch. - Plan for change. Checkpoints from last month's schema will be loaded by this month's code; add new keys as optional and avoid renaming keys in place.
Where non-state values go
Python
1from dataclasses import dataclass23@dataclass4class Ctx:5 user_id: str6 tenant: str78builder = StateGraph(RefundState, context_schema=Ctx)9graph.invoke(inputs, config, context=Ctx(user_id="u-17", tenant="in"))Nodes read it with a runtime: Runtime[Ctx] parameter.
A real-life example
A refund agent's first schema had only messages. The approval edge had to ask the model "was this approved?" by reading the last few messages, and one day it read a customer's line "my manager approved this" as an approval. The rich schema above fixed it: the approval node writes approved_by="ops-priya" from the reviewer's real reply, and the edge checks that field. The audit log now answers "who approved refund OD-88123 and when" from state history alone.
Follow-up questions to expect
- "How do you evolve a schema with live threads?" — Add keys as optional with defaults, read them with
.get(), and never change the meaning of an existing key; old checkpoints must still load. - "Pydantic or TypedDict for state?" —
TypedDictby default for speed; Pydantic when runtime validation of writes is worth the cost. - "Where does the user id go?" — The runtime context, so it is available to every node but not written into checkpoints as data a model could alter.