# Understanding Director Graph Topology in OpenMAIC: Single vs. Multi-Agent Orchestration

> Explore OpenMAIC's director graph topology. Learn how it orchestrates single-agent linear chains and multi-agent parallel collaboration for unified output.

- Repository: [MAIC/OpenMAIC](https://github.com/THU-MAIC/OpenMAIC)
- Tags: deep-dive
- Published: 2026-09-04

---

**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`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/director-graph.ts), the core data structures define three primary node types:

- **`DirectorNode`** – Maintains the current turn count, budget ledger, and routing decisions
- **`AgentNode`** – Encapsulates individual agent execution contexts and response generation
- **`ToolNode`** – 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`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/director-prompt.ts) generates a prompt encoding the current state, and `parseDirectorDecision()` extracts a single target agent.

```typescript
// 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.

```typescript
// 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`](https://github.com/THU-MAIC/OpenMAIC/blob/main/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`](https://github.com/THU-MAIC/OpenMAIC/blob/main/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`](https://github.com/THU-MAIC/OpenMAIC/blob/main/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`](https://github.com/THU-MAIC/OpenMAIC/blob/main/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.

```typescript
// 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 `AgentNode` executing independently before results merge back into the unified `directorState`.
- Key functions `buildDirectorPrompt()` and `parseDirectorDecision()` in [`lib/orchestration/director-prompt.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/director-prompt.ts) drive 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`](https://github.com/THU-MAIC/OpenMAIC/blob/main/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`](https://github.com/THU-MAIC/OpenMAIC/blob/main/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`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/director-graph.ts). Prompt construction and decision parsing logic are implemented in [`lib/orchestration/director-prompt.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/director-prompt.ts), while the high-level execution loop that traverses the graph is located in [`lib/chat/pi/director-loop.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/chat/pi/director-loop.ts). Test suites in [`tests/lib/chat/pi/route-cue-user.test.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/tests/lib/chat/pi/route-cue-user.test.ts) demonstrate the routing behavior for both single and multi-agent configurations.