# How LangGraph Checkpoint Persistence Stores Conversation History in SQLite

> LangGraph SqliteSaver stores conversation history as JSON blobs in SQLite for automatic chat session recovery and continuation. Learn how this persistence works.

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

---

**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`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py). The configuration establishes a dedicated subdirectory within the data folder to isolate checkpoint files from other application data.

```python
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`](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 initialization follows this pattern:

```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)  # 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`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py) demonstrates:

```python
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 `ThreadState` or `SourceChatState` dictionary serialized as JSON
- **Messages**: The `messages` field 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`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/graph_utils.py) and uses `graph.get_state` with a `RunnableConfig` containing the target thread ID.

```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"])

```

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:

```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)
checkpoint = SqliteSaver(conn)

```

### Compile a Chat Graph with Persistence

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

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

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

```python
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.sqlite` defined in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py)
- The `SqliteSaver` class wraps a standard `sqlite3` connection to handle automatic state serialization
- Graphs are compiled with `checkpointer=memory` to enable transparent persistence of `ThreadState` after every node execution
- The complete conversation history is stored as JSON blobs keyed by thread ID, allowing retrieval via `graph.get_state` wrapped in `asyncio.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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py) and [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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.