Understanding Director Graph Topology in OpenMAIC: Single vs. Multi-Agent Orchestration
The director graph in OpenMAIC is a directed acyclic graph where a central director node routes conversations through linear chains for single-agent scenarios or branches into parallel agent nodes for multi-agent collaboration, merging outputs back into a unified state before the next turn.
The director graph topology serves as the backbone of conversational orchestration in the OpenMAIC repository, transforming dialogue management into a structured graph traversal problem. This architecture separates high-level routing decisions from agent-specific execution, enabling consistent handling of both solitary agents and coordinated multi-agent teams. Understanding how this graph expands and contracts based on agent count is essential for customizing orchestration behavior.
What Is the Director Graph Topology?
The director graph models conversation flow as a directed acyclic structure centered around DirectorNode entities. Each node represents a decision point where the system chooses between invoking tools, routing to specific agents, or terminating the session.
In lib/orchestration/director-graph.ts, the core data structures define three primary node types:
DirectorNode– Maintains the current turn count, budget ledger, and routing decisionsAgentNode– Encapsulates individual agent execution contexts and response generationToolNode– Represents external tool invocations with parameter bindings
State flows downward through the graph via an immutable directorState object that carries the conversation ledger, budget consumption metrics, and accumulated agent responses. This design prevents cycles by enforcing a monotonically increasing turn counter and hard tool-call budgets that trigger terminal nodes when exhausted.
Single-Agent vs. Multi-Agent Graph Expansion
The topology adapts its branching behavior dynamically based on the number of active agents specified in the routing decision.
Linear Execution in Single-Agent Scenarios
When the director determines only one agent should act, the graph maintains a linear chain. The buildDirectorPrompt() function in lib/orchestration/director-prompt.ts generates a prompt encoding the current state, and parseDirectorDecision() extracts a single target agent.
// Single-agent routing creates a simple linear path
const decision = parseDirectorDecision(llmResult);
if (decision.type === 'agent' && decision.agents.length === 1) {
// Linear expansion: one child node for the sole agent
graph.addAgentNode({
parent: currentDirectorNode,
agentId: decision.agents[0],
turnCount: decision.turnCount + 1
});
}
Control returns to the director after the agent produces its response, creating a simple ping-pong pattern between director and agent nodes. No branching occurs, and the directorState accumulator updates with exactly one new response entry per turn.
Branching and Merging in Multi-Agent Scenarios
Multi-agent scenarios trigger parallel graph expansion. When parseDirectorDecision() identifies multiple target agents, the director node spawns independent AgentNode branches for each participant.
// Multi-agent branching in lib/orchestration/director-graph.ts
if (decision.type === 'agent' && decision.agents.length > 1) {
// Create parallel branches for each active agent
decision.agents.forEach(agentId => {
graph.addAgentNode({
parent: currentDirectorNode,
agentId,
turnCount: decision.turnCount + 1,
// Each branch receives the shared state but executes independently
inheritedState: directorState
});
});
}
Each branch executes its own agent loop—potentially invoking tools or sub-chains—independently. The runPiDirectorLoop() function in lib/chat/pi/director-loop.ts manages this concurrency, collecting results from all branches before constructing a merged directorState. This merged state contains an array of agentResponses indexed by agent ID, allowing the next director turn to see the collective output of the previous multi-agent step.
Core Implementation Files and Functions
The orchestration logic spans three critical modules:
lib/orchestration/director-graph.ts – Defines the graph structure and node relationship APIs. This file manages the parent-child relationships between director decisions and agent executions.
lib/orchestration/director-prompt.ts – Implements the prompt construction and parsing logic. The buildDirectorPrompt() function serializes the current graph state into an LLM-readable format, while parseDirectorDecision() deserializes the LLM's routing choice into a structured decision object containing the agents array and action type.
lib/chat/pi/director-loop.ts – Contains the high-level execution engine. This module coordinates the graph traversal, manages the transition between director turns and agent execution phases, and handles the merge logic for multi-agent branches.
// High-level orchestration pattern from director-loop.ts
async function runPiDirectorLoop(initialState: DirectorState) {
let state = initialState;
while (!state.done) {
// Build prompt from current graph topology
const prompt = buildDirectorPrompt(state);
const llmResult = await callLlm(prompt);
const decision = parseDirectorDecision(llmResult);
// Expand graph based on single vs multi-agent decision
if (decision.type === 'agent') {
state = await executeAgentBranches(decision.agents, state);
} else if (decision.type === 'tool') {
state = await executeTool(decision);
} else {
state.done = true;
}
}
return state;
}
Managing State and Budget Constraints
The director graph enforces resource limits through hard and soft budgets tracked in the directorState.ledger. Hard budgets cap the total number of tool calls across all graph branches, ensuring the multi-agent expansion cannot consume unlimited resources. When the hard budget reaches zero, the director automatically injects a terminal tool invocation that closes the graph.
Soft budgets apply per-agent limits, constraining individual branch depth without affecting sibling branches. This allows fine-grained control over expensive agents while permitting simpler agents to continue processing.
Summary
- The director graph topology in OpenMAIC structures conversations as a directed acyclic graph with typed nodes for directors, agents, and tools.
- Single-agent scenarios produce linear graphs where the director and single agent alternate turns without branching.
- Multi-agent scenarios create parallel branches from the director node, with each
AgentNodeexecuting independently before results merge back into the unifieddirectorState. - Key functions
buildDirectorPrompt()andparseDirectorDecision()inlib/orchestration/director-prompt.tsdrive the graph expansion logic. - Hard budgets prevent infinite graph growth by forcing termination when tool-call limits are exhausted across all branches.
Frequently Asked Questions
What is the director graph in OpenMAIC?
The director graph is a directed acyclic graph defined in lib/orchestration/director-graph.ts that models conversation flow as a series of routing decisions. It consists of DirectorNode entities that decide whether to invoke tools or route to agents, AgentNode entities that handle response generation, and ToolNode entities for external integrations. This structure separates high-level conversation control from low-level agent execution.
How does OpenMAIC handle multiple agents simultaneously?
When the director decision identifies multiple target agents, the graph expands by creating parallel AgentNode branches from the current director node. Each branch executes its agent loop independently, potentially making tool calls, while the runPiDirectorLoop() function in lib/chat/pi/director-loop.ts collects all responses. These responses merge into a single directorState object containing an array of agent outputs before the next director turn begins.
What prevents infinite loops in the director graph?
The graph acyclicity is enforced through a hard budget mechanism tracked in the directorState.ledger. This budget decrements with each tool invocation across all branches, and when exhausted, the director automatically routes to a terminal finish node. Additionally, the monotonically increasing turnCount property ensures each graph level represents forward progress rather than cycles.
Where is the director graph defined in the codebase?
The core graph data structures and manipulation utilities reside in lib/orchestration/director-graph.ts. Prompt construction and decision parsing logic are implemented in lib/orchestration/director-prompt.ts, while the high-level execution loop that traverses the graph is located in lib/chat/pi/director-loop.ts. Test suites in tests/lib/chat/pi/route-cue-user.test.ts demonstrate the routing behavior for both single and multi-agent configurations.
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 →