How LangGraph Checkpoint Persistence Stores Conversation History in SQLite
LangGraph's SqliteSaver persists the complete conversation state as JSON blobs keyed by thread ID in a local SQLite file, enabling automatic recovery and continuation of chat sessions across API calls.
Open Notebook leverages LangGraph's built-in checkpointing system to maintain durable conversation history without external database dependencies. By implementing SQLite-based persistence, the application stores the full state of each chat interaction—including message sequences and metadata—directly in a local file that survives server restarts.
Checkpoint File Configuration and Location
The SQLite database path is defined centrally in open_notebook/config.py. The configuration establishes a dedicated subdirectory within the data folder to isolate checkpoint files from other application data.
DATA_FOLDER = "./data"
sqlite_folder = f"{DATA_FOLDER}/sqlite-db"
LANGGRAPH_CHECKPOINT_FILE = f"{sqlite_folder}/checkpoints.sqlite"
On first execution, LangGraph creates the checkpoints.sqlite file at ./data/sqlite-db/checkpoints.sqlite if it does not exist. This location serves as the persistent store for all conversation checkpoints across both standard chat and source-chat workflows.
Initializing the SQLite Saver
Both the chat and source-chat graph implementations establish a connection to the SQLite file using Python's standard sqlite3 module. The connection is configured with check_same_thread=False to accommodate LangGraph's threading requirements, then wrapped with LangGraph's SqliteSaver class.
In open_notebook/graphs/chat.py and open_notebook/graphs/source_chat.py, the initialization follows this pattern:
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) # Checkpoint persistence object
The SqliteSaver instance acts as the checkpointer object that handles serialization and deserialization of graph states to the SQLite backend.
Compiling Graphs with Checkpoint Persistence
To enable automatic persistence, the graph compilation process injects the SqliteSaver instance via the checkpointer parameter. The StateGraph is defined with a ThreadState TypedDict that includes a messages field to hold the conversation history.
The compilation pattern in open_notebook/graphs/chat.py demonstrates:
from langgraph.graph import StateGraph
agent_state = StateGraph(ThreadState)
# ... node and edge definitions ...
graph = agent_state.compile(checkpointer=memory)
When compiled with a checkpointer, LangGraph automatically writes the updated ThreadState (including the messages list) to SQLite after every node transition. This ensures that the conversation history is preserved incrementally without manual intervention.
What Gets Stored in the SQLite Database
The SQLite table managed by LangGraph stores the complete state dictionary as a JSON blob for each active thread. The storage schema includes:
- Thread ID: A unique identifier (passed via
configurable={"thread_id": session_id}) that serves as the primary lookup key - State Values: The entire
ThreadStateorSourceChatStatedictionary serialized as JSON - Messages: The
messagesfield containing the ordered list of LangChain message objects representing the full conversation transcript
Each update replaces the previous row for that thread ID, ensuring the database always reflects the latest conversation state. This write-ahead pattern guarantees durability while maintaining a single source of truth for each session's history.
Retrieving Persisted Conversation State
Since SqliteSaver operates synchronously, Open Notebook wraps retrieval calls in asyncio.to_thread to integrate with the async API layer. The retrieval logic resides in open_notebook/utils/graph_utils.py and uses graph.get_state with a RunnableConfig containing the target thread ID.
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"])
This pattern allows the application to resume conversations by fetching the complete message history from SQLite before processing new inputs, ensuring continuity across separate API requests.
Implementation Examples
Initialize the Checkpoint Saver
Run this once during application startup to create the persistent connection:
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)
Compile a Chat Graph with Persistence
from langgraph.graph import StateGraph, END, START
from open_notebook.graphs.chat import ThreadState, call_model_with_messages
graph_builder = StateGraph(ThreadState)
graph_builder.add_node("agent", call_model_with_messages)
graph_builder.add_edge(START, "agent")
graph_builder.add_edge("agent", END)
chat_graph = graph_builder.compile(checkpointer=checkpoint)
Append a Message and Automatically Persist
from langchain_core.runnables import RunnableConfig
result = await chat_graph.ainvoke(
current_state,
config=RunnableConfig(configurable={"thread_id": "session-123"})
)
# The updated state is automatically saved to SQLite
Load the Persisted Conversation History
from open_notebook.utils.graph_utils import get_session_message_count
msg_count = await get_session_message_count(chat_graph, "session-123")
print(f"Session has {msg_count} messages stored in SQLite")
Reset a Session by Deleting Its Checkpoint
import sqlite3
from open_notebook.config import LANGGRAPH_CHECKPOINT_FILE
conn = sqlite3.connect(LANGGRAPH_CHECKPOINT_FILE)
cursor = conn.cursor()
cursor.execute("DELETE FROM checkpoints WHERE thread_id = ?", ("session-123",))
conn.commit()
Summary
- LangGraph checkpoint persistence in Open Notebook uses a local SQLite file at
./data/sqlite-db/checkpoints.sqlitedefined inopen_notebook/config.py - The
SqliteSaverclass wraps a standardsqlite3connection to handle automatic state serialization - Graphs are compiled with
checkpointer=memoryto enable transparent persistence ofThreadStateafter every node execution - The complete conversation history is stored as JSON blobs keyed by thread ID, allowing retrieval via
graph.get_statewrapped inasyncio.to_thread - This architecture provides durable, server-local storage of chat sessions without requiring external database infrastructure
Frequently Asked Questions
Where does Open Notebook store LangGraph checkpoint data?
Open Notebook stores checkpoint data in a SQLite file located at ./data/sqlite-db/checkpoints.sqlite. This path is constructed in open_notebook/config.py by concatenating the DATA_FOLDER constant with sqlite-db/checkpoints.sqlite. The file is created automatically on first use if the directory structure exists.
How does LangGraph handle concurrent access to the SQLite checkpoint file?
The implementation uses check_same_thread=False when opening the SQLite connection in open_notebook/graphs/chat.py and source_chat.py. This allows LangGraph's internal threading model to operate correctly, though it delegates thread-safety management to Python's GIL and LangGraph's own synchronization mechanisms rather than SQLite's thread-safety checks.
What specific data is stored in each SQLite checkpoint row?
Each row stores a JSON-serialized representation of the graph's state dictionary (either ThreadState or SourceChatState). This includes the messages field containing the ordered list of LangChain message objects, along with any other state variables defined in the graph's TypedDict. The data is keyed by a unique thread ID passed through the configurable parameter in RunnableConfig.
How can I retrieve the message count for a specific conversation session?
Use the get_session_message_count utility in open_notebook/utils/graph_utils.py, or call graph.get_state directly with the appropriate thread ID configuration. Since SqliteSaver is synchronous, wrap the call in asyncio.to_thread when using async patterns. The returned state object contains a values dictionary with a messages list whose length represents the conversation count.
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 →