erectl semantic-router
Read and write the project's semantic-router settings, manage dynamic routing rules, and inspect why each request went where it did -- everything the dashboard settings page offers, from the terminal.
Overview
The semantic router picks a generative endpoint per turn by embedding the
request and ranking your endpoints against it. The
semantic-router command group configures that behaviour and
reports on it: the enablement flag and its ranking tunables, the exclusion
list and the deterministic fallback, the dynamic routing rules that fire
above the classifier, the last 24 hours of routing decisions, and the
aggregate health and consumption counters.
Every subcommand hits the router's management plane at
/{project_id}/v1/management/semantic-router and requires an API
key carrying the management scope. A key without it gets
403 with code: "scope_insufficient". There are no
project UUIDs to pass -- each subcommand resolves the active
erectl context and authenticates with that project's API key.
Use --context <name> to target a different deployment, and
--dry-run on any of the writing verbs to print the request
without sending it. Full REST reference:
Semantic Router
Management API.
Scope and Prompt Content
Read this before you issue a management-scoped key.
erectl semantic-router decisions returns an excerpt of the user
prompt behind each routing decision, and it returns those rows for
every workspace in the project.
The dashboard shows the same view narrowed to the workspaces the signed-in
operator is allowed to read, so an operator never sees another member's
private workspace there. An API key carries no user identity, so there is no
principal to evaluate that rule against and no equivalent filter can be
applied. The holder of a management-scoped key can therefore read
prompt text from workspaces they would not be shown in the web UI.
This is deliberate -- it is what gives the CLI parity with the dashboard --
but it means a management key is as sensitive as the prompts in
the project. Scope keys accordingly, and prefer
erectl semantic-router health when you only need to know whether
routing is working: that command reads counts and totals and never touches
prompt content.
Usage Pattern
erectl semantic-router config show # Show the config
erectl semantic-router config set <key> <value> # Set one setting
erectl semantic-router rules list # List routing rules
erectl semantic-router rules add ... # Add a routing rule
erectl semantic-router rules update <id> # Patch a routing rule
erectl semantic-router rules enable <id> # Enable a routing rule
erectl semantic-router rules disable <id> # Disable a routing rule
erectl semantic-router rules delete <id> # Delete a routing rule
erectl semantic-router decisions # Recent route decisions
erectl semantic-router health # Routing health + usage
config and rules each have a default subcommand, so
erectl semantic-router config is config show and
erectl semantic-router rules is rules list. The
group itself has none: bare erectl semantic-router prints help
rather than guessing.
-o json switches the read verbs -- config show,
rules list, decisions, health -- to a
machine-readable dump of the whole response. The writing verbs print a
human-readable success line before their JSON, so pipe those through
-q as well if you intend to parse them.
config show
Prints the project's effective semantic-router configuration: the enablement flag, the resolved availability, whether an embedding endpoint is present, whether multi-model dispatch is on, the three ranking tunables (each showing the deployment default when the project has not overridden it), the fallback pin, the exclusion list, and the rule count. It also lists the endpoints the router can currently route over, and -- separately -- any endpoint a stored setting still references that the router cannot route over today.
erectl semantic-router config show
erectl semantic-router config show -o json
config set
Sets exactly one setting. Keys the command does not name are left untouched,
so a single-key write can never clobber a setting you did not mention. On
success it prints Setting '<key>' updated. and nothing else;
run config show if you want the resulting configuration back.
erectl semantic-router config set semantic_router_enabled false
erectl semantic-router config set semantic_router_similarity_floor 0.35
erectl semantic-router config set semantic_router_similarity_floor default
erectl semantic-router config set semantic_router_excluded_endpoint_ids none
Keys
| Key | Value | Meaning |
|---|---|---|
semantic_router_enabled |
true | false |
Whether semantic routing runs for this project |
semantic_router_multi_model_enabled |
true | false |
Whether multi-model set-cover dispatch runs |
semantic_router_similarity_floor |
0..1 | default |
Minimum cosine score an endpoint must clear |
semantic_router_switch_margin |
0..1 | default |
Margin a challenger must beat the incumbent by mid-conversation |
semantic_router_proximity_bonus |
0..1 | default |
Region-locality ranking bonus |
semantic_router_fallback_endpoint_id |
<uuid> | default |
Endpoint used when nothing clears the floor |
semantic_router_excluded_endpoint_ids |
<uuid,uuid,...> | none |
Endpoints kept out of semantic routing |
default clears an override so the deployment default applies
again; none clears the exclusion list. The two boolean keys also
accept yes/no, on/off and
1/0, case-insensitively.
The 0..1 range is inclusive of both ends, and a value outside it
is rejected rather than clamped. The router discards a value it cannot use and
falls back to the deployment default, so a stored out-of-range number would
save cleanly and then do nothing -- a setting that reads back as changed and
changes no behaviour is worse than an error.
A key outside this table is rejected by the server, so a
management key cannot reach unrelated project preferences through
this endpoint.
Routing rules are not settable here. They have their own verbs, which mutate one rule at a time instead of asking you to retype the whole array.
rules list
Lists the project's rules in stored order, which is the order
they were written, not the order they are evaluated in. The router evaluates
by priority, highest first; read the Priority column,
not the row order, to predict which rule fires. Stored order does matter for
one thing: it is the index used in a validation error's
param field. A rule whose target endpoint has since been deleted
is still listed rather than hidden -- hiding it would hide the reason your
routing broke.
erectl semantic-router rules list
erectl semantic-router rules list -o json
rules add
Adds a rule. Rules are evaluated in priority order above the classifier: when one fires, no embedding is computed and no ranking runs. The rule id is assigned by the server.
erectl semantic-router rules add \
--name "Refactors" \
--query-contains "refactor,rewrite" \
--endpoint-id 11111111-2222-3333-4444-555555555555 \
--priority 10
Options
| Option | Description |
|---|---|
--name <name> |
Rule display name (required) |
--endpoint-id <uuid> |
Target endpoint (required) |
--query-contains <list> |
Comma-separated substrings that must ALL appear in the query |
--min-input-tokens <n> |
Only fire at or above this input-token count |
--exact-request-model <name> |
Only fire for this requested model name |
--priority <n> |
Evaluation order; higher runs first. Omit it and the server stores 0. |
--disabled |
Create the rule without enabling it |
--no-fallback-to-classification |
Fail the request instead of falling back to classification when the target cannot serve |
A rule must be narrowed by at least one condition. A rule with an empty
--query-contains and no token or model condition matches every
request -- an all-satisfy over an empty list is vacuously true -- so it would
hijack all of the project's traffic. Both the CLI and the server reject it.
Server-side limits: at most 20 rules per project, at most 10
--query-contains substrings per rule, and at most 200 characters
per substring. The target must be a functional generative endpoint in the
project; a suspended one is still a legal target.
rules update
Patches one rule. Only the options you supply change; every other field of that rule, and every other rule in the project, is carried through untouched by the server. At least one option is required.
erectl semantic-router rules update 11111111-2222-3333-4444-555555555555 --priority 20
erectl semantic-router rules update 11111111-2222-3333-4444-555555555555 \
--query-contains "refactor,rewrite,extract"
Options
| Option | Description |
|---|---|
--name <name> |
New display name |
--endpoint-id <uuid> |
Repoint the rule at a different endpoint |
--query-contains <list> |
New comma-separated substrings, replacing the stored list whole |
--min-input-tokens <n> |
New input-token floor |
--exact-request-model <name> |
New requested-model condition |
--priority <n> |
New evaluation priority |
update deliberately carries a narrower option set than
add. --disabled and
--no-fallback-to-classification do not exist here: use
rules enable / rules disable for the switch, and
delete and re-add to change the fallback behaviour. There is also no way to
clear --min-input-tokens or
--exact-request-model through a patch -- an omitted option means
"leave it alone", so a rule that must lose a condition has to be deleted and
re-added.
rules enable / disable
Flips a rule's participation without deleting it. Useful for bisecting a
routing problem: disable the rule, watch
erectl semantic-router health, re-enable.
erectl semantic-router rules disable 11111111-2222-3333-4444-555555555555
erectl semantic-router rules enable 11111111-2222-3333-4444-555555555555
rules delete
Removes a rule. Prompts for confirmation unless --force is
supplied.
erectl semantic-router rules delete 11111111-2222-3333-4444-555555555555
erectl semantic-router rules delete 11111111-2222-3333-4444-555555555555 --force
decisions
Answers "why did this request go to that endpoint" for the last 24 hours, newest first. Each row shows the timestamp, the chosen endpoint, the cosine score behind the choice, the arm that produced it, the routing time, and an excerpt of the query.
This command returns prompt content from every workspace in the project. See Scope and Prompt Content.
erectl semantic-router decisions
erectl semantic-router decisions --limit 200
erectl semantic-router decisions -o json
Arms
| Arm | Meaning |
|---|---|
rule |
A dynamic routing rule dispatched the request; the classifier never ran |
fallback |
Nothing cleared the similarity floor, so the deterministic endpoint served the turn |
direct |
No single cosine score governed the turn: the single-endpoint short-circuit, or multi-model dispatch |
sticky |
Conversation affinity kept the incumbent endpoint |
matched |
A fresh classifier match |
--limit defaults to 50 and is clamped to 1..200; a
non-numeric value is rejected rather than quietly defaulted. The 24-hour
window is fixed and not a parameter. Excerpts are truncated to 500 characters
at the moment they are recorded, and a row whose stored excerpt cannot be
decrypted shows an empty preview rather than failing the page.
health
Counts and totals only. This command reads no prompt content, so it is safe to poll and safe to paste into a ticket.
erectl semantic-router health
erectl semantic-router health -o json
Fallback share is a percentage of classified decisions,
not of all decisions. On a single-endpoint project the router short-circuits
before classifying, and counting those turns would drive the ratio toward zero
while telling you nothing about how classification is going.
Whole-Array Rule Writes
The routing rules live in a single stored array. The rule verbs each send one rule, and the server does the read-modify-write -- which is what lets it carry every other rule through untouched -- but it then validates the resulting array whole. One consequence is worth knowing before it surprises you: a pre-existing rule whose target endpoint has since been deleted will reject an edit to a completely unrelated rule.
The error names the offending rule in its param field, so you can
see which one is blocking you:
Router returned HTTP 400: {"error":{"message":"Invalid dynamic routing rule at
semantic_router_dynamic_rules[2].action.endpoint_id: Must reference a functional
generative endpoint in this project","type":"invalid_request_error",
"code":"invalid_semantic_router_config",
"param":"semantic_router_dynamic_rules[2].action.endpoint_id"}}
The index is the rule's position in rules list. Delete or repoint
that rule and the rest of your edits go through. Deleting the blocking rule
always succeeds, so you can never be wedged.
Examples
Turn Semantic Routing On and Tune It
erectl semantic-router config set semantic_router_enabled true
# Require a stronger match before switching endpoints
erectl semantic-router config set semantic_router_similarity_floor 0.35
erectl semantic-router config set semantic_router_switch_margin 0.15
# Confirm what the router now resolves
erectl semantic-router config show
Pin Long Prompts to a Large-Context Endpoint
erectl semantic-router rules add \
--name "Long context" \
--min-input-tokens 32000 \
--endpoint-id 11111111-2222-3333-4444-555555555555 \
--priority 100
Diagnose Unexpected Routing
# Is classification falling back a lot?
erectl semantic-router health
# Which turns, and why
erectl semantic-router decisions --limit 100 -o json
# Suspect a rule? Disable it and watch the health counters
erectl semantic-router rules disable 11111111-2222-3333-4444-555555555555