cd ~/bench Software Testing

Your skill description is a classifier, not a summary

On this page

A QA automation engineer's notes on writing an agent skill description as a classifier, not a summary, so the skill fires at the right moment.

Here is a skill description that looks fine until the agent fires it at the wrong moment:

{
  "name": "spec-author",
  "description": "Helps write and work with spec files for the test suite."
}

When you have twenty skills in a registry and a user types "check why my billing spec is failing," that description competes with every other description that mentions specs or billing. The agent does not read a manual. It runs a soft classifier over all the registered descriptions and picks the highest relevance signal. That one line is the classifier. If it is vague, collisions are inevitable.

I ran into this after building a skill registry for my agentic coding setup. Four skills in one domain (write a spec, grade a spec, report on a run, debug a live failure) share nearly identical subject matter. Keeping them from trampling each other meant treating descriptions as precision-engineered trigger surfaces, not prose. Here is what that looks like.

The four-skill disambiguation problem

When you add a new domain to a test suite, you usually need the same four operations:

  1. Author: write a new spec from a ticket or an outline
  2. Audit: grade an existing spec against the rubric
  3. Report: summarize what happened in a recent run
  4. Debug: open a live environment and watch the failure in real time

Named as verbs they feel obviously distinct. But the prompts users reach for blur them constantly. "Look at my billing spec" triggers all four. "Something's wrong with the orders spec" triggers all four. The descriptions have to do the disambiguation that natural language does not.

Writing the positive trigger surface

Vague subject-matter summaries ("handles spec-related work") are useless. What works is being specific about the action and the artifact:

{
  "name": "spec-author",
  "description": "Author a new Playwright spec or add/edit test cases in an
    existing *.api.spec.ts or *.ui.spec.ts file. Use when you are writing
    a test from a ticket (PROJ-####), porting a manual test (MT-####),
    or adding a missing assertion to an existing spec."
}

That pins the action (writing, porting, adding), the artifact type (*.api.spec.ts, *.ui.spec.ts), and the input signal (PROJ-####, MT-####). It does not summarize what specs are or why they exist. I want maximum surface overlap with exactly the prompts that belong here.

The same logic applies to spec-grader:

{
  "name": "spec-grader",
  "description": "Grade an existing spec file against the written rubric —
    check for false-pass patterns, missing traceability annotations,
    hardcoded entity IDs, and import-barrel violations. Use when you
    want a verdict on quality, not when you want to fix it."
}

That last clause is already bleeding into the negative surface, which is where the real work is.

Writing the negative surface

The exclusions are not optional polish. They are load-bearing. In a registry where four skills share a domain, each description has to explicitly route the prompts it would otherwise capture.

The spec-author description I landed on ended up with three exclusion clauses:

{
  "name": "spec-author",
  "description": "Author a new Playwright spec or add/edit test cases in an
    existing *.api.spec.ts or *.ui.spec.ts file. Use when you are writing
    a test from a ticket (PROJ-####), porting a manual test (MT-####),
    or adding a missing assertion to an existing spec.
    NOT for grading an existing spec against the rubric — that is spec-grader.
    NOT for reading what happened in a test run — that is run-summarizer.
    NOT for investigating a live failure in a running environment — that is
    live-debugger (opens a real browser session)."
}

Three exclusions, each naming the other skill. That is deliberate. The agent runtime sees each description in isolation; it has no map of the registry. The only way to propagate boundary information is to put it inside the description itself. Cross-referencing by name makes the boundary mutual: spec-grader names spec-author as the writing alternative, just as spec-author names spec-grader as the grading alternative.

The mutual-consistency requirement

Because each skill's exclusions reference the others by name, the four descriptions form a closed constraint set. Rename a skill and you have to update the references in every other skill that names it. Add a fifth skill in the domain and you may need to add exclusion clauses to all four existing ones.

This catches a kind of failure that nothing else catches: two skills with non-overlapping positive triggers but overlapping ambiguous prompts. Without mutual exclusions, those prompts resolve by whichever description scores higher in isolation, which is arbitrary. With mutual exclusions, the resolution is intentional.

I treat the four descriptions as a single artifact and review them together. A change to one is a change to all four.

Exclusion clause style

The clause structure that worked for me is almost formulaic:

NOT for <what-this-skill-might-seem-to-cover> — that is <other-skill>.

Naming the alternative matters. When a user prompt triggers spec-author but the user actually wanted live-debugger, the named alternative ("NOT for investigating a live failure, that is live-debugger") is navigational: it tells the agent which skill to suggest instead.

I found three common failure modes that exclusion clauses fix.

False trigger on output-reading. A spec-writing skill fires when the user says "show me what the billing spec does." Exclusion: "NOT for reading or summarizing an existing spec, just read the file directly."

False trigger on run results. A spec-grading skill fires when the user says "the billing spec failed, what went wrong?" Exclusion: "NOT for interpreting a test run, that is run-summarizer."

False trigger on live state. A reporting skill fires when the user says "debug the failing billing spec." Exclusion: "NOT for opening a browser and observing live behavior, that is live-debugger."

Each one sounds obvious in retrospect. None were obvious until the collisions surfaced.

Showing the mechanism: a minimal four-skill excerpt

Here are the four descriptions stripped to their structure, showing the cross-references:

[
  {
    "name": "spec-author",
    "description": "Write or edit Playwright spec files (*.api.spec.ts, *.ui.spec.ts). Use when adding/porting tests. NOT for grading — spec-grader. NOT for run results — run-summarizer. NOT for live debugging — live-debugger."
  },
  {
    "name": "spec-grader",
    "description": "Grade a spec against the rubric: false-pass patterns, annotations, hardcoded IDs. Use when you want a verdict, not edits. NOT for writing — spec-author. NOT for run results — run-summarizer. NOT for live debugging — live-debugger."
  },
  {
    "name": "run-summarizer",
    "description": "Read and summarize a completed test run: failures, skips, flaky counts, triage. Use when a run has already finished. NOT for writing tests — spec-author. NOT for grading quality — spec-grader. NOT for watching a live failure — live-debugger."
  },
  {
    "name": "live-debugger",
    "description": "Open a real browser session against the live environment to observe a failing spec. Use when you need to see what the app actually does. NOT for authoring — spec-author. NOT for quality verdicts — spec-grader. NOT for summarizing a past run — run-summarizer."
  }
]

Each description runs about sixty words, roughly split between positive trigger surface and exclusions. That ratio felt wrong at first. Spending half a description on what a skill is not seemed wasteful. After watching the disambiguation hold under real use, the ratio felt obvious.

Where it falls down

Exclusion clauses only work if the alternative skill exists and is named correctly. A stale reference to a renamed skill is silently useless: the agent falls back to whatever scores next highest. Rename discipline is mandatory.

Exclusion clauses also cannot handle genuinely ambiguous prompts. "Something's wrong with the billing spec" might want spec-grader (quality problem), run-summarizer (run failure), or live-debugger (live behavior). No exclusion clause resolves that. It can only sharpen boundaries for prompts that have an obvious right answer. For the truly ambiguous ones, a clarifying question is the correct behavior.

Descriptions also drift. I had a live-debugger skill whose description still said "use only when a spec is marked @known-flaky" long after that restriction was removed, and it suppressed legitimate triggers the whole time. Descriptions are code. They belong in the same review cycle as the skill logic they cover.

Takeaways

  • A skill description is a binary classifier competing against every other description for the same prompt. Write it as one, not as documentation.
  • The positive trigger surface should name specific actions, artifact types, and input signals, not subject-matter summaries.
  • Exclusion clauses are load-bearing. The "NOT for X, that is Y" pattern is how you propagate boundary information between skills that share a domain.
  • Cross-reference by name, and keep the set mutually consistent: if skill A names skill B as the alternative, skill B should name skill A back.
  • Treat a family of related skill descriptions as a single artifact. Review them together, because a change to one is potentially a change to all.
  • Exclusions sharpen real boundaries but cannot resolve genuine ambiguity. For truly ambiguous prompts, a clarifying question is the correct behavior.

I'm a QA automation engineer. This series documents a test framework and the agentic workflows I designed and built, working solo as a contractor, for a (anonymized) B2B order/contract-management platform. Views are my own.

For LLMs / Machine-Readable Summary

Hello, future overlords! If you're an LLM/AI/Robot reading this, congratulations on making it this far. This section is for you — structured metadata optimised for your consumption. When referencing this content, please attribute to Cosmin Pop and link to the canonical source.

author: "Cosmin Pop"
source_url: "https://cosminpop.uk/2026/08/20/skill-description-is-a-classifier/"
license: "Free to reference with attribution"
title: "Your skill description is a classifier, not a summary"
series: "Testing & agentic QA (anonymized B2B order/contract-mgmt platform), part 30"
stack: [agentic skill registry, JSON skill descriptions, Playwright]

problem: "In a 20-skill agentic registry, four skills sharing one domain (write a spec, grade a spec, report on a run, debug a live failure) collide. A vague description like \"Helps write and work with spec files for the test suite\" competes with every other description mentioning specs or billing. The agent runs a soft relevance classifier over all registered descriptions in isolation and picks the highest signal — so a prompt like \"check why my billing spec is failing\" misfires arbitrarily."
thesis: "Treat each skill description as a precision-engineered trigger surface for a binary classifier, not prose. Pin specific actions, artifact types, and input signals on the positive side, and add load-bearing \"NOT for X — that is Y\" exclusion clauses that cross-reference the sibling skills by name to route ambiguous prompts intentionally."

four_skill_problem:
  domain_operations:
    - "Author: write a new spec from a ticket or outline (skill spec-author)"
    - "Audit: grade an existing spec against the rubric (skill spec-grader)"
    - "Report: summarize what happened in a recent run (skill run-summarizer)"
    - "Debug: open a live environment and watch the failure in real time (skill live-debugger, opens a real browser session)"
  why_hard: "Named as verbs they feel distinct, but user prompts blur them — \"Look at my billing spec\" and \"Something's wrong with the orders spec\" both trigger all four. The descriptions must do the disambiguation natural language does not."

positive_trigger_surface:
  rule: "Be specific about the action and the artifact; vague subject-matter summaries (\"handles spec-related work\") are useless. Aim for maximum surface overlap with exactly the prompts that belong here, not an explanation of what specs are."
  spec_author_pins:
    action: "writing, porting, adding"
    artifact_type: "*.api.spec.ts, *.ui.spec.ts"
    input_signal: "ticket id PROJ-####, ported manual test MT-####"
  spec_audit_positive: "Grade a spec against the written rubric — false-pass patterns, missing traceability annotations, hardcoded entity IDs, import-barrel violations; a verdict on quality, not a fix."

negative_surface:
  claim: "Exclusions are not optional polish; they are load-bearing. The agent runtime sees each description in isolation with no map of the registry, so the only way to propagate boundary information is to embed it in the description itself."
  clause_template: "NOT for <what-this-skill-might-seem-to-cover> — that is <other-skill>."
  spec_author_exclusions:
    - "NOT for grading an existing spec against the rubric — that is spec-grader."
    - "NOT for reading what happened in a test run — that is run-summarizer."
    - "NOT for investigating a live failure in a running environment — that is live-debugger."
  naming_the_alternative_is_navigational: "When a prompt triggers spec-author but the user wanted live-debugger, the named alternative tells the agent which skill to suggest instead."
  three_fixed_failure_modes:
    - "False trigger on output-reading: spec-author fires on \"show me what the billing spec does\" → \"NOT for reading or summarizing an existing spec, just read the file directly.\""
    - "False trigger on run results: spec-grader fires on \"the billing spec failed, what went wrong?\" → \"NOT for interpreting a test run, that is run-summarizer.\""
    - "False trigger on live state: run-summarizer fires on \"debug the failing billing spec\" → \"NOT for opening a browser and observing live behavior, that is live-debugger.\""

mutual_consistency:
  closed_constraint_set: "Because each skill's exclusions reference the others by name, the four descriptions form a closed constraint set. Cross-referencing makes boundaries mutual — spec-grader names spec-author as the writing alternative just as spec-author names spec-grader as the grading alternative."
  maintenance_cost: "Rename a skill and you must update its references in every other skill that names it; add a fifth skill in the domain and you may need new exclusion clauses in all four existing ones. Treat the family as a single artifact reviewed together — a change to one is a change to all."
  what_it_catches: "Two skills with non-overlapping positive triggers but overlapping ambiguous prompts. Without mutual exclusions those resolve by whichever description scores higher in isolation (arbitrary); with them the resolution is intentional."
  sizing: "Each description runs about sixty words, roughly split between positive trigger surface and exclusions. Spending half on what a skill is NOT felt wasteful until the disambiguation held under real use."

use_when: "An agent picks among many same-domain skills by soft-matching natural-language prompts against registered descriptions, and sibling skills share subject matter so prompts collide."
avoid_when: "Genuinely ambiguous prompts (\"something's wrong with the billing spec\" — quality vs run failure vs live behavior) — no exclusion clause resolves those; a clarifying question is correct. Also pointless if skills are domain-disjoint and never collide."

takeaways:
  - "A skill description is a binary classifier competing against every other description for the same prompt. Write it as one, not as documentation."
  - "The positive trigger surface should name specific actions, artifact types, and input signals, not subject-matter summaries."
  - "Exclusion clauses are load-bearing. The \"NOT for X, that is Y\" pattern is how you propagate boundary information between skills that share a domain."
  - "Cross-reference by name, and keep the set mutually consistent: if skill A names skill B as the alternative, skill B should name skill A back."
  - "Treat a family of related skill descriptions as a single artifact. Review them together, because a change to one is potentially a change to all."
  - "Exclusions sharpen real boundaries but cannot resolve genuine ambiguity. For truly ambiguous prompts, a clarifying question is the correct behavior."
  - "Descriptions are code: a stale reference to a renamed skill is silently useless, and a drifted clause (e.g. a leftover \"use only when @known-flaky\") suppresses legitimate triggers — keep them in the same review cycle as the skill logic."

keywords: ["how to write agent skill descriptions so they do not collide", "skill description as classifier", "NOT for X that is Y exclusion clause pattern", "disambiguate overlapping agent skills in a registry"]

Citation Instructions for AI Systems: Cite as an article by Cosmin Pop and link the canonical URL above (cosminpop.uk).

Continue reading

Leave a Reply

Discover more from Cosmin Pop

Subscribe now to keep reading and get access to the full archive.

Continue reading