mirror of
https://github.com/JackChen-me/open-multi-agent.git
synced 2026-10-22 02:53:26 -05:00
Systematic documentation review: 12 new docs pages (coordinator, streaming, budgets and limits, errors, hooks and callbacks, glossary, production checklist, Run Viewer, MCP, sandbox and shell, evaluation in CI, routing evaluation) plus a docs index; the three longest guides split with stub sections that keep existing anchors; observability release records moved to docs/internal; statements re-verified against the code and corrected; READMEs, AGENTS.md, CONTRIBUTING.md, and issue templates synced. Documentation only, no behavior change.
224 lines
11 KiB
Markdown
224 lines
11 KiB
Markdown
# Execution routing
|
|
|
|
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](model-routing.md), 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:
|
|
|
|
```ts
|
|
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 thrown class and how
|
|
to clear it are in
|
|
[errors](errors.md#routingdeclarationrequirederror).
|
|
|
|
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
|
|
|
|
```ts
|
|
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:
|
|
|
|
```ts
|
|
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`.
|
|
|
|
```ts
|
|
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](./observability.md#trace-spans),
|
|
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:
|
|
|
|
```ts
|
|
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.
|