# How LangGraph Orchestrates Workflows in Open Notebook: A Deep Dive into the State Machine Architecture

> Discover how LangGraph orchestrates workflows in open-notebook using state machines. Learn how typed states and async nodes build complex AI pipelines for search-then-answer and source ingestion.

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

---

**LangGraph orchestrates workflows in open-notebook by defining each AI operation as a composable state machine where typed states flow through async nodes connected by conditional edges, enabling complex multi-step pipelines like search-then-answer and source ingestion.**

The `lfnovo/open-notebook` repository leverages **LangGraph**—the state-machine engine from the LangChain ecosystem—to coordinate every multi-step AI operation. By modeling workflows as directed graphs with persistent state, Open Notebook transforms complex LLM interactions into manageable, debuggable, and extensible pipelines.

## Core Architecture: State, Nodes, and Edges

Open Notebook implements three fundamental LangGraph concepts to control execution flow: typed state definitions, node functions, and edge wiring.

### Typed State Definitions

Every workflow begins with a **state schema** defined as a `TypedDict`. In [`open_notebook/graphs/ask.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/ask.py), the `ThreadState` type captures the user question, a generated `Strategy` object, an accumulating list of intermediate answers, and the final output. Similarly, [`open_notebook/graphs/source.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source.py) defines `SourceState` to track raw content, transformation flags, and database IDs. These type definitions enforce contracts between nodes, ensuring that data passed through the graph remains predictable and statically checkable.

### Node Functions and Edge Wiring

**Nodes** are Python callables—often async—that receive the current state and a `RunnableConfig`, then return a dictionary merging new values into the state. For example, in [`open_notebook/graphs/source.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source.py), the `content_process` node extracts text while `save_source` persists records to the database.

Edges connect these nodes using `add_edge`, `add_conditional_edges`, and the special constants `START` and `END` to mark graph entry and exit points. The Ask workflow in [`ask.py`](https://github.com/lfnovo/open-notebook/blob/main/ask.py) wires the graph as `START → agent`, followed by conditional branching, then `write_final_answer → END`.

### Conditional Branching and Control Flow

**Conditional edges** enable dynamic routing based on runtime state. After the `agent` node generates a `Strategy` containing multiple search queries, the `trigger_queries` function in [`ask.py`](https://github.com/lfnovo/open-notebook/blob/main/ask.py) inspects the `searches` list and creates a `Send` object for each term. This spawns parallel executions of the `provide_answer` node, allowing the system to gather evidence from multiple vector searches before synthesizing a final response.

## The Ask Workflow: Multi-Step Search and Synthesis

The primary question-answering pipeline demonstrates how LangGraph handles complex orchestration through declarative state management.

1. **State Initialization** – The graph receives a `ThreadState` containing only the user question.
2. **Strategy Generation** – The `agent` node calls an LLM with a system prompt from `ask/entry`, parsing the JSON response into a `Strategy` with up to five `Search` objects.
3. **Parallel Execution** – The `trigger_queries` conditional edge fans out to multiple `provide_answer` nodes, one per search term.
4. **Evidence Retrieval** – Each `provide_answer` node executes `vector_search` and asks an LLM to synthesize an answer from retrieved snippets.
5. **Final Aggregation** – The `write_final_answer` node gathers all intermediate answers and prompts the LLM for a polished final response.
6. **Compilation** – The graph compiles via `agent_state.compile()` into a runnable object invoked with `ainvoke`.

This entire flow is defined in [`open_notebook/graphs/ask.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/ask.py), where the compiled `graph` variable serves as the main entry point for the API.

## The Source Ingestion Pipeline

Document processing follows a similar state-machine pattern in [`open_notebook/graphs/source.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source.py).

The workflow begins with `SourceState` containing a `ProcessSourceState` and transformation flags. The `content_process` node extracts text via `content_core`, while `save_source` writes results to the database. A **conditional edge** named `trigger_transformations` checks whether transformations were requested, routing the flow to `transform_content` only when needed. Finally, the graph compiles as `source_graph = workflow.compile()`, producing a runnable pipeline that handles extraction, persistence, and optional embedding.

## Persistent Chat with Checkpointing

For conversational interfaces, Open Notebook utilizes **LangGraph checkpointing** to maintain state across restarts. In [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py), the implementation initializes a `SqliteSaver` connection:

```python
memory = SqliteSaver(conn)

```

The compiled graph includes this checkpointer: `graph = agent_state.compile(checkpointer=memory)`. This allows the single `agent` node—which builds system prompts from `chat/system` templates—to maintain conversation history indefinitely. When invoked via the API, the graph automatically persists message lists, enabling seamless session recovery.

## Practical Implementation Examples

### Building a Simple LangGraph Workflow

The following pattern illustrates the core primitives used throughout Open Notebook:

```python
from langgraph.graph import StateGraph, END, START
from typing_extensions import TypedDict

class SimpleState(TypedDict):
    count: int
    log: list

async def increment(state, config):
    return {"count": state["count"] + 1, "log": state["log"] + ["inc"]}

async def double(state, config):
    return {"count": state["count"] * 2, "log": state["log"] + ["dbl"]}

g = StateGraph(SimpleState)
g.add_node("inc", increment)
g.add_node("dbl", double)
g.add_edge(START, "inc")
g.add_edge("inc", "dbl")
g.add_edge("dbl", END)

graph = g.compile()
result = await graph.ainvoke({"count": 1, "log": []})

# result → {"count": 4, "log": ["inc", "dbl"]}

```

### Invoking the Ask Graph

To programmatically run the search-and-answer workflow:

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

async def answer_question(question: str):
    init_state = {"question": question}
    cfg = {
        "configurable": {
            "strategy_model": "gpt-4o",
            "answer_model": "gpt-4",
            "final_answer_model": "gpt-4"
        }
    }
    result = await graph.ainvoke(init_state, config=cfg)
    return result["final_answer"]

```

### Running Source Ingestion

For document processing pipelines:

```python
from open_notebook.graphs.source import source_graph

async def ingest_source(source_id: str, notebook_ids: list[str]):
    init = {
        "content_state": {"url": "https://example.com/file.pdf"},
        "apply_transformations": [],
        "source_id": source_id,
        "notebook_ids": notebook_ids,
        "embed": True,
    }
    result = await source_graph.ainvoke(init)
    return result["source"]

```

## Summary

- **LangGraph** provides the state-machine foundation for all AI workflows in Open Notebook, replacing ad-hoc orchestration with explicit graph structures.
- **TypedDict** state definitions in [`ask.py`](https://github.com/lfnovo/open-notebook/blob/main/ask.py), [`source.py`](https://github.com/lfnovo/open-notebook/blob/main/source.py), and [`chat.py`](https://github.com/lfnovo/open-notebook/blob/main/chat.py) enforce data contracts between nodes.
- **Conditional edges** enable dynamic parallelism, such as spawning multiple search queries simultaneously in the Ask workflow.
- **Checkpointing** via `SqliteSaver` in [`chat.py`](https://github.com/lfnovo/open-notebook/blob/main/chat.py) ensures conversational state persists across application restarts.
- **Compilation** via `graph.compile()` transforms declarative state graphs into async-runnable objects that integrate seamlessly with the FastAPI backend.

## Frequently Asked Questions

### How does LangGraph handle parallel execution in Open Notebook?

LangGraph executes nodes in parallel when conditional edges return multiple `Send` objects. In [`open_notebook/graphs/ask.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/ask.py), the `trigger_queries` function inspects the `searches` list and generates a `Send` for each search term, causing the `provide_answer` node to run concurrently for each query. The graph automatically aggregates results before proceeding to the `write_final_answer` node.

### What is the purpose of SqliteSaver in the chat workflow?

The `SqliteSaver` provides persistent checkpointing for conversational state. Defined in [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py) as `memory = SqliteSaver(conn)`, it allows the compiled graph to save and reload thread state from SQLite. This enables chat sessions to survive application restarts and supports long-running conversations without memory loss.

### Can I customize the LLM models used in LangGraph workflows?

Yes, Open Notebook passes model configurations through the `RunnableConfig` parameter. When invoking graphs like [`ask.py`](https://github.com/lfnovo/open-notebook/blob/main/ask.py), you can specify model IDs via the `configurable` dictionary: `{"configurable": {"strategy_model": "gpt-4o", "answer_model": "gpt-4"}}`. The node functions read these values to instantiate the appropriate LLM client dynamically.

### Where are the prompt templates stored for LangGraph nodes?

Prompt templates reside in [`open_notebook/graphs/prompt.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/prompt.py) and are referenced by node functions across the workflow files. For example, the `agent` node in [`ask.py`](https://github.com/lfnovo/open-notebook/blob/main/ask.py) loads the entry prompt using Jinja templates from the `ask/entry` path, while the chat system uses `chat/system` templates. This centralized approach allows modifications to LLM prompts without changing the graph structure.