# How to Persist Chat Graph Message History with SQLite Checkpoint Storage in Open Notebook

> Learn how to persist chat graph message history using Open Notebook and SQLite checkpoint storage. This guide shows you how to wire LangGraph into your graph compilation for reliable state saving.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-07-05

---

**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`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py), which establishes a dedicated directory for the SQLite database and defines the checkpoint file path.

```python

# 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`](https://github.com/lfnovo/open-notebook/blob/main/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.

```python

# 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.

```python

# 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.

```python

# 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.

```python
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.

```bash
sqlite3 sqlite-db/checkpoints.sqlite "SELECT * FROM checkpoints;"

```

## Summary

- Configure the SQLite database path in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py) to ensure the `sqlite-db` directory exists.
- Initialize `SqliteSaver` with `check_same_thread=False` in [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py) to support LangGraph's multi-threaded execution.
- Pass the saver instance to `StateGraph.compile(checkpointer=memory)` to enable automatic checkpointing.
- Define `ThreadState` with `Annotated[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`](https://github.com/lfnovo/open-notebook/blob/main/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.