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

ConceptWhat it is
StateGraphThe graph container; you define a state schema and add nodes/edges
StateA typed dict (or object) that every node reads and updates; the app's memory within a run
NodesPython functions or async functions that receive state and return updates
EdgesDeclare which node runs after which; START and END are special pseudo-nodes
Conditional edgesEdges that pick the next node at runtime (e.g., "if the model says it needs a tool, go to the tool node")
CheckpointerPersistence 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.

AGENT LOOP AS A GRAPH START model node calls the LLM tools node executes tool calls conditional: needs tool? checkpointer saves state every step

What else the graph gives you

  • Human-in-the-loop โ€” interrupt() pauses the graph for human input; a Command(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), and messages (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?

Further Reading