How to Persist Chat Graph Message History with SQLite Checkpoint Storage in Open Notebook
Open Notebook persists chat graph message history across restarts by wiring LangGraph's SqliteSaver into the graph compilation step, storing state transitions in a local SQLite database file.
Open Notebook leverages LangGraph to orchestrate conversational AI workflows, but by default, LangGraph keeps graph state in memory—meaning chat sessions vanish when the process restarts. To solve this durability problem, the project implements a SQLite checkpoint storage strategy that serializes the entire conversation history to disk. This approach requires no external services and ensures every message, context, and state transition survives application restarts.
Why SQLite Checkpoint Storage Matters
LangGraph's default in-memory state management works for ephemeral workloads but fails for production chat applications that must survive restarts. The SqliteSaver class implements LangGraph's CheckpointSaver interface, automatically persisting the ThreadState (including the messages list) to a SQLite database after every state transition. This file-based approach eliminates external dependencies while providing atomic writes and thread-safe access across multiple execution threads.
Configuring the SQLite Checkpoint Database
The persistence layer starts with environment configuration in open_notebook/config.py, which establishes a dedicated directory for the SQLite database and defines the checkpoint file path.
# open_notebook/config.py
sqlite_folder = f"{DATA_FOLDER}/sqlite-db"
os.makedirs(sqlite_folder, exist_ok=True)
LANGGRAPH_CHECKPOINT_FILE = f"{sqlite_folder}/checkpoints.sqlite"
Initializing the Database Connection
In open_notebook/graphs/chat.py, the application opens a SQLite connection with check_same_thread=False—a critical setting because LangGraph's executor may access the database from multiple threads. The connection is then wrapped in a SqliteSaver instance.
# open_notebook/graphs/chat.py (lines 88-93)
conn = sqlite3.connect(
LANGGRAPH_CHECKPOINT_FILE,
check_same_thread=False,
)
memory = SqliteSaver(conn)
Compiling the Graph with Persistence
The SqliteSaver object is passed as the checkpointer parameter when compiling the StateGraph. This single step enables automatic checkpointing for every node transition.
# open_notebook/graphs/chat.py (lines 94-99)
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)
Defining the Persistable State Schema
The ThreadState TypedDict defines the structure of what gets saved. The messages field uses LangGraph's add_messages annotation to ensure proper message merging during state updates.
# open_notebook/graphs/chat.py (lines 22-28)
class ThreadState(TypedDict):
messages: Annotated[list, add_messages]
notebook: Optional[Notebook]
context: Optional[str]
context_config: Optional[dict]
model_override: Optional[str]
How Message History is Retrieved and Restored
When you invoke the compiled graph, LangGraph automatically reads the latest checkpoint from the SQLite database if a thread ID is provided, restoring the previous messages list and context. The SqliteSaver handles JSON serialization and deserialization transparently, storing the full state payload in the checkpoints table. This means subsequent calls to the same chat thread resume exactly where the conversation left off, with the complete message history intact.
Running a Persisted Chat Session
Once configured, the graph object embeds the SQLite saver, so persistence happens automatically behind the scenes. You only need to manage the state input; the checkpoint storage handles durability.
from open_notebook.graphs.chat import graph # compiled with SQLite checkpoint
# First turn – creates a new checkpoint entry
result = graph.ainvoke(
{
"messages": [], # start with empty history
"notebook": None,
"context": None,
},
config={"configurable": {"model_id": "gpt-4o"}},
)
# Subsequent calls automatically restore from SQLite
result2 = graph.ainvoke(
{
"messages": result["messages"], # feed back persisted list
"notebook": None,
"context": None,
},
config={"configurable": {"model_id": "gpt-4o"}},
)
Inspecting the Checkpoint Database
You can verify persistence directly using the SQLite CLI. The checkpoints table contains checkpoint_id, state (JSON), and metadata columns.
sqlite3 sqlite-db/checkpoints.sqlite "SELECT * FROM checkpoints;"
Summary
- Configure the SQLite database path in
open_notebook/config.pyto ensure thesqlite-dbdirectory exists. - Initialize
SqliteSaverwithcheck_same_thread=Falseinopen_notebook/graphs/chat.pyto support LangGraph's multi-threaded execution. - Pass the saver instance to
StateGraph.compile(checkpointer=memory)to enable automatic checkpointing. - Define
ThreadStatewithAnnotated[list, add_messages]to ensure proper message history merging. - Access persisted chat history automatically on subsequent invocations using the same thread configuration.
Frequently Asked Questions
What is the purpose of check_same_thread=False in the SQLite connection?
This parameter allows the SQLite connection to be shared across multiple threads. LangGraph's executor may access the checkpoint database from different threads during parallel node execution, so disabling the same-thread check is essential for preventing thread-related errors while maintaining thread safety through SQLite's file locking mechanisms.
Where does Open Notebook store the chat checkpoint files?
According to open_notebook/config.py, checkpoints are stored in {DATA_FOLDER}/sqlite-db/checkpoints.sqlite. The application creates this directory automatically at startup if it doesn't exist, placing the database file alongside other application data.
Does adding new fields to ThreadState require database migration?
No. LangGraph stores the entire state as a JSON blob in the state column of the checkpoints table. Adding new fields to the ThreadState TypedDict simply changes the JSON structure stored in new checkpoints, making schema evolution transparent without requiring ALTER TABLE operations.
How does SqliteSaver handle concurrent chat sessions?
The SqliteSaver implements LangGraph's CheckpointSaver interface with atomic write operations. Each chat thread uses a unique checkpoint identifier (derived from the configuration), allowing multiple concurrent conversations to write to the same SQLite file without corruption, as SQLite handles row-level locking and transaction isolation.
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 →