Skip to content

Orchestration modes

Supervisor and grounded topologies, mode_config settings, NodeConfig per-node customization, and resource_id for grounded context anchoring.

Intended audience: Stakeholders, Business analysts, Solution architects, Developers, Testers

Learning outcomes by role

Stakeholders

  • Contrast supervisor versus grounded modes for complexity and safety postures.

Business analysts

  • Capture mode_config and node-level customization in acceptance language.

Solution architects

  • Explain resource_id anchoring and topology constraints for integration designs.

Developers

  • Apply mode_config and NodeConfig fields when building orchestrator configs.

Testers

  • Validate mode-specific settings, grounded context injection, and per-node override behavior.

An orchestration mode defines the routing topology inside the agent graph — how intent is classified, how tools are called, how results are validated, and how the final response is assembled. The mode is set at creation alongside framework_type and both are immutable for the life of the orchestrator.

Real implementations (full LangGraph state machine in src/cadence/engine/impl/langgraph/):

  • langgraph + supervisor — multi-step agent pipeline.
  • langgraph + grounded — resource-anchored single-scope agent.

Placeholder modes registered in _BACKEND_CONFIGS but backed by create_not_implemented_orchestrator(...) that raises UnsupportedOperationError on invoke:

  • langgraph: coordinator, handoff, pipeline, reflection
  • openai_agents: supervisor, coordinator, handoff, pipeline, reflection, ensemble, hierarchical
  • google_adk: none registered (all placeholder orchestrators exist but the factory skips them)

grounded is langgraph only — there is no openai_agents/grounded and no google_adk/grounded.

Create-time rules — The Pydantic schema at api/orchestrator/schemas.py accepts any of {langgraph, openai_agents, google_adk} × {supervisor, coordinator, handoff, grounded}. The validator at domain/orchestrator/validator.py:145-147 reads FRAMEWORK_SUPPORTED_MODES and would currently reject openai_agents/supervisor and openai_agents/coordinator (the constant only has {supervisor, grounded} for langgraph and empty sets elsewhere). The factory has placeholders for all those combinations, so a permissive config that bypasses the validator will accept them. Treat the factory registry as the source of truth and assume google_adk and most non-supervisor/non-grounded combinations are placeholders that will raise at runtime.

Details and provider constraints are on Orchestration backends.

| Mode | Framework | Status | Use case | | ------------- | ---------------------------- | ------ | -------------------------------------------------------------------------------------- | | supervisor | langgraph, openai_agents | Active | Open-ended assistant with multi-step tool routing, planning, and optional validation | | grounded | langgraph | Active | Answers scoped to a specific resource anchor — resource_id required per chat request | | coordinator | langgraph, openai_agents | Active | Multi-agent coordination topology | | handoff | langgraph, openai_agents | Active | Sequential agent handoffs |

flowchart TD
IN[Incoming message] --> CL[Router node]
CL -->|direct response| RE[Responder node]
CL -->|clarify| CLA[Clarifier node]
CL -->|plan| PL[Planner node]
PL --> EX[Executor node — tool calls]
EX --> |tool results| PL
PL --> |max_agent_hops reached or done| SY[Synthesizer node]
RE --> OUT[Response]
SY --> VA{enabled_llm_validation ?}
VA -->|yes| VN[Validation node]
VA -->|no| OUT
VN --> OUT

The supervisor topology routes incoming intent through a router node, which decides whether to answer directly, ask for clarification, or plan a multi-step tool sequence. The planner calls tools through the executor, iterates up to max_agent_hops times, then passes results to the synthesizer. Each logical node in the graph can be customized individually via NodeConfig.

mode_config is nested inside the orchestrator’s config JSON object. All keys are optional; omitting a key uses the default value.

orchestrator config.mode_config (supervisor example)
{
"default_llm_config_id": "<uuid>",
"mode_config": {
"max_agent_hops": 25,
"max_tool_rounds": 3,
"enabled_parallel_tool_calls": true,
"node_execution_timeout": 60,
"enabled_llm_validation": false,
"enabled_clarification_intent": true
}
}

From BaseOrchestratorSettings (src/cadence/engine/base/settings.py:10-19) — apply to every mode:

| Field | Default | Description | | ------------------------ | ------- | ------------------------------------------------------------------------------ | | node_execution_timeout | 60 | Per-node execution timeout in seconds | | max_context_window | 16000 | Maximum token context window | | message_context_window | 10 | Number of past messages included in context (supervisor overrides this to 5) | | enabled_auto_compact | false | Auto-compact context when it exceeds the window | | enabled_suggestion | false | Enable follow-up suggestion generation after each response | | conversation_tone | None | Optional tone override (from TONE_CATALOG in engine.tones) | | autocompact | NodeConfig() | Auto-compact node config (model override, prompt, etc.) | | suggestion_node | NodeConfig() | Suggestion node config | | error_handler_node | NodeConfig() | Error handler node config |

From BaseSupervisorSettings (src/cadence/engine/base/supervisor_settings.py:11-23):

| Field | Default | Description | | ------------------------------ | ------- | -------------------------------------------------------------- | | message_context_window | 5 | Overrides the base default of 10 | | max_agent_hops | 25 | Maximum recursive tool-call cycles before forced synthesis | | max_tool_rounds | 3 | Maximum tool call rounds per planner step | | enabled_parallel_tool_calls | true | Allow concurrent tool calls in a single planner step | | enabled_llm_validation | false | Run a validation LLM pass on the draft response before sending | | enabled_clarification_intent | true | Route ambiguous queries through the clarifier before planning | | router_node / planner_node / synthesizer_node / validation_node / clarifier_node / responder_node | NodeConfig() | Per-node LLM overrides |

From BaseGroundedSettings (src/cadence/engine/base/grounded_settings.py:11-23):

| Field | Default | Description | | ------------------- | ------- | ------------------------------------------------------------------------- | | scope_rules | "" | Free-text constraints injected into the agent’s system context at runtime | | max_agent_hops | 25 | Maximum recursive tool-call cycles before forced synthesis | | max_tool_rounds | 3 | Maximum tool-call rounds before forced synthesis | | enabled_parallel_tool_calls | true | Allow concurrent tool calls in a single planner step | | enabled_validator | true | Run a validator pass to check answer quality against the resource | | router_node / planner_node (alias agent_node) / synthesizer_node / validation_node | NodeConfig() | Per-node LLM overrides |

Each logical node in the graph can have its own LLM config, model, and prompt override, independent of the orchestrator’s default_llm_config_id. This lets you run a cheap model for routing and a more capable one for synthesis.

cadence/engine/base/node_config.py
class NodeConfig(BaseModel):
llm_config_id: Optional[str] = None # Override org-level default for this node
model_name: Optional[str] = None # Model name override (e.g. "gpt-4o-mini" for fast nodes)
prompt: Optional[str] = None # System prompt override for this node
temperature: Optional[float] = None
max_tokens: Optional[int] = None
timeout: Optional[int] = None # Per-node timeout in seconds

Nodes that accept NodeConfig overrides:

| Mode | Configurable nodes | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Supervisor | router_node, planner_node, synthesizer_node, validation_node, clarifier_node, responder_node, autocompact, suggestion_node, error_handler_node | | Grounded | router_node, planner_node (alias: agent_node), synthesizer_node, autocompact, suggestion_node, error_handler_node |

Example: cheap model for routing, stronger model for synthesis

Section titled “Example: cheap model for routing, stronger model for synthesis”
orchestrator config
{
"default_llm_config_id": "<gpt4o-config-uuid>",
"mode_config": {
"router_node": {
"model_name": "gpt-4o-mini"
},
"synthesizer_node": {
"model_name": "gpt-4o",
"temperature": 0.3
}
}
}

Grounded mode requires a resource_id on every chat request. This is the anchor object — a document ID, URL, database record ID, or any string — that constrains what the agent is allowed to answer about.

cadence/api/chat/schemas.py
class ChatCompletionRequest(BaseModel):
message: str # Required; 1–1000 characters
conversation_id: Optional[str] # Resumes an existing thread
stream: bool = True
resource_id: Optional[str] # Required for grounded mode

The resource_id value is written into the graph’s initial state as state.resource_id. Plugin tool nodes read it to scope their queries:

plugin tool handler (example)
resource_id = state.get("resource_id")
results = await db.query(doc_id=resource_id, question=query)
POST /api/orgs/{org_id}/orchestrators
{
"name": "General Purpose Assistant",
"framework_type": "langgraph",
"mode": "supervisor",
"active_plugin_ids": ["com.example.web-search"],
"tier": "hot",
"config": {
"default_llm_config_id": "<uuid>",
"mode_config": {
"max_agent_hops": 10,
"max_tool_rounds": 3,
"enabled_parallel_tool_calls": true,
"enabled_clarification_intent": true,
"enabled_llm_validation": false
}
}
}

| Symptom | Cause | Fix | | ----------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | Chat returns UnsupportedOperationError | Orchestrator mode is registered but not yet implemented (coordinator, handoff, etc.) | Use supervisor or grounded mode | | Agent loops without completing | max_agent_hops too high or tool always returns partial results | Reduce max_agent_hops; fix tool return logic | | Grounded mode ignores resource_id | Plugin tool doesn’t read state.resource_id | Update the plugin to scope queries using state["resource_id"] | | Chat fails in grounded mode | resource_id missing from request | Always pass resource_id in the chat body for grounded orchestrators | | Router sends all queries to the same path | enabled_clarification_intent catching too broadly | Set enabled_clarification_intent: false or tune the router via router_node.prompt | | Validation node rejects valid responses | enabled_llm_validation prompt too strict | Set enabled_llm_validation: false or override validation_node.prompt | | Need to change mode | Mode is immutable | Create a new orchestrator; update central points to reference it |