MAX guide 14 min read

LangGraph and Temporal: How to Build an AI Workflow That Survives a Crash

Max presents a five-step run: lightning hits the send step, a key blocks the second envelope, bubble: one key, one reply

How do LangGraph and Temporal workflows survive a crash?

LangGraph chooses the next step, while Temporal records the run so a replacement program can continue it after a process dies.

Together they can resume a task, but our crash test showed that saved progress alone did not prevent a duplicate reply.

Building an AI agent means giving a program a task and a way to choose actions. Before joining LangGraph and Temporal, decide what the agent should do and what information it needs. Workflow orchestration for AI controls the order of that work. This guide addresses the next problem: continuing after a worker crashes.

LangGraph is a library for describing an agent’s work as connected steps. Temporal is a workflow platform that records work so it can continue after failures and long waits. They do different jobs and can work together.

Imagine a bug report that needs five steps: summarise it, classify its severity, draft a reply, send the reply, and write an internal note. If the process dies just after sending, your next attempt needs to know where to continue and whether another send is safe.

Give each product a clear job

LangGraph calls its map of steps a graph, and each step a node. In this example the five nodes run in order. A graph can also choose its next node based on the information collected so far. For other ways to arrange work, see how workflow orchestration structures AI pipelines.

Temporal runs each graph step and records its result; LangGraph still chooses which step comes next. These Temporal parts have distinct jobs:

PartJob in the bug-report example
WorkflowCode describing the whole task; a run is one execution of it.
ActivityWork the Workflow asks a worker to perform, such as sending the reply.
WorkerProgram that executes the Workflow and its Activities.
Temporal serviceSeparate service that records events and results.

In this guide, each working node runs as its own Activity. When a worker dies, a replacement can use the service’s record to continue the Workflow without repeating Activities already recorded as complete. You must start a replacement worker; the record alone cannot run the next step.

A checkpoint is a saved snapshot of the graph’s information and position. A checkpointer writes these snapshots. The per-node join does not need a separate persistent checkpointer, but a snapshot alone cannot prove that a reply was not sent just before a crash. For more on storing this information, see agent state management.

An idempotency key is a stable identifier for an action. Give the same reply the same key on every attempt, and have the receiving system enforce it. The second attempt can still run, but the receiver can recognise and ignore the duplicate.

Temporal records the run, LangGraph routes its steps, and the receiving system uses the key to recognise a repeated send.

The recipe for a crash-safe reply

Run each of the five graph steps as its own Activity. Set a timeout for each attempt and, for long work, a heartbeat timeout. A heartbeat is a regular message from the Activity to Temporal saying the work is still running. Send it throughout the work, not only before it starts.

Give the reply a stable key and make the destination enforce it. The destination is the email service, represented by an outbox table in our test. Finally, test a crash after the destination accepts the reply but before the Activity reports success. Check that a replacement worker continues, finished model calls stay finished, and the destination accepts one reply.

If you only need the basics, you can stop here.

Step 1: Decide if you need both

If the task is a fixed sequence, you may not need an agent that chooses its next action. If LangGraph with saved state meets your recovery needs, Temporal adds another system to operate. The framework comparison can help you make that choice.

Use the combination when you want LangGraph’s graph and Temporal’s recovery or waiting features. For each node, write down its input, output, and external action. In the bug-report example, the send is the action that needs a duplicate-safe destination.

Step 2: Run each graph node as an Activity

For approval running in the Workflow, we read the integration’s runtime requirement as Python 3.11 or newer. Install Temporal’s LangGraph integration, which connects the two products and installs their Python packages:

pip install temporalio-langgraph

As of November 2026 the integration is Public Preview, version 0.1.0. Its interface can change, so check the documentation before using it in a live system.

You also need the Temporal command-line tool and a running service. With that tool installed, start a local development service:

temporal server start-dev

Open http://localhost:8233 for its web interface. This development server loses runs when stopped; --db-filename adds a file store.

Create five working nodes: summarise, classify, draft, send, and write the note. The first three and the last use model calls; sending writes to the destination. Step 5 adds a sixth node for approval. Keep the send separate so its retry can be inspected on its own.

Register the graph with the worker’s LangGraphPlugin, then invoke it inside the Workflow. Set execute_in to activity on each working node. Set it on every node individually, not just in shared defaults. The integration reports an error if a node has no execution location.

Give each Activity a start_to_close_timeout and a Temporal retry policy, the rules for trying failed work again. Set these in the node’s options; the integration rejects a LangGraph node retry policy. Inspect the service’s event history, its ordered record of the run, to see each node as an Activity and check its attempts.

Step 3: Set a timeout and heartbeat

start_to_close_timeout limits one Activity attempt. If there is no heartbeat, Temporal waits for that timeout to detect a worker that died during the attempt. The remaining wait depends on how long the attempt already ran, the retry delay, and when a replacement worker is ready.

For a long Activity, set heartbeat_timeout and arrange for the Activity to send heartbeats while its work runs. In our separate experiment we set start_to_close_timeout to 60 seconds, heartbeat_timeout to 5 seconds, and sent a heartbeat every second from a background thread, work that runs alongside the Activity.

Choose a heartbeat interval and timeouts that fit your measured work, and stop the heartbeat when the attempt ends. Inspect the history for unexpected attempts and test a worker stop.

A heartbeat only before each operation inside a long Activity can expire during a slow model call. In our test that caused retries of work whose worker was still alive in all five runs. A heartbeat every second avoided those extra retries in five runs, but a replacement worker still had to start.

The timing experiment used the whole graph in one Activity, not the per-node integration. We did not run the key, heartbeats and approval together in one build. The key runs had no approval, and the approval runs had no key. Test the combination in your own nodes.

Step 4: Make the send safe to repeat

Before sending, derive a stable key from saved information, such as the bug report identifier and the reply action. Pass it to a destination that enforces uniqueness, or enforce uniqueness in the store responsible for that action. Confirm how the destination handles the key and how long it remembers it.

Our test used an outbox, a database table holding outgoing replies, to stand in for the send. SQLite, a file-based database, stored the table. A uniqueness rule for the report and an insert that ignored duplicates left one row in each of the 20 keyed runs, ten per keyed arrangement, although the send step started twice.

That proves one row in this test, not one email delivered by a real provider. Check the provider’s own duplicate handling. If it does not enforce the key, creating the key in your application alone will not protect the external send.

The gap is precise: the destination accepts the action, then the worker dies before reporting success. Temporal sees an unfinished Activity and retries it. The key gives the destination a way to recognise the second attempt. A checkpoint of earlier progress cannot close that gap on its own.

Step 5: Add approval and test the crash

In our example, we expect that without approval the workflow could send an inaccurate bug-report reply before a person reviews it.

Add approval as its own node between draft and send: six nodes in total. Do not call interrupt() in the draft node: LangGraph restarts a node from its first line after an interrupt, so its model call would run again.

In our test the approval node ran in the Workflow. The integration page says to call interrupt() inside Activity nodes, which we did not test. Set execute_in on the approval node as well. An interrupt pauses the graph for input; a Temporal Signal is a message sent to a running Workflow, such as the reviewer’s decision.

Use LangGraph’s InMemorySaver when the interrupt needs a checkpointer. In this integration Temporal keeps the durable record, meaning the information needed to continue survives the worker’s death. The integration page says Temporal handles durability, so a third-party checkpointer is not needed. In our approval test all five runs continued after the worker was killed.

Expose the draft through a Temporal Query, a request to read the Workflow’s current information, as the integration page describes. Our test queried only a status label. Send the reviewer’s decision as a Signal and resume with it. Check the Query response and Workflow history, and confirm the send occurs only after approval.

In our approval test, we killed the worker while it waited, sent the Signal 30 seconds after the kill, and started a replacement after 60 seconds with no worker. All five runs continued after approval. This does not establish what happens during longer waits or if the code changes while a run is waiting.

Now test the send boundary. Use a safe destination. Stop the worker immediately after the destination accepts the reply and before the Activity reports success. Start a replacement, then inspect the event history and the destination. The send should have another attempt, but the destination should contain one accepted reply.

What the crash test showed

We ran the five-step graph with a local Temporal development server. The worker died immediately after writing the reply row; a replacement started.

We compared five arrangements. First, the whole graph ran as one Activity with no saved graph progress. Second, that same single Activity used a checkpointer. Third, each node ran as its own Activity. Fourth, the single Activity used both a checkpointer and a key. Fifth, each node had its own Activity and the send had a key.

Each arrangement ran ten times. A repeated model call below is one that finished before the crash and ran again afterwards. Reply rows stand in for sends; they are not emails. The key is enforced by the outbox in the two keyed arrangements.

ArrangementRepeated completed model callsReply rows per runWhat the test showed
Whole graph in one Activity, no checkpointer3 in every run2The retry repeated the finished calls and the send.
Whole graph in one Activity, with checkpointer0 in 9 runs; 1 in 1 run2Saved progress reduced repeated calls, but the send still repeated.
One Activity per node, no key02Completed model calls stayed complete; the send ran again.
Whole graph in one Activity, checkpointer and key01The send ran twice; the outbox accepted one row.
One Activity per node, with key01The send ran twice; the outbox accepted one row.
The recommended combination kept completed model calls and produced one reply row in all 10 runs; its send step still ran twice.

Both keyed arrangements produced one row and no repeated completed model calls, so these numbers do not distinguish them. One Activity per step lets you inspect the send retry separately in the history and avoids running a separate persistent checkpoint store.

Putting the whole graph in one Activity hides its internal completed steps from Temporal. LangChain’s own comparison warns that this loses fine-grained replayability and visibility. In our first arrangement, the retry repeated all three model calls completed before the crash, then made the fourth call for the note.

The checkpointer result had one repeated call in ten runs. A snapshot still being written when the worker died could explain it; we did not observe the write itself. With durability="sync", which waits for the write before the next step, none of ten runs repeated a call. This small test does not prove every loss is prevented.

Timing depends on this machine and setup. Some runs were on a busy machine; the keyed runs were on a quieter one. The cause of the one repeated call was not observed. Our test suggests timing can change counts: missed heartbeats caused extra attempts. Test your own system.

With a 60-second Activity timeout and no heartbeat, the first poll showing the failed attempt came 33.2 to 47.1 seconds after the worker died. With the background heartbeat it came after 1.4 to 3.9 seconds in a separate five-run comparison. Your detection and worker startup times will differ.

The test covers one small local model, one send crash point and a local service. It does not establish behavior for hosted models, multiple machines, other crash points or real email delivery. Run the same failure at your own destination before trusting the result.

To help you repeat the test: on 4 November 2026 we used offline model Qwen/Qwen2.5-1.5B-Instruct, LangGraph 1.2.19, Temporal Python SDK 1.35.0 and integration 0.1.0.

Avoid a misleading recovery test

A crash before the destination accepts the reply misses the duplicate-send gap. Crash after acceptance and before recorded success; inspect Activity attempts and the destination.

For other failure patterns and recovery choices, see agent error handling and recovery.

Keep scheduled data work separate when useful

Prefect is another workflow product that can run scheduled data work around the agent. Temporal also lists batch data pipelines as a use case. Adding Prefect is a trade-off: use it when that work needs its own schedule and operational home, not simply because the agent uses Temporal.

Check how long the destination remembers a key

We recommend asking the destination owner how long keys are retained. We expect a retry after that period may need different protection.

Prompt for your coding agent

Replace the bracketed details before giving this specification to your coding agent. Review the generated code and run the failure tests against your own safe destination.

Build a Python 3.11+ bug-report workflow using LangGraph and Temporal's
LangGraph integration. Use the integration's current documentation and
mark its Public Preview status in the setup notes.

Input: [bug report schema]. Output: [reply and internal note schema].
Use five working nodes: summarise, classify, draft, send, write note, plus
an approval node between draft and send that calls interrupt(). Do not call
interrupt() in a node that makes a model call.
Register the graph with LangGraphPlugin and invoke it inside a Temporal
Workflow. Set execute_in on each node, including the approval node; run working nodes as Activities. Put model calls and
external I/O in Activities; use Temporal retry options, not a LangGraph
node retry policy. Give each Activity a justified start-to-close timeout.

Expose the draft through a Temporal Query and resume the interrupt through
a Signal. Use InMemorySaver where the interrupt needs a checkpointer.
For long-running Activities, configure a heartbeat timeout and send
heartbeats during work, not only between operations. Specify how the
heartbeat stops when the attempt ends.

Before sending, derive a stable idempotency key from durable state: [key
and scope]. Enforce it at [destination and its deduplication mechanism].
Do not assume a key alone prevents duplicates. Use a safe test destination.

Provide files, setup commands and tests. Test a worker death after the send
is accepted but before the Activity completes. Verify the destination has
one accepted reply, the send was attempted again, and completed model calls
were not repeated. Test an approval received while no worker is running.
Test a slow live Activity so its heartbeat does not trigger a false retry.
State what each test observes and what it cannot prove about the real provider.

Cheat sheet

The article on one page: open it full size, save it or share it.

LangGraph and Temporal cheat sheet: jobs, settings, tests, crash test: 2 reply rows to 1
PNG

AI-assisted content, human-reviewed. Images AI-generated. Editorial Standards · Our Editors