How Open Notebook Persists Chat History with LangGraph Checkpointing in SQLite
Open Notebook uses LangGraph's SqliteSaver to automatically serialize conversation states into a SQLite database, enabling persistent chat histories across sessions without custom database logic.
Open Notebook is an open-source project that manages AI conversations through LangGraph's checkpointing system. The implementation stores complete message histories in a dedicated SQLite file, allowing stateful interactions to resume seamlessly across API calls. This architecture leverages the SqliteSaver class from langgraph.checkpoint.sqlite to handle serialization and state management automatically.
Configuring the SQLite Checkpoint Database
Checkpoint File Location
The repository centralizes checkpoint configuration in open_notebook/config.py. This file defines a dedicated folder for SQLite storage and establishes the path to the checkpoint database.
# 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"
The LANGGRAPH_CHECKPOINT_FILE constant points to data/sqlite-db/checkpoints.sqlite, which serves as the persistent store for all LangGraph states across the application.
Directory Initialization
The configuration ensures the directory exists at runtime using os.makedirs with exist_ok=True. This prevents startup errors when the data folder is missing while maintaining a predictable location for the database file.
Initializing the SqliteSaver
Graphs that require persistence import SqliteSaver from langgraph.checkpoint.sqlite and bind it to the configured database file. In open_notebook/graphs/chat.py, the implementation establishes a SQLite connection with specific threading parameters to support concurrent access.
# 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 pattern appears in open_notebook/graphs/source_chat.py for source-specific chat implementations. The check_same_thread=False parameter is critical for FastAPI deployments, allowing multiple concurrent requests to safely share the same SQLite connection.
Defining Conversation State with Messages
The chat graph defines its state using a TypedDict that includes a messages field annotated with add_messages. This annotation instructs LangGraph on how to merge new messages into existing conversation history during graph execution.
# open_notebook/graphs/chat.py
from typing import Annotated
class ThreadState(TypedDict):
messages: Annotated[list, add_messages] # Persists the full message list
notebook: Optional[Notebook]
# ... additional fields
The add_messages reducer function ensures that when a node returns new messages, they are appended to the existing list rather than replacing it. This mechanism preserves the complete conversation thread across checkpoint saves.
Compiling the Graph with Persistence
After defining nodes and edges, the graph is compiled with the SqliteSaver instance passed as the checkpointer parameter. This binding enables automatic state persistence at each step.
# open_notebook/graphs/chat.py
agent_state = StateGraph(ThreadState)
agent_state.add_node("agent", call_model_with_messages)
agent_state.add_edge(START, "agent")
agent_state.add_edge("agent", END)
graph = agent_state.compile(checkpointer=memory) # Enables persistence
The compile(checkpointer=memory) call configures the graph to automatically serialize the current state to SQLite after each node execution, storing the complete ThreadState including the accumulated messages.
Running and Resuming Conversations
When invoking the graph, you pass a RunnableConfig containing a unique checkpoint_id. LangGraph uses this identifier to load the previous state from SQLite, inject the saved message history, and execute the next step.
# Example usage pattern
from open_notebook.graphs.chat import graph
from langchain_core.messages import HumanMessage
# First invocation creates the checkpoint
config = {"configurable": {"checkpoint_id": "chat-abc-123", "model_id": "gpt-4"}}
result = graph.invoke(
{"messages": [HumanMessage(content="Hello")], "notebook": None},
config=config
)
# Subsequent calls with the same checkpoint_id automatically restore history
config = {"configurable": {"checkpoint_id": "chat-abc-123", "model_id": "gpt-4"}}
continuation = graph.invoke(
{"messages": [HumanMessage(content="Tell me more")], "notebook": None},
config=config
)
The SqliteSaver writes the updated state back to checkpoints.sqlite under the same identifier, ensuring that continuation["messages"] contains the full conversation history including the new exchange.
Handling Concurrent Access
The SQLite connection is initialized with check_same_thread=False to accommodate asyncio-based servers like FastAPI. This setting allows the same connection object to be used across different threads, which is safe because SqliteSaver handles synchronization internally. The checkpoints table stores each state as a JSON blob keyed by checkpoint_id, with individual rows containing the serialized message list and other state fields.
Summary
- Open Notebook stores LangGraph checkpoints in
data/sqlite-db/checkpoints.sqliteas defined inopen_notebook/config.py. - SqliteSaver instances are created in
open_notebook/graphs/chat.pyandsource_chat.pyusing connections configured withcheck_same_thread=Falsefor concurrent access. - ThreadState uses
Annotated[list, add_messages]to ensure message lists are merged rather than overwritten during state updates. - Graph compilation with
checkpointer=memoryenables automatic persistence of the full state after each execution step. - Conversation continuity is achieved by passing consistent
checkpoint_idvalues in theRunnableConfig, allowing LangGraph to retrieve and update the stored message history across separate API calls.
Frequently Asked Questions
Where are chat histories physically stored in Open Notebook?
Chat histories are stored in a SQLite database file located at ./data/sqlite-db/checkpoints.sqlite relative to the application root. This path is defined in open_notebook/config.py through the LANGGRAPH_CHECKPOINT_FILE constant, which is imported by graph implementations to ensure a centralized storage location.
How does LangGraph determine which conversation to restore?
LangGraph identifies conversations using the checkpoint_id parameter passed within the configurable dictionary of a RunnableConfig. When you invoke the graph with a specific checkpoint_id, the SqliteSaver loads the corresponding row from the SQLite checkpoints table and injects the previously saved state—including the messages list—into the graph's execution context.
Can multiple users access the SQLite database simultaneously?
Yes, the implementation supports concurrent access because the SQLite connection is created with check_same_thread=False. This configuration is essential for FastAPI applications where multiple requests may access the database from different threads. The SqliteSaver manages internal synchronization, allowing safe shared access to the checkpoint file across concurrent API calls.
What format are the messages stored in?
Messages are serialized as JSON within the SQLite database. The SqliteSaver serializes the entire ThreadState dictionary—including the list of LangChain Message objects—into a JSON format stored in the checkpoints table. When the conversation resumes, this JSON is deserialized back into Python objects, preserving the complete conversation structure and metadata.
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 →