OrchestratorState Annotation in OpenMAIC: The Complete LangGraph State Schema Guide

The OrchestratorState in OpenMAIC is defined using LangGraph's Annotation.Root in lib/orchestration/director-graph.ts, serving as the centralized type-safe state container that separates immutable input fields from mutable fields updated by graph nodes during multi-agent orchestration.

OpenMAIC implements its multi-agent conversation flow through a LangGraph orchestration graph. At the heart of this system lies the OrchestratorState annotation—a carefully structured schema that declares every piece of data flowing through the director-graph. This article examines the complete state annotation, its field categories, reducer patterns, and practical usage based on the THU-MAIC/OpenMAIC source code.

What Is the OrchestratorState Annotation?

The OrchestratorState is a LangGraph Annotation.Root object that functions as the shared state contract for all nodes in the orchestration graph. It distinguishes between two fundamental categories of fields:

  • Input fields — set once at graph entry and remain immutable
  • Mutable fields — updated by nodes during execution, some using reducers for array accumulation

This design pattern ensures type safety across the entire multi-agent pipeline while enabling predictable state mutations.

Complete OrchestratorState Declaration

The full annotation is implemented in [lib/orchestration/director-graph.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/director-graph.ts):

const OrchestratorState = Annotation.Root({
  // Input (set once at graph entry)
  messages: Annotation<StatelessChatRequest['messages']>,
  storeState: Annotation<StatelessChatRequest['storeState']>,
  availableAgentIds: Annotation<string[]>,
  languageModel: Annotation<LanguageModel>,
  thinkingConfig: Annotation<ThinkingConfig | null>,
  discussionContext: Annotation<{ topic: string; prompt?: string } | null>,
  triggerAgentId: Annotation<string | null>,
  userProfile: Annotation<{ nickname?: string; bio?: string } | null>,
  agentConfigOverrides: Annotation<Record<string, AgentConfig>>,

  // Mutable (updated by nodes)
  currentAgentId: Annotation<string | null>,
  turnCount: Annotation<number>,
  agentResponses: Annotation<AgentTurnSummary[]>({
    reducer: (prev, update) => [...prev, ...update],
    default: () => [],
  }),
  whiteboardLedger: Annotation<WhiteboardActionRecord[]>({
    reducer: (prev, update) => [...prev, ...update],
    default: () => [],
  }),
  shouldEnd: Annotation<boolean>,
  totalActions: Annotation<number>,
});
type OrchestratorStateType = typeof OrchestratorState.State;

The OrchestratorStateType export provides the TypeScript type used throughout the codebase for function signatures and node implementations.

Input Fields: Graph Entry Parameters

These nine fields are populated once when the orchestration graph initializes and remain read-only during execution:

Field Type Purpose
messages StatelessChatRequest['messages'] Incoming chat message history
storeState StatelessChatRequest['storeState'] Persistent conversation state
availableAgentIds string[] IDs of agents eligible to participate
languageModel LanguageModel LLM instance for director decision-making
thinkingConfig ThinkingConfig | null Optional reasoning configuration
discussionContext { topic: string; prompt?: string } | null Topic and prompt context
triggerAgentId string | null Optional first agent to activate
userProfile { nickname?: string; bio?: string } | null User identity information
agentConfigOverrides Record<string, AgentConfig> Runtime agent configuration modifications

These fields originate from the StatelessChatRequest type defined in [lib/types/chat.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/types/chat.ts).

Mutable Fields: Node-Updatable State

These six fields can be modified by graph nodes during execution:

Field Type Update Pattern
currentAgentId string | null Direct assignment (which agent speaks now)
turnCount number Incremented by nodes tracking conversation progress
agentResponses AgentTurnSummary[] Reducer appends — new entries merged with existing array
whiteboardLedger WhiteboardActionRecord[] Reducer appends — records accumulated across turns
shouldEnd boolean Flag set to terminate conversation
totalActions number Counter incremented per action

The reducer pattern on agentResponses and whiteboardLedger is critical—these annotations specify:

reducer: (prev, update) => [...prev, ...update],
default: () => [],

This ensures that when a node returns partial state with new array items, they merge rather than replace existing entries.

Practical Usage Examples

Accessing State in a Node

Nodes receive the full OrchestratorStateType and can read any field:

import type { OrchestratorStateType } from '@/lib/orchestration/director-graph';

function exampleNode(state: OrchestratorStateType) {
  const current = state.currentAgentId; // e.g., 'agent-1' or null
  const messages = state.messages;      // readonly chat history
  // …director logic using availableAgentIds, languageModel, etc.
}

Updating Scalar Fields

Return a partial state object with updated values:

async function incrementTurn(
  state: OrchestratorStateType,
): Promise<Partial<OrchestratorStateType>> {
  return { turnCount: (state.turnCount ?? 0) + 1 };
}

Appending to Reducer-Backed Arrays

For fields with reducers, return only the new items—the reducer handles merging:

function addAgentResponse(
  state: OrchestratorStateType,
  response: AgentTurnSummary,
): Partial<OrchestratorStateType> {
  return { agentResponses: [response] }; // reducer automatically appends
}

The same pattern applies to whiteboardLedger for recording collaborative actions.

Key Source Files

Understanding the OrchestratorState annotation requires familiarity with these OpenMAIC files:

File Relevance to State Annotation
[lib/orchestration/director-graph.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/director-graph.ts) Contains the OrchestratorState annotation and complete graph definition
[lib/orchestration/registry/types.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/registry/types.ts) Defines AgentConfig used in agentConfigOverrides
[lib/orchestration/registry/store.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/registry/store.ts) Agent registry that consumes availableAgentIds
[lib/orchestration/prompt-builder.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/orchestration/prompt-builder.ts) Constructs prompts from OrchestratorState fields
[lib/types/chat.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/types/chat.ts) Source of StatelessChatRequest types referenced in input fields

Summary

  • The OrchestratorState annotation in OpenMAIC is a LangGraph Annotation.Root declared in lib/orchestration/director-graph.ts
  • It separates immutable input fields (9 total) from mutable node-updatable fields (6 total)
  • Reducer functions on agentResponses and whiteboardLedger enable safe array accumulation without manual merging
  • The OrchestratorStateType derived type enforces type safety across all graph nodes
  • This state schema enables the director node to coordinate multi-agent conversations while maintaining predictable, traceable state mutations

Frequently Asked Questions

What is Annotation.Root in LangGraph?

Annotation.Root is a LangGraph utility that declares the complete state schema for a graph. In OpenMAIC, it wraps individual Annotation<> field declarations to create the OrchestratorState object, which serves as the single source of truth for type definitions and runtime state shape.

Why do some OrchestratorState fields use reducers?

Reducer functions specify how to merge updates when nodes return partial state. OpenMAIC uses reducers on agentResponses and whiteboardLedger so that nodes can return new array items without overwriting existing entries—the reducer automatically spreads previous and updated values together.

How do I extend OrchestratorState with custom fields?

Add new fields to the Annotation.Root declaration in lib/orchestration/director-graph.ts. For arrays requiring accumulation, include a reducer function; for scalar values, a simple Annotation<Type> suffices. Rebuild the project to regenerate OrchestratorStateType across dependent files.

What triggers the initial population of input fields?

Input fields are populated from the StatelessChatRequest received at graph entry. The orchestration initialization code maps request properties—including messages, storeState, availableAgentIds, and optional configuration—to their corresponding OrchestratorState fields before the first node executes.

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 →