How a LangGraph agent runs, seen from the program that calls it — one invoke per turn on a thread, the nodes updating the thread's state, a version saved by the checkpointer after every step, and a call that returns at END or at an interrupt; with a resume after the user's answer, a reply regenerated from an older version, the history read to explain it, and two threads running at once.
A LangGraph agent is a graph that runs only while our program calls it.
- The chat server, our program behind the chat window, calls the graph once per turn:
graph.invoke(input, config).input: what is new, such as the user's message, or the answer to an interrupt.config: the thread_id, one per conversation, and optionally a checkpoint_id, an older saved state to
start from.
- LangGraph runs the nodes on the thread's state: the messages, and the node due next.
- The checkpointer saves a version of the state after every step, in a database.
- The call returns when no node is due: at END, or at an interrupt.
- Between calls no graph runs. The thread's state lives in the checkpointer alone, and the next call loads
it.
This page is for the programmer writing that app: it uses LangGraph's names, and no code.
Reading the drawing
- Users, top left: one chat per user, each with the thread the chat server keeps for it.
- The chat server: what it does on each event, and every call it made to the graph, with its arguments.
- An open call is a bar with a moving edge; it closes with the reason it returned.
- A call blocks until its run stops, however many nodes run inside it.
- LangGraph: the graph, and the state of each thread whose call is running, held in memory.
- A running call lights the node it is at, in its thread's colour.
- Between calls the state is empty: it is only in the checkpointer.
- Services the agent calls: the model, the forecast service, and the reminders the tools write to.
- The checkpointer, bottom: one row of versions per thread, each linked to its parent.
- The latest version is filled; the label under a version is the node due next.
- A version off the thread's current branch sits above the row.
- A click on a version opens it beside the rows.
The agent is the one from the
Simple Loop AI Agent
, with one rule
added: a node, ask_user, that asks the user before add_reminder.
The reasons a call returns
A call returns when no node is due to run next.
- END: an edge leads to END. In an agent, that is a reply with no tool call: the end of the turn.
- An interrupt: a node calls
interrupt().- The call returns with the interrupt's value, and the thread waits.
- A later call resumes it with an answer; the node then runs again from its first line.
- An error: a node raises. The versions saved so far stay, and a later call can resume from the last one.
- The step limit: a run that loops past its recursion limit is stopped.
A slow tool is not a reason to return. The call stays open while the tool waits; with the async API the wait
is an await, and our process serves other threads meanwhile. A wait of hours belongs in an interrupt, not in
an open call.
interrupt(): a node split in two
A node that calls interrupt() works like two nodes: the first half ends the call, as END does, and the
next call starts at the second half.
- The first call:
interrupt(value) raises an exception, LangGraph's GraphInterrupt.- The node stops at that line; the code after it does not run.
- LangGraph catches the exception and saves the interrupt with the thread's latest version, whose next node
is this one.
- The call returns, with
value for our server to show the user.
- The resume: the next call brings the answer, and LangGraph runs the node again from its first line.
- This time
interrupt() finds the answer, and returns it instead of raising. - The code after it runs, and the graph goes on.
- Nothing of the node is kept between the two calls: no paused Python frame, no suspended generator or
coroutine.
- The resume may come days later, from another server process.
- Only data in the checkpointer lasts that long.
The picture of two nodes breaks in one place: the code before interrupt() runs twice, once in each call.
- Put
interrupt() at the start of its node, - or keep the code before it safe to repeat: no payment, no message sent,
- or split the node into two real nodes, which makes the picture true.
A node may call interrupt() more than once; on resume, LangGraph matches the answers to the calls by their
order in the node. In the viewer, ask_user does nothing before its interrupt(), so there the picture and
the mechanism agree.
What the checkpointer keeps
Every run reads only the thread's latest version: the next turn, a resume, a recovery after a crash.
The older versions are kept for people:
- going back: a call can name an older version instead of the latest, as a Regenerate button does;
- LangGraph writes a copy of that version, whose parent is the version named;
- the run continues from the copy, and the thread forks: the old branch stays in the history;
- debugging and audit: an older version is the state a node read, so it shows why the node did what it did.
What the viewer draws follows langgraph 1.2.11:
- A version is saved after every node that runs.
- A call with an input saves two versions before the first node: the input, then the start node's write. The
viewer draws them as one.
- An interrupt saves no version. The version before it records the interrupt.
- Going back never undoes the world: a reminder added on the old branch stays added.
- The
durability setting of a call picks when versions are written: in the background while the next step
runs (the default), before the next step starts, or only when the call returns.
Other frameworks
The run model carries over to the other agent frameworks; the saved versions are LangGraph's own.
- Shared: an app calls a run, the run returns with a reason, a thread or session keeps the conversation
between runs, and a question to the user pauses a run until a later call resumes it.
- LangGraph's: the graph of nodes over one state, a version after every step, and a fork from any of
them.
In the langgraph reference