# How the Open Notebook Chat Graph Maintains Conversation History: LangGraph State Management Explained

> Discover how the Open Notebook chat graph sustains conversation history using LangGraph ThreadState and SQLite checkpointing. Learn about effective state management for your LLM applications.

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

---

**The Open Notebook chat graph maintains conversation history by using LangGraph's `ThreadState` with an `add_messages` annotation on the messages field, combined with SQLite checkpointing that persists the full state between turns.**

Open Notebook leverages LangGraph to power its conversational AI capabilities, implementing a robust state machine that preserves multi-turn dialogue across sessions. Understanding how the chat graph maintains conversation history requires examining the interplay between in-memory state management and persistent storage mechanisms in the [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py) module.

## ThreadState: The Foundation of Conversation Memory

The conversation history lives in a strictly typed state container called `ThreadState`, defined in [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py). This `TypedDict` declares all fields that travel with every graph execution, ensuring type safety and clear data contracts.

### Understanding the State Schema

The `ThreadState` class defines five key fields, with `messages` being the primary carrier of 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 **critical mechanism** for history preservation is the `Annotated[list, add_messages]` type hint on the `messages` field. The `add_messages` reducer function from LangGraph automatically appends new AI and user messages to the existing list without overwriting previous entries. This annotation ensures that every graph execution receives the complete conversation history accumulated up to that point.

## SQLite Checkpointing for Persistent Storage

While `ThreadState` holds conversation data in memory during execution, Open Notebook ensures durability through SQLite-backed checkpointing. This persistence layer allows the chat graph to maintain conversation history across process restarts and server redeployments.

### How SqliteSaver Works

In [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py), the graph compiles with a `SqliteSaver` instance connected to a dedicated SQLite database:

```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 executes, LangGraph serializes the entire `ThreadState`—including the accumulated `messages` list—and stores it in this database. When a new request arrives for an existing session, the graph restores the latest checkpoint, making the full prior conversation available to the language model.

## The Message Update Cycle

The actual conversation flow happens in the `call_model_with_messages` node, which handles the interaction between state retrieval, model invocation, and history updates.

### Building Context and Invoking the Model

The node function constructs the payload for the LLM by combining a system prompt with the stored message history:

```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 function returns a dictionary containing the new AI message under the `messages` key. LangGraph's `add_messages` reducer merges this return value into the existing state, appending the assistant's response to the conversation history. This updated state is then checkpointed to SQLite, ensuring the next turn will include this exchange in the historical context.

## Optional Context and Notebook Integration

Beyond raw message history, the chat graph maintains conversation history alongside rich notebook context. The `context` and `context_config` fields in `ThreadState` allow the system to inject notebook-specific information into the conversation.

The API endpoint `/api/chat/context` (implemented in [`api/context_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/context_service.py)) can populate these fields using 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 present, this contextual data is rendered into the system prompt on subsequent calls, allowing the assistant to reference specific notes, sources, or insights while maintaining awareness of the ongoing conversation thread.

## Summary

- **State Definition**: The `ThreadState` TypedDict in [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py) uses `Annotated[list, add_messages]` to ensure messages accumulate rather than overwrite.
- **Persistence**: SQLite checkpointing via `SqliteSaver` stores the full state to `data/sqlite-db/checkpoints.sqlite`, enabling history survival across restarts.
- **Update Cycle**: The `call_model_with_messages` node retrieves history, invokes the LLM, and returns new messages that LangGraph appends to the checkpoint.
- **Context Integration**: Optional notebook context travels with the state, allowing the assistant to reference specific materials while maintaining conversation continuity.

## Frequently Asked Questions

### How does the chat graph handle message history across multiple sessions?

Each chat session receives a unique identifier that maps to a specific checkpoint in the SQLite database. When a session resumes, LangGraph loads the corresponding `ThreadState` from `data/sqlite-db/checkpoints.sqlite`, restoring the complete `messages` list and any associated context. This allows users to continue conversations exactly where they left off, even after closing and reopening the application.

### What happens if the SQLite database is deleted?

Deleting the checkpoint database removes all persisted conversation history. The `ThreadState` schema remains functional, but new sessions will start with empty message lists. The application will recreate the database file on the next graph execution, though previous conversation threads will be irrecoverable unless backed up separately.

### Can conversation history be exported or migrated?

The checkpoint system stores state as serialized data in standard SQLite tables. While the repository does not provide explicit export utilities, the `checkpoints.sqlite` file contains structured data that can be queried using standard SQL. Each checkpoint row includes the thread ID and serialized state, making it technically possible to extract conversation history for backup or migration purposes.

### How does context from notebooks integrate with message history?

Notebook context is stored separately in the `context` field of `ThreadState` while the conversation proceeds. When the `call_model_with_messages` node executes, it renders this context into the system prompt alongside the message history. This dual-layer approach ensures the assistant has access both to the conversational thread (via `messages`) and to relevant notebook materials (via `context`), creating coherent, context-aware responses.