# How the Open Notebook Chat Graph Maintains Conversation History and Context

> Discover how Open Notebook uses LangGraph and SQLite to maintain conversation history and context with its innovative chat graph.

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

---

**Open Notebook persists multi-turn conversations using a LangGraph state machine with SQLite checkpointing, where the `ThreadState` TypedDict stores message history annotated with `add_messages` to automatically append new interactions while preserving context across sessions.**

The chat functionality in Open Notebook relies on a sophisticated state management system built on LangGraph. By combining an in-memory state representation with persistent SQLite storage, the application ensures that conversation history and notebook context remain available across multiple turns and process restarts, as implemented in the `lfnovo/open-notebook` repository.

## ThreadState: The Schema for Conversation Memory

The foundation of conversation persistence lies in the `ThreadState` TypedDict defined in [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py). This state declaration determines which data travels with every graph execution.

The `messages` field uses LangGraph's `add_messages` annotation to automatically handle conversation history:

```python
class ThreadState(TypedDict):
    messages: Annotated[list, add_messages]      # ← the full message history

    notebook: Optional[Notebook]                # optional notebook context

    context: Optional[str]                      # optional text-based context

    context_config: Optional[dict]              # configuration for building context

    model_override: Optional[str]               # per-request model selection

```

The `add_messages` reducer ensures that new AI and user messages append to the existing list rather than replacing it. This creates the in-memory representation of the conversation history that persists throughout the graph's execution cycle.

## Persistent Storage with SQLite Checkpointing

While `ThreadState` maintains conversation data in memory during execution, Open Notebook ensures durability through SQLite-backed checkpointing. The graph compiles with a `SqliteSaver` instance that automatically persists state to disk.

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

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

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)

```

The `LANGGRAPH_CHECKPOINT_FILE` constant, defined in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py), points to `data/sqlite-db/checkpoints.sqlite`. Each time the graph runs, LangGraph stores the entire `ThreadState` in this database. When a new request arrives, the graph restores the latest checkpoint, giving the model access to the full prior `messages` list and any persisted `context` or `notebook` data. This mechanism survives process restarts and enables true multi-turn dialogues.

## Message Processing and State Updates

The `call_model_with_messages` function in [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py) handles the actual interaction with the language model while maintaining state continuity. This node builds a system prompt from the current state, prepends it to the stored messages, executes the LLM, and returns a structured update that merges back into the checkpoint.

When utilizing the chat service API, you interact with this graph through the `execute_chat` method:

```python
import asyncio
from api.chat_service import chat_service

async def chat_turn(session_id: str, user_msg: str):
    response = await chat_service.execute_chat(
        session_id=session_id,
        message=user_msg,
        context={},                     # empty if no extra context

        model_override=None,
    )
    print("Assistant:", response["message"])

# Example usage

asyncio.run(chat_turn("abc123", "What did I write in the last note?"))

```

The internal implementation constructs the payload as follows:

```python
system_prompt = Prompter(prompt_template="chat/system").render(data=state)
payload = [SystemMessage(content=system_prompt)] + state.get("messages", [])

# ...

ai_message = model.invoke(payload)

# ...

cleaned_message = ai_message.model_copy(update={"content": cleaned_content})
return {"messages": cleaned_message}

```

The returned dictionary updates the `messages` field in the state. Because the graph uses the checkpointer, this new AI reply becomes part of the persistent conversation history for the next turn.

## Injecting Notebook Context into Conversations

Beyond raw message history, Open Notebook supports rich context injection through optional state fields. The `context` and `context_config` fields in `ThreadState` store notebook-specific information that influences the model's responses.

The API endpoint `/api/chat/context` (implemented in [`api/chat_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/chat_service.py)) populates these fields via the **ContextBuilder** utility from [`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py). When executing a chat turn with context, the system passes this data through the `execute_chat` method:

```python

# Build context for notebook "nb-01"

context = await chat_service.build_context(
    notebook_id="nb-01",
    context_config={"include_sources": True, "include_insights": True},
)

# Then start a chat session using that context

await chat_service.execute_chat(
    session_id="sess-42",
    message="Summarize the key insights from this notebook.",
    context=context,
)

```

The resulting `context` dict stores in `ThreadState.context`, and the system prompt automatically includes this information on subsequent turns. This allows the model to reference specific notebook content while maintaining awareness of the ongoing conversation history.

## Summary

- **ThreadState** defines the conversation schema using a `TypedDict` with `add_messages` annotation to preserve message history across graph executions.
- **SQLite checkpointing** via `SqliteSaver` persists the full state to `data/sqlite-db/checkpoints.sqlite`, enabling conversation recovery across process restarts.
- **State updates** occur through the `call_model_with_messages` node, which returns new messages that merge into the checkpoint for the next turn.
- **Context injection** supports notebook-specific data through `context` and `context_config` fields, populated via `ContextBuilder` and `execute_chat`.

## Frequently Asked Questions

### How does Open Notebook store chat history persistently?

Open Notebook uses LangGraph's `SqliteSaver` to store the entire `ThreadState` in a SQLite database located at `data/sqlite-db/checkpoints.sqlite`. Each conversation turn creates a checkpoint that survives process restarts, allowing the graph to restore the complete message history and context when a session resumes.

### What is the role of ThreadState in the chat graph?

`ThreadState` is a `TypedDict` defined in [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py) that declares the fields traveling with every graph execution. It includes the `messages` list (annotated with `add_messages` to handle appends), optional `notebook` context, and configuration fields. This state object serves as the single source of truth for the conversation's current condition.

### How does the chat graph handle notebook-specific context?

The graph stores notebook context in the `context` and `context_config` fields of `ThreadState`. The `ContextBuilder` utility generates this payload based on notebook content, and the `call_model_with_messages` function incorporates it into the system prompt. This allows the model to reference specific notes while maintaining conversation continuity.

### Can the chat graph survive process restarts?

Yes. Because the graph compiles with a `SqliteSaver` checkpointer pointing to a persistent SQLite file, the entire conversation state writes to disk after each turn. When the application restarts, the graph restores the latest checkpoint from `data/sqlite-db/checkpoints.sqlite`, recovering the full message history and any associated context.