# Checkpointing and Message History Persistence in Open Notebook Chat Sessions

> Open Notebook saves chat session history with LangGraph checkpointing to a local SQLite database, ensuring message persistence through process restarts via thread-isolated state.

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

---

**Open Notebook persists every chat session using LangGraph checkpointing stored in a local SQLite database, enabling message history to survive process restarts through thread-isolated state management.**

The `lfnovo/open-notebook` repository implements durable chat history by combining LangGraph's checkpointing system with SQLite persistence. This architecture ensures that every message, context override, and model response is automatically preserved across API restarts and container redeployments.

## SQLite Checkpoint Configuration

The checkpoint file location is defined in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py), which points to a persistent SQLite database at `data/sqlite-db/checkpoints.sqlite`. This file stores the serialized state of all active and historical chat sessions, making it the single source of truth for conversation continuity.

## LangGraph State Graph Setup

The chat workflow is declared in [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py), where a **LangGraph** state graph manages the conversation flow. Lines 88-92 instantiate a `SqliteSaver` with a connection to the checkpoint file:

```python

# Conceptual excerpt from open_notebook/graphs/chat.py

memory = SqliteSaver.from_conn_string(Config.LANGGRAPH_CHECKPOINT_FILE)

```

The graph (`agent_state`) is compiled with `checkpointer=memory`, instructing LangGraph to read and write session state from that SQLite saver. This binding ensures that every state transition is automatically persisted to disk.

## Session Isolation via Thread IDs

When a request hits the `/chat` endpoints, the session identifier is passed as the LangGraph `thread_id`. In [`api/routers/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/chat.py) (lines 119-124), this key isolates each conversation's state inside the same SQLite file, preventing cross-contamination between different chat sessions. This mechanism allows multiple conversations to share one database while maintaining strict separation of their message histories.

## Message Persistence Flow

Each time the model is invoked via `call_model_with_messages` in [`graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/graphs/chat.py), the returned `AIMessage` is stored back into the `messages` field of the state dictionary. Because the `SqliteSaver` persists this state after every graph step, the full message list survives process restarts and can be reconstructed on subsequent API calls. The persistence happens automatically within the LangGraph framework without requiring explicit database writes in the business logic.

## Querying Message History

To retrieve conversation metadata without loading entire message payloads, [`open_notebook/utils/graph_utils.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/graph_utils.py) provides `get_session_message_count()`. This helper fetches the current `messages` list from the checkpoint via a thread-safe call to `graph.get_state` and returns its length. The API uses this in [`api/routers/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/chat.py) (lines 112-114) to report `message_count` on session resources efficiently.

## Implementation Examples

The following examples demonstrate how to interact with persisted chat sessions using the Open Notebook API.

### Creating a New Session

```python
import httpx

# Create a session for notebook "nb-123"

resp = httpx.post(
    "http://localhost:5055/chat/sessions",
    json={"notebook_id": "nb-123", "title": "Research Q&A"},
)
session = resp.json()
print(session["id"])      # → e.g. "chat_session:abcd1234"

```

The session ID is automatically prefixed with `chat_session:` because the router uses that format when persisting the state.

### Sending Messages

```python
msg = {
    "session_id": session["id"],
    "message": "What are the key findings from the latest PDF?",
    "context": {},                # optional source/note context

}
resp = httpx.post("http://localhost:5055/chat/execute", json=msg)
print(resp.json()["messages"][-1]["content"])

```

Behind the scenes, [`graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/graphs/chat.py) builds the prompt, provisions the model, and stores the new `AIMessage` in the checkpoint.

### Retrieving Full Message History

```python
resp = httpx.get(f"http://localhost:5055/chat/sessions/{session['id']}")
history = resp.json()["messages"]
for m in history:
    print(f"{m['type']}: {m['content']}")

```

The router calls `graph.get_state` (via `utils/graph_utils.get_session_message_count`) to pull the persisted `messages` list from the SQLite checkpoint.

### Getting Message Counts

```python
resp = httpx.get("http://localhost:5055/chat/sessions", params={"notebook_id": "nb-123"})
for s in resp.json():
    print(f"Session {s['id']} – {s['message_count']} messages")

```

The count is calculated without loading the entire message payload, using the helper in [`open_notebook/utils/graph_utils.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/graph_utils.py).

## Key Implementation Files

- **[`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py)** – Defines `LANGGRAPH_CHECKPOINT_FILE` path and creates the SQLite folder structure.
- **[`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py)** – Declares the `ThreadState`, builds the LangGraph, and wires the `SqliteSaver` checkpoint (lines 88-92).
- **[`api/routers/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/chat.py)** – FastAPI endpoints for creating sessions, sending messages, and retrieving history; passes `thread_id` to LangGraph (lines 119-124) and reports message counts (lines 112-114).
- **[`open_notebook/utils/graph_utils.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/graph_utils.py)** – Helper `get_session_message_count` reads the checkpoint in a thread-safe way.
- **[`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py)** – Demonstrates the same checkpointing pattern for source-specific chats.

## Summary

- **SQLite-backed persistence**: All chat states are stored in `data/sqlite-db/checkpoints.sqlite` as defined in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py).
- **LangGraph integration**: The `SqliteSaver` checkpoint is bound to the state graph in [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py) (lines 88-92), ensuring automatic state serialization.
- **Session isolation**: Each chat session uses a unique `thread_id` passed via [`api/routers/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/chat.py) (lines 119-124) to isolate conversations within the shared database.
- **Automatic message storage**: Model outputs are persisted through the `call_model_with_messages` flow without manual database operations.
- **Efficient metadata queries**: 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) provides thread-safe access to conversation length without loading full histories.

## Frequently Asked Questions

### Where are Open Notebook chat sessions stored?

Chat sessions are persisted in a SQLite database located at `data/sqlite-db/checkpoints.sqlite`, as configured in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py). This file contains the LangGraph checkpoints for all conversation states, ensuring durability across process restarts.

### How does Open Notebook isolate different chat sessions?

The system uses LangGraph's `thread_id` parameter to isolate sessions. In [`api/routers/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/chat.py) (lines 119-124), each session's unique identifier is passed as the `thread_id`, which keys the state in the SQLite checkpoint file. This allows multiple conversations to coexist in one database without state leakage.

### Can message history survive a container restart?

Yes. Because checkpoints are written to a persistent SQLite file on disk rather than held in memory, the full message history survives Docker container restarts, API process restarts, and host machine reboots. The `SqliteSaver` ensures state is committed to disk after each graph step.

### How does the API retrieve message counts without loading full histories?

The API uses `get_session_message_count()` from [`open_notebook/utils/graph_utils.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/graph_utils.py), which calls `graph.get_state` with the session's `thread_id` and returns only the length of the `messages` list. This approach, utilized in [`api/routers/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/chat.py) (lines 112-114), avoids the overhead of deserializing entire conversation histories when only metadata is needed.