# Source Chat vs Ask LangGraph Workflows in Open Notebook: Architecture and Implementation

> Understand Source Chat vs. Ask LangGraph workflows in Open Notebook. Explore their architecture, implementation, and differences in state, topology, and model provisioning.

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

---

**Open Notebook implements two distinct LangGraph state-graph workflows—Source Chat for single-source contextual dialog and Ask for multi-search knowledge synthesis—each utilizing different state definitions, graph topologies, and model provisioning strategies.**

Open Notebook is an open-source knowledge management platform that leverages LangGraph to orchestrate complex LLM interactions across your personal knowledge base. The repository defines specialized workflow patterns in [`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py) and [`open_notebook/graphs/ask.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/ask.py) to handle fundamentally different query patterns: deep exploration of individual documents versus open-ended questions across the entire vector store. Understanding these LangGraph workflow differences is critical for developers extending the platform or debugging conversation flows.

## Source Chat Workflow: Single-Source Contextual Conversation

The **Source Chat** workflow facilitates conversational exploration of a single source—such as a PDF, webpage, or video transcript—along with its extracted insights. This workflow builds a comprehensive full-text context from the source and injects it directly into the LLM prompt.

### Graph Structure and Entry Point

The workflow compiles via `source_chat_state.compile(...)` to create a state graph with a **single node** named `source_chat_agent`. This node executes the `call_model_with_source_context` function to generate responses.

The execution flow is strictly linear:

```

START → source_chat_agent → END

```

Because the workflow only needs to generate a single contextualized response, it contains **no conditional branching** or sub-graphs.

### State Management with SourceChatState

The workflow uses the `SourceChatState` TypedDict defined in [`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py):

- **messages**: List of conversation history (AIMessage/SystemMessage objects)
- **source_id**: The specific source being discussed
- **source** and **insights**: The loaded source record and its derived insights
- **context**: The formatted text up to 50k tokens injected into the prompt
- **context_indicators**: Dictionary tracking which sources and insights were referenced
- **model_override**: Optional model ID override for this specific conversation

### Context Building Strategy

Unlike the Ask workflow, Source Chat utilizes the `ContextBuilder` class from [`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py) to assemble source content. The node pulls the source record and insights, then formats them using `_format_source_context` into a structured text block. This full context is rendered via the `source_chat/system` Jinja2 template and passed directly to the model, enabling precise citations to specific document sections.

### Synchronous Model Provisioning

The `source_chat_agent` node calls `provision_langchain_model` **synchronously** within a new event loop using `run_in_new_loop` (see implementation in [`open_notebook/ai/provision.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/provision.py)). This design accommodates the synchronous context in which the graph often executes while supporting optional `model_override` parameters.

## Ask Workflow: Multi-Step Knowledge Synthesis

The **Ask** workflow answers open-ended questions by orchestrating multiple vector searches, generating intermediate answers for each search result, and synthesizing a final comprehensive response.

### Graph Structure and Entry Point

Compiling via `agent_state.compile()` builds a graph with three primary nodes:
1. **`agent`**: Generates a search strategy by deciding which queries to execute
2. **`provide_answer`**: Executes vector searches and generates answers for each search term
3. **`write_final_answer`**: Synthesizes all sub-answers into the final response

### State Management with ThreadState

The `ThreadState` defined in [`open_notebook/graphs/ask.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/ask.py) tracks multi-step progress:

- **question**: The original user query
- **strategy**: A list of `Search` objects (JSON-generated by the LLM) describing search terms
- **answers**: Accumulated list of intermediate answers from each search
- **final_answer**: The synthesized final response string

### Conditional Execution and Sub-Graphs

Unlike the linear Source Chat flow, Ask implements **conditional branching** using the `trigger_queries` edge function. After the `agent` node generates a strategy, the graph evaluates `trigger_queries` to create **separate sub-graph executions** for each `Search` object in the strategy. Each sub-graph runs the `provide_answer` node independently, enabling parallel processing of multiple search results before the mandatory `write_final_answer` node finalizes the output.

### Search-Based Context Retrieval

The Ask workflow does not use the `ContextBuilder`. Instead, it passes raw **vector search results** from SurrealDB directly to the LLM. The `provide_answer` node retrieves relevant chunks and renders them via the `ask/query_process` prompt template—a concise format containing the raw search hits and user instructions rather than a pre-built 50k token context.

### Asynchronous Model Provisioning

The Ask workflow calls `await provision_langchain_model` **asynchronously** throughout, supporting different model configurations for each phase:
- `strategy_model`: For generating the search strategy
- `answer_model`: For processing individual search results
- `final_answer_model`: For synthesizing the comprehensive response

## Critical Architectural Differences

**Graph Topology**: Source Chat uses a **linear single-node** execution path, while Ask implements **conditional branching** with dynamic sub-graph creation via `trigger_queries`.

**Context Strategy**: Source Chat pre-materializes up to 50k tokens of source context using `ContextBuilder` and `_format_source_context`, whereas Ask retrieves **raw vector search results** from SurrealDB on-demand during sub-graph execution.

**Execution Model**: Source Chat provisions models **synchronously** using `run_in_new_loop` to bridge async/sync contexts, while Ask operates fully **asynchronously** with `await provision_langchain_model`.

**State Complexity**: `SourceChatState` tracks source-specific metadata and context indicators, while `ThreadState` manages a complex strategy-and-answer accumulation pattern across multiple LLM calls.

**Error Handling Scope**: Source Chat wraps the entire execution in a single try/except block using `classify_error` from [`open_notebook/utils/error_classifier.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/error_classifier.py), while Ask implements per-node error handling in `call_model_with_messages`, `provide_answer`, and `write_final_answer`.

## Practical Code Examples

### Invoking the Source Chat Graph

```python
from open_notebook.graphs.source_chat import source_chat_graph
from langgraph.checkpoint.memory import MemorySaver

# Prepare initial state

state = {
    "messages": [],
    "source_id": "src_12345",
    "model_override": None,
}

# Execute the linear workflow

result = source_chat_graph.invoke(state)
print(result["messages"][-1].content)

```

### Invoking the Ask Graph

```python
from open_notebook.graphs.ask import graph

state = {
    "question": "What are the performance trade-offs of LangGraph?",
}

# Execute the multi-step workflow with conditional branches

answer = graph.invoke(state)
print(answer["final_answer"])

```

## Summary

- **Source Chat** workflows provide deep, contextual conversation about single sources using pre-built 50k token contexts and linear graph execution in [`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py).
- **Ask** workflows answer broad questions through multi-step search strategies, conditional sub-graphs for each `Search` term, and asynchronous model provisioning defined in [`open_notebook/graphs/ask.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/ask.py).
- **State definitions** differ fundamentally: `SourceChatState` tracks source metadata while `ThreadState` manages search strategies and accumulated answers.
- **Execution models** vary: Source Chat uses synchronous bridging via `run_in_new_loop`, while Ask leverages native async/await patterns with distinct model configurations for strategy, answer, and final synthesis phases.

## Frequently Asked Questions

### When should I use Source Chat instead of Ask?

Use **Source Chat** when you need to discuss specific content within a single document or source and its extracted insights, requiring deep contextual understanding of that specific material. Use **Ask** when you need to answer open-ended questions that require searching across multiple sources in your knowledge base, as it automatically generates search strategies and synthesizes results from the vector store.

### How does the Ask workflow handle multiple concurrent searches?

The Ask workflow uses the `trigger_queries` edge condition to evaluate the `strategy` generated by the `agent` node. For each `Search` object in the strategy, it creates a **separate sub-graph** execution of the `provide_answer` node. Each sub-graph runs independently to perform its vector search and generate an intermediate answer, with all results accumulated in the `ThreadState` before `write_final_answer` synthesizes the final response.

### Can Source Chat work without insights or handle multiple sources?

According to the source code in [`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py), the `SourceChatState` includes optional `insights` but requires a specific `source_id`. The `ContextBuilder` is designed to pull a single source record and its associated insights. The workflow does not support multi-source context building in its current implementation; for cross-source queries, use the Ask workflow instead.

### What happens when model provisioning fails in these workflows?

Both workflows utilize `classify_error` from [`open_notebook/utils/error_classifier.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/error_classifier.py) to normalize exceptions to specific `OpenNotebookError` subclasses. In Source Chat, the entire invocation is wrapped in a try/except block. In Ask, each node (`agent`, `provide_answer`, `write_final_answer`) implements its own error handling, allowing the graph to fail gracefully at specific steps and return meaningful error classifications to the API layer.