How LangGraph Powers Multi‑Agent Orchestration in OpenMAIC
OpenMAIC uses LangGraph's stateful graph engine to coordinate turn‑based conversations across multiple AI agents, routing each request through a director node that selects the next speaker and an agent node that generates streaming responses.
The THU-MAIC OpenMAIC repository implements a stateless multi‑agent system where LangGraph supplies the core orchestration primitives—state annotations, graph topology, conditional edges, and custom streaming hooks. This architecture keeps the server stateless while enabling complex, real‑time dialogues between specialized agents.
LangGraph's Core Responsibilities in OpenMAIC
Stateful Graph Foundation
LangGraph provides the graph execution engine that manages conversation flow. In lib/orchestration/director-graph.ts, OpenMAIC constructs a StateGraph with two primary nodes: director and agent_generate. The graph processes each request as a single "round" (director → agent → end), with the client responsible for chaining multiple rounds into longer discussions.
Typed State with Annotations
LangGraph's Annotation system defines OrchestratorState at lib/orchestration/director-graph.ts#L48-L78. This typed state container holds:
- Static fields: request payload, available
agents,languageModel - Mutable fields:
turnCount,currentAgentId,agentResponses - Reducer functions: for accumulating agent outputs and whiteboard actions
// Conceptual structure of OrchestatorState annotations
const OrchestratorState = Annotation.Root({
request: Annotation<StatelessChatRequest>,
agents: Annotation<AgentConfig[]>,
languageModel: Annotation<LanguageModel>,
turnCount: Annotation<number>,
currentAgentId: Annotation<string | undefined>,
agentResponses: Annotation<AgentResponse[]>, // with reducer for accumulation
// ... additional fields for whiteboard and flow control
});
Node Implementation: Director Logic
The director node (lib/orchestration/director-graph.ts#L90-L124) implements turn‑selection logic:
- Single‑agent mode: Pure code path that routes directly to the configured agent
- Multi‑agent mode: LLM invocation via LangGraph's streaming APIs to determine which agent (or user) should speak next
The director sets a shouldEnd flag that controls graph termination.
Node Implementation: Agent Generation
The agent_generate node (lib/orchestration/director-graph.ts#L30-L44) executes the selected agent's response generation. It streams three event types through LangGraph's custom writer:
| Event Type | Purpose |
|---|---|
text_delta |
Incremental text output for real‑time display |
action |
Tool or whiteboard actions requested by agent |
agent_end |
Signal completion with final state updates |
Conditional Edge Routing
After the director executes, directorCondition (lib/orchestration/director-graph.ts#L25-L31) routes the flow:
// Simplified conditional edge logic
function directorCondition(state: typeof OrchestratorState.State): "agent_generate" | "__end__" {
return state.shouldEnd ? "__end__" : "agent_generate";
}
This enforces OpenMAIC's single‑round contract: each HTTP request processes exactly one director decision and one agent response. Long conversations require sequential client requests with updated directorState.
Streaming Architecture with Custom Writers
LangGraph's custom stream mode enables Server‑Sent Events (SSE) for real‑time UI updates. Both nodes receive a config.writer callback that pushes StatelessEvent chunks immediately:
// Stream configuration pattern from director-graph.ts
await graph.stream(initialState, {
writer: (event: StatelessEvent) => {
// Forward to SSE client for live thinking stages, partial text, errors
res.write(`data: ${JSON.stringify(event)}\n\n`);
},
});
This design decouples generation latency from response delivery—the user sees agent "thinking" indicators and incremental output without waiting for full completion.
Provider Abstraction via AISdkLangGraphAdapter
The AISdkLangGraphAdapter class in lib/orchestration/ai-sdk-adapter.ts#L2-L12 bridges LangGraph to the ai SDK's LanguageModel interface. This adapter exposes:
_generate— synchronous completion for single‑shot director decisionsstreamGenerate— streaming completion for agent response generation
// Adapter usage pattern
import { AISdkLangGraphAdapter } from '@/lib/orchestration/ai-sdk-adapter';
// Wrap any ai-compatible model (OpenAI, Anthropic, local, etc.)
const languageModel = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
const adapter = new AISdkLangGraphAdapter(languageModel);
// Use in LangGraph nodes for provider-agnostic LLM calls
const result = await adapter._generate(prompt, options);
This abstraction lets OpenMAIC swap LLM providers without modifying orchestration logic.
Complete Integration Example
// lib/orchestration/director-graph.ts — full orchestration setup
import {
createOrchestrationGraph,
buildInitialState,
} from '@/lib/orchestration/director-graph';
import { createOpenAI } from '@ai-sdk/openai';
// 1. Configure language model through adapter
const lm = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
// 2. Prepare stateless request (from client payload)
const request: StatelessChatRequest = {
messages: [{ role: 'user', content: 'Compare Q1 and Q2 sales forecasts' }],
storeState: { /* prior conversation context */ },
config: {
agentIds: ['data-analyst', 'finance-reviewer'],
triggerAgentId: 'data-analyst',
},
directorState: undefined, // fresh round
};
// 3. Build graph and initial state
const graph = createOrchestrationGraph();
const initState = buildInitialState(request, lm);
// 4. Execute with streaming for live UI updates
const events: StatelessEvent[] = [];
await graph.stream(initState, {
writer: (event) => {
events.push(event);
broadcastToClient(event); // SSE push
},
});
// 5. Final state contains complete round result
console.log('Selected agent:', events.find(e => e.type === 'agent_start')?.agentId);
console.log('Generated text:', events.filter(e => e.type === 'text_delta').map(e => e.delta).join(''));
Key Design Decisions
Statelessness at the server level: Agent configurations, conversation history, and graph state travel with each request. LangGraph's state management operates per‑request, not per‑session. This enables horizontal scaling and eliminates server‑side session storage.
Single‑round execution model: The graph always terminates after one agent response. Complex multi‑turn conversations are client‑orchestrated sequences of single‑round requests. This simplifies reasoning about timeouts, retries, and partial failures.
Structured event streaming: Rather than raw token streams, LangGraph nodes emit semantic events (thinking, text_delta, action, error) that map directly to UI components.
Summary
- LangGraph provides the graph engine:
StateGraphwithdirectorandagent_generatenodes defined inlib/orchestration/director-graph.ts - Annotations enable typed state:
OrchestratorStateholds request context, mutable round state, and reducer‑managed accumulators - Conditional edges control flow:
directorConditionimplements single‑round termination based onshouldEndflag - Custom writers power SSE streaming: Real‑time events for thinking stages, partial text, and actions
- AISdkLangGraphAdapter decouples providers: Any
aiSDKLanguageModelworks with LangGraph's generation APIs
Frequently Asked Questions
What makes OpenMAIC's use of LangGraph "stateless"?
The server retains no conversation state between requests. Each HTTP request carries the complete OrchestratorState—including message history, agent configs, and prior directorState—which LangGraph processes as a fresh graph invocation. The graph itself is stateful during execution (tracking turnCount, accumulating responses), but that state is discarded after the single‑round response returns to the client.
Why does OpenMAIC use single‑round execution instead of persistent graph sessions?
Single‑round execution simplifies deployment and scaling. Timeouts, client disconnections, and server restarts become recoverable—clients retry with the same or updated state. Persistent graph sessions would require sticky routing, session storage, and complex failure handling. The client‑chained round model achieves equivalent conversational depth with simpler operational characteristics.
How does the director node decide which agent speaks next?
In multi‑agent mode, the director prompts an LLM (via AISdkLangGraphAdapter) with the conversation history, available agent descriptions, and turn‑selection instructions. The LLM returns either an agentId from the configured pool or a signal to return to the user. The implementation at lib/orchestration/director-graph.ts#L90-L124 parses this decision and sets currentAgentId and shouldEnd accordingly.
Can OpenMAIC work with LLM providers other than OpenAI?
Yes. The AISdkLangGraphAdapter in lib/orchestration/ai-sdk-adapter.ts wraps any ai SDK‑compatible LanguageModel. This includes Anthropic Claude, Google Gemini, local models via Ollama or LM Studio, and custom providers. The orchestration graph operates on the standardized adapter interface, making provider swaps configuration‑only changes.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →