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
OrchestratorStateannotation in OpenMAIC is a LangGraphAnnotation.Rootdeclared inlib/orchestration/director-graph.ts - It separates immutable input fields (9 total) from mutable node-updatable fields (6 total)
- Reducer functions on
agentResponsesandwhiteboardLedgerenable safe array accumulation without manual merging - The
OrchestratorStateTypederived 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →