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

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 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. 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:

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, the graph compiles with a SqliteSaver instance connected to a dedicated SQLite database:

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, 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:

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) can populate these fields using the ContextBuilder utility from 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →