How OpenMAIC Handles Multi-Turn Discussions With Its Director Graph

OpenMAIC orchestrates multi-turn conversations through a stateless, single-round Director Graph where the client serializes turns by feeding updated directorState back into sequential requests.

The THU-MAIC/OpenMAIC repository implements a unique approach to multi-agent conversation management. Rather than maintaining long-running server-side loops, its Director Graph treats each turn as an isolated execution, pushing state persistence to the client layer. This design enables scalable, stateless deployment while supporting complex multi-turn dialogues across multiple AI agents.

Understanding the Single-Round Director Graph Architecture

The Director Graph in lib/orchestration/director-graph.ts follows a deliberately constrained topology: START → director → (agent_generate) → END [^1^]. This structure contains no internal maxTurns limit—the boundary is the graph execution itself, not a counter within the graph logic.

// Simplified topology from lib/orchestration/director-graph.ts
// Lines 4-12 define this fixed structure

Because the graph terminates after each agent response, multi-turn discussions are achieved externally. The client receives a complete directorState object after every request, including:

  • turnCount: Current turn number
  • agentResponses: Accumulated outputs from all agents
  • whiteboard: Shared ledger of actions and decisions
  • shouldEnd: Termination flag

The client then initiates a new request with this updated state, creating the illusion of continuous conversation through serialized state transfer.

Director Node Decision Logic

The director node determines the next speaker through three distinct pathways, implemented in lib/orchestration/director-graph.ts [^2^][^3^][^4^].

Single-Agent Mode: Code-Only Dispatch

When only one agent is configured, the director operates without LLM invocation:

  • Turn 0: Automatically dispatches the sole available agent
  • Subsequent turns: Returns USER cue, prompting client input

This fast-path avoids unnecessary model calls for trivial routing decisions [^2^].

Multi-Agent Mode: LLM-Powered Arbitration

With multiple agents, the director constructs a comprehensive prompt via lib/orchestration/director-prompt.ts containing:

  • Agent descriptions and capabilities
  • Conversation summary
  • Prior agent responses
  • Whiteboard action history

The LLM returns one of: an agentId, USER, or END [^3^][^4^].

Trigger Agent Fast-Path

If triggerAgentId is specified in the request configuration, the director skips LLM deliberation for turn 0 and immediately dispatches the designated agent. This optimizes known-start scenarios like scripted introductions [^3^].

Agent Execution and State Propagation

Once an agent is selected, the director emits a thinking event and transitions to the agent_generate node. This node:

  1. Streams the agent's response generation
  2. Captures tool actions and whiteboard updates
  3. Updates turnCount and response history
  4. Returns the complete directorState to the client [^5^]

The graph then terminates, handing control back to the client layer.

Client-Side Turn Serialization

The actual multi-turn loop resides in lib/chat/pi/director-loop.ts. This client component:

  • Maintains conversation continuity across HTTP requests
  • Manages directorState lifecycle between turns
  • Detects termination conditions (shouldEnd, END response, or error states)
  • Handles user input integration when USER is cued

Practical Implementation Example

// Build the graph once at server initialization
import { createOrchestrationGraph, buildInitialState } from '@/lib/orchestration/director-graph';
import { myLanguageModel } from '@/lib/ai-models';

const graph = createOrchestrationGraph();

// First request: initialize new discussion
let request = {
  messages: [],
  storeState: initialStoreState,
  config: {
    agentIds: ['researcher', 'critic', 'synthesizer'],
    triggerAgentId: 'researcher',  // Optional: bypass LLM for turn 0
  },
};
let state = buildInitialState(request, myLanguageModel);
let result = await graph.invoke(state);

// Subsequent requests: feed back directorState
while (!result.directorState.shouldEnd) {
  request = {
    ...request,
    directorState: result.directorState,  // Carry forward accumulated state
    messages: result.messages,
  };
  state = buildInitialState(request, myLanguageModel);
  result = await graph.invoke(state);
  // Client handles USER cues or termination
}

Key Files in the Multi-Turn Pipeline

File Responsibility
lib/orchestration/director-graph.ts LangGraph definition, director routing logic, graph construction [^1^][^2^][^3^][^4^][^5^]
lib/orchestration/director-prompt.ts LLM prompt construction for multi-agent arbitration
lib/chat/pi/director-loop.ts Client-side turn serialization and conversation loop
lib/orchestration/stateless-generate.ts Stream parsing and event structuring for agent outputs

Summary

  • OpenMAIC's Director Graph executes exactly one turn per request with a fixed topology that intentionally excludes internal looping
  • Multi-turn discussions are achieved through client-managed state serialization, where directorState is passed between sequential requests
  • Routing decisions use code-only paths for single-agent or triggered scenarios, falling back to LLM arbitration for complex multi-agent selections
  • Stateless architecture enables horizontal scaling and simplifies server deployment at the cost of client-side complexity

Frequently Asked Questions

What is the Director Graph in OpenMAIC?

The Director Graph is a LangGraph-based orchestration layer defined in lib/orchestration/director-graph.ts that manages which agent speaks next in a multi-agent conversation. It uses a fixed three-node topology (START → director → agent_generate → END) with no internal iteration capability [^1^].

Why doesn't OpenMAIC support server-side multi-turn loops?

The stateless, single-round design eliminates long-running server processes, enabling easier scaling and fault tolerance. By pushing turn management to the client via directorState serialization, the server treats each request as independent and idempotent [^2^][^5^].

How does the director choose between agents?

The director checks agent count first: with one agent, it uses deterministic code; with multiple agents, it either fast-paths a triggerAgentId or constructs an LLM prompt describing all agents and conversation context, then parses the model's selection [^2^][^3^].

What happens when the director returns USER?

A USER response signals that no agent should speak next—the conversation requires human input. The graph terminates, returning control to the client with directorState intact; the client collects user input and initiates the next request to continue the dialogue [^4^].

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 →