Files
poimen-memory/tasks/M3.7.3-skill-matching.md

5.1 KiB
Raw Permalink Blame History

M3.7.3 — GET /memory/skills?task= — match a subset to the work

Field Value
Phase M3.7 — Tool context
Size M — 13 days
Status Not started
Flags
Spec inlined below
Blocks M3.7.6
Depends M3.5.5, M3.2, M2.1

Goal

Given a task, return the few skills that apply, so the orchestrator stops cloning the same static list for every piece of work.

Facts (inlined — no spec read needed)

GET /memory/skills?task=fix+the+kubectl+parsing+in+the+pod+debugger&project=homelab
→ 200 [{"name":"infra-root-causes","score":0.81,
        "matched_on":"when_to_use","when_to_use":"When troubleshooting cluster…"}]

Match on description and when_to_use, never on the body. Anthropic's own skill guidance, encoded in the installed grafana-core:skill-authoring rubric, makes description the field that decides whether a skill fires. Bodies are long, full of example output, and match everything — a skill whose body mentions kubectl in passing would be selected for every Kubernetes task. Matching the field the author wrote for this purpose also gives authors a lever they can reason about.

Embed the metadata, rerank the shortlist. Same two-stage shape as mem query (M3.3): embed description + when_to_use, cosine-recall a shortlist, then rerank against the task text with bge-reranker-base (M3.2). The corpus is small enough that recall could be exhaustive, but the reranker is what separates "mentions Kubernetes" from "is about diagnosing a failing pod".

Empty is a valid answer and must stay cheap. Most tasks match no skill. The endpoint returns [], not the closest thing it found, and the caller proceeds with tools and knowledge alone. A floor applies here for the same reason it does in M3.6.5: a plausible-but-wrong skill actively steers the implementer.

_drafts/ stays excluded. M3.5.5's rule is unchanged and load-bearing — matching must not become a side channel that loads an unpromoted skill.

Deterministic ties. Two skills at the same score sort by name, so an orchestrator that caches on the response is not invalidated by rank flapping between identical requests.

Steps

  1. Extend the M3.5.5 handler with ?task= and ?limit= (default 3).
  2. Build the match index over description + when_to_use for promoted skills.
  3. Recall then rerank against the task text; apply the score floor.
  4. Return score and matched_on so a bad match is diagnosable without a rerun.
  5. Rebuild the index on skill promotion; no restart required.
  6. ?task= absent keeps the existing full-catalog behaviour exactly.

Acceptance

  • A Kubernetes debugging task matches the infra skill; an unrelated task does not.
  • Draft skills never appear.
  • No match returns [] with 200.
  • Omitting task returns the full catalog, byte-identical to today.
  • Equal scores order deterministically.

Verify

Harness: vault fixture with 6 promoted skills across distinct domains plus 2 drafts. Live reranker for scoring; deterministic embedder elsewhere.

Integration testtests/it_skill_matching.rs:

  1. a1_relevant_match — a pod-debugging task returns the infra skill first.
  2. a2_irrelevant_no_match — "update the README changelog" returns [].
  3. a3_drafts_excluded — a task whose text matches a draft's description verbatim returns [].
  4. a4_body_not_matched — a skill whose body mentions kubectl but whose description is about something else is not returned for a kubectl task. This is the assertion that proves the field restriction.
  5. a5_no_task_unchanged — omit task; assert byte-identical to M3.5.5's existing fixture output.
  6. a6_floor_applies — a weakly-related task returns [] rather than the best-of-bad.
  7. a7_deterministic_ties — two identically-described skills; assert stable name-ordered output across 10 calls.
  8. a8_reranker_reorders — capture pre- and post-rerank order; assert they differ on at least one fixture task, proving the reranker is wired.
  9. a9_promotion_visible — promote a draft, re-query without restart; assert it is now matchable.

Command: cargo test -p mem-api skill_matching

False pass:

  • Fixtures whose descriptions share no vocabulary. Any embedder separates unrelated topics; assertion 4 needs a deliberate body/description conflict, and assertion 6 needs a genuinely borderline task, or both pass with a keyword LIKE.
  • Asserting only that the right skill is present. Returning all 6 sorted also contains the right one; assert the length and the floor.

Traps

  • Indexing skill bodies "for better recall". It inverts the design: bodies are where every skill looks alike, and the author's description stops being the control surface it was written to be.
  • Tuning the floor against the same fixtures used to assert matching. It converges on a threshold that fits six skills and fails on sixty; hold out tasks.
  • Rebuilding the index per request. It is small, but this endpoint sits in the path of every task the orchestrator runs.

Background: DESIGN.md — tool context, skills · M3.5.5