Execution routing

July 26, 2026 · View on GitHub

Execution Routing decides the execution topology for an automatic runTeam() call: use one Agent directly (single) or ask the Coordinator to build and execute a Team plan (team). It is orthogonal to Model Routing, which chooses the model for calls inside the selected topology.

Precedence

runTeam() resolves topology in this order:

  1. Explicit mode: 'single' | 'team'.
  2. Declared governance topology or preferredUnderBudget degradation policy.
  3. The per-run or orchestrator-level custom executionRouter.
  4. The built-in DeterministicRouter.
  5. When executionRouting.strategy is hybrid and the default/fallback deterministic result is Single, the semantic TaskProfiler and deterministic semantic policy.

Routers run only for automatic, non-planOnly topology selection. They never override an explicit mode, declared role topology, or governance budget policy. governanceIntent: 'none' retains automatic topology selection, while still counting as an explicit governance declaration for consequential-tool confirmation.

A valid custom Router decision is final and is never reinterpreted by the Profiler. If a custom Router fails and its deterministic fallback selects Single, Hybrid routing may profile that fallback candidate.

Hybrid semantic routing

Hybrid is opt-in. It preserves the cheap deterministic Team decision and adds at most one no-tool model call only when that router would otherwise choose Single:

const orchestrator = new OpenMultiAgent({
  executionRouting: {
    strategy: 'hybrid',
    confidenceThreshold: 0.7,
    failurePolicy: 'fallback',
  },
})

LLMTaskProfiler produces a strict, provider-neutral TaskProfile containing only inferred semantic signals: independent evidence sources, independent review, conflicting objectives, side-effect intent, permission isolation, decomposability, parallelism, complexity, confidence, and bounded reasons. The goal is treated as untrusted data. The Profiler receives no Agent/Coordinator system prompts, credentials, tool implementations, or complete permission details and cannot call tools.

The model does not choose the topology. A deterministic policy consumes the profile together with framework-computed facts:

  • high-confidence independent evidence, review, conflict, or genuinely parallel work may upgrade Single to Team;
  • inferred side-effect/isolation needs that intersect with consequential effective grants or multiple caller-declared permissionBoundary values produce ROUTING_DECLARATION_REQUIRED;
  • low confidence or no material signal keeps Single;
  • V1 never changes Team to Single.

ROUTING_DECLARATION_REQUIRED is raised before any Coordinator, worker, or tool-capable Agent starts. A profile never creates requiredRoles, approves a side effect, or proves that governance was satisfied.

The built-in Profiler resolves its adapter in this order:

  1. per-run executionRouting.adapter;
  2. orchestrator executionRouting.adapter;
  3. the effective Coordinator adapter;
  4. an adapter created from the orchestrator's default provider/model.

The selected adapter receives the goal text as untrusted user data. In particular, step 4 can send the goal to defaultProvider even when every worker uses its own adapter and the deterministic Single path previously needed no default-provider call. Applications with data-residency or provider-boundary requirements should configure executionRouting.adapter, provide a Coordinator adapter, or select strategy: 'deterministic'.

executionRouting.model follows the same per-run-over-orchestrator precedence, then the Coordinator model and default model. Profiler usage is charged to the run token/cost budget and appears in routing tracing and TeamRunResult.totalTokenUsage.

Contract

import type {
  ExecutionRouter,
  RoutingContext,
  RoutingDecision,
} from '@open-multi-agent/core'

class ApplicationRouter implements ExecutionRouter {
  readonly version = 'application-router-v1'

  decide(context: RoutingContext): RoutingDecision {
    const mode = context.goal.startsWith('[team]') ? 'team' : 'single'
    return {
      mode,
      reasons: [`Application policy selected ${mode}.`],
      routerVersion: this.version,
    }
  }
}

RoutingContext contains the goal, a structured roster summary, and optional remaining token/cost ceilings at the point routing begins. Roster entries contain name, effective model, and an optional directly declared tool count. Full systemPrompt values, credentials, API keys, tool implementations, and model output are never supplied to the router. The summary is intentionally small so a later structured capability model can extend it without exposing prompts.

Set a default on the orchestrator or override it for one call:

const orchestrator = new OpenMultiAgent({
  executionRouter: new ApplicationRouter(),
})

const result = await orchestrator.runTeam(team, goal, {
  executionRouter: requestScopedRouter,
})

The per-run router wins over the orchestrator router. A decision is valid only when mode is single or team, reasons is a string array, routerVersion matches the router's non-empty version, optional confidence is between 0 and 1, and single has at least one roster member.

Failure behavior

The default failurePolicy: 'fallback' keeps routing infrastructure advisory. If a custom Router throws, rejects, times out, or returns an invalid decision, OMA falls back to DeterministicRouter. Profiler timeout, refusal, throw, or invalid structured output keeps that deterministic route.

Set failurePolicy: 'fail' to terminate instead. Profiler failures use ROUTING_PROFILER_FAILED; Router/Profiler deadlines use ROUTING_TIMEOUT. The run trace is closed with the corresponding error or timeout status before the typed error is rethrown, so failed routing does not leave an incomplete run. Machine-readable status, requestedRouterVersion, and fallbackCode fields remove the need to parse human-readable reasons.

result.routingDecision
// {
//   mode: 'single',
//   reasons: [
//     'The goal has one concise action and no multi-stage structure.',
//     'Execution router fallback: custom decision failed (Error).'
//   ],
//   routerVersion: 'deterministic-v1',
//   status: 'fallback',
//   requestedRouterVersion: 'application-router-v1',
//   fallbackCode: 'router-error'
// }

Every runTeam() topology choice exposes TeamRunResult.routingDecision. Its source distinguishes caller override, governance declared, framework policy, and automatic router decisions; only router records carry the actual routerVersion. See the five routing trace source classifications, including the compatibility-only legacy-deterministic label.

When semantic profiling ran, semanticRoutingAssessment is attached both to the result and routing record. It reports the inferred profile/version, deterministic legacy decision, semantic recommendation, actual topology, usage, and structured fallback state. The executed task topology, final tool grants, and ExecutionReceipt remain governance truth.

Deterministic default

Omitting executionRouting uses the no-profiler deterministic router. Set the strategy explicitly when you want configuration-level clarity:

const orchestrator = new OpenMultiAgent({
  executionRouting: { strategy: 'deterministic' },
})

This makes no extra model call and retains deterministic Router results and custom-Router fallback behavior. Hybrid should be enabled only for providers and models that have passed the documented Shadow gate.

Explicitly installing new DeterministicRouter() as executionRouter also makes that valid Router decision final under the precedence rules, so the Profiler does not reinterpret it. Prefer strategy: 'deterministic' when the intent is compatibility mode because it states the no-profiler behavior directly.

Built-in deterministic policy

DeterministicRouter wraps the single isSimpleGoal() heuristic; OMA does not maintain a second competing heuristic.

  • An empty roster cannot select the Single path; otherwise the goal heuristic decides.
  • English sequence, coordination, parallelism, and multi-deliverable signals remain supported.
  • Chinese sequence markers (先…然后, 第一步/第二步), circled steps, action enumerations, semicolon-separated clauses, and connected action verbs are recognized.
  • Japanese (まず…次に, 第一に…第二に, ステップ1/手順1) and Korean (먼저…그다음, 첫째…둘째, 1단계/2단계) sequence markers are recognized, and dense enumeration counts for kana- and Hangul-initial lists as well as Han. Each marker pair must appear, so a lone marker does not force Team.
  • Length uses a cheap script-aware information estimate. CJK characters — Han, Japanese kana, and Korean Hangul — count as 2.25 units; ordinary Latin word runs approximate token density; long unbroken runs keep their raw length.

Language coverage is tiered honestly rather than claimed uniform:

  • Chinese, Japanese, and Korean are one tier: the structural markers above plus 2.25-unit length weighting.
  • Latin-script languages share English's length treatment (word runs approximate token density). The structural word patterns are English-specific, so other Latin languages are routed by length and punctuation, not by their own sequence words.
  • Other scripts fall back conservatively: each non-space character counts as one unit and no structural markers apply, so routing relies on the length threshold alone.

CJK verb-connective sequencing is a deliberate non-goal. Chinese keeps a verb-connective pattern because its verbs are invariant tokens, but Japanese and Korean verbs inflect (Japanese て-form, Korean agglutinative endings), so matching them would require morphological analysis this heuristic intentionally avoids; explicit markers and enumeration cover the structural signal instead.

This policy remains intentionally cheap rather than semantically ambitious. Hybrid routing corrects high-confidence false-Single cases after this step. Applications that need domain-specific, authoritative decisions should still inject an ExecutionRouter or declare an explicit mode/governance topology.

Governance boundary

Execution Routing does not declare governance. The consequential-tool fallback still treats only governanceIntent === undefined as undeclared, regardless of which router selected Single or Team. Router decisions and TaskProfiles cannot satisfy named-role or independent-review requirements; those facts continue to come from structured governance declarations and the executed topology.

Promotion criteria

Shadow evaluation is a release-engineering technique, not a user-facing runtime mode: run fixed fixtures in CI and canaries, compare its recommendation without changing production topology, and promote a provider/model to supported Hybrid use only after the documented accuracy, invalid-output, cost, and latency gates pass. Online historical-success learning and Team-to-Single optimization are outside V1.