Stage 1 of the sqlite delta history filtered `checkpoint_id <= target` and streamed `ORDER BY checkpoint_id DESC`. Both predicates encode the same extra assumption: that every child's checkpoint id sorts above its parent's. Ancestry is defined by `parent_checkpoint_id`, and nothing in the contract requires ids to be monotonic. A parent whose id sorted above its child's was dropped from the stream, so its stored value and its writes were lost with no error raised. Removing the range filter alone would not help: in DESC order that parent arrives before the target, so the walk passes it before it has started. A single-pass ordered stream cannot express this walk. Replace it with a recursive CTE anchored at the target that follows `parent_checkpoint_id`. Rows arrive in walk order, so `step_walk_with_row` keeps its existing shape, and the query now reads only true ancestors instead of every row at or below the target, which is strictly less IO than before. Following pointers can loop where a bounded id scan could not, and a loop is reachable through `put` alone rather than only by corruption: it writes with `INSERT OR REPLACE`, so re-putting an existing checkpoint id under a descendant's config repoints that checkpoint at its own descendant. The walk therefore stops on a repeated checkpoint id. sqlite yields recursive rows lazily, so abandoning the cursor ends the recursion instead of waiting on it. Postgres needs no equivalent change. It pages the whole thread and follows parent pointers already, so it returns the correct history for this scenario. Fixes #8550 Co-authored-by: lylelllll <59271327+lylelllll@users.noreply.github.com>
LangGraph SQLite Checkpoint
To help you ship LangGraph apps to production faster, check out LangSmith. LangSmith is a unified developer platform for building, testing, and monitoring LLM applications.
Quick Install
uv add langgraph-checkpoint-sqlite
🤔 What is this?
This library provides a SQLite implementation of LangGraph's checkpoint saver, with both sync and async support via aiosqlite. Use it when you want LangGraph state persistence backed by SQLite for local development, testing, or lightweight deployments.
📖 Documentation
For full documentation, see the API reference. For conceptual guides on persistence and memory, see the LangGraph Docs.
Security
Important
Set
LANGGRAPH_STRICT_MSGPACK=trueor pass an explicitallowed_msgpack_moduleslist when creating your checkpointer. This restricts checkpoint deserialization to known-safe types, preventing code execution if the database is compromised. See the langgraph-checkpoint README for details.
Usage
from langgraph.checkpoint.sqlite import SqliteSaver
write_config = {"configurable": {"thread_id": "1", "checkpoint_ns": ""}}
read_config = {"configurable": {"thread_id": "1"}}
with SqliteSaver.from_conn_string(":memory:") as checkpointer:
checkpoint = {
"v": 4,
"ts": "2024-07-31T20:14:19.804150+00:00",
"id": "1ef4f797-8335-6428-8001-8a1503f9b875",
"channel_values": {
"my_key": "meow",
"node": "node"
},
"channel_versions": {
"__start__": 2,
"my_key": 3,
"start:node": 3,
"node": 3
},
"versions_seen": {
"__input__": {},
"__start__": {
"__start__": 1
},
"node": {
"start:node": 2
}
},
}
# store checkpoint
checkpointer.put(write_config, checkpoint, {}, {})
# load checkpoint
checkpointer.get(read_config)
# list checkpoints
list(checkpointer.list(read_config))
Async
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
async with AsyncSqliteSaver.from_conn_string(":memory:") as checkpointer:
checkpoint = {
"v": 4,
"ts": "2024-07-31T20:14:19.804150+00:00",
"id": "1ef4f797-8335-6428-8001-8a1503f9b875",
"channel_values": {
"my_key": "meow",
"node": "node"
},
"channel_versions": {
"__start__": 2,
"my_key": 3,
"start:node": 3,
"node": 3
},
"versions_seen": {
"__input__": {},
"__start__": {
"__start__": 1
},
"node": {
"start:node": 2
}
},
}
# store checkpoint
await checkpointer.aput(write_config, checkpoint, {}, {})
# load checkpoint
await checkpointer.aget(read_config)
# list checkpoints
[c async for c in checkpointer.alist(read_config)]
📕 Releases & Versioning
See our Releases and Versioning policies.
💁 Contributing
As an open-source project in a rapidly developing field, we are extremely open to contributions, whether it be in the form of a new feature, improved infrastructure, or better documentation.
For detailed information on how to contribute, see the Contributing Guide.