# How the Source Chat Graph Differs from the General Chat Graph in Open-Notebook

> Discover how the source chat graph in Open-Notebook enriches LLM chats with document context from SurrealDB, unlike the general chat graph's basic message loop.

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

---

**The source chat graph automatically enriches LLM conversations with document-specific context by loading source content and insights from SurrealDB, while the general chat graph operates as a simple message loop without external context retrieval.**

Open-Notebook implements two distinct **LangGraph** workflows for handling chat interactions. Understanding how the **source chat graph** differs from the **general chat graph** is critical for developers building context-aware AI features. While both graphs share the same underlying checkpointing and model provisioning infrastructure, they diverge significantly in state management, context construction, and prompt engineering strategies.

## State Shape: ThreadState vs SourceChatState

The most fundamental difference lies in the state definitions that drive each workflow.

**ThreadState** (defined in [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py)) maintains a minimal conversation footprint. It tracks `messages`, an optional `notebook` reference, generic `context`, and an optional `model_override`. This lean structure supports free-form dialogue without external dependencies.

**SourceChatState** (defined in [`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py)) expands this schema to support grounded conversations. It requires a mandatory `source_id` and maintains the loaded `Source` object, a list of `SourceInsight` objects, formatted `context` strings, and `context_indicators` for tracking which insights were utilized. This richer state enables the assistant to reason over specific documents, PDFs, or web pages rather than relying solely on the conversation history.

## Context Construction: The ContextBuilder Pipeline

The general chat graph employs **no automatic context building**. The system prompt renders statically from the `chat/system` template, supplemented only by whatever `context` the caller manually supplies. This creates a truly stateless conversational experience suitable for general Q&A.

The source chat graph implements a sophisticated **ContextBuilder** pipeline (located in [`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py)). When invoked, the graph:

1. Retrieves the source record from SurrealDB using the provided `source_id`
2. Fetches associated insights and metadata
3. Formats them via `_format_source_context` into structured text
4. Injects the built context into the `source_chat/system` prompt template

This automatic enrichment ensures the LLM receives relevant source material with every turn, enabling grounded responses that cite specific document content.

## Prompt Template Architecture

The two graphs utilize distinct Jinja2 templates that reflect their operational modes.

**`chat/system`** serves the general graph with a generic system prompt designed for unconstrained conversation. It expects minimal variables and focuses on conversational continuity.

**`source_chat/system`** accepts four specific fields: `source`, `insights`, `context`, and `context_indicators`. This template structure forces the LLM to acknowledge and reference the provided source material, creating a Retrieval-Augmented Generation (RAG) pattern without requiring manual prompt engineering at the call site.

## Checkpointing and State Persistence

Both graphs leverage identical checkpointing mechanisms through **SQLite** (`SqliteSaver`) using the file path defined in `LANGGRAPH_CHECKPOINT_FILE` (configured in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py)). However, the source chat graph stores significantly richer checkpoint data due to its additional state fields. When resuming a thread, the source chat graph restores not just the message history, but the complete source object and insight cache, maintaining conversation continuity across sessions.

## Model Provisioning

Both workflows utilize the `provision_langchain_model` helper from [`open_notebook/ai/provision.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/provision.py) to handle async-to-sync model initialization. The key difference lies in the payload construction: the general chat graph sends only the system prompt plus message history, while the source chat graph includes the enriched source-aware system prompt containing formatted document content.

## Practical Implementation Examples

### Invoking the General Chat Graph

Use this pattern for simple conversational interactions without external document context:

```python
from open_notebook.graphs.chat import graph as chat_graph
from langgraph.graph import RunnableConfig
from langchain_core.messages import HumanMessage

# Simple state with a user message

state = {
    "messages": [HumanMessage(content="Hey, how are you?")],
    "model_override": None,
}

# Run the graph (no source context)

result = chat_graph.ainvoke(state, config=RunnableConfig())
print(result["messages"].content)   # → LLM reply

```

### Invoking the Source Chat Graph

Use this pattern when you need document-grounded responses. The `source_id` must exist in SurrealDB:

```python
from open_notebook.graphs.source_chat import source_chat_graph
from langgraph.graph import RunnableConfig
from langchain_core.messages import HumanMessage

# State must include a source identifier

state = {
    "messages": [HumanMessage(content="Summarize the key points.")],
    "source_id": "src_12345",      # Identifier of the source record in SurrealDB

    "model_override": None,
}

# Run the graph – it will automatically load the source and its insights

result = source_chat_graph.ainvoke(state, config=RunnableConfig())
print(result["messages"].content)          # LLM reply based on source

print(result["source"].title)              # Title of the source document

print([i.insight_type for i in result["insights"]])  # Types of insights used

```

The second example demonstrates how the source chat graph enriches the conversation with domain-specific data, returning the `source`, `insights`, `context`, and `context_indicators` alongside the messages for downstream UI rendering or further processing.

## Summary

- **The general chat graph** ([`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py)) provides a minimal **ThreadState** implementation for stateless, free-form conversation using the `chat/system` prompt template.
- **The source chat graph** ([`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py)) extends this with **SourceChatState**, requiring a `source_id` and automatically fetching document content via **ContextBuilder**.
- Both graphs share **SQLite checkpointing** and model provisioning infrastructure, but the source chat graph persists richer state including insights and metadata.
- **Template specialization** drives the behavioral difference: `source_chat/system` expects source-specific variables while `chat/system` remains generic.
- Choose the general graph for simple Q&A and the source graph when you need citations and reasoning grounded in specific documents stored in SurrealDB.

## Frequently Asked Questions

### When should I use the source chat graph versus the general chat graph?

Use the **source chat graph** when your application requires the LLM to answer questions about specific documents, PDFs, or web pages stored in SurrealDB. The automatic context injection ensures grounded, citeable responses. Use the **general chat graph** for open-ended conversations, brainstorming, or tasks that do not require reference to external source material, as it offers lower latency and simpler state management.

### How does the ContextBuilder integrate with the database?

The **ContextBuilder** (in [`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py)) queries SurrealDB using the `source_id` provided in the state. It retrieves the raw source content, associated metadata, and any pre-computed **SourceInsight** objects, then formats them into a structured context string via `_format_source_context`. This eliminates the need for manual database queries in your application code.

### Can I manually provide context to the general chat graph?

Yes. While the general chat graph lacks automatic context retrieval, you can populate the `context` field in **ThreadState** with custom strings before invocation. However, this requires manual formatting and does not provide the structured insight tracking or automatic source object management available in the dedicated source chat implementation.

### What happens if the source_id is missing when invoking the source chat graph?

The **SourceChatState** requires `source_id` as a mandatory field. If omitted, the graph will fail during state validation before reaching the LLM call. Ensure your application validates the presence of a valid SurrealDB record identifier before invoking `source_chat_graph.ainvoke()`, as the graph depends on this ID to fetch the source content and insights.