Resuming a run they walked away from
Durability turns “come back tomorrow” into a product feature, and the UI work is almost entirely in reading a stored snapshot well enough to reconstruct where the user was and what is still owed.
The broker comes back the next morning, opens the link from her own email, and lands on the review page for a run that was paused nineteen hours ago. She has forgotten which document this was. She has forgotten whether she already approved two of the fields. She does not know whether anything is waiting on her or whether she is looking at a finished job.
Everything she needs is in the thread. None of it is on the screen yet. Closing that gap is almost the entire UI job of a durable agent, and it is a reading problem before it is a rendering problem.
What durability is documented to buy you
LangChain’s persistence page — vendor documentation for a framework whose hosted version the same company sells — states the purpose of checkpointers in one line:
“Use them for short-term, thread-scoped memory, including conversation continuity, human-in-the-loop workflows, time travel, and fault tolerance.” (LangChain, Persistence)
Four items, and the first and last are the ones this lesson lives on. Continuity is what lets the same thread pick up where it stopped. Fault tolerance is what makes that true after a crash rather than only after a polite exit. Put together, they are what turns “come back tomorrow” from an apology into a feature — though that framing is this course’s compression of the two, not a phrase LangChain uses.
Now the part the page is silent about, and the silence is the finding. There is no notification. No inbox. No “resume where you left off” component. No email when a run has been parked for a day. The framework persists state and exposes reads for it. Every surface a human actually returns through is yours to build, and if you do not build it, the durable run is a durable run that nobody ever comes back to.
Four fields, four sentences on the screen
The snapshot you read back has seven fields. Re-entry needs four of them, and each one maps to a sentence a returning user needs before they can do anything.
| Field | The sentence it produces |
|---|---|
values | “This is the mortgage offer for 14 Ashgrove. Three fields were extracted.” |
metadata.step | “You were partway through, not at the start.” |
next | “Once you answer, it will go on to write the summary.” An empty array means there is nothing to answer. |
tasks[].interrupts | “It is waiting on you, and here is the question it asked.” |
createdAt earns a fifth sentence when the gap is long enough to matter. Nineteen hours is worth saying out loud, because it changes what the person assumes about whether the document is still current. Whether that threshold is an hour or a week is a product decision and nothing in the framework has an opinion on it.
The ordering above is the design, not an accident. Identity first, position second, obligation third. A returning user asks “what is this” before “what do you want from me,” and a page that opens on the pending question with no context makes them scroll upward to find out what they are agreeing to. That sequencing argument is this course’s own, drawn from the field list rather than from any vendor guidance.
What is still owed
“What is still owed” is this course’s phrase for what next and tasks encode between them, and it is worth separating into two obligations that a single spinner routinely conflates.
- Owed by the machine. A non-empty
nextwith no interrupts. The run has work it can do unaided. Nothing is required of the person, and telling them otherwise wastes their attention. - Owed by the human. A task carrying interrupts. This is the one that belongs in a queue, a badge, a notification, and any overnight escalation you build. It is also the only one where the run will sit indefinitely, because nothing times it out on its own.
A run parked on a human is inert. It consumes no compute and makes no progress and generates no signal. There is no documented timeout, no expiry and no reminder in the framework, which means an abandoned review is silent by default. If your product cares that a mortgage offer has been sitting unreviewed for four days, the thing that notices is a job you wrote, running the waiting-on-human predicate across threads.
Where people get burned
Resuming restores state, and only what the schema declares as state. A channel declared with UntrackedValue is live during execution and never checkpointed, so it is not there after a resume. A review screen that renders the page crop straight out of state will look perfect all through development and break the first time a real person closes their laptop. If a resumed screen needs an image, a signed URL or a large blob, it fetches it from wherever that thing actually lives — the checkpoint will not bring it back.
Resuming is a second invocation, not a page load
One thing to keep straight while designing this: reading the snapshot and resuming the run are different operations, and only one of them changes anything.
getState is a read. Opening the review page, refreshing it, forwarding the link to a colleague and having them open it too — all reads, all safe, all repeatable, and none of them advance the run by a single superstep. The run moves only when someone invokes the graph again on that thread, carrying the answer. What that answer looks like is the interrupt module’s subject; the interrupt-and-resume lesson is where the payload contract gets designed.
Keeping the two apart matters for a reason that shows up in real products. If two people open the same review link and both answer, you have two invocations against one thread. Nothing in the runtime arbitrates that. Whether the second one is a duplicate, a conflict or a correction is your product’s question, and the cheapest time to answer it is while you are designing the page rather than after the first double-approval.
Check your recall
Answer from memory — no scrolling back.
Retrieval check
Write the four lines at the top of the resumed review page, in order, and say which snapshot field each one comes from.
Check your answer
“Mortgage offer — 14 Ashgrove Road.” From values. Identity first, because a person who has been away cannot evaluate anything until they know what they are looking at.
“Paused yesterday at 16:40, part way through extraction.” From createdAt and metadata.step. This is the line that tells her she is rejoining rather than starting, which is the difference between reading the page and re-reading it.
“Waiting on you: confirm the completion date.” From tasks[].interrupts, whose value is exactly the payload the node passed to interrupt(). The question survived in the thread, so it is readable cold by anyone holding the link.
“Then it will draft the summary.” From next. This is the one people leave out, and it is what makes the answer feel consequential rather than like filling in a form. If next is empty, this line is replaced by “finished” and the page has no button at all.
Hands on
Build the re-entry screen from a cold snapshot
Done when: You can kill the process, reopen the parser’s review route in a fresh browser with only the thread id, and the page states what the document is, how far the run got, what it wants from you, and what happens next — all read from getState, with nothing carried over from the session that started it.
- Run the parser until it parks on the review interrupt. Then kill the process. This step is the entire test, and skipping it is why the bug in the warning above ships so often.
- Start fresh, call
getStatewith the thread config alone, and paste the whole snapshot intoARTIFACT.mdunedited. Do not tidy it. What is missing fromvaluescompared to what you expected is the finding. - Write the four re-entry lines as a pure function from that snapshot. One function, snapshot in, four strings out. If any line needs data the snapshot does not carry, note where that data would have to come from instead — that list is a real dependency of your review page.
- Handle the empty
nextcase explicitly. A finished thread opened from an old link is a screen, not an error, and it is the one most often left to render as a permanent spinner. - Write down, in one sentence each, what your product does when two people open the same link and both answer, and what notices a review parked for four days. You do not have to build either. You do have to have decided, because the runtime has not.
- Bring the raw snapshot and the four lines into the chat. I will look for a line that quietly depends on something untracked, and for a missing empty-
nextstate.
What this does not cover
Every read here was of the latest checkpoint. The thread holds all of them, and the moment a broker says “that field was right before the last pass changed it,” you are reading history rather than state. Walking backwards, re-running from an earlier point and branching off one are three different operations with three different costs, and confusing two of them loses somebody’s work. That is the time-travel-and-forking lesson, and it is the one where the cheapest sounding verb turns out to re-run paid work.
The payload half of resuming was deferred on purpose. Designing what the pause asks and what the answer carries — approve, edit, reject, and what each of those means as a resume value — belongs to the interrupt module, in the interrupt-and-resume and decision-surface lessons.
And this lesson kept saying “render values” as if that were obvious. It is not. A checkpoint of a document parser holds the document, and deciding which channels reach a browser at all is the state-you-can-show lesson, which closes this module.
Read this next — primary source
PersistenceLangChain — docs.langchain.com, JavaScript docs, fetched 2026-09-05. Vendor documenting its own product, and the docs lead to the hosted platform it sells
Read this page for one sentence and for what it does not say. The sentence is the four-item list of what persistence is for, which is the closest thing the framework has to a statement of what durability buys a product. What the page does not say is anything at all about how a person comes back to a run — no notification, no inbox, no resume surface. That silence is accurate, and it is the whole reason this lesson exists.
Stuck, curious, or think this lesson is wrong? Ask your teaching agent. The lessons are the scaffold; the conversation is where the learning gets unstuck.