LangGraph Agents

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_messages

Right when the transcript already holds everything: a chat assistant or a simple tool loop.

Rich state

Python
import operatorfrom typing import Annotated, Literal, TypedDictfrom langgraph.graph.message import add_messagesclass RefundState(TypedDict):    messages: Annotated[list, add_messages]          # conversation    order_id: str                                    # input    order: dict                                      # artifact: looked-up order    amount_inr: int                                  # artifact    route: Literal["auto", "needs_approval", "reject"]  # decision    approved_by: str | None                          # audit    attempts: int                                    # loop budget    error: str | None                                # recovery signal    audit: Annotated[list[str], operator.add]        # append-only log

Each 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
from dataclasses import dataclass@dataclassclass Ctx:    user_id: str    tenant: strbuilder = StateGraph(RefundState, context_schema=Ctx)graph.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?" — TypedDict by 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.