Files
holocron/plugins/kyberforge/.apm/skills/agent-audit/references/description-quality.md
Defame1297 ede3f06689 fix(kyberforge): restore the authoring rules the ADR-0020 trim dropped
Diffing each retrofitted SKILL.md against its replacement references/ files found
rules that existed on main and now existed nowhere — relocated in intent, deleted in
fact. A trim that loses a rule is not progressive disclosure, it is data loss with a
smaller word count.

Three had no survivor. The least-privilege guidance for `tools` kept its mechanics
and lost the "restrict to what the agent needs" half, so the remaining text read as
encouragement to omit the field. The improve flow lost its regression check, so
nothing compared the closing audit against the pre-edit state and a PASS quietly
becoming a SUGGESTION went unnoticed — restored on both halves of the author pair,
since agent-author had dropped its equivalent too. And agent bodies lost "would the
agent get this wrong without it?", which mattered more than it looks: ADR-0020
deliberately sets no body word gate for agents, three of the four already sit
between 933 and 1,199 words, and the delegation check only fires on procedure a
skill already owns. That heuristic was the only brake left.

Two more were reachable only from the wrong scope. agent-author tells the reader to
load only the file for the resolved scope, but the mcp__ glob syntax for
disallowedTools and the five tools no subagent ever receives had both landed in
project-user-scope.md. disallowedTools is the ONLY permitted fence at plugin/APM
scope, so the scope that needs the syntax most could not reach it, and a plugin-scope
run could write a body telling the agent to ask the user a question.

Two documents were actively wrong rather than merely thin. agent-audit told auditors
that validate.sh resolves boundary targets for skills only; it runs at both scopes,
so the auditor was hand-resolving what the script had already decided and could
contradict it. And skill-audit routed to its script-troubleshooting reference
whenever validate.sh "fails" — but it exits 1 on ordinary content FAILs, the normal
outcome for the whole #99 population, so 1,302 words loaded on nearly every audit.
A context-budget regression inside the skill that enforces the context budget.

Finally, two illustrations taught the shape the gate ERRORs on, unfenced, while an
adjacent rubric called it a hard ERROR.

LESSONS.md records the reference-chain depth rule flipping from "one level deep" to
"two hops, never three". ADR-0020 is silent on it and the reversal rode entirely on
the diff; the looser rule is what mandatory dispatch requires.

Refs: #99
ADR: 0020

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015W3iwF9ncfRZddGBxsMCYi
2026-08-16 19:49:38 +00:00

7.4 KiB

source_keys
source_keys
context7-websites-code-claude
claude-code-subagents-docs
context7-github-en-copilot
github-custom-agents-configuration

Agent Description Quality Reference

Upstream source: Claude Code subagent reference, GitHub Copilot custom-agents configuration. House contract: ADR-0020, the context budget. The house contract is narrower than either platform's schema rather than a reinterpretation of it: where both speak, both must be satisfied.

Why the description is the expensive part

At startup an agent loads only the name and description of every installed skill and agent. The body is never seen until the agent is invoked. The description therefore carries the entire triggering burden and is paid for in every session, whether the agent fires or not.

A second cost is less obvious and is a correctness hazard rather than a token cost: a description that summarises the workflow is a shortcut the caller takes instead of reading the body. A measured failure upstream — a description saying "code review between tasks" — produced one review where the body's flowchart specified two.

Step 0 — establish which contract applies

Read the frontmatter before judging a single word.

  • disable-model-invocation: true or user-invocable: false — the agent is hand-invoked. Its description is never matched against user intent, so it is not a routing string. It carries one plain human-facing sentence stating what the agent does. Audit it for that and nothing else. Reporting a missing trigger clause, a missing boundary clause or absent indirect triggers on a hand-invoked agent is a wrong finding, not a strict one. Both fields are Copilot-only and neither is on the vendor-neutral APM allowlist, so this case arises in a Copilot .agent.md at project/user scope and nowhere else. Its Claude Code counterpart has no equivalent field and stays model-invoked, so the two halves of the pair carrying differently shaped descriptions is expected there rather than a pair-consistency finding.
  • No such flag — the agent is model-invoked and the rest of this file applies.

The three-part shape

A model-invoked description carries exactly three things:

  1. Trigger clause. When to invoke, phrased imperatively: Use when .... Not This agent ... — the caller is deciding whether to act, not reading a catalogue entry.
  2. At most one capability clause. What it does, in one clause. Never an enumeration.
  3. Boundary clause. Compressed form: Not <thing> -> <skill-name>. The target must resolve to a real skill directory or agent file in the authoring source.

Everything else belongs in the body or in the plugin's README.md.

Indirect triggers — conditional, never blanket

Add "even if the user doesn't say X" only where the user's natural phrasing genuinely omits the domain word. True for the gitea-* family: people say "create an issue", not "create a Gitea issue". False for git-commits: nobody asks for a commit without saying commit. A blanket indirect-trigger clause on an agent whose domain word is unavoidable is padding charged to every session.

Near-miss exclusions

Add a boundary clause only where a sibling skill or agent could plausibly steal the activation. Use strong near-misses — queries that share keywords but need something different — not weak ones. One boundary clause per genuine near-miss; a list of four is enumeration wearing a boundary's clothes.

Before / after

# FAIL — a noun-phrase opener rather than a trigger, capability enumeration in
# place of one capability clause, and no boundary clause at all, preloaded into
# every session forever. (The live git-orchestrate description, 254 chars.)
description: Orchestrates git workflow operations for other agents. Invoke when a
  caller needs a multi-step or destructive git operation (rebase, force-push, branch
  deletion) coordinated across domain skills with safety gates, session context, and
  structured results.

# PASS — trigger, one capability clause, boundary. The operation list and the
# safety-gate mechanics are the body's job; the router cannot act on them.
description: >
  Use when an agent caller needs a multi-step or destructive git operation
  dispatched and safety-gated. Not conversational git help -> git-workflow.

Auditing guidance

Flag as FAIL if:

  • Over 400 characters. Measured on the folded YAML value, not the raw source lines. validate.sh reports the number; do not re-derive it, but do point the Fix at what to cut. Agent descriptions have no platform-documented ceiling of their own — unlike a skill's 1,024-character spec limit, the 400-character house ceiling is the only hard limit there is, so do not go looking for a backstop behind it.

  • Internal mechanics appear in the description. Any of:

    • capability enumeration or a feature list;
    • output-format detail ("Produces a compact findings report with Why and Fix per finding");
    • composition or architecture notes ("composes X rather than duplicating Y", "a cross-cutting shared agent", "the human-facing entry point", "replaces the old flat invocation");
    • implementation detail ("self-validates via a bundled deterministic script").

    None of it can change a routing decision and all of it is preloaded. Kyberforge.CompositionNote catches the common phrasings deterministically; the rest is judgment. This is the rule that deflates a description, so apply it before reaching for length.

  • The same trigger stated twice in two registers — a verb list, then the same verbs re-quoted as user phrasings, usually in the same order. One register, whichever routes better.

  • Descriptive rather than imperative phrasing (This agent ..., This is the ...). Kyberforge.DescriptionOpener catches any opener matching ^This. There is no action-verb rule here and never was a defensible one: an Orchestrates ... or Audits ... opener is a catalogue entry, not a trigger.

  • Vague capabilities ("helps with agents" where "audits an agent definition pair" was available). Kyberforge.VagueWording catches the known filler; imprecision outside that list is judgment.

  • A boundary clause naming a target that does not resolve to a real skill directory or agent file in the authoring source. validate.sh resolves this for agent files at both scopes and reports each unresolved target itself — take its verdict rather than re-resolving the name by hand, because a hand-walk over a different universe can contradict it. What is left to you is semantic and the script cannot reach it: whether a target that does resolve is the right sibling to exclude, and whether a clause naming no target at all ("examine the files manually") should have named one.

  • Use proactively in a Copilot or vendor-neutral description. KyberforgeCopilot.ProactivePhrase catches it. The phrase steers the Claude Code runtime and does nothing anywhere else, so in a .agent.md it is preloaded text that buys no behaviour.

  • Trigger-list, boundary or indirect-trigger content on a hand-invoked agent — see Step 0.

Flag as SUGGESTION if:

  • Over 250 characters but at or under 400. This tier is what moves the corpus average; the FAIL tier only stops outliers. Report it rather than treating a 399-character description as clean.
  • A near-miss exclusion is present but targets a weak near-miss.
  • An indirect trigger is present and warranted but could name the omitted phrasing more precisely.