Smart Routing Governance¶
A router enrolled in Continuum Hub can receive a smart-routing policy as part of its policy envelope. In this model, Continuum Hub defines routing intent (tiers, domain/complexity rules, model profiles, and classifier method), while the local router retains full ownership of infrastructure and execution (backend URLs, credentials, enablement switches, and runtime limits).
For the underlying local configuration schema, see Smart Routing.
Prerequisites¶
Hub-governed smart routing requires all three of the following conditions. If any condition is not met, the router uses its local smart_routing: section exclusively:
- Compiled feature: The binary is built with the
control-planeCargo feature (included in all official release binaries). - Control-plane policy enabled: Both
control_plane.enabled: trueandcontrol_plane.policy.enabled: trueare set. - Configuration manager attached: The router is started from a configuration file (standard CLI mode) or built with an attached
ConfigManager. Embedded library builds (ContinuumRouter::from_config,serve_embedded) or builders with hot reload disabled do not attach the governance reconcilers and do not advertisesmart_routing_policy_v1orguardrail_policy_v1; see Library Usage.
Version compatibility¶
Hub-governed smart routing is supported starting in router v1.32.0:
- Router older than v1.32.0: Ignores the envelope's
smart_routingfield and does not advertisesmart_routing_policy_v1. The Hub immediately marks the router asunsupportedrather than waiting for an acknowledgment. - Hub older than v1.32.0: Omits the
smart_routingfield. A v1.32.0+ router advertisessmart_routing_policy_v1and continues running its localsmart_routing:configuration.
See the Governance compatibility matrix for mixed-version fleet behavior across both smart routing and guardrails.
Hub-governed policy fields¶
The Hub policy schema is closed and strongly typed (capped at 256 KiB of canonical JSON). Every top-level field is optional; omitting a field (or passing an empty list) leaves the corresponding local setting in effect:
| Field | Allowed values | Limits |
|---|---|---|
routing_policies | Ordered rules containing name, when (complexity, domain, requires, language, header), and route_to (tier, prefer_domains, require_capabilities; explicit model targets are declined by the router) | Up to 64 rules; rule names up to 128 bytes |
model_profiles | Profile entries specifying model or model_pattern, tier, and domains | Up to 256 profiles |
classifier | method (rule, llm, hybrid, system_one, hybrid_system_one), model, and backend (logical backend name) | Names up to 256 bytes |
virtual_model | Model name that triggers smart routing (e.g., "auto") | Up to 256 bytes |
intercept_all | Boolean flag |
Wire format notes:
- Named tiers: On the wire, tiers are represented by name (
flagship,standard,lightweight) rather than local numeric IDs (1,2,3). - No per-key/per-org conditions: Hub rules cannot use the local-only
when.key_tierorwhen.orgconditions.
Local-only settings (never controlled by the Hub)¶
Infrastructure wiring and local safety bounds are never represented on the governance wire. A Hub policy cannot set, override, or inspect any of the following:
- Master enable switch (
smart_routing.enabled): A localenabled: falsealways takes precedence; the Hub can never turn smart routing on when it is disabled locally. - Backend URLs and credentials (
backends[]): Every backend'surl,api_key,auth,type, and transport settings remain strictly local. The Hub can reference a classifierbackendonly by its logical name, which the router resolves against its ownbackends[]list. - Load management and cost metadata:
load_management,default_tier,debug_headers, and model profile cost fields (cost_per_1k_input_tokens,cost_per_1k_output_tokens). - Classifier tuning and endpoints:
classifier.llmoperational settings (timeouts, retries, temperature, prompts, cache),classifier.rulekeyword/length parameters, andclassifier.system_onetarget endpoints and credentials (endpoint,api_key_env). - Tenant-scoped rule conditions:
when.key_tierandwhen.org.
How Hub and local policies compose¶
The router merges the Hub policy with the local smart_routing: configuration according to the following rules:
| Setting | Effective value |
|---|---|
enabled | Always uses the local value (enabled: false locally cannot be overridden). |
routing_policies | Uses the Hub's rules when non-empty; otherwise uses the local rules. |
model_profiles | Uses the Hub's profiles when non-empty; otherwise uses the local profiles. Each Hub profile inherits the cost fields of any matching local profile (model or model_pattern). |
virtual_model | Uses the Hub's value when specified; otherwise uses the local value. |
intercept_all | true only when both the Hub policy and the local section set true (logical AND). |
classifier.method, classifier.llm.model, classifier.llm.backend | Uses the Hub's value when specified; otherwise uses the local value. |
| All other settings | Always local (load_management, classifier tuning, System One target, debug_headers, default_tier). |
Classifier backend resolution rules¶
When the composed policy uses an LLM-based classifier (llm, hybrid, or hybrid_system_one):
- Name resolution:
classifier.llm.backend(whether specified by the Hub or pinned locally) is resolved against the router's activebackends[]list (including backends synced viacontrol_plane.config_sync.backends). - Eligibility: Backends marked
internal: trueare permitted. Backends that do not exist or haveenabled: falseare rejected, and the router never substitutes a different backend automatically. - Mandatory pin: If a Hub statement selects an LLM-based classifier method (
llm,hybrid,hybrid_system_one) but neither the Hub statement nor the local configuration pins aclassifier.llm.backend, the statement is declined so prompt text is never sent to an unpinned default backend.
Atomic recomputation¶
Composition is recomputed automatically whenever either the local configuration reloads or a new Hub envelope arrives. Updates are applied atomically across the tier registry, smart router, classifiers, and load monitor so in-flight requests never observe a partially updated policy.
Worked example¶
Local configuration:
backends:
- name: primary
type: vllm
url: http://vllm.internal:8000
models: [big-model, small-model]
- name: guard-classifier
type: vllm
url: http://classifier.internal:8000
api_key: ${CLASSIFIER_API_KEY}
internal: true
models: [judge]
smart_routing:
enabled: true
virtual_model: auto
intercept_all: true
classifier:
method: rule
routing_policies:
- name: local_all_light
when: {}
route_to:
tier: 3
model_profiles:
- model: big-model
tier: 1
cost_per_1k_input_tokens: 0.010
- model: small-model
tier: 3
cost_per_1k_input_tokens: 0.001
Hub statement delivered in the policy envelope:
{
"schema_version": 1,
"routing_policies": [
{ "name": "hub_code_to_flagship", "when": { "domain": ["code"] }, "route_to": { "tier": "flagship" } },
{ "name": "hub_default", "when": {}, "route_to": { "tier": "standard" } }
],
"model_profiles": [
{ "model": "big-model", "tier": "flagship", "domains": ["code", "reasoning"] },
{ "model": "small-model", "tier": "standard" }
],
"classifier": { "backend": "guard-classifier", "model": "judge", "method": "hybrid" },
"intercept_all": false
}
Composed configuration executed by the router:
| Setting | Local | Hub | Effective | Reason |
|---|---|---|---|---|
enabled | true | cannot be stated | true | Always local |
routing_policies | local_all_light | 2 rules | hub_code_to_flagship, hub_default | Hub rules replace local rules |
model_profiles | big-model (tier 1), small-model (tier 3) | big-model (flagship), small-model (standard) | big-model tier 1 (0.010), small-model tier 2 (0.001) | Hub profiles apply while preserving matching local cost fields |
virtual_model | auto | omitted | auto | Falls back to local value |
intercept_all | true | false | false | Logical AND (true && false) |
classifier.method | rule | hybrid | hybrid | Hub value applies |
classifier.llm.model | default | judge | judge | Hub value applies |
classifier.llm.backend | unset | guard-classifier | guard-classifier | Resolved in local backends[] (internal: true permitted) |
| Classifier URL & credential | http://classifier.internal:8000, ${CLASSIFIER_API_KEY} | not representable | Local URL and key | Wiring is strictly local |
If the same Hub statement arrives while smart_routing.enabled: false locally, smart routing remains inactive (auto passes through untouched, no classifier runs) and the router reports smart_routing_unavailable until the operator enables smart routing locally.
Tri-state envelope delivery¶
The smart_routing field in the policy envelope has three distinct states:
| Envelope representation | Meaning | Router behavior |
|---|---|---|
| Field omitted | No change | Retains the last received Hub smart-routing policy |
| Field contains a policy object | Replace policy | Replaces the previous Hub policy with the new statement |
Field is an empty object ({}) | Clear policy | Removes the Hub policy layer and runs the local smart_routing: section alone |
Delivery and persistence semantics:
- Explicit clear acknowledgment: When a policy is cleared (
{}), the router acknowledges with the canonical cleared-policy digest so the Hub's propagation state converges toactiverather than remainingpendingorstale. - In-memory only: The Hub smart-routing policy is stored in memory only. Across a router restart, the router starts with its local
smart_routing:configuration and restores the Hub policy on the next heartbeat/sync cycle.
Policy rejection and fallback behavior¶
When a Hub statement is rejected, the router logs a warn message and reports one of three rejection codes to the Hub. How the router falls back depends on whether the failure occurred during wire validation or local composition:
| Rejection code | Reported by | Trigger conditions | Fallback behavior |
|---|---|---|---|
invalid_smart_routing_policy | Policy store (on envelope receipt) | Schema version mismatch, size/count bounds exceeded, rule missing a route target, or unknown tier/classifier enum from a newer Hub version. | Keeps previous policy: Composition does not run; the previously active policy remains in effect. |
digest_mismatch | Policy store (on envelope receipt) | Envelope digest does not match the router's canonical digest recomputation (for either a replacement or a clear). | Keeps previous policy: The malformed envelope is ignored and the previously active policy remains in effect. |
smart_routing_unavailable | Smart-routing reconciler (during composition) | Envelope is valid on the wire, but cannot be executed by this deployment (see list below), or smart_routing.enabled: false locally. | Reverts to local configuration: Drops any prior Hub smart-routing layer and runs the local smart_routing: section alone. |
Causes of smart_routing_unavailable¶
A syntactically valid Hub policy is declined with smart_routing_unavailable when:
smart_routing.enabledisfalselocally.- The named
classifier.llm.backenddoes not exist inbackends[]or hasenabled: false, or an LLM-using method is selected with no backend pinned in either the Hub statement or local config. - A routing rule targets an explicit
modelID rather than atier. - The composed configuration fails local
smart_routingvalidation (for example,method: system_onewithout a localclassifier.system_onetarget block). - The composed configuration violates cross-section validation rules (for example, a Hub
virtual_modelthat collides with a localmodel_experiments[].modelentry).
Because the policy store retains the latest wire-valid statement in memory even when declined with smart_routing_unavailable, fixing the local prerequisite (such as adding the missing classifier backend or setting enabled: true via hot reload) automatically activates the Hub policy without waiting for a new envelope.
Hub propagation status¶
The router advertises smart_routing_policy_v1 whenever the smart-routing reconciler is attached (even if smart_routing.enabled: false locally). It acknowledges smart_routing_policy_digest only after the reconciler has applied the composed policy to the live components.
Continuum Hub derives each router's propagation status from the heartbeat report:
| Hub status | Condition reported by router |
|---|---|
unsupported | smart_routing_policy_v1 is absent from capabilities (router older than v1.32.0 or embedded build without ConfigManager). |
pending | smart_routing_policy_v1 is advertised, but the reconciler has not yet acknowledged or rejected the latest digest. |
active | Acknowledged smart_routing_policy_digest matches the digest authored by the Hub (including the canonical cleared-policy digest). |
stale | Acknowledged digest corresponds to an older policy revision than the Hub's current target. |
error | The router reported invalid_smart_routing_policy, smart_routing_unavailable, or digest_mismatch. |
The heartbeat carries a single rejection slot shared between guardrails and smart routing. If both tracks fail simultaneously, the guardrail rejection code takes precedence in the shared slot; see What the hub sees.
Local edits and Admin API behavior¶
When a Hub policy is active, local configuration reloads and Admin API endpoints interact with the Hub layer as follows:
- File hot reload and
PUT /admin/smart-routing/{policies,model-profiles}:- Writes update the local configuration layer and trigger recomposition; they never mutate the Hub layer or copy Hub values into local config.
- Edits to fields not set by the Hub take effect immediately; edits to fields governed by the Hub remain overridden by the Hub statement, and
PUTresponses include"hub_governed": true. - Fields omitted from a
PUT /admin/smart-routing/policiesrequest (virtual_model,intercept_all) are populated from the local configuration rather than the live composed state. - If a Hub-governed router was built without hot reload,
PUTrequests return HTTP503with{"error": {"code": "hub_governed_no_config_channel", "section": "smart_routing", ...}}.
- Inspection (
config showvs.GET /admin/smart-routing/*):continuum-router config showand the on-disk config file always reflect only what the operator wrote locally.GET /admin/smart-routing/policiesandGET /admin/smart-routing/model-profilesinspect the live runtime state and therefore return the composed rules, profiles,virtual_model, andintercept_all.
Read-only Hub inventory (smart_routing_inventory)¶
Whenever the control-plane agent is enabled, enrollment and heartbeat payloads include a read-only smart_routing_inventory summary of the live routing state (regardless of whether smart_routing_policy_v1 is advertised):
| Field | Description |
|---|---|
enabled | Whether smart routing is currently active (true / false). |
policy_count | Number of active routing policies. |
model_profile_count | Number of explicit exact and pattern model profiles in effect (including Admin API additions; inferred profiles are excluded). |
classifier_method | Active classifier method (rule, llm, hybrid, system_one, or hybrid_system_one). |
classifier_backend | Configured classifier.llm.backend name (reported only when set and the active method uses the LLM classifier). |
virtual_model | Model name that triggers smart routing. |
intercept_all | Whether all requests are intercepted for smart routing. |
Privacy and disabled-state guarantees:
- Disabled state: When smart routing is disabled, the router sends
enabled: false, zero counts, noclassifier_backend, andintercept_all: falserather than omitting the block (an omitted block indicates an older router version or an invalid >256-byte identifier). - Metadata only: The inventory reports only counts and top-level identifiers. Policy rule conditions, model lists, classifier prompts, System One endpoints, and
api_key_envnames are never transmitted.