The most load-bearing session in the track
Persistence looks like a convenience feature - "the bot remembers" - and is actually the load-bearing wall of production agents. Because LangGraph checkpoints the full state after every super-step, you get, from the same mechanism: conversations that resume, crashes that cost nothing, humans who can pause and approve (b7), and a rewindable history you can debug from (b7). One argument to compile() buys all of it, on the graph you already built.
The amnesia problem 8 min live
A graph run is a series of super-steps. A checkpointer photographs the full state after each one and files the photo under a thread_id. Everything else tonight is consequences of that sentence.
LiveCheckpointers and thread_id - conversations get an identity4 min▶
Compile with a checkpointer and pass a thread_id at invoke time - that is the whole API surface:
- thread_id is a conversation's name. Same id = same accumulated state, so follow-up questions ("and the median of that column?") resolve. New id = clean slate. In an app, thread_id maps to a user session, a ticket, a pipeline run.
- The ladder is a deployment decision, not a code change:
| Checkpointer | Lives in | Use for |
|---|---|---|
InMemorySaver | Process RAM (langgraph.checkpoint.memory) | Notebooks, tests - dies with the process |
SqliteSaver | A local .db file | Local dev, single-machine tools - survives restarts |
PostgresSaver / AsyncPostgresSaver | Your Postgres | Production - shared, concurrent, operable |
LiveResume = durable execution in one concept4 min▶
Because the checkpoint is written AFTER each super-step, a crash mid-run only loses the step in flight. Invoke again with the same thread_id and LangGraph loads the latest checkpoint and continues - it does not replay finished work, does not re-bill you for completed model calls, does not lose the conversation. The industry name is durable execution; the mental model is a video game save file.
- What this quietly buys you: long-running agents that survive deploys, laptop-lid-close resilience in dev, and the ability to park a run for hours - which is exactly how b7's human approval gates will work (a pause is just a checkpoint nobody has resumed yet).
- The honest cost: every super-step writes state to storage. For chatty graphs with fat states that is real I/O - one reason state should carry references (a path, an id) rather than payloads (the whole CSV).
The overnight backfill that shrugged off a deploy. A data team ran a 4-hour agent job profiling hundreds of tables. Mid-run, the platform team deployed and every pod restarted. With a Postgres checkpointer the job resumed from table 212 of 400 as if nothing happened. The same job a month earlier, pre-checkpointer, had restarted from table 1 - twice in one night.
Self-studyWhat exactly gets saved - and how to look at it3 min read▶
- The full state, every super-step. A checkpoint holds the complete state values (all channels, not a diff), plus metadata: which nodes ran, what is scheduled next, a unique checkpoint_id. That completeness is what makes b7's time travel possible - any checkpoint is a valid restart point.
- History is a list you can read.
graph.get_state(config)returns the current snapshot;graph.get_state_history(config)yields every checkpoint of the thread, newest first. Print a few after tonight's demo - seeing your conversation as a stack of snapshots makes b7 obvious before it is taught. - Checkpoints are per-thread. Two thread_ids never share checkpoint state. Cross-thread sharing is a different tool - the Store, in Part 2.
Two memories, not one 6 min live
"Memory" hides two different products. Short-term memory is the thread: this conversation's accumulated state. Long-term memory is the Store: facts that outlive any conversation. Confusing them is the most common memory design bug.
LiveThe Store - facts that cross conversations6 min▶
The Store is a namespaced key-value memory, passed at compile time alongside the checkpointer. Any node can read or write it, from any thread:
- Namespaces are tuples - ("users", "phoebe") - so one Store cleanly holds many users, teams, or projects. Design them like folder paths.
- The division of labor: a fact belongs in the Store when it should be true in the NEXT conversation too. "The amount column we discussed" = thread. "Phoebe prefers medians" = Store.
- Production swap: like checkpointers, InMemoryStore has database-backed siblings - same API, deployment-grade storage. The code you write tonight is the code you ship.
Self-studyLong histories and memory schemas4 min read▶
- Threads get fat. Checkpointing preserves every message, and models have context limits and per-token prices. The standard moves: trim (keep the last N messages), or summarize (compress old turns into a running summary message). You already met the productized version - b4's SummarizationMiddleware does exactly this at the create_agent altitude; at graph level you own the policy in a node.
- Two shapes of long-term memory. A profile is one document per subject, updated in place ("phoebe.prefs" - tonight's demo). A collection is many small records searched at recall time (every stats caveat DataDesk ever learned). Profiles are simpler and stay consistent; collections scale further but need retrieval discipline - which is b8's topic wearing a memory hat.
- Write policy matters more than storage. Decide WHEN memories get saved: explicitly ("remember this"), on a schedule (end of thread), or model-decided (a memory tool). Start explicit - model-decided writes are how bots end up "remembering" things users never said.
DataDesk remembers ★ 12 min · everyone builds
Three acts: give conversations identity with InMemorySaver, climb one ladder rung to SqliteSaver, then the beat this session exists for - kill the process and watch the conversation survive.
Compile your b5 graph with InMemorySaver(). Ask on thread "phoebe-monday": "The amount column in data.csv - what is its mean?" then follow up: "And the median of that same column?" The pronoun resolves - state is accumulating.
Switch to thread_id="fresh-start" and ask the follow-up alone. DataDesk has no idea what "that same column" is. Threads are isolation, working as designed.
Climb one rung: pip install langgraph-checkpoint-sqlite, swap InMemorySaver for SqliteSaver - the compile line is the only change. Run one turn on "phoebe-monday".
The resurrection: kill the process (Ctrl+C - be dramatic about it). Run the file again, same thread_id, and ask "So which of the two should I report?" It answers with full memory of a conversation from a process that no longer exists. That .db file on disk IS the conversation.
Look at the evidence: graph.get_state(cfg) - print the snapshot and count the messages. Then peek at history with get_state_history - the stack of photos from Part 1's diagram, real on your machine.
DataDesk learns your taste ★ 10 min · build your own
Long-term memory: teach DataDesk one preference in one conversation, then watch a brand-new conversation already know it. DataDesk v3 complete.
Create an InMemoryStore and pass it at compile: g.compile(checkpointer=saver, store=store).
Upgrade the stats node: add the injected store parameter, read the ("users", "phoebe") prefs, and prepend any found preference as a system hint before calling the b3 agent.
Write the memory: store.put(("users", "phoebe"), "prefs", {"style": "prefers medians over means"}) - explicitly for now; a "remember this" tool is homework.
Open a brand NEW thread_id and ask "Give me the typical value of amount in data.csv". The answer leads with the median - a fact from outside this conversation just shaped it. Cross-thread memory, working.
Peek behind the curtain: store.search(("users", "phoebe")) - list exactly what DataDesk knows about you. Memory you can audit is memory you can govern.
The analyst bot that stopped re-asking. A BI team's assistant asked every user, every session, which fiscal calendar to use. Moving that one answer into a per-user Store profile cut the most-complained-about friction in the tool overnight. Long-term memory rarely needs to be clever - it needs to hold the three facts users are tired of repeating.
Try it yourself - this week ◐ 30-45 min total
- Finish both demos if you did not complete them live - the kill-and-resume especially. Do it on both engines; persistence is engine-agnostic and you should SEE that.
- Add a
remembertool to the b3 agent: it takes a short fact string and writes it to ("users", "phoebe") in the Store. Say "remember that our fiscal year starts in February" in one thread, then use it from another. - Print
len(list(graph.get_state_history(cfg)))after a 5-turn conversation. Reflect: every one of those is a full state snapshot - what does that imply for what you should keep IN state? (Rule from Part 1: references, not payloads.) - Sketch (paper is fine) where thread_id would come from in one real system you own - a Slack bot, a ticket tool, a notebook. Bring it to b7; approval gates need exactly this mapping.
- Optional reading: the persistence page on docs.langchain.com - the time-travel section is a preview of b7.
Official sources covered
This track teaches from the official docs and the free LangChain Academy curricula (login required for lesson content; certificates stay with the Academy - all free). This page covers:
Three questions before you go 🎯 ◐ 90 seconds
1 · What does a checkpointer actually save, and when?
Full state, every super-step. That completeness is why any checkpoint can be resumed from - and why b7 can rewind to any of them.
2 · "Phoebe prefers medians over means" should live in...
Thread state is this conversation; the Store is cross-thread knowledge. The test: should a brand-new thread_id already know it? If yes, Store.
3 · Your agent crashes 40 minutes into a 60-minute run. With a database checkpointer, resuming means...
Durable execution: the last photo is loaded and the walk continues. You lose at most the super-step in flight - not the 40 minutes.