There is a performance bug hiding in the most-recommended pattern in LangGraph, and it is not in your code. It is in the shape of persistence itself.
Here is the setup every tutorial teaches. You define a graph with a message state, add a checkpointer so runs can pause and resume, and let the agent loop. Each turn appends to the message list; the checkpointer quietly saves your progress. It works. It is durable. It is also, on a long-running thread, doing far more work than you think — and the cost is invisible until the thread is long enough to hurt.
The write you never profiled#
LangGraph's durability comes from a simple contract: after every superstep — one round of the graph advancing — it serializes the current channel values and writes them to the checkpointer. That write is what makes an agent resumable. Kill the process, restart, and the run picks up from the last checkpoint. It is the whole reason you added the checkpointer.
The part the docs are honest about, but most builders skim past, is what gets written. A default reducer channel — the kind holding your accumulating message list — re-serializes the full accumulated value on every step. Not the delta. The whole thing. Step 501 of a thread does not write one new message; it writes all 501, again.
Follow that to its conclusion. On step 2 you write 2 messages. On step 200, 200. Over an N-step thread, the total volume you push to storage is 1 + 2 + 3 + … + N — which is proportional to N². The individual write on a 30-turn support chat is nothing. The same pattern on a deep-research agent that runs for hours, or a workflow that spans days, is a channel that gets more expensive to save precisely as it does more work. The agent isn't slowing down because the model got slower. It's slowing down because you are rewriting its entire history on every heartbeat.
This is the least-glamorous kind of scaling problem: nothing errors, nothing crashes, the graph is "correct." You just watch checkpoint latency and storage climb on your longest, most valuable sessions and reach for the wrong knob — a bigger Postgres, a faster disk — when the issue is write amplification baked into the channel semantics.
DeltaChannel makes the write linear#
The fix shipped quietly in the 1.2 line (it lands in the 1.2.0a6 pre-release notes): a new channel type, DeltaChannel, that "stores only the incremental delta at each step rather than re-serializing the full accumulated value." A message list backed by DeltaChannel writes the new messages from this step, and nothing more.
That single change flips the curve. Per-step write cost stops tracking the size of the whole thread and starts tracking the size of the step. Total write volume over the run goes from roughly O(N²) to O(N). For the exact workloads where durability matters most — long, stateful, expensive runs — this is the highest-leverage line in the release.
There is one honest tradeoff, and DeltaChannel names it. If every step is a delta, restoring state means replaying the delta chain, and an unbounded chain makes reads slow. So it gives you snapshot_frequency=K: write a full snapshot every K steps to cap how far a restore ever has to replay. You are trading a little write cost back for bounded read latency, and you get to pick the ratio. That is the right shape for a knob — a dial, not a default you can't see.
The non-obvious takeaway isn't "use DeltaChannel." It's that checkpoint frequency and state-growth interact, and the interaction is quadratic by default. Once you internalize that persistence writes scale with your accumulated state and not just your step count, you start designing state to be checkpoint-cheap: keep the append-only, ever-growing things (messages, scratchpads, tool logs) in delta-backed channels, and keep small mutable control state wherever is simplest.
The reliability primitives that ship alongside it#
Durability is not only about how you write state — it's about surviving the moment a step goes wrong. The same 1.2 release rounds out that story, and the pieces are meant to be used together.
- Per-node timeouts. Pass
run_timeoutfor a hard wall-clock cap, oridle_timeoutfor a limit that resets whenever the node makes progress — the right choice for a streaming node that's slow but alive. On expiry LangGraph raisesNodeTimeoutError, clears the writes from that attempt, and hands the situation to the retry policy. That "clears the writes" clause is the important one: a timed-out node doesn't leave half-written garbage in your next checkpoint. Note the constraint — timeouts apply to async nodes; a sync node given one is rejected at compile time, which is a design signal to make anything that can hangasync. - Node-level error handlers. An
error_handler=on a node runs after retries are exhausted, giving you a place to compensate — the saga pattern, where step 4 failing triggers the undo of steps 1–3. Without it, recovery logic tends to metastasize into the business nodes themselves. - Graceful shutdown. Cooperatively stop in-flight runs after the current superstep completes and save a resumable checkpoint, instead of hard-killing mid-step. Deploys stop being a gamble on what state you left behind.
Read as a set, these are LangGraph conceding that "long-running agent" is now a first-class target, not a chatbot with extra turns. A chatbot never runs long enough to feel quadratic checkpoint writes, never has a node worth timing out, never needs saga compensation. An agent that operates for hours needs all three — and needed them a year ago.
What to actually do Monday#
You do not need to rewrite anything. You need to profile one number you probably never have: checkpoint write size as a function of thread length, on your longest real session. If it climbs with the conversation, your growing channels are re-serializing full state, and DeltaChannel with a sane snapshot_frequency is the targeted fix — not more database.
Then wrap your model and tool nodes in timeouts with a retry policy, and give any multi-step transaction an error_handler that can undo. And know the boundary: LangGraph's checkpointer makes the LLM loop durable, but the moment your agent orchestrates side effects the framework doesn't own — payments, provisioning, cross-system writes — you're in durable-execution-engine territory, not checkpointer territory. The framework finally treats durable, long-running execution as the thing it is: the hard part of agents, not the afterthought. The default settings still assume you're building a chatbot. Most people reading this aren't anymore.



