How OpenMAIC Uses LangGraph for Multi-Agent Orchestration: A Deep Dive into Stateless StateGraph Design

OpenMAIC implements deterministic multi-agent chat workflows using LangGraph's StateGraph, with a two-node topology (director → agent_generate) that executes at most one cycle per request and streams fine-grained events via custom stream modes.

OpenMAIC, the open-source multi-agent AI conversation framework from Tsinghua University's MAIC lab, leverages LangGraph to orchestrate complex agent interactions without maintaining server-side session state. This article examines how the codebase structures its orchestration graph, handles streaming generation, and enforces bounded execution cycles.

The Core LangGraph Architecture

At the heart of OpenMAIC's orchestration is a deliberately simple StateGraph defined in lib/orchestration/director-graph.ts. The topology follows a single-round contract that guarantees termination:


START → director ──(end)──→ END
            │
            └─(next)→ agent_generate ──→ END

This design choice means each HTTP request executes at most one director → agent cycle. The client—not the server—handles multi-turn conversations by serializing multiple requests. No maxTurns parameter is required because the graph structure itself bounds execution.

Building and Compiling the Graph

The createOrchestrationGraph() function constructs the compiled graph:

// lib/orchestration/director-graph.ts
export function createOrchestrationGraph() {
  const graph = new StateGraph(OrchestratorState)
    .addNode('director', directorNode)
    .addNode('agent_generate', agentGenerateNode)
    .addEdge(START, 'director')
    .addConditionalEdges('director', directorCondition, {
      agent_generate: 'agent_generate',
      [END]: END,
    })
    .addEdge('agent_generate', END);

  return graph.compile();
}

The conditional edge from director uses directorCondition to route either to agent_generate (when an agent should speak) or directly to END (for user-directed turns or termination).

State Definition with Annotation.Root

OpenMAIC uses LangGraph's Annotation.Root to declare typed state fields. Lines 48-77 of lib/orchestration/director-graph.ts define:

  • Immutable inputs: messages, agentIds, languageModel, config
  • Mutable fields: currentAgentId, turnCount, agentResponses, shouldEnd

This explicit immutability boundary prevents accidental state mutations while allowing the graph to track progression across the single-cycle execution.

The Director Node: Routing Logic

The director node (directorNode) determines which participant speaks next. Its implementation demonstrates three distinct decision paths:

Scenario Behavior
Single agent Pure code path—no LLM invocation
Multi-agent, first turn with trigger Fast-tracks the triggerAgentId immediately
Multi-agent, general case Invokes LLM via AISdkLangGraphAdapter to select next speaker

The LLM-driven decision uses conversation summarization (lib/orchestration/summarizers/conversation-summary.ts) to fit context windows, then parses the response with parseDirectorDecision. Lines 91-122 and 150-190 contain the core routing implementations.

Director reasoning is streamed to clients through LangGraph's custom stream mode, emitting thinking and cue_user events via config.writer.

The Agent-Generate Node: Streaming Generation

Once routed, the agent-generate node (agentGenerateNode) handles actual LLM inference:

  1. Prompt construction: buildStructuredPrompt creates the system prompt
  2. Message conversion: Chat history transforms to OpenAI-compatible format
  3. Streaming execution: Raw LLM chunks flow through a partial-JSON parser
  4. Event emission: Parsed content streams as text_delta, action, and agent_end events

The partial-JSON parser (parseStructuredChunk in lib/orchestration/stateless-generate.ts, lines 31-44 and 135-176) enables interleaved text and action items—critical for agents that must emit structured tool calls alongside natural language.

import { parseStructuredChunk, createParserState } from '@/lib/orchestration/stateless-generate';

let parser = createParserState();
for await (const chunk of llmStream) {
  const parsed = parseStructuredChunk(chunk, parser);
  // parsed.textChunks and parsed.actions contain newly emitted items
}

Actions are filtered through getEffectiveActions before recording to the whiteboard, enforcing agent capability boundaries.

Stateless Execution Model

OpenMAIC's stateless design eliminates server-side session storage. The generate entry point in lib/orchestration/stateless-generate.ts demonstrates this pattern:

export async function generate(
  request: StatelessChatRequest,
  languageModel: LanguageModel,
  thinkingConfig?: ThinkingConfig,
) {
  const graph = createOrchestrationGraph();
  const initialState = buildInitialState(request, languageModel, thinkingConfig);
  return await graph.invoke(initialState);
}

Each request:

  • Creates a fresh graph instance
  • Builds complete initial state from StatelessChatRequest
  • Streams events back to the client
  • Discards all state after END node completion

This architecture enables horizontal scaling and simplifies deployment—no Redis, no database, no sticky sessions required.

Trigger Agent Configuration

Multi-agent scenarios support trigger agents for immediate routing:

const request: StatelessChatRequest = {
  messages: [...],
  storeState: ...,
  config: {
    agentIds: ['teacher', 'assistant'],
    triggerAgentId: 'teacher',  // Dispatch immediately on turn 0
  },
};

When triggerAgentId matches an available agent on the first turn, the director short-circuits LLM deliberation and routes directly to that agent.

Key Implementation Files

File Responsibility
lib/orchestration/director-graph.ts StateGraph definition, node implementations, state annotations
lib/orchestration/stateless-generate.ts Entry point, graph invocation, partial-JSON streaming
lib/orchestration/ai-sdk-adapter.ts Adapter for ai-compatible language models
lib/orchestration/prompt-builder.ts System prompt construction for agents and director
lib/orchestration/summarizers/conversation-summary.ts Context compression for director LLM decisions

Summary

  • OpenMAIC uses a fixed two-node StateGraph (director → agent_generate) with topology-bound execution cycles
  • Stateless by design: all request context travels in StatelessChatRequest, no server-side persistence
  • Streaming-first architecture: custom stream modes emit thinking, text_delta, action, and agent_end events via SSE
  • Flexible routing: pure-code single-agent paths, trigger-agent shortcuts, and LLM-driven multi-agent selection
  • Partial-JSON parsing enables structured action interleaving with natural language generation

Frequently Asked Questions

What makes OpenMAIC's LangGraph implementation stateless?

OpenMAIC rebuilds the complete LangGraph state from each incoming StatelessChatRequest and discards it after the END node fires. No session data persists on the server—all conversation history, agent configurations, and turn counters travel with the request payload. This eliminates server-side storage requirements and enables effortless horizontal scaling.

Why does OpenMAIC limit the graph to a single director → agent cycle?

The single-cycle constraint simplifies reasoning about termination and prevents runaway LLM costs. The graph topology itself guarantees completion without explicit maxTurns checks. Clients implement multi-turn conversations by chaining multiple stateless requests, giving application developers explicit control over conversation depth and cost exposure.

How does the partial-JSON parser handle streaming agent actions?

The parseStructuredChunk utility maintains parser state across streaming chunks, detecting complete JSON objects while buffering incomplete ones. It separates textChunks (natural language) from actions (structured tool calls), allowing the frontend to render streaming text while preparing action handlers. This enables agents to emit interleaved content without waiting for complete generation.

Can OpenMAIC work with non-OpenAI language models?

Yes. The AISdkLangGraphAdapter in lib/orchestration/ai-sdk-adapter.ts bridges any ai-compatible language model to the LangGraph orchestration layer. The adapter handles message format conversion and streaming abstractions, making the core graph implementation model-agnostic.

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 →