# How the LangGraph StateGraph is Constructed in OpenMAIC's Director Graph

> Discover how OpenMAIC builds its LangGraph StateGraph for multi-agent conversations. Learn about typed states, pure-function nodes, and conditional edges in director graph construction.

- Repository: [MAIC/OpenMAIC](https://github.com/THU-MAIC/OpenMAIC)
- Tags: internals
- Published: 2026-09-04

---

**OpenMAIC constructs a LangGraph StateGraph in [`lib/orchestration/director-graph.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/director-graph.ts) that orchestrates multi-agent conversations through a typed state object, two pure-function nodes (director and agent generator), and conditional edges implementing a single-round execution contract.**

OpenMAIC leverages LangGraph to manage complex multi-agent interactions through a deterministic, stateless orchestration pipeline. The core construction logic resides in the [`director-graph.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/director-graph.ts) module, where the StateGraph is defined, compiled, and exported for server-side execution.

## Defining the Typed State with Annotation.Root

The foundation of the director graph is a strongly-typed state definition using LangGraph's `Annotation.Root`. Located at lines 48–77 in [`lib/orchestration/director-graph.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/director-graph.ts), the `OrchestratorState` interface enumerates all fields required for conversation management:

- **Immutable inputs**: Messages, agent list, and the language model instance
- **Mutable fields**: `currentAgentId`, `turnCount`, `agentResponses`, `whiteboardLedger`, and the termination flag `shouldEnd`

This annotation ensures that every node receives a consistent, typed state object while allowing specific fields to be updated and passed to subsequent nodes in the graph.

## Implementing Graph Nodes

The StateGraph consists of two primary computational nodes implemented as pure functions that transform the state and emit Server-Sent Events (SSE).

### Director Node Logic

The `directorNode` function (lines 91–223) serves as the orchestration brain. It determines which agent should speak next by handling three distinct cases:

1. **Single-agent fast-path**: Immediately selects the sole available agent
2. **Multi-agent first-turn trigger**: Invokes a specific agent for conversation initialization
3. **General LLM decision**: Queries the language model to select the next speaker based on conversation context

Throughout execution, the node emits real-time SSE events (including `thinking` and `cue_user` types) and returns a partial state update containing the selected `currentAgentId` and the `shouldEnd` termination signal.

### Agent Generation Node

The `agentGenerateNode` function (lines 331–667) executes the selected agent's response generation. This node:

- Streams text deltas and tool actions via SSE
- Records whiteboard actions to the ledger
- Updates mutable state fields including `turnCount`, `totalActions`, and `agentResponses`

## Controlling Execution Flow with Conditional Edges

After the director node completes, flow control passes to `directorCondition` (lines 124–127), a conditional edge function that inspects `state.shouldEnd`. The logic follows:

- If `shouldEnd` is `true`, the graph transitions to the `END` node, terminating execution
- Otherwise, the graph follows the `"agent_generate"` edge to invoke the generator node

This conditional routing ensures the graph respects the single-round contract, preventing infinite loops within a single request.

## Assembling the StateGraph Topology

The `createOrchestrationGraph` function (lines 71–95) wires the components into a compiled, executable graph. The topology implements a linear flow with a conditional branch:

```ts
export function createOrchestrationGraph() {
  const graph = new StateGraph(OrchestratorState)
    .addNode('director', directorNode)                 // ← director logic
    .addNode('agent_generate', agentGenerateNode)       // ← agent generation
    .addEdge(START, 'director')                         // START → director
    .addConditionalEdges('director', directorCondition, {
      agent_generate: 'agent_generate',
      [END]: END,
    })                                                  // director → (agent_generate | END)
    .addEdge('agent_generate', END);                    // agent_generate → END
  return graph.compile();
}

```

### The Single-Round Contract Architecture

Unlike persistent conversation loops, OpenMAIC's director graph implements a **single-round contract** where each HTTP request flows through `START → director → (agent_generate) → END`. The client serializes multiple requests to achieve multi-turn dialogs, ensuring the server graph remains stateless and bounded. This design prevents resource exhaustion and simplifies horizontal scaling.

## Initializing State for Stateless Execution

To support the stateless architecture, `buildInitialState` (lines 98–149) constructs a fresh `OrchestratorState` instance for every request. The function accepts a `StatelessChatRequest` and a `LanguageModel`, injecting request-scoped agent configuration overrides. This guarantees that no conversation state persists in memory between separate graph invocations.

## Practical Implementation Examples

### Creating the Orchestration Graph

```ts
import { createOrchestrationGraph } from '@/lib/orchestration/director-graph';

const graph = createOrchestrationGraph();   // compiled LangGraph ready for execution

```

### Building the Initial State

```ts
import { buildInitialState } from '@/lib/orchestration/director-graph';
import { myLanguageModel } from '@/lib/ai-models';

const request = /* a StatelessChatRequest from the client */;
const initState = buildInitialState(request, myLanguageModel, /* optional ThinkingConfig */);

```

### Executing the Graph with SSE Streaming

```ts
import { Runnable } from '@langchain/langgraph';

const runnable: Runnable = graph;               // the compiled graph implements Runnable
const result = await runnable.invoke(initState, {
  writer: (event) => sendSSE(event),            // stream events to the client
});

```

The `writer` callback receives `StatelessEvent` objects (e.g., `thinking`, `text_delta`, `action`, `cue_user`) emitted by both nodes during execution.

## Summary

- **State definition**: Uses `Annotation.Root` at lines 48–77 to create a typed `OrchestratorState` with immutable inputs and mutable conversation tracking fields.
- **Node architecture**: Implements `directorNode` (lines 91–223) for agent selection and `agentGenerateNode` (lines 331–667) for response generation, both emitting SSE events.
- **Flow control**: Employs `directorCondition` (lines 124–127) to conditionally route to `END` or the agent generation node based on `shouldEnd`.
- **Topology**: `createOrchestrationGraph` (lines 71–95) assembles a linear StateGraph with a conditional branch, compiling to a `Runnable` interface.
- **Statelessness**: `buildInitialState` (lines 98–149) ensures each request initializes fresh state, supporting horizontal scaling via the single-round contract.

## Frequently Asked Questions

### What is the single-round contract in OpenMAIC's director graph?

The single-round contract is an architectural constraint where the LangGraph StateGraph processes exactly one conversational turn per invocation, following the path `START → director → (agent_generate) → END`. The graph never loops internally; instead, the client maintains conversation history and re-invokes the graph for subsequent turns. This design ensures bounded execution time and complete statelessness on the server.

### How does the director node determine which agent speaks next?

According to the source code in [`lib/orchestration/director-graph.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/director-graph.ts) (lines 91–223), the `directorNode` implements a three-tier decision hierarchy: it checks for a single-agent fast-path, handles multi-agent conversation initialization, or prompts the configured language model to analyze conversation context and select the next speaker. The node updates `currentAgentId` in state and emits `thinking` events via SSE during deliberation.

### Where is the LangGraph StateGraph state defined in OpenMAIC?

The StateGraph state is defined in [`lib/orchestration/director-graph.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/director-graph.ts) starting at line 48, where `OrchestratorState` is declared using LangGraph's `Annotation.Root` utility. The definition includes fields for messages, agent lists, the language model reference, and mutable tracking fields like `whiteboardLedger` and `turnCount` that nodes update during execution.

### How does OpenMAIC handle multi-turn conversations without graph loops?

OpenMAIC handles multi-turn conversations by treating each turn as an independent, stateless request. The `buildInitialState` function (lines 98–149) reconstructs the conversation context from the request payload for every invocation, and the compiled graph always terminates at `END` after one pass. The client application sequences multiple HTTP requests to create the illusion of a continuous dialog, while the server graph remains a single-round, deterministic processor.