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

OpenMAIC constructs a LangGraph StateGraph in 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 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, 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:

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

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

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

Building the Initial State

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

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

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 →