LangGraph Checkpoint Storage with SQLite: How Open Notebook Persists Chat State
LangGraph's SqliteSaver automatically serializes the complete chat state—including every message exchange—to a local SQLite file, enabling seamless conversation resumption across API requests.
The open-notebook repository implements persistent chat sessions by leveraging LangGraph's checkpoint storage capabilities with SQLite. This architecture ensures that the full conversation history survives application restarts and remains accessible across asynchronous API calls. Understanding how LangGraph checkpoint storage works with SQLite reveals the mechanics behind resilient, stateful AI interactions in production environments.
Configuring the SQLite Checkpoint Database
The checkpoint file location is centralized in open_notebook/config.py. The repository defines a dedicated data directory structure to ensure persistent storage across deployments:
DATA_FOLDER = "./data"
sqlite_folder = f"{DATA_FOLDER}/sqlite-db"
LANGGRAPH_CHECKPOINT_FILE = f"{sqlite_folder}/checkpoints.sqlite"
On first execution, this configuration creates a SQLite database at ./data/sqlite-db/checkpoints.sqlite. This file stores the complete graph state for every active chat thread, providing durable, file-based persistence that survives container restarts.
Initializing the SqliteSaver Connection
Both the main chat graph and the source-chat graph instantiate the checkpoint mechanism identically. In open_notebook/graphs/chat.py and open_notebook/graphs/source_chat.py, the code establishes a synchronous SQLite connection and wraps it with LangGraph's SqliteSaver class:
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 check_same_thread=False parameter is critical for async environments. It allows the same connection to be accessed across different execution threads, while LangGraph's internal synchronization ensures thread-safe state management.
Compiling the Graph with Persistence Enabled
The chat graph is compiled with the checkpoint object to trigger automatic state serialization. In open_notebook/graphs/chat.py, the StateGraph uses ThreadState—a TypedDict containing a messages field that holds the conversation history:
from langgraph.graph import StateGraph
agent_state = StateGraph(ThreadState)
# ... nodes and edges configured ...
graph = agent_state.compile(checkpointer=memory)
By passing checkpointer=memory to the compile() method, every graph transition automatically persists the updated ThreadState to SQLite. After each node execution, the current state—including the ordered messages list—replaces the previous checkpoint record.
Retrieving Persisted Conversation State
To resume a chat session, the application retrieves the stored state using the session ID as a thread identifier. Since SqliteSaver operates synchronously, the async utility functions in open_notebook/utils/graph_utils.py use asyncio.to_thread to prevent event loop blocking:
from langchain_core.runnables import RunnableConfig
import asyncio
thread_state = await asyncio.to_thread(
graph.get_state,
config=RunnableConfig(configurable={"thread_id": session_id}),
)
message_count = len(thread_state.values["messages"])
The RunnableConfig specifies the thread_id which serves as the primary lookup key in the SQLite database. This allows LangGraph to fetch the exact conversation state for that session, including the complete message history required to continue the dialogue.
Data Structure and Storage Format
The SQLite database stores a JSON blob for each unique thread ID. This blob contains the complete serialized state dictionary—either ThreadState for standard chats or SourceChatState for source-specific interactions. The messages field holds the list of LangChain message objects that constitute the conversation transcript. On every node execution, LangGraph replaces the existing row for that thread ID, ensuring the database always contains the latest state.
Complete Implementation Pattern
The following code demonstrates the end-to-end checkpoint flow used throughout open-notebook:
# 1. Initialize the checkpoint saver (executed once at module load)
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)
checkpoint = SqliteSaver(conn)
# 2. Compile the graph with checkpoint support
from langgraph.graph import StateGraph, START, END
from open_notebook.graphs.chat import ThreadState, call_model
builder = StateGraph(ThreadState)
builder.add_node("agent", call_model)
builder.add_edge(START, "agent")
builder.add_edge("agent", END)
chat_graph = builder.compile(checkpointer=checkpoint)
# 3. Invoke with thread_id to enable persistence
from langchain_core.runnables import RunnableConfig
result = await chat_graph.ainvoke(
{"messages": user_input},
config=RunnableConfig(configurable={"thread_id": "user-session-123"})
)
Summary
- File location: Checkpoints are stored at
./data/sqlite-db/checkpoints.sqliteas defined inopen_notebook/config.py - Connection setup:
SqliteSaverwraps asqlite3.Connectionwithcheck_same_thread=Falseto support async usage - Graph compilation: The
checkpointerparameter inStateGraph.compile()enables automatic persistence ofThreadStateafter each node execution - State retrieval: Use
graph.get_state()with aRunnableConfigcontainingthread_id, wrapped inasyncio.to_thread()for async compatibility - Storage mechanism: JSON blobs keyed by thread ID containing complete message history and graph state, updated atomically after each transition
Frequently Asked Questions
Where does Open Notebook store LangGraph checkpoint data?
Open Notebook stores checkpoints in a local SQLite file defined by LANGGRAPH_CHECKPOINT_FILE in open_notebook/config.py. By default, this creates a file at ./data/sqlite-db/checkpoints.sqlite that persists the full conversation state for each chat session, including the complete message history.
How does the application handle SQLite operations in async contexts?
Since SqliteSaver uses synchronous SQLite connections, the codebase wraps state retrieval calls in asyncio.to_thread() as implemented in open_notebook/utils/graph_utils.py. This prevents blocking the async event loop while maintaining thread-safe access to the checkpoint database.
What specific data is stored in each checkpoint?
Each checkpoint contains a serialized JSON representation of the graph's state—typically ThreadState or SourceChatState—including the complete messages array with all LangChain message objects exchanged in the conversation. The data is keyed by the thread_id specified in the RunnableConfig during graph invocation.
Can I manually delete or reset a specific chat session?
Yes. Since the checkpoint uses a standard SQLite schema, you can delete specific session records by executing SQL against the checkpoints table using the thread_id as the lookup key, or remove the entire ./data/sqlite-db/checkpoints.sqlite file to reset all conversations.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →