How to Implement LangGraph Checkpoint Persistence with SqliteSaver in Python

The Open Notebook repository demonstrates how to make LangGraph workflows resumable across server restarts by persisting state to SQLite using the SqliteSaver class, configured in open_notebook/config.py and applied to compiled graphs in open_notebook/graphs/chat.py.

Open Notebook uses LangGraph to model conversational workflows as state graphs. To ensure these workflows survive server restarts, the implementation leverages LangGraph checkpoint persistence with SqliteSaver, storing graph state in a local SQLite database. This approach requires no external database infrastructure and provides synchronous, thread-safe state management for FastAPI applications.

Configuring the SQLite Checkpoint Database

Before compiling any graphs, the repository establishes a dedicated location for the SQLite database. In open_notebook/config.py, the code creates a subdirectory under the main data folder and defines the checkpoint file path:


# open_notebook/config.py

DATA_FOLDER = "./data"
sqlite_folder = f"{DATA_FOLDER}/sqlite-db"
os.makedirs(sqlite_folder, exist_ok=True)
LANGGRAPH_CHECKPOINT_FILE = f"{sqlite_folder}/checkpoints.sqlite"

This centralized configuration ensures all graph instances reference the same persistence layer. The LANGGRAPH_CHECKPOINT_FILE constant is imported by graph modules to maintain consistency across the application.

Initializing SqliteSaver in Graph Modules

Each LangGraph workflow establishes a connection to the shared SQLite file and wraps it in a SqliteSaver instance. The main chat graph in open_notebook/graphs/chat.py implements this pattern:


# open_notebook/graphs/chat.py

import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
from open_notebook.config import LANGGRAPH_CHECKPOINT_FILE

conn = sqlite3.connect(
    LANGGRAPH_CHECKPOINT_FILE,
    check_same_thread=False,
)
memory = SqliteSaver(conn)

The same implementation appears in open_notebook/graphs/source_chat.py for source-specific chat workflows. The check_same_thread=False parameter is critical for FastAPI compatibility, allowing the synchronous SQLite connection to operate safely across multiple threads handling concurrent requests.

Compiling Graphs with Persistent Checkpoints

The final step binds the saver to the graph during compilation. When calling compile() on a StateGraph, the checkpointer argument receives the SqliteSaver instance:


# open_notebook/graphs/chat.py

graph = agent_state.compile(checkpointer=memory)

Similarly, the source chat graph uses:


# open_notebook/graphs/source_chat.py

source_chat_graph = source_chat_state.compile(checkpointer=memory)

Once compiled, every graph transition automatically persists the new state to the SQLite database. On application restart, the graph loads the last saved state from this checkpoint, enabling seamless conversation continuity.

Why SQLite for LangGraph Checkpoints?

The Open Notebook architecture selects SQLite for checkpoint persistence based on three technical advantages:

  • Synchronous Write API: SqliteSaver performs blocking writes synchronously, avoiding complexity with async event loops. Since checkpoints occur only when nodes complete their work, this design prevents blocking the main FastAPI event loop while ensuring durability.
  • File-Based Storage: No external database service is required. The checkpoint file resides at ./data/sqlite-db/checkpoints.sqlite, simplifying deployment and backup procedures.
  • Thread-Safe Connections: The check_same_thread=False configuration allows FastAPI's thread pool to handle multiple concurrent requests without triggering SQLite's thread-safety errors, making the solution production-ready for concurrent workloads.

Complete Implementation Example

Below is a minimal, self-contained example mirroring the Open Notebook pattern. This implementation can be integrated into any FastAPI project requiring durable LangGraph state:

import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import END, START, StateGraph
from typing import TypedDict, Optional

# 1️⃣ Define the graph's state shape

class SimpleState(TypedDict):
    counter: int
    message: Optional[str]

# 2️⃣ Node logic (adds 1 to the counter)

def increment(state: SimpleState, _: dict) -> dict:
    return {"counter": state.get("counter", 0) + 1}

# 3️⃣ Build the graph

graph_builder = StateGraph(SimpleState)
graph_builder.add_node("inc", increment)
graph_builder.add_edge(START, "inc")
graph_builder.add_edge("inc", END)

# 4️⃣ Set up SQLite persistence

conn = sqlite3.connect("./checkpoints.sqlite", check_same_thread=False)
saver = SqliteSaver(conn)

# 5️⃣ Compile the graph with the checkpointer

simple_graph = graph_builder.compile(checkpointer=saver)

# 6️⃣ Run the graph – state persists automatically

result = simple_graph.invoke({"counter": 0})
print(result)  # → {'counter': 1}

Running this script multiple times resumes from the last saved state rather than starting from zero. You can also manually retrieve checkpoints:

last_state = saver.load()  # Returns the most recent state dict

print(last_state)  # e.g., {'counter': 1, 'message': None}

Summary

  • Configuration: Define LANGGRAPH_CHECKPOINT_FILE in open_notebook/config.py with proper directory creation to ensure a consistent storage location.
  • Connection Setup: Use sqlite3.connect() with check_same_thread=False and wrap the connection in SqliteSaver to enable thread-safe persistence.
  • Graph Compilation: Pass the saver instance to the checkpointer argument when calling compile() on your StateGraph.
  • Automatic Persistence: Once configured, LangGraph automatically writes state transitions to SQLite and reloads the last checkpoint on application startup.
  • Production Ready: The synchronous SQLite API integrates cleanly with async FastAPI applications while handling concurrent requests safely.

Frequently Asked Questions

How does SqliteSaver handle concurrent requests in FastAPI?

The implementation uses check_same_thread=False when creating the SQLite connection, as seen in open_notebook/graphs/chat.py. This parameter allows the synchronous SqliteSaver to operate across FastAPI's thread pool without triggering SQLite's cross-thread usage errors. Since checkpoint writes occur synchronously after node execution completes, they do not block the async event loop while still ensuring durable state persistence.

Can I use a different database backend for LangGraph checkpoints?

While this article focuses on LangGraph checkpoint persistence with SqliteSaver, LangGraph supports alternative checkpointer implementations. However, the Open Notebook repository specifically chose SQLite for its zero-dependency, file-based architecture that requires no external database service, making it ideal for self-hosted deployments.

Where is the checkpoint data physically stored?

According to open_notebook/config.py, checkpoint data resides in ./data/sqlite-db/checkpoints.sqlite relative to the application root. This path is constructed by joining DATA_FOLDER with the sqlite-db subdirectory, ensuring all persistent data remains organized within a single parent directory.

When are checkpoints written to the database?

Checkpoints are written synchronously immediately after each node completes execution and the state transitions. When you invoke graph.invoke() or stream results, the SqliteSaver persists the state at each step, allowing the graph to resume from the exact point of interruption if the server restarts between operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →