feat(core): add hybrid semantic execution routing

This commit is contained in:
Jack Chen committed 2026-07-26 13:56:28 +08:00
1 parent 136d8ed56e
commit f373b638cf
24 files changed
+2150 -71

No files matched your search

+114 -9
View File
@@ -8,12 +8,68 @@ Execution Routing decides the execution topology for an automatic `runTeam()` ca
1. Explicit `mode: 'single' | 'team'`.
2. Declared governance topology or `preferredUnderBudget` degradation policy.
3. The per-run `executionRouter`.
4. The orchestrator-level `executionRouter`.
5. The built-in `DeterministicRouter`.
3. The per-run or orchestrator-level custom `executionRouter`.
4. The built-in `DeterministicRouter`.
5. When 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 the default strategy. 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 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.
`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
@@ -55,7 +111,15 @@ The per-run router wins over the orchestrator router. A decision is valid only w
## Failure behavior
Routing is advisory infrastructure and must not turn into a run failure. If a custom router throws, rejects, or returns an invalid decision, OMA falls back to `DeterministicRouter`. The returned decision uses the built-in router version and appends a fallback reason to `reasons`.
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`.
Machine-readable `status`, `requestedRouterVersion`, and `fallbackCode` fields
remove the need to parse human-readable `reasons`.
```ts
result.routingDecision
@@ -65,7 +129,10 @@ result.routingDecision
// 'The goal has one concise action and no multi-stage structure.',
// 'Execution router fallback: custom decision failed (Error).'
// ],
// routerVersion: 'deterministic-v1'
// routerVersion: 'deterministic-v1',
// status: 'fallback',
// requestedRouterVersion: 'application-router-v1',
// fallbackCode: 'router-error'
// }
```
@@ -76,6 +143,27 @@ 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 compatibility mode
Use one line to restore the previous no-profiler behavior:
```ts
const orchestrator = new OpenMultiAgent({
executionRouting: { strategy: 'deterministic' },
})
```
This makes no extra model call and retains the existing deterministic Router
result and custom-Router fallback behavior. The compatibility path remains
available for offline use, incident degradation, and the entire major release
cycle.
## Built-in deterministic policy
`DeterministicRouter` wraps the single `isSimpleGoal()` heuristic; OMA does not maintain a second competing heuristic.
@@ -94,12 +182,29 @@ Language coverage is tiered honestly rather than claimed uniform:
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 is intentionally honest, cheap, and language-neutral rather than semantically ambitious. Semantically equivalent English, Chinese, Japanese, and Korean goals should select the same execution mode. Applications that need domain-specific semantics should inject an `ExecutionRouter` or declare an explicit mode/governance topology.
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 also cannot satisfy named-role or independent-review requirements; those facts continue to come from structured governance declarations and the executed topology.
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.
## Behavior change
## Major-version migration
The built-in automatic route recognizes short CJK (Chinese, Japanese, Korean) multi-stage goals and script-weighted length instead of relying on English-only word patterns plus raw character count. Equivalent goals translated between English, Chinese, Japanese, and Korean no longer change Single/Team topology in the routing stability gate. Existing English and Chinese outcomes, including structural-pattern regressions, retain their previous behavior. Script weighting intentionally changes a detailed, long English single-action goal from `team` to `single` when its estimated information length remains bounded; this case is locked by a regression test. Multilingual applications may also observe corrected automatic routes for CJK structured goals.
The next major release changes automatic `runTeam()` from deterministic-only to
Hybrid by default. A short goal may therefore make one additional model call
and upgrade from Single to Team, increasing latency, token use, and cost.
Applications that require exact old call counts or fully offline routing should
set `executionRouting: { strategy: 'deterministic' }` before upgrading.
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 only after the documented accuracy,
invalid-output, cost, and latency gates pass. Online historical-success learning
and Team-to-Single optimization are outside V1.