How the OpenMAIC Director Graph Decides Which Agent Speaks Next
The OpenMAIC Director Graph decides which agent speaks next by evaluating deterministic fast-paths for single-agent or trigger-agent scenarios first, then falling back to an LLM-based selection process that returns a structured decision containing nextAgentId and shouldEnd parameters.
The Director Graph serves as the core orchestration engine in the OpenMAIC multi-agent framework, managing conversation flow through a state-driven graph architecture where the client serializes additional turns across requests. For each user request, the system executes exactly one director-to-agent cycle, with the Director Node determining whether to dispatch an agent, cue the user, or terminate the session. This mechanism relies on a tiered decision hierarchy that prioritizes efficiency through code-level logic before invoking expensive LLM calls.
Single-Agent Deterministic Mode
When the system operates with only one available agent, the Director Graph bypasses the LLM entirely for maximum efficiency. According to the source code in lib/orchestration/director-graph.ts (lines 15-32), the logic follows a strict turn-based protocol:
- Turn 0: The director immediately dispatches the sole available agent.
- Turn ≥ 1: The director cues the user for follow-up input and ends the current round.
This pure code-level implementation eliminates latency from model inference in single-agent scenarios, as the orchestration logic requires no semantic reasoning to select participants.
Multi-Agent Trigger Fast Path
For multi-agent configurations, the Director Graph implements a fast-path optimization for the initial turn. Located at lines 34-44 in lib/orchestration/director-graph.ts, this logic checks if a triggerAgentId exists in the request state and validates it against state.availableAgentIds:
// Inside directorNode – fast-path for a trigger agent
if (state.turnCount === 0 && state.triggerAgentId) {
const triggerId = state.triggerAgentId;
if (state.availableAgentIds.includes(triggerId)) {
write({ type: 'thinking', data: { stage: 'agent_loading', agentId: triggerId } });
return { currentAgentId: triggerId, shouldEnd: false };
}
}
If the trigger agent exists in the registry, the director dispatches it immediately without constructing prompts or calling the language model. This optimization ensures predictable first-turn behavior while conserving computational resources.
LLM-Based Agent Selection
When neither deterministic path applies, the Director Graph constructs a rich contextual prompt and delegates the decision to a language model. This process involves three distinct phases: prompt construction, generation, and structured output parsing.
Prompt Construction with buildDirectorPrompt
The director initiates LLM-based decisions by calling buildDirectorPrompt, implemented starting at line 64 in lib/orchestration/director-graph.ts. This function assembles a comprehensive system prompt containing:
- Available agent descriptions and capabilities
- Conversation summary and history
- Prior agent responses stored in state
- Current whiteboard state (shared memory)
- Discussion context and metadata
The resulting prompt is passed to AISdkLangGraphAdapter._generate, which interfaces with the configured language model to produce a structured decision.
Parsing and Executing the Decision
The raw LLM output undergoes processing by parseDirectorDecision (lines 86-88), which extracts a structured object with two critical fields:
nextAgentId: The identifier of the selected agent, or the special tokenUSERto indicate the human participant should speak next.shouldEnd: A boolean flag indicating whether the conversation should terminate.
The execution logic at lines 89-110 implements the following branching behavior:
- If
shouldEndis true ornextAgentIdis falsy, the graph transitions to the END state. - If
nextAgentId === 'USER', the system emits acue_userevent and ends the round. - Otherwise, the system sets
state.currentAgentIdto the chosen agent and proceeds to theagent_generatenode.
// Example LLM decision parsing (simplified)
const decision = parseDirectorDecision(llmOutput);
if (decision.shouldEnd) {
return { shouldEnd: true };
}
if (decision.nextAgentId === 'USER') {
write({ type: 'cue_user', data: { fromAgentId: state.currentAgentId } });
return { shouldEnd: true };
}
Integration with the Orchestration Graph
The Director Graph itself is instantiated via createOrchestrationGraph in lib/orchestration/director-graph.ts, while the execution loop resides in lib/chat/pi/director-loop.ts. The graph follows this flow:
START → director ──(end)──→ END
│
└─(next)→ agent_generate → END
Supporting infrastructure includes lib/orchestration/director-prompt.ts for prompt templates, lib/orchestration/registry/store.ts for agent registry access, and lib/prompts/index.ts for base prompt construction utilities.
// Example: building the initial state for a request
import { buildInitialState, createOrchestrationGraph } from '@/lib/orchestration/director-graph';
import { someLanguageModel } from '@/lib/ai';
// 1. Create the graph once
const graph = createOrchestrationGraph();
// 2. Build the request‑scoped state
const initState = buildInitialState(request, someLanguageModel);
// 3. Run a single director → agent iteration
const result = await graph.invoke(initState);
Summary
- The Director Graph uses deterministic logic for single-agent configurations, bypassing the LLM entirely on lines 15-32 of
director-graph.ts. - A trigger-agent fast path at lines 34-44 allows immediate first-turn dispatch without model inference.
- LLM-based decisions rely on
buildDirectorPrompt(lines 64-71) andparseDirectorDecision(lines 86-88) to select agents based on conversation context. - The special
USERtoken signals the director to cue human input rather than dispatching an agent. - Decision execution (lines 89-110) handles termination, user cues, and transitions to the
agent_generatenode.
Frequently Asked Questions
What happens if the Director Graph cannot determine a valid next agent?
If parseDirectorDecision returns a falsy nextAgentId or sets shouldEnd to true, the graph immediately transitions to the END state, terminating the current conversation round. This safety mechanism prevents the orchestration from entering undefined states when the LLM produces malformed output or when no suitable agent exists for the current context.
How does the trigger agent fast path optimize performance?
The trigger agent fast path checks state.turnCount === 0 and validates state.triggerAgentId against state.availableAgentIds before any LLM calls occur. By returning { currentAgentId: triggerId, shouldEnd: false } immediately at lines 34-44, the system avoids prompt construction and model inference latency, reducing first-turn response time significantly in multi-agent workflows.
What information does the Director Graph send to the LLM for agent selection?
The buildDirectorPrompt function constructs a comprehensive context packet including available agent capabilities, conversation summaries stored in state, prior agent responses, whiteboard state, and discussion metadata. This rich prompt enables the language model to make informed routing decisions based on agent specialties and conversation history.
Where does the Director Graph store its decision state?
The Director Graph maintains ephemeral state through the LangGraph StateGraph architecture defined in lib/orchestration/director-graph.ts. Key decision parameters like currentAgentId, turnCount, and shouldEnd persist within the graph's state object during the single director-to-agent cycle, while long-term agent registry data resides in lib/orchestration/registry/store.ts.
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 →