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 numberagentResponses: Accumulated outputs from all agentswhiteboard: Shared ledger of actions and decisionsshouldEnd: 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
USERcue, 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:
- Streams the agent's response generation
- Captures tool actions and whiteboard updates
- Updates
turnCountand response history - Returns the complete
directorStateto 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
directorStatelifecycle between turns - Detects termination conditions (
shouldEnd,ENDresponse, or error states) - Handles user input integration when
USERis 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
directorStateis 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →