Skill Router
Concept of the skill
What it is: skill-router is the request-time dispatch discipline for choosing which existing skill should handle an agent request, plus which adjacent skills should be co-loaded or excluded.
Mental model: Read the compiled manifest, score activation signals additively, filter by project fit, apply quality and specificity tiebreakers, exclude conflicting ownership, expand dependencies and verification partners, and report coverage gaps instead of guessing.
Why it exists: Skill libraries decay when ambiguous requests silently activate the wrong skill. Routing needs explicit evidence and evals so maintainers can see whether the library covers real requests.
What it is NOT: It is not a one-skill metadata audit, new-skill authoring guide, library-wide infrastructure health audit, or general debugging workflow. It is also not model routing (choosing which LLM handles the request) and not a workflow orchestrator (sequencing multi-step subagent runs) — it is the one-shot decision of which skill's methodology the agent should follow before execution begins.
Adjacent concepts: graph-audit checks one skill or manifest surface; skill-scaffold creates new skills; skill-infrastructure watches library-wide health and routing-miss patterns; eval-driven-development helps design routing evals; context-graph supplies the graph context the router consumes.
One-line analogy: The router is an air-traffic controller for skills: it assigns the request to the right runway, co-loads support, and says when no runway is fit.
Common misconception: A router is not a nearest-neighbor guesser; a no-match result is useful evidence, not a failure to be hidden.
Coverage
- Routing by keyword pattern: matching inbound query terms to skill
keywords arrays to identify the best candidate, with stopword filtering and per-token dedup so a keyword bag cannot stuff its way to the top
- Routing by trigger label: matching explicit skill-router labels (
triggers field) — the highest-weighted activation signal
- Routing by file path: matching touched or mentioned file paths against skill
paths arrays for file-activated skills, including gitignore-style negation semantics
- Additive scoring and tiebreaking: how the surfaces combine into one weighted score, and how ties resolve through project-anchoring, quality boosts, and legacy type
- Verdict gating: how the four-verdict Audit Status (structural / truth / comprehension / application) hard-blocks broken skills, gates out proven-negative behavior, and rank-boosts certified skills
- Relation-aware co-loading and exclusion: suppression-before-co-loading order, transitive
depends_on, one-hop verify_with and broader, and the score-aware suppression guard
- Coverage gaps: detecting when no skill matches a request and how to surface that gap as an authoring signal
- Routing at scale: how deterministic surface-scoring relates to embedding retrieval, LLM-reasoning routing, and retrieve-and-rerank, and where the body (not the metadata) becomes the decisive signal
Philosophy of the skill
Routing is adversarial against convenience. The tempting move — "if nothing matches exactly, just pick the closest skill and activate it" — is the one that silently degrades every agent that depends on the router. A wrong skill that activates confidently is worse than a coverage gap that surfaces loudly, because silent wrongness has no signal for anyone to fix. The router's job is to produce either a certain winner or an explicit non-answer, never a confident guess.
Five principles follow from that stance:
- Priority is encoded as weight, not as a short-circuit. The reference router (
scripts/skill-graph-route.js) does not stop at the first matching surface. It scores every surface additively into a single number — a trigger hit is +5, a keyword exact-token match is +3, a keyword substring match is +1, a --path glob hit is +2 — then ranks by the total. Triggers are weighted highest precisely so a declared label dominates, but the score is cumulative: two precise keyword matches (+6) can outrank a single trigger (+5), and that is intended — convergent evidence should win. The intuition "a declared trigger should decide" holds because of the weight gap, not because evaluation halts.
- Tiebreakers favor fit and evidence, not popularity. When total scores tie, a project-anchored skill (non-empty
project[]) wins over an ambient one only when its declared project[] membership fits the active workspace; otherwise a project-anchored skill out of context is wrong context, not more specific. Certified application_verdict skills get a gentle additive boost, and stale or unverified skills are annotated rather than silently promoted. Never rank by skill age, usage count, or author preference.
- Explicit coverage gaps beat silent wrong fallback. If no surface produces a winner, surface the gap to the caller — recommend authoring a new skill or broadening a keyword list. Silent fallback to a default skill is a bug that no test catches, because the misrouted query looks successful to the router but nonsensical to the downstream agent.
- Load the minimum sufficient context. Co-loading exists to support the winner, not to pad the result. Pulling in weak matches or walking expansion edges further than the mechanics allow "poisons" the context window with irrelevant instructions and degrades the downstream agent's reasoning. This is why suppression runs before co-loading, why
verify_with and broader are one-hop only, and why narrower is never co-loaded.
- The router is a mapping, not a judge. It decides which skill owns a query; it does not decide whether the query is well-formed, worth handling, or strategically important. Those are the activated skill's concerns. Overloading the router with domain judgment makes it harder to audit and harder to change.
Routing Rules
The router scores each candidate skill across the surfaces below, sums the weights into a single score, and ranks. The surfaces are not a stop-at-first-match chain; their weights encode priority. Graph edges then explain co-loading, dependency expansion, and exclusions on top of the ranked result.
| Weight |
Surface |
Field consulted |
Match rule |
| +5 |
Trigger label |
triggers |
Exact match against the normalized full query, a retained query token, or a normalized trigger phrase. The dominant signal. |
| +3 |
Keyword exact |
keywords |
Each query content-token earns +3 at most once per skill, no matter how many keyword phrases contain it — this is the keyword-stuffing guard. Keyword tokens shorter than 3 chars are ignored. |
| +1 |
Keyword substring |
keywords |
A query token (≥3 chars) that did not earn an exact match, found as a substring of any keyword phrase, earns +1 once. Run as a separate second pass so a substring credit cannot poison a later exact match. |
| +2 |
File path |
paths |
Only when the caller passes --path. Glob match against the touched path after gitignore-style !negation patterns are subtracted. |
Description semantics is NOT scored by the deterministic harness. The reference router does not text-match the description field; description quality drives routing in the LLM-reasoning runtimes described under "Routing at scale" below. Keep description a sharp routing contract regardless, because that is the surface that decides routing when a native Agent-Skills runtime — not this deterministic harness — does the dispatch.
Scoring hygiene
- Stopwords are dropped from both sides. Function words (
the, this, how, with, …) are stripped from the query and from keyword phrases before comparison, so a prompt like "fix this" cannot exact-match every keyword phrase containing "this" and trigger library-wide false positives.
- Per-token credit is capped. Each query token contributes at most one exact (+3) or one substring (+1) credit per skill. This is why
keywords is capped at 10 in the contract: more phrases cannot buy more score for the same token, so padding the array is wasted authoring effort and a routing smell.
Project-fit and quality gates
Project membership is checked before a score is accepted. A skill with a non-empty project[] only routes when no project filter is active, or when one of its project[] handles matches the active project; otherwise it is excluded by the project filter entirely. On a score tie, a project-anchored skill (non-empty project[]) outranks an ambient one — project-anchoring, not publishability, is the specificity signal. (The retired deployment_target enum and the boolean public gate are not routing-specificity signals; public only governs whether a skill is publishable.)
The router also surfaces quality state. eval_state is an opt-in gate via --min-eval-state (default: no gating), and lifecycle.stale_after_days lets the route explanation mark stale skills instead of hiding freshness risk.
Verdict gating (four-verdict Audit Status — Decision A)
After scoring, the router applies the four-verdict Audit Status in a fixed order. This is the current behavior in scripts/skill-graph-route.js; treat it as the canonical gate semantics:
- Hard integrity block.
structural_verdict: FAIL or truth_verdict: BROKEN removes the skill — a genuinely broken skill never routes. Critically, UNVERIFIED does not block: the corpus default is UNVERIFIED, and gating on PASS would delete most of the library. "Unknown" is treated as routable, not as broken.
- Behavior gate-out. An active proven-negative
application_verdict — HARMFUL, REDUNDANT, or FALSE_POSITIVE — excludes the skill. MIXED is not gated out (it means "applicable on some cases", not proven-bad). A negative verdict expires so a since-fixed skill is not tombstoned forever: it lapses when the skill's last_changed is newer than the grading receipt, or when the grade is older than 90 days. A negative verdict with no eval_last_run receipt is conservatively treated as still active.
- Rank-weight boost.
application_verdict: APPLICABLE adds +2 and PROVISIONAL adds +1 to the sort key. The boost is a tiebreaker, never an override: a strong keyword match on an UNVERIFIED skill still outranks a weak match on an APPLICABLE one. UNVERIFIED and MIXED are neutral (0).
Graph expansion and exclusion
Relation edges run in a fixed order. The canonical exclusion edge is relations.suppresses (ADR-0018; the router reads suppresses first and falls back to the deprecated boundary alias for unmigrated skills):
- Suppression runs BEFORE co-loading. When a selected skill
suppresses another selected skill, the suppressed skill is removed and so are its own co-loads — a suppressed skill must not contribute its verify_with / depends_on / broader partners to the result.
- Suppression is score-aware. A suppression only fires when the suppressing skill scored ≥ the target on this query. If the target outscored the declarer, the target stays — this guards the
boundary_exclusion_removes_stronger_match failure mode, where a weak owner would otherwise evict the genuinely stronger match.
depends_on expands transitively. A→B→C co-loads all three, because the selected skill requires those supporting skills.
verify_with co-loads one hop only for cross-checking — it is not treated as a transitive dependency.
broader co-loads the generalisation parent one hop (SKOS skos:broader): when a specific child matches, its broader parent is pulled in for context. narrower is deliberately not co-loaded — a parent matching does not make its children relevant.
disjoint_with marks incompatible ownership and must not be treated as a dependency.
Fallback behavior
If no skill matches any surface, the router does not fall back to a default skill. It surfaces a coverage gap and recommends authoring a new skill or broadening an existing skill's keywords array. Silent fallback to a wrong skill is worse than an explicit coverage-gap signal. Capture the missed query as a future routing-eval case so the gap is closed with evidence, not just patched.
Routing at Scale
The deterministic surface-scoring router teaches the mechanics of explainable dispatch, but it is one of three routing strategies the field uses, and it has a known scaling boundary. Situate it honestly:
- Deterministic surface-scoring (this skill). Cheap, fully explainable, auditable case-by-case. Best for a curated library (tens to low hundreds of skills) where authors control the metadata. The whole point is the explanation: every selection, co-load, and exclusion is traceable to a declared field.
- Embedding retrieval. Embed the task and the skill metadata; load the top-k by cosine similarity. Scales to large pools but is opaque and depends entirely on what text is embedded.
- LLM-reasoning routing. The runtime reasons over skill descriptions and picks — the model Anthropic's Agent Skills standard uses, where progressive disclosure exposes name+description first and "description quality directly determines routing accuracy." Native runtimes do this dispatch for you; the deterministic router is the auditable complement, not a competitor.
- Hybrid retrieve-and-rerank. A bi-encoder narrows to a candidate set over the full skill records (body text, examples, relations), then a cross-encoder reranks using full text; the Skill Graph
suppresses / depends_on edges are applied to the reranked winner as a post-process. This is the shape of the SkillRouter system (74.0% top-1, +6pp over a much larger zero-shot 8B pipeline).
The scaling boundary — the body becomes the decisive signal. Surface metadata (keywords, triggers, name, description) is sufficient discrimination only while the library is small and low-overlap. Recent retrieval research finds that in large, highly overlapping pools the full skill body is the decisive routing signal: removing the body degrades top-1 accuracy by roughly 29–44 percentage points across BM25, embedding, and reranking methods, and an attention analysis attributes 91.7% of routing attention to the body versus 7.3% to the name and 1.0% to the description. The lesson for a maintainer: keyword/trigger metadata buys precision in a curated library, but it does not scale to a sprawling, overlapping one — past that point, routing must read the body, and the right move is to split or sharpen overlapping skills, not to keep tuning keyword arrays. (Sources: SkillRouter, arXiv 2603.22455; Anthropic, "Equipping agents for the real world with Agent Skills.")
Universal Anti-Patterns
| Anti-pattern |
Problem |
Fix |
| Silent default fallback |
Hides missing expertise and poisons context with a wrong frame; the misroute looks successful to the router. |
Surface a coverage gap loudly; recommend a new skill or broader keywords. |
| Keyword bag-stuffing |
Padding keywords to win routing. |
Per-token credit is capped — repeating a token across phrases earns nothing extra. Sharpen the phrase instead. |
| Ambiguous descriptions / overlapping siblings |
The router cannot distinguish two near-identical skills. |
Add anti_examples and a suppresses edge naming the owner; at scale, split or sharpen the overlapping skills. |
| Stale verdict poisoning routing |
Gating forever on an old HARMFUL/REDUNDANT verdict. |
Rely on the 90-day / last_changed-supersession expiry so a since-fixed skill is not tombstoned. |
Evals
This skill's routability is tested by skill-graph/scripts/skill-graph-routing-eval.js, which evaluates the skill's manifest examples[], anti_examples[], and relation boundaries after generating a fresh manifest. The harness runs each examples[] prompt and requires this skill as the top-1 winner (positive class), and each anti_examples[] prompt and requires this skill to NOT win (a winner named in relations.suppresses[] is a boundary-target pass; a null winner is an informational coverage gap, not a failure). It emits a confusion matrix (expected → actual) to help maintainers spot overlapping keywords or missing boundary edges, and a skill only earns routing_eval: present when all its example cases pass. The skill also ships application and comprehension evals alongside the skill. The eval prompts test weighted-additive match scoring, explicit coverage-gap behavior, relation-aware routing, and the refusal to fall back to a default. Consumers in other agent runtimes can translate those cases to their own grading harness.
Verification
After applying this skill, verify:
Do NOT Use When
| Use instead |
When |
| The target skill directly |
The correct skill is already known — skip the router and load it |
documentation |
The task is writing or structuring doc prose, not routing |
graph-audit |
The task is auditing whether routing metadata is consistent, not dispatching a query |
skill-scaffold |
The task is authoring a new skill from scratch, not dispatching to an existing one |
skill-infrastructure |
The task is analyzing routing-miss patterns or health across the whole library, not one request's dispatch |
context-graph |
The task is designing the underlying relation graph the router traverses, not the dispatch decision itself |
| Generic RAG / similarity search |
The task needs typed Skill Graph metadata (relations, verdicts, project-fit), not raw text similarity |
1---2name: skill-router3description: Use when routing an agent request across multiple skills, building or auditing a routing table, detecting routing coverage gaps, or answering questions like 'which skill handles this?', 'who routes X?', or 'why did skill A activate instead of B?'. Covers trigger-label matching, file-path matching, keyword matching, description-based semantic matching, project-fit filtering, relation-aware co-loading and exclusion, eval/staleness annotations, and coverage-gap detection. Do NOT use when the target skill is already known (load it directly), when authoring a new skill (use `skill-scaffold` instead), or when evaluating a SINGLE skill's quality (use `graph-audit`).4license: MIT5---6# Skill Router78## Concept of the skill910**What it is:** `skill-router` is the request-time dispatch discipline for choosing which existing skill should handle an agent request, plus which adjacent skills should be co-loaded or excluded.1112**Mental model:** Read the compiled manifest, score activation signals additively, filter by project fit, apply quality and specificity tiebreakers, exclude conflicting ownership, expand dependencies and verification partners, and report coverage gaps instead of guessing.1314**Why it exists:** Skill libraries decay when ambiguous requests silently activate the wrong skill. Routing needs explicit evidence and evals so maintainers can see whether the library covers real requests.1516**What it is NOT:** It is not a one-skill metadata audit, new-skill authoring guide, library-wide infrastructure health audit, or general debugging workflow. It is also not *model* routing (choosing which LLM handles the request) and not a *workflow orchestrator* (sequencing multi-step subagent runs) — it is the one-shot decision of which skill's methodology the agent should follow before execution begins.1718**Adjacent concepts:** `graph-audit` checks one skill or manifest surface; `skill-scaffold` creates new skills; `skill-infrastructure` watches library-wide health and routing-miss patterns; `eval-driven-development` helps design routing evals; `context-graph` supplies the graph context the router consumes.1920**One-line analogy:** The router is an air-traffic controller for skills: it assigns the request to the right runway, co-loads support, and says when no runway is fit.2122**Common misconception:** A router is not a nearest-neighbor guesser; a no-match result is useful evidence, not a failure to be hidden.2324## Coverage2526- Routing by keyword pattern: matching inbound query terms to skill `keywords` arrays to identify the best candidate, with stopword filtering and per-token dedup so a keyword bag cannot stuff its way to the top27- Routing by trigger label: matching explicit skill-router labels (`triggers` field) — the highest-weighted activation signal28- Routing by file path: matching touched or mentioned file paths against skill `paths` arrays for file-activated skills, including gitignore-style negation semantics29- Additive scoring and tiebreaking: how the surfaces combine into one weighted score, and how ties resolve through project-anchoring, quality boosts, and legacy type30- Verdict gating: how the four-verdict Audit Status (structural / truth / comprehension / application) hard-blocks broken skills, gates out proven-negative behavior, and rank-boosts certified skills31- Relation-aware co-loading and exclusion: suppression-before-co-loading order, transitive `depends_on`, one-hop `verify_with` and `broader`, and the score-aware suppression guard32- Coverage gaps: detecting when no skill matches a request and how to surface that gap as an authoring signal33- Routing at scale: how deterministic surface-scoring relates to embedding retrieval, LLM-reasoning routing, and retrieve-and-rerank, and where the body (not the metadata) becomes the decisive signal3435## Philosophy of the skill36Routing is adversarial against convenience. The tempting move — "if nothing matches exactly, just pick the closest skill and activate it" — is the one that silently degrades every agent that depends on the router. A wrong skill that activates confidently is worse than a coverage gap that surfaces loudly, because silent wrongness has no signal for anyone to fix. The router's job is to produce either a certain winner or an explicit non-answer, never a confident guess.3738Five principles follow from that stance:3940- **Priority is encoded as weight, not as a short-circuit.** The reference router (`scripts/skill-graph-route.js`) does not stop at the first matching surface. It scores every surface additively into a single number — a trigger hit is +5, a keyword exact-token match is +3, a keyword substring match is +1, a `--path` glob hit is +2 — then ranks by the total. Triggers are weighted highest precisely so a declared label dominates, but the score is cumulative: two precise keyword matches (+6) can outrank a single trigger (+5), and that is intended — convergent evidence should win. The intuition "a declared trigger should decide" holds because of the weight gap, not because evaluation halts.41- **Tiebreakers favor fit and evidence, not popularity.** When total scores tie, a project-anchored skill (non-empty `project[]`) wins over an ambient one only when its declared `project[]` membership fits the active workspace; otherwise a project-anchored skill out of context is *wrong* context, not more specific. Certified `application_verdict` skills get a gentle additive boost, and stale or unverified skills are annotated rather than silently promoted. Never rank by skill age, usage count, or author preference.42- **Explicit coverage gaps beat silent wrong fallback.** If no surface produces a winner, surface the gap to the caller — recommend authoring a new skill or broadening a keyword list. Silent fallback to a default skill is a bug that no test catches, because the misrouted query looks successful to the router but nonsensical to the downstream agent.43- **Load the minimum sufficient context.** Co-loading exists to support the winner, not to pad the result. Pulling in weak matches or walking expansion edges further than the mechanics allow "poisons" the context window with irrelevant instructions and degrades the downstream agent's reasoning. This is why suppression runs before co-loading, why `verify_with` and `broader` are one-hop only, and why `narrower` is never co-loaded.44- **The router is a mapping, not a judge.** It decides which skill owns a query; it does not decide whether the query is well-formed, worth handling, or strategically important. Those are the activated skill's concerns. Overloading the router with domain judgment makes it harder to audit and harder to change.4546## Routing Rules4748The router scores each candidate skill across the surfaces below, sums the weights into a single score, and ranks. The surfaces are not a stop-at-first-match chain; their *weights* encode priority. Graph edges then explain co-loading, dependency expansion, and exclusions on top of the ranked result.4950| Weight | Surface | Field consulted | Match rule |51|---|---|---|---|52| +5 | Trigger label | `triggers` | Exact match against the normalized full query, a retained query token, or a normalized trigger phrase. The dominant signal. |53| +3 | Keyword exact | `keywords` | Each query content-token earns +3 **at most once per skill**, no matter how many keyword phrases contain it — this is the keyword-stuffing guard. Keyword tokens shorter than 3 chars are ignored. |54| +1 | Keyword substring | `keywords` | A query token (≥3 chars) that did **not** earn an exact match, found as a substring of any keyword phrase, earns +1 once. Run as a separate second pass so a substring credit cannot poison a later exact match. |55| +2 | File path | `paths` | Only when the caller passes `--path`. Glob match against the touched path after gitignore-style `!negation` patterns are subtracted. |5657**Description semantics is NOT scored by the deterministic harness.** The reference router does not text-match the `description` field; description quality drives routing in the *LLM-reasoning* runtimes described under "Routing at scale" below. Keep `description` a sharp routing contract regardless, because that is the surface that decides routing when a native Agent-Skills runtime — not this deterministic harness — does the dispatch.5859### Scoring hygiene6061- **Stopwords are dropped from both sides.** Function words (`the`, `this`, `how`, `with`, …) are stripped from the query and from keyword phrases before comparison, so a prompt like "fix this" cannot exact-match every keyword phrase containing "this" and trigger library-wide false positives.62- **Per-token credit is capped.** Each query token contributes at most one exact (+3) or one substring (+1) credit per skill. This is why `keywords` is capped at 10 in the contract: more phrases cannot buy more score for the same token, so padding the array is wasted authoring effort and a routing smell.6364### Project-fit and quality gates6566Project membership is checked **before** a score is accepted. A skill with a non-empty `project[]` only routes when no project filter is active, or when one of its `project[]` handles matches the active project; otherwise it is excluded by the project filter entirely. On a *score tie*, a project-anchored skill (non-empty `project[]`) outranks an ambient one — project-anchoring, not publishability, is the specificity signal. (The retired `deployment_target` enum and the boolean `public` gate are **not** routing-specificity signals; `public` only governs whether a skill is publishable.)6768The router also surfaces quality state. `eval_state` is an **opt-in** gate via `--min-eval-state` (default: no gating), and `lifecycle.stale_after_days` lets the route explanation mark stale skills instead of hiding freshness risk.6970### Verdict gating (four-verdict Audit Status — Decision A)7172After scoring, the router applies the four-verdict Audit Status in a fixed order. This is the current behavior in `scripts/skill-graph-route.js`; treat it as the canonical gate semantics:7374- **Hard integrity block.** `structural_verdict: FAIL` or `truth_verdict: BROKEN` removes the skill — a genuinely broken skill never routes. Critically, `UNVERIFIED` does **not** block: the corpus default is UNVERIFIED, and gating on `PASS` would delete most of the library. "Unknown" is treated as routable, not as broken.75- **Behavior gate-out.** An active proven-negative `application_verdict` — `HARMFUL`, `REDUNDANT`, or `FALSE_POSITIVE` — excludes the skill. `MIXED` is **not** gated out (it means "applicable on some cases", not proven-bad). A negative verdict **expires** so a since-fixed skill is not tombstoned forever: it lapses when the skill's `last_changed` is newer than the grading receipt, or when the grade is older than 90 days. A negative verdict with no `eval_last_run` receipt is conservatively treated as still active.76- **Rank-weight boost.** `application_verdict: APPLICABLE` adds +2 and `PROVISIONAL` adds +1 to the sort key. The boost is a tiebreaker, never an override: a strong keyword match on an UNVERIFIED skill still outranks a weak match on an APPLICABLE one. `UNVERIFIED` and `MIXED` are neutral (0).7778### Graph expansion and exclusion7980Relation edges run in a fixed order. The canonical exclusion edge is `relations.suppresses` (ADR-0018; the router reads `suppresses` first and falls back to the deprecated `boundary` alias for unmigrated skills):81821. **Suppression runs BEFORE co-loading.** When a selected skill `suppresses` another selected skill, the suppressed skill is removed *and so are its own co-loads* — a suppressed skill must not contribute its `verify_with` / `depends_on` / `broader` partners to the result.832. **Suppression is score-aware.** A suppression only fires when the suppressing skill scored **≥** the target on this query. If the target outscored the declarer, the target stays — this guards the `boundary_exclusion_removes_stronger_match` failure mode, where a weak owner would otherwise evict the genuinely stronger match.843. **`depends_on` expands transitively.** A→B→C co-loads all three, because the selected skill requires those supporting skills.854. **`verify_with` co-loads one hop only** for cross-checking — it is not treated as a transitive dependency.865. **`broader` co-loads the generalisation parent one hop** (SKOS `skos:broader`): when a specific child matches, its broader parent is pulled in for context. `narrower` is deliberately **not** co-loaded — a parent matching does not make its children relevant.876. **`disjoint_with`** marks incompatible ownership and must not be treated as a dependency.8889### Fallback behavior9091If no skill matches any surface, the router does not fall back to a default skill. It surfaces a coverage gap and recommends authoring a new skill or broadening an existing skill's `keywords` array. Silent fallback to a wrong skill is worse than an explicit coverage-gap signal. Capture the missed query as a future routing-eval case so the gap is closed with evidence, not just patched.9293## Routing at Scale9495The deterministic surface-scoring router teaches the *mechanics* of explainable dispatch, but it is one of three routing strategies the field uses, and it has a known scaling boundary. Situate it honestly:9697- **Deterministic surface-scoring (this skill).** Cheap, fully explainable, auditable case-by-case. Best for a curated library (tens to low hundreds of skills) where authors control the metadata. The whole point is the *explanation*: every selection, co-load, and exclusion is traceable to a declared field.98- **Embedding retrieval.** Embed the task and the skill metadata; load the top-k by cosine similarity. Scales to large pools but is opaque and depends entirely on what text is embedded.99- **LLM-reasoning routing.** The runtime reasons over skill descriptions and picks — the model Anthropic's Agent Skills standard uses, where progressive disclosure exposes name+description first and "description quality directly determines routing accuracy." Native runtimes do this dispatch for you; the deterministic router is the *auditable complement*, not a competitor.100- **Hybrid retrieve-and-rerank.** A bi-encoder narrows to a candidate set over the full skill records (body text, examples, relations), then a cross-encoder reranks using full text; the Skill Graph `suppresses` / `depends_on` edges are applied to the reranked winner as a post-process. This is the shape of the SkillRouter system (74.0% top-1, +6pp over a much larger zero-shot 8B pipeline).101102**The scaling boundary — the body becomes the decisive signal.** Surface metadata (keywords, triggers, name, description) is sufficient discrimination only while the library is small and low-overlap. Recent retrieval research finds that in large, highly overlapping pools the **full skill body is the decisive routing signal**: removing the body degrades top-1 accuracy by roughly 29–44 percentage points across BM25, embedding, and reranking methods, and an attention analysis attributes 91.7% of routing attention to the body versus 7.3% to the name and 1.0% to the description. The lesson for a maintainer: keyword/trigger metadata buys precision in a curated library, but it does not scale to a sprawling, overlapping one — past that point, routing must read the body, and the right move is to *split or sharpen overlapping skills*, not to keep tuning keyword arrays. (Sources: SkillRouter, arXiv 2603.22455; Anthropic, "Equipping agents for the real world with Agent Skills.")103104## Universal Anti-Patterns105106| Anti-pattern | Problem | Fix |107|---|---|---|108| **Silent default fallback** | Hides missing expertise and poisons context with a wrong frame; the misroute looks successful to the router. | Surface a coverage gap loudly; recommend a new skill or broader keywords. |109| **Keyword bag-stuffing** | Padding `keywords` to win routing. | Per-token credit is capped — repeating a token across phrases earns nothing extra. Sharpen the phrase instead. |110| **Ambiguous descriptions / overlapping siblings** | The router cannot distinguish two near-identical skills. | Add `anti_examples` and a `suppresses` edge naming the owner; at scale, split or sharpen the overlapping skills. |111| **Stale verdict poisoning routing** | Gating forever on an old `HARMFUL`/`REDUNDANT` verdict. | Rely on the 90-day / `last_changed`-supersession expiry so a since-fixed skill is not tombstoned. |112113## Evals114115This skill's routability is tested by `skill-graph/scripts/skill-graph-routing-eval.js`, which evaluates the skill's manifest `examples[]`, `anti_examples[]`, and relation boundaries after generating a fresh manifest. The harness runs each `examples[]` prompt and requires this skill as the top-1 winner (positive class), and each `anti_examples[]` prompt and requires this skill to NOT win (a winner named in `relations.suppresses[]` is a boundary-target pass; a null winner is an informational coverage gap, not a failure). It emits a confusion matrix (`expected → actual`) to help maintainers spot overlapping keywords or missing boundary edges, and a skill only earns `routing_eval: present` when all its example cases pass. The skill also ships application and comprehension evals alongside the skill. The eval prompts test weighted-additive match scoring, explicit coverage-gap behavior, relation-aware routing, and the refusal to fall back to a default. Consumers in other agent runtimes can translate those cases to their own grading harness.116117## Verification118119After applying this skill, verify:120121- [ ] The task matches the declared scope, coverage, or positive examples.122- [ ] The response follows this skill's workflow or checks instead of generic advice.123- [ ] Routing claims describe additive weighted scoring (not a stop-at-first-surface chain) and name the actual gate order: project filter → score → verdict gate → suppression → co-loading.124- [ ] Coverage gaps were surfaced explicitly rather than hidden behind a nearest-neighbor fallback.125- [ ] The exclusions in `## Do NOT Use When` do not point to a better skill.126127## Do NOT Use When128| Use instead | When |129|---|---|130| The target skill directly | The correct skill is already known — skip the router and load it |131| `documentation` | The task is writing or structuring doc prose, not routing |132| `graph-audit` | The task is auditing whether routing metadata is consistent, not dispatching a query |133| `skill-scaffold` | The task is authoring a new skill from scratch, not dispatching to an existing one |134| `skill-infrastructure` | The task is analyzing routing-miss patterns or health across the whole library, not one request's dispatch |135| `context-graph` | The task is designing the underlying relation graph the router traverses, not the dispatch decision itself |136| Generic RAG / similarity search | The task needs typed Skill Graph metadata (relations, verdicts, project-fit), not raw text similarity |