# How LangGraph SQLite Checkpoint Storage Persists Workflow State in Open Notebook

> Learn how LangGraph SQLite checkpoint storage persists workflow state. Discover how SqliteSaver serializes your state graph to a SQLite database for seamless session recovery and continuity. Explore the Open Notebook repository.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: deep-dive
- Published: 2026-06-07

---

**LangGraph's SQLite checkpoint storage persists workflow state by serializing the full state graph to a SQLite database file using the `SqliteSaver` class, enabling seamless session recovery and conversation continuity across process restarts.**

The open-notebook project implements durable workflow state management by leveraging LangGraph's built-in checkpoint mechanism backed by SQLite. This file-based persistence layer ensures that chat sessions and source ingestion workflows survive application restarts without requiring external database infrastructure.

## Configuring the SQLite Checkpoint File

The checkpoint database path is defined in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py), which establishes a dedicated directory structure at startup. The configuration creates a `./data/sqlite-db` folder and constructs the absolute path for the checkpoint store:

```python

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

```

This centralized configuration ensures all graph instances reference the same persistence file, making the stored state easy to back up, inspect with standard SQLite clients, or delete when cleaning up notebook data.

## Initializing the SqliteSaver Connection

Each LangGraph workflow initializes a `SqliteSaver` instance by opening a SQLite connection with `check_same_thread=False`. This parameter is critical because LangGraph may invoke the saver from multiple threads during state transitions.

In [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py), the connection is established as follows:

```python

# open_notebook/graphs/chat.py

conn = sqlite3.connect(
    LANGGRAPH_CHECKPOINT_FILE,
    check_same_thread=False,
)
memory = SqliteSaver(conn)

```

The same pattern appears in [`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py), ensuring consistent checkpoint behavior across both chat and source-ingestion workflows.

## Attaching the Saver to the State Graph

To enable automatic persistence, the state graph is compiled with the `checkpointer` argument. This binds the `SqliteSaver` instance to the graph, instructing LangGraph to write every state transition to the SQLite database under a unique **thread identifier**:

```python
graph = agent_state.compile(checkpointer=memory)

```

Once compiled, each call to `graph.update_state()` or automated state transitions triggers an `INSERT` or `REPLACE` operation in the underlying database, capturing the complete workflow state including message history, notebook references, and execution metadata.

## How State Persistence Works Internally

The `SqliteSaver` creates an internal `checkpoints` table containing columns for `thread_id`, `checkpoint`, and a JSON-encoded representation of the LangGraph state. This structure stores serialized versions of the `messages` list, notebook context, and other workflow variables.

When a state transition occurs, LangGraph automatically serializes the current state graph and persists it to this table. The `thread_id` acts as the session identifier, allowing multiple concurrent conversations to coexist in the same database file without collision.

## Retrieving Persisted State Across Sessions

When resuming a conversation, the application retrieves the stored state by calling `graph.get_state()` with a `RunnableConfig` containing the original `thread_id`. Because `SqliteSaver` operates synchronously, [`open_notebook/utils/graph_utils.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/graph_utils.py) wraps this call in an asynchronous thread:

```python

# open_notebook/utils/graph_utils.py

thread_state = await asyncio.to_thread(
    graph.get_state,
    config=RunnableConfig(configurable={"thread_id": session_id}),
)

```

The returned `thread_state.values["messages"]` contains the exact list of `Message` objects from the previous session, allowing the workflow to continue precisely where it left off.

## Why SQLite for Workflow State Persistence?

SQLite provides zero-configuration, file-based persistence ideal for the lightweight deployment targets of the open-notebook project, including Docker containers and local development machines. Unlike PostgreSQL or Redis alternatives, SQLite requires no separate server process while still providing ACID guarantees for state durability.

The checkpoint file location at `./data/sqlite-db/checkpoints.sqlite` makes persisted state portable and manageable through standard filesystem operations.

## Complete Implementation Example

The following pattern demonstrates creating a persistent LangGraph workflow using the SQLite checkpoint storage:

```python
from langgraph.graph import StateGraph, END, START
from langgraph.checkpoint.sqlite import SqliteSaver
from langchain_core.runnables import RunnableConfig
import sqlite3
import asyncio
from typing import TypedDict, Optional

# Configuration from open_notebook/config.py

LANGGRAPH_CHECKPOINT_FILE = "./data/sqlite-db/checkpoints.sqlite"

# Define state schema

class ThreadState(TypedDict):
    messages: list
    notebook: Optional[dict]

# Initialize SQLite saver

conn = sqlite3.connect(LANGGRAPH_CHECKPOINT_FILE, check_same_thread=False)
checkpoint = SqliteSaver(conn)

# Build graph

graph_builder = StateGraph(ThreadState)
graph_builder.add_node("agent", lambda state: state)  # Replace with actual node logic

graph_builder.add_edge(START, "agent")
graph_builder.add_edge("agent", END)

# Compile with persistence

chat_graph = graph_builder.compile(checkpointer=checkpoint)

# Resume session async wrapper

async def resume_session(session_id: str):
    state = await asyncio.to_thread(
        chat_graph.get_state,
        config=RunnableConfig(configurable={"thread_id": session_id}),
    )
    return state.values.get("messages", [])

```

## Summary

- **LangGraph SQLite checkpoint storage** uses the `SqliteSaver` class to serialize workflow state to a file-based database.
- The checkpoint file path is centralized in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py) at `./data/sqlite-db/checkpoints.sqlite`.
- Graphs connect to SQLite with `check_same_thread=False` to support multi-threaded state updates.
- State persistence occurs automatically when compiling graphs with `compile(checkpointer=memory)`.
- The `checkpoints` table stores JSON-encoded state indexed by `thread_id` for session isolation.
- Resume operations use `graph.get_state()` with `RunnableConfig`, wrapped in `asyncio.to_thread` for async compatibility.

## Frequently Asked Questions

### How does LangGraph store state in SQLite?

LangGraph stores state in SQLite through the `SqliteSaver` class, which creates an internal table named `checkpoints`. Each row contains a `thread_id`, checkpoint metadata, and a JSON-encoded blob of the complete state graph. When state transitions occur, LangGraph automatically executes `INSERT` or `REPLACE` operations to update the persisted snapshot.

### What is the purpose of check_same_thread=False in the SQLite connection?

The `check_same_thread=False` parameter is required because LangGraph's execution model may invoke the saver from multiple threads during workflow transitions. Without this flag, SQLite would raise threading errors when the checkpoint saver attempts to write state updates from different execution contexts.

### Where is the checkpoint data stored in Open Notebook?

According to [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py), checkpoint data persists to `./data/sqlite-db/checkpoints.sqlite` relative to the application root. This file location is defined by the `LANGGRAPH_CHECKPOINT_FILE` constant and created automatically at startup if it does not exist.

### Can multiple workflows share the same SQLite checkpoint file?

Yes, multiple workflows can share the same SQLite checkpoint file because LangGraph uses `thread_id` as a unique namespace for each session. As implemented 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), different graph instances can reference the same `LANGGRAPH_CHECKPOINT_FILE` while maintaining isolated state through distinct thread identifiers passed via `RunnableConfig`.