To course

The agent loop

One step of an agent loopPractice: one step of the loopThe loop in codePractice: the loop in codeStopping and retryingPractice: stopping and retryingWhat only code can decidePractice: what code decides

Reliability in production

Timeout and idempotency keyPractice: timeout and idempotency keySays done, but nothing happenedPractice: false successDurable agent: checkpoints and resumePractice: durable agent

Agent designs

Workflow or agentPractice: workflow or agentReAct and plan-and-executePractice: ReAct and plan-and-executePlan-and-SolvePractice: Plan-and-SolveReflexionPractice: ReflexionOne agent or a swarmPractice: one agent or a swarm

Context and cost

Why the agent bill growsPractice: tokens and cacheContext engineeringPractice: managing the windowAgent memoryPractice: what the agent keeps

Tools and protocols

MCPPractice: MCPTool descriptions and injectionPractice: tool descriptions and injectionCode instead of a toolsetPractice: code instead of a toolsetAgent skills and A2APractice: agent skills and A2A

Frameworks

LangGraph or the raw loopPractice: LangGraph or the raw loopAgent SDKs: what you are buyingPractice: agent SDKs

Coding agents

Driving a coding agent from a specPractice: driving a coding agent from a spec
←→RUSign in
Agents › Reliability in productionDeep dive

Durable agent: checkpoints and resume

Builds onSays done, but nothing happened

This block explains what survives when the process running an agent dies partway through, and what happens to steps that had already touched the outside world.

A $420 refund at Northwind needs a manager's approval. The run has six steps: look up order 8890, check the policy, email the customer that the refund is under review, wait for approval, refund, confirm. At 18:00 the run reaches the wait. At 02:00 the server restarts for a deploy. At 09:00 the manager approves.

The process is gone, along with its variables, the message history it held in memory and the knowledge of which steps were finished. Without help, the run is lost, or it starts over and emails the customer again.

The notebook

Your colleague on the phone has you doing errands, and you keep a notebook. Each time you finish one, you write a line: what you did and what came of it. You go home sick. The next person on shift opens the notebook, sees three finished lines, does not repeat them and picks up at line four.

Durable execution is that notebook, plus something that hands it to the next shift. The runtime records progress outside the process, and a new process continues from the record.

Process 1 dies during step 4. Process 2 takes steps 1 to 3 from the journal and runs from step 4 on.
Process 1 dies during step 4. Process 2 takes steps 1 to 3 from the journal and runs from step 4 on.

The smallest version:

def step(run_id, name, fn):
    saved = journal.find(run_id, name)    # a database table
    if saved is not None:
        return saved.result               # finished before: fn does not run
    result = fn()
    journal.write(run_id, name, result)
    return result

The moment between doing and writing

Look at the two lines after the if. You post a letter, and you collapse before writing the line in the notebook. The next shift sees no line and posts the letter again.

The effect happened and the record did not. The new worker has to run the step again.
The effect happened and the record did not. The new worker has to run the step again.

Writing the line first does not help. A crash after the write would then skip a refund that never happened. So recording after the work is the usual choice, and this order gives the guarantee called at-least-once: every step runs one or more times.

A step with an effect therefore has to be safe to repeat. For a payment that means the idempotency key from the timeouts block, and the key must be saved before the attempt. If the step creates a fresh key each time it runs, the second run sends a different key. Temporal likewise recommends that its Activities be idempotent, and LangGraph tells you to design nodes so that re-execution is safe.

One word, two meanings

"Replay" means different things in two systems you may meet.

Temporal

LangGraph

What is saved

Event History of the Workflow, including Activity results

A checkpoint of graph state at each super-step

After a crash

Workflow code runs again and is checked against the history

invoke(None, config) loads the latest checkpoint and retries unfinished work

"Replay" means

Normal recovery. Activities stay outside the replay path

Starting from an earlier checkpoint_id. Nodes before it are skipped. Nodes after it run again, with their LLM and API calls

Rule for your code

Workflow code must be deterministic. Put API and LLM calls in Activities, and take time and random numbers from the SDK's replay-safe APIs

Checkpoints sit between super-steps, so a node may run again from its first line

A pause inside a node

LangGraph's interrupt() pauses a run for a person. On resume, the node starts again from its beginning, so any code above the interrupt() runs a second time.

With the email and the pause in one node, the email goes twice. With two nodes, a checkpoint sits between them.
With the email and the pause in one node, the email goes twice. With two nodes, a checkpoint sits between them.

Move the effect out of the pausing node:

def notify(state):
    send_email(state["order_id"], template="refund_under_review")
    return {"notified": True}

def approval(state):
    decision = interrupt({
        "order_id": state["order_id"],
        "amount_cents": state["amount_cents"],
    })
    return {"approved": decision}

The answer arrives later as graph.invoke(Command(resume=True), config) with the same thread_id. Wrapping the effect in a LangGraph task also works, because completed tasks return stored results. The email itself can still repeat after a crash, as any step can.

Where the notebook is kept

A notebook left in the process dies with it. LangGraph's InMemorySaver keeps checkpoints in RAM, and a restart loses them. Production needs a persistent checkpointer such as PostgresSaver.

LangGraph also lets you choose when the notebook gets written:

durability

When it writes

A crash mid-run

"exit"

Only when the graph exits

Intermediate state is lost

"async"

While the next step runs

Small risk of a missing checkpoint

"sync"

Before the next step starts

Every checkpoint is written, at some cost in speed

A pause can last days

Do not keep a process alive for the manager. The state sits in the database, and the answer comes in through Command(resume=...) or, in Temporal, a Signal.

The world moves on meanwhile. By 09:00 a colleague may have refunded order 8890 by hand. So after a long wait, read the order, the refundable amount and the approver's rights again.

The code may also have changed. LangGraph resumes with saved state and the graph you compile now. In Temporal, changing a Workflow Definition that running executions depend on can produce a non-determinism error, which is what its versioning tools exist for.

Durable execution protects you from a dead process. The model's context window still fills up on a long run, and that is a separate problem.

Conclusion

Go deeper

The definitive guide to Durable Execution (Temporal)What a crash takes away, and how a run continues across processes.Checkpointers (LangGraph)How to continue a thread, durability modes and the trap of in-memory savers.Interrupts (LangGraph)What runs again on resume.Workflow Definition (Temporal)Determinism, replay and versioning, with examples of what breaks.
‹ Previous
Suggest an improvementFound a mistake