# LangGraph Checkpoint Storage with SQLite: How Open Notebook Persists Chat State

> Learn how LangGraph checkpoint storage with SQLite persists chat state by automatically saving every message exchange to a local SQLite file for seamless conversation resumption.

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

---

**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`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py). The repository defines a dedicated data directory structure to ensure persistent storage across deployments:

```python
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`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py) and [`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py), the code establishes a synchronous SQLite connection and wraps it with LangGraph's `SqliteSaver` class:

```python
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`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py), the `StateGraph` uses `ThreadState`—a TypedDict containing a `messages` field that holds the conversation history:

```python
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`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/graph_utils.py) use `asyncio.to_thread` to prevent event loop blocking:

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

```python

# 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.sqlite` as defined in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py)
- **Connection setup**: `SqliteSaver` wraps a `sqlite3.Connection` with `check_same_thread=False` to support async usage
- **Graph compilation**: The `checkpointer` parameter in `StateGraph.compile()` enables automatic persistence of `ThreadState` after each node execution
- **State retrieval**: Use `graph.get_state()` with a `RunnableConfig` containing `thread_id`, wrapped in `asyncio.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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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.