LangGraph โ Orchestration & State
Lesson 4: LangGraph โ Orchestration & State
LangGraph is the orchestration engine of the universe: a library for building LLM applications as explicit graphs โ nodes connected by edges, with a shared state object flowing through them. It's what you reach for when an app is more than a linear chain: agents, multi-step workflows, human-in-the-loop, and anything that must survive interruptions.
Key idea: LangGraph turns "agent" from a vague concept into a precise artifact: a directed graph where each node is a step (call a model, call a tool, ask a human) and each edge says what happens next โ possibly based on the model's output. The graph is the program, and it's inspectable, replayable, and resumable.
Core concepts
| Concept | What it is |
|---|---|
StateGraph | The graph container; you define a state schema and add nodes/edges |
| State | A typed dict (or object) that every node reads and updates; the app's memory within a run |
| Nodes | Python functions or async functions that receive state and return updates |
| Edges | Declare which node runs after which; START and END are special pseudo-nodes |
| Conditional edges | Edges that pick the next node at runtime (e.g., "if the model says it needs a tool, go to the tool node") |
| Checkpointer | Persistence layer that saves state after each super-step; enables memory, replay, and time travel |
A minimal agent loop
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.checkpoint.memory import MemorySaver
class State(TypedDict):
messages: Annotated[list, add_messages] # accumulate, don't overwrite
def chatbot(state: State) -> dict:
return {"messages": [model.invoke(state["messages"])]}
graph = StateGraph(State)
graph.add_node("chatbot", chatbot)
graph.add_edge(START, "chatbot")
graph.add_edge("chatbot", END)
app = graph.compile(checkpointer=MemorySaver())
# thread_id = conversation memory key
config = {"configurable": {"thread_id": "user-42"}}
result = app.invoke({"messages": [{"role": "user", "content": "Hi!"}]}, config)
Notice the checkpointer: with MemorySaver (in-memory) or SqliteSaver/Postgres (durable), the graph remembers every run keyed by thread_id. That single mechanism gives you conversation memory, crash recovery, and the ability to branch a past conversation.
What else the graph gives you
- Human-in-the-loop โ
interrupt()pauses the graph for human input; aCommand(resume=...)continues it. Perfect for approval gates (e.g., "review this email before sending"). - Time travel โ
get_state()/update_state()let you inspect or fork any past checkpoint, then replay from there. - Streaming โ modes for
values(full state),updates(per-node diffs), andmessages(token-level) so UIs can render live. - Subgraphs โ a compiled graph can be a node inside another graph, enabling multi-agent hierarchies.
- Durable execution โ with a real checkpointer (SQLite/Postgres), a graph survives process restarts and picks up where it left off.
Pitfall: State updates matter. If your state field is a plain list, each node overwrites it unless you use a reducer like
add_messages (the Annotated annotation above). The reducer tells LangGraph how to merge old and new values โ forgetting it is the #1 "my agent loses context" bug.
Rule of thumb: Any app where a decision depends on the model's previous output โ tool-calling agents, multi-step research, approval workflows โ is a graph, not a chain.
๐ง Knowledge Check
1. What is a checkpointer in LangGraph?
2. How does the graph decide between "call the model again" and "end"?
3. What does interrupt() enable?