Whetstone.
Persistence, Threads, and Time TravelCheckpointers and threads
Module 4, Lesson 120 min

Checkpointers and threads

Ark’s continuity is hand-rolled: session.json, a writeback hook, an incident log. LangGraph ships this as first-class primitives. This module puts a Postgres checkpointer under the module 3 dice graph, kills the process mid-run, resumes, and walks the checkpoint lineage.

A checkpointer persists graph state after every step. Compile the graph with one and every node transition writes a snapshot. If the process dies, the thread resumes from the last snapshot instead of the start.

Compiling a graph with a Postgres checkpointer
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";

const checkpointer = PostgresSaver.fromConnString("postgresql://…");
await checkpointer.setup(); // creates the tables, run once

const graph = builder.compile({ checkpointer });

For dev, MemorySaver from @langchain/langgraph is the in-memory equivalent, gone when the process exits. The Postgres and SQLite savers live in their own packages: @langchain/langgraph-checkpoint-postgres and -sqlite.

A thread is a conversation’s identity. You pass a thread_id in config; all checkpoints for that thread are its history. Same thread_id resumes; a new thread_id starts fresh.

Resuming a thread after a process restart
const config = { configurable: { thread_id: "impulse-2026-07-04" } };
await graph.invoke(inputs, config);
// process dies here…
// …new process, same config:
await graph.invoke(null, config); // resumes from last checkpoint
Practice

Try it yourself

Recall

What thread_id identifies

One value decides this, and it is passed at call time rather than configured on the graph.

What determines resume vs fresh start when you call graph.invoke?

Reveal answer

The thread_id passed in config.configurable. Reusing the same thread_id with a checkpointer under the graph resumes from that thread's last checkpoint; a new thread_id starts a fresh run with no prior history.

Quiz

Why null inputs on resume

graph.invoke(null, config) resumes a thread using null as the inputs. Where does the graph actually get its state from?

  1. AIt re-derives all state from scratch using default values
  2. BThe checkpointer supplies state from the last checkpoint for that thread_id, so no inputs needed
  3. Cnull is a special sentinel value that triggers a complete graph reset
  4. DIt reads from an environment variable
Show answer

Correct answer: B — The checkpointer supplies state from the last checkpoint for that thread_id, so no inputs needed

Because the checkpointer already has the thread's last saved state, invoking with null inputs tells the graph "don't start over, continue from what's persisted." The state comes from the checkpoint, not from the invoke call's arguments.

Sign in to track your progress →