new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

LangChain · LangGraph · all subjects

persistence/checkpointing

103 notes in this subject, read out of this brain and free to use. This is page 2 of 2.

Checkpoints growing unboundedly over long conversations

Over long conversations, checkpoints accumulate. This can increase latency and storage costs. Fix: Prune old checkpoints periodically or set a retention policy. Consider adding a cron job to delete checkpoints older than N days.

Checkpointers persist thread graph state

Checkpointers persist a thread's graph state as checkpoints and are used for short-term, thread-scoped memory. Use cases include conversation continuity, human-in-the-loop workflows, time travel, and fault tolerance.

InMemorySaver and InMemoryStore in-memory implementations

LangGraph provides in-memory implementations for both checkpointers and stores. InMemorySaver is imported from langgraph.checkpoint.memory and InMemoryStore is imported from langgraph.store.memory (Python). In TypeScript, both MemorySaver and MemoryStore are imported from @langchain/langgraph.

Using checkpointer and store in graph compilation

Compile your graph with a checkpointer, a store, or both. Most applications can use both: a checkpointer tracks the current thread, and a store tracks durable information across threads. In Python: graph = builder.compile(checkpointer=checkpointer, store=store). In TypeScript: const graph = builder.compile({ checkpointer, store });

DeltaChannel stores incremental deltas instead of full values

DeltaChannel (requires langgraph>=1.2, currently in beta) stores only the incremental delta at each step rather than the full accumulated value. This is most useful for channels that are written frequently and accumulate large values over time, such as a conversation message list in a long-running thread. Without delta storage, the full list is re-serialized into every checkpoint; with DeltaChannel, only the new messages written at each step are stored.

DeltaChannel requires bulk reducer that is associative

The reducer passed to DeltaChannel is a bulk reducer: it receives the current state and a sequence of all writes from the current step in a single call, not pairwise like a standard reducer. The bulk reducer must be associative (batching-invariant): reducer(reducer(state, [xs]), [ys]) == reducer(state, [xs, ys]). If the reducer is not associative, the reconstructed state may differ depending on how LangGraph batches writes across steps, producing inconsistent behavior.

DeltaChannel reducer runs on reconstruction, not on write

Unlike BinaryOperatorAggregate whose reducer is invoked at write time, a DeltaChannel reducer is invoked when the channel value is rebuilt from its persisted writes. The raw per-step writes are serialized; the reducer is only called when the value is materialized—on the next read, on the next step's actors, or when replaying history. The reducer must be a pure function of (state, writes); side effects, randomness, or wall-clock reads execute every time the value is reconstructed and produce different results on each replay. Do not rely on mutations to incoming writes being persisted. Attach identity and other stable metadata upstream if downstream code needs to reference an item by ID across turns.

DeltaChannel snapshot_frequency bounds read latency

Without snapshots, reading a DeltaChannel value requires replaying the full write history—O(N) for a thread with N steps. Setting snapshot_frequency=K writes a full snapshot every K pregel steps, bounding read depth to at most K steps. Higher values of snapshot_frequency reduce storage overhead but increase read latency. Lower values bound latency more tightly at the cost of larger checkpoints. None (the default) skips snapshots entirely, appropriate when reads are rare or threads are short.

DeltaChannel version compatibility and rollback limitations

Rolling back to a version without DeltaChannel support is not supported. langgraph>=1.2 writes delta channel checkpoints in a new format that earlier versions cannot read. Once a thread has used DeltaChannel, downgrading LangGraph leaves those checkpoints unreadable as older runtimes do not understand the delta format and cannot reconstruct channel state. To roll back, use the delta-channel-dump recovery script to migrate affected threads, or discard them, before downgrading.

DeltaChannel bulk reducer examples

Two common bulk reducer implementations for DeltaChannel: List reducer (append all writes in order): ```python def list_reducer(state: list[Any], writes: Sequence[list[Any]]) -> list[Any]: result = list(state) for write in writes: result.extend(write) return result ``` Dict reducer (merge all writes, last write wins on key conflicts): ```python def dict_reducer( state: dict[str, Any], writes: Sequence[dict[str, Any]] ) -> dict[str, Any]: result = dict(state) for write in writes: result.update(write) return result ``` Both are associative: applying batches one at a time produces the same result as applying them together.

DeltaChannel Annotated type annotation example

Example of using DeltaChannel in an Annotated type annotation: ```python from typing import Annotated, Sequence from typing_extensions import TypedDict from langgraph.channels import DeltaChannel def my_reducer(state: list[str], writes: Sequence[list[str]]) -> list[str]: result = list(state) for write in writes: result.extend(write) return result class State(TypedDict): messages: Annotated[list[str], DeltaChannel(my_reducer)] ```

DeltaChannel snapshot_frequency configuration example

Example of configuring snapshot_frequency for DeltaChannel to bound read latency: ```python class State(TypedDict): messages: Annotated[ list[str], DeltaChannel(my_reducer, snapshot_frequency=5), ] ``` This configuration writes a full snapshot every 5 Pregel steps, bounding read depth to at most 5 steps.

Persistence layer enables pausing and resuming SQL agent runs

LangGraph's persistence layer allows runs to be paused indefinitely (or as long as the persistence layer is alive). A checkpointer must be included in the graph configuration to enable pause and resume functionality for human-in-the-loop review workflows.

MemorySaver for persistence and human-in-the-loop

To enable human-in-the-loop with interrupt(), compile graph with checkpointer. Python: from langgraph.checkpoint.memory import MemorySaver; memory = MemorySaver(); app = workflow.compile(checkpointer=memory). JavaScript: const memory = new MemorySaver(); const app = workflow.compile({ checkpointer: memory }). Checkpointer saves state between runs so graph can pause and resume.

Checkpointing at node boundaries for resilience

LangGraph creates checkpoints at node boundaries. When workflow resumes after interruption or failure, it starts from beginning of node where execution stopped. Smaller nodes mean more frequent checkpoints and less work to repeat on failure. Larger nodes mean fewer checkpoints but more repeated work on failure.

Async durability mode for background checkpointing

LangGraph writes checkpoints in background by default (async durability mode), so graph continues running without waiting for checkpoints to complete. This enables frequent checkpoints with minimal performance impact. Can adjust behavior: use 'exit' mode to checkpoint only at completion, or 'sync' mode to block until each checkpoint written.

View thread history in Functional API

Call graph.get_state_history(config) to retrieve an iterator of all StateSnapshot objects for a thread, ordered from most recent to oldest. Each snapshot shows the state at that checkpoint, metadata about the source of changes, and parent/child relationships between checkpoints.

Resuming workflows after errors using checkpointer

When a task fails in a workflow with persistence enabled, prior task results are saved in the checkpoint. Resuming execution by invoking the workflow again with the same config will skip previously completed tasks and continue from the failure point.

View thread state in Functional API

Call graph.get_state(config) where config contains configurable.thread_id (and optionally configurable.checkpoint_id) to retrieve the current StateSnapshot for that thread. Returns values, next nodes, config, metadata, created_at, parent_config, tasks, and interrupts.

entrypoint.final: decouple return value from saved value

Use entrypoint.final(value=X, save=Y) to return X to the caller while persisting Y in the checkpoint. This decouples what the caller receives from what gets saved for the next invocation. Useful for returning computed results while saving internal state.

Example: resuming after error with checkpoint

Example with global attempts counter: get_info() fails on first call (raises ValueError), slow_task() sleeps 1 second. First invoke() raises exception. Resuming with invoke(None, config) skips slow_task, returning its cached result, then retries get_info which now succeeds.

Example: entrypoint.final for accumulation

accumulate(n, previous=None) computes total = (previous or 0) + n, returns entrypoint.final(value=previous, save=total). First call with n=1 returns 0, saves 1. Second with n=2 returns 1, saves 3. Third with n=3 returns 3, saves 6. Shows return vs save decoupling.

Time travel overview: replay and fork

LangGraph supports time travel through checkpoints with two capabilities: Replay allows retrying from a prior checkpoint by invoking the graph with that checkpoint's config, and Fork allows branching from a prior checkpoint with modified state to explore an alternative path. Both work by resuming from a prior checkpoint; nodes before the checkpoint are not re-executed (results are already saved), while nodes after the checkpoint re-execute, including any LLM calls, API requests, and interrupts which may produce different results.

Replay re-executes nodes, not cache reads

Replay re-executes nodes—it doesn't just read from cache. LLM calls, API requests, and interrupts fire again and may return different results. Replaying from the final checkpoint (no next nodes) is a no-op.

How to replay from a checkpoint using get_state_history and invoke

Use get_state_history to find the checkpoint you want to replay from, then call invoke with that checkpoint's config. In Python, get_state_history returns checkpoints in reverse chronological order. Each checkpoint has a next field indicating which nodes run next, and a config field with the checkpoint_id. In JavaScript, use getStateHistory to find the checkpoint, then invoke with the checkpoint's config.

Replay example: retrying from a prior checkpoint

Python example: Use graph.get_state_history(config) to get checkpoints, find the checkpoint before the desired node with next == ('write_joke',), then call graph.invoke(None, before_joke.config). The node at the checkpoint (write_joke) re-executes, while prior nodes (generate_topic) do not. JavaScript equivalent: Use getStateHistory to get states, find the state with s.next.includes('writeJoke'), then invoke with that state's config.

Fork creates new branch, doesn't roll back thread

update_state does not roll back a thread. It creates a new checkpoint that branches from the specified point. The original execution history remains intact.

How to fork from a checkpoint using update_state

Fork creates a new branch from a past checkpoint with modified state. Call update_state on a prior checkpoint with the values parameter to create the fork, then invoke with None to continue execution. In Python: graph.update_state(before_joke.config, values={'topic': 'chickens'}). In JavaScript: await graph.updateState(beforeJoke.config, { topic: 'chickens' }).

Fork example: branching with modified state

Python example: Call graph.update_state(before_joke.config, values={'topic': 'chickens'}) to create fork_config, then graph.invoke(None, fork_config). The subsequent nodes re-execute with the new state. JavaScript equivalent: await graph.updateState(beforeJoke.config, { topic: 'chickens' }) to get fork_config, then invoke(null, fork_config).

update_state as_node parameter for explicit node specification

When calling update_state, values are applied using the specified node's writers (including reducers). The checkpoint records that node as having produced the update, and execution resumes from that node's successors. By default, LangGraph infers as_node from the checkpoint's version history, which is almost always correct. Specify as_node explicitly when: multiple nodes updated state in the same step (causing InvalidUpdateError), setting up state on a fresh thread (common in testing), or skipping nodes (to make the graph think a node already ran).

Specifying as_node in update_state example

Python example: graph.update_state(before_joke.config, values={'topic': 'chickens'}, as_node='generate_topic') treats the update as if generate_topic produced it, and execution resumes at write_joke. JavaScript example: await graph.updateState(beforeJoke.config, { topic: 'chickens' }, { asNode: 'generateTopic' }).

Interrupts re-trigger during time travel

If your graph uses interrupt for human-in-the-loop workflows, interrupts are always re-triggered during time travel. The node containing the interrupt re-executes, and interrupt() pauses for a new Command(resume=...).

Checkpointer purpose

A Checkpointer persists state for durable execution and time-travel debugging capabilities.

DeltaChannel-aware pruning preserves minimum ancestor checkpoints

DeltaChannel-aware pruning preserves only the minimum ancestor checkpoints needed for state reconstruction, replacing the previous approach that refused to prune threads with active delta channels. This is supported across Postgres, SQLite, DeferredDelete, in-memory, and MongoDB runtimes.

Delete run skips checkpoint deletion for DeltaChannel threads

Delete run now skips checkpoint deletion for threads using DeltaChannel and removes only the run record. Checkpoints that store delta writes that later checkpoints depend on are preserved. Use thread prune APIs to reclaim checkpoint storage on delta-channel threads.

Fixed DeltaChannel replay for channels migrated from non-delta

Fixed DeltaChannel replay for channels that migrated from a non-delta channel to DeltaChannel. The checkpointer did not correctly recognize the head seed checkpoint, which could produce incorrect reconstructed state for non-additive reducers.

DeltaChannel support in Agent Server

Delta channels are now supported so checkpoints can store incremental state updates instead of repeatedly storing full channel payloads, which helps with large, append-heavy state like message histories. To use, define state channels with LangGraph's DeltaChannel reducer pattern in your graph state. This behavior is enabled when the installed langgraph is >= 1.2.

Event Streaming v2 checkpoint replay targets honored

Fixed Event Streaming v2 run start handling so checkpoint replay targets supplied via config.configurable.checkpoint_id are honored.

Thread checkpoint_map persistence bug fixed

Fixed a bug where a thread's checkpoint_map from a prior time-travel run would persist and contaminate a subsequent Command(resume=...), causing nested subgraphs to incorrectly replay from the start.

keep_latest TTL strategy preserves latest state

Introduced a keep_latest TTL strategy to preserve the latest state while pruning older checkpoints via the core API.

GET /threads/{thread_id} include=ttl query parameter

Added include=ttl query parameter to the GET /threads/{id} endpoint for optional TTL information retrieval without affecting standard read performance.

Postgres checkpointer pool tuning for large checkpoints

Added Postgres checkpointer pool tuning knobs for cases when loading lots of large checkpoints at once. LANGGRAPH_CHECKPOINTER_POSTGRES_POOL_MIN_SIZE and LANGGRAPH_CHECKPOINTER_POSTGRES_POOL_TIMEOUT_SECONDS can now be set.

state_updated_at field tracks meaningful state changes

Added state_updated_at field to threads for tracking meaningful state changes, allowing filtering and sorting based on these changes.

Give your agent this brain