Runs over the entire library from the compact routing manifest. Each entry gets scored from deterministic signals with hard weights:
| Signal | Weight |
|---|---|
| Name full-match | 0.60 |
| Alias full-match | 0.55 |
| use_when trigger (≥50% coverage) | 0.45 |
| Intent phrase | 0.35 |
| Keyword full-match | 0.25 |
| Capability | 0.20 |
| Object | 0.15 |
| Action | 0.15 |
Uses credit_ratio() for multi-token phrases — a single shared token earns
zero credit (≥2 tokens required). Entries scoring ≥ filter_floor (0.15)
become candidates, capped at max_candidates (20).
Runs only on the reduced candidate set. Computes 8 weighted dimensions using
RANK_WEIGHTS:
| Dimension | Weight | Description |
|---|---|---|
| intent | 0.35 | explicit intent phrase match |
| object | 0.18 | the thing acted on |
| action | 0.16 | what is being done |
| capability | 0.16 | what the skill can do |
| trigger | 0.14 | positive use_when trigger strength |
| name_alias | 0.16 | explicit skill name / alias |
| specificity | 0.08 | long matched phrases → more specific |
| domain | 0.16 | request names a domain the skill covers |
Each dimension is gated — it contributes zero unless the credit_ratio is
≥ 0.5. The trigger score scales with phrase length
(min(0.9, ratio * (0.40 + 0.12*(k-1)))).
If the request literally names the skill (name or full alias match), it gets a
+0.45 raw bonus — but only if the skill has at least one strong anchor
(intent, object, or domain score of 1.0). This prevents adversarial traps like
"make my code impeccable" routing to the prose skill impeccable.
Applied right after raw confidence:
-
not_when: If a negative trigger phrase ≥60% matches the request and the skill has weak positive anchors (intent+trigger+object+action < 0.15), the skill is hard-disqualified (×0.15 multiplier). Otherwise it gets a soft penalty (×0.6). This allows mixed requests like "review for complexity AND check the endpoint" to keep both candidates for multi-skill planning. -
_object_mismatch(): Only fires when all concrete objects in the request are foreign to the skill (not just some). Applies penalty up to 0.5 per missing concrete object.not_whenphrases are deliberately excluded from coverage counting.
Only fires when a conflicting skill is a real competitor (confidence ≥
max(0.30, best−0.10)) AND they overlap on task dimensions (shared
triggers/intents/objects). Applies CONFLICT_PENALTY (0.30). Skills with
completely disjoint dimensions are NOT penalized — this enables multi-skill
plans.
- Try multi-skill plan first via
try_multi_plan(): if ≥2 skills match disjoint dimensions with confidence ≥multi_floor(0.33), returns an ordered plan capped atmulti_cap(3). - ROUTE: best confidence ≥
route_floor(0.45) AND best − second >ambiguity_gap(0.15) - AMBIGUOUS: best confidence ≥
no_route_floor(0.14) but gap too small — returns up to 4 candidates - NO_ROUTE: best confidence below
no_route_floor(0.14) — returns empty
try_multi_plan builds a minimal ordered multi-skill plan when ≥2 skills
match disjoint dimensions with sufficient confidence. Ordering uses "then" /
"first" hints and sorts by the position of the first matched object phrase in
the original request.
Task identity dimensions are objects, intents, and triggers — NOT generic actions like "write". Two skills may both "write" yet handle different objects.
_empty_result() returns: decision="no_route", status="no_match",
skill=None, skills=[], command=None, confidence=0.0,
evidence="no matching skill", validated=False, alternatives=[].
Triggered when: (a) tokenized request is empty, (b) no candidates pass Stage A
filtering, or (c) best ranked confidence < no_route_floor.
resolve_command() picks the best command by scoring name token overlap (0.55)
- keyword full-match (0.30). If there's only one command, it's returned
automatically. The result always includes
validated: trueonly if the command name appears in the manifest's declared commands list.