feat(kyberforge): enforce the ADR-0020 context contract for skills and agents

Skill name+description pairs are preloaded into every session, costing
~6,200 tokens across 39 skills before any skill is invoked. The authoring
rules mandated that growth: skill-author:104 and description-quality.md:21
both required padding, while skill-author:102 (the deflating rule) had no
FAIL condition behind it.

Gates (blocking, no baseline file):
- description 250 chars SUGGESTION / 400 FAIL, measured on the folded
  YAML value
- body-only 600 words SUGGESTION / 900 FAIL, independent of the unchanged
  whole-file 2770-word / 500-line spec backstop
- every boundary-clause routing target must resolve to a real skill or
  agent; catches skill-improve, neuledge-context and gitea-labels
- agents take the description gates but deliberately no body gate; a test
  pins that absence

Vale: DescriptionOpener widened to ^This\b, new CompositionNote rule
banning architecture notes from descriptions. 10 hits, 0 false positives.

Kyberforge's own four skills retrofitted: descriptions 3,364 -> 938 chars
(-72%), bodies 8,306 -> 2,487 words (-70%), all via the apm-workflow
dispatch pattern. Fixes the skill-improve dangling route and the
agent-author misroute to manual review.

Also fixes a pre-existing false positive where any line-initial 'read '
was flagged as interactive input, which had already caused two scripts to
be rewritten around it.

Refs: ADR-0020
This commit is contained in:
2026-08-14 21:13:13 +00:00
parent 1c6eababb0
commit 4a5c3c0cff
104 changed files with 6272 additions and 1880 deletions

View File

@@ -0,0 +1,117 @@
---
source_keys:
- claude-code-subagents-docs
- github-custom-agents-configuration
---
# The agent description and body contract
House contract, set by ADR-0020. The counts and the boundary targets are enforced by
`agent-audit`'s `scripts/validate.sh`; the prose patterns by the Vale styles it bundles; the
judgment calls by its reference files.
## Why the budget exists
An agent's `name` and `description` is loaded into every session's context at startup, whether or
not the agent is ever delegated to — the same cost a skill's description carries, so agents take
the same numbers. The body is different: it is not loaded into the caller's conversation at all,
it *becomes the system prompt of a fresh context* when the agent runs. That is why the body has no
word gate here and a skill body has one.
## Description
A description carries exactly three things:
1. **Trigger clause** — when to delegate, imperative: "Use when …", never "This agent …". Describe
the user's intent and the triggering condition, not the agent's internal mechanics.
2. **At most one capability clause** — what it does, one clause, no enumeration. Be specific
("reviews a diff for injected credentials", not "helps with security").
3. **Boundary clause** — form: `Not <thing> -> <name>.` Add one only where a near-miss agent or
skill could steal delegations.
Banned from a description; move it to the body or to `README.md`:
- Capability enumeration or feature lists
- Per-scope emission mechanics — which files the author skill writes at which scope changes no
delegation decision
- Output-format detail ("Produces a compact findings report with Why and Fix per finding")
- Composition or architecture notes ("composes X rather than duplicating Y", "cross-cutting")
- Implementation detail ("Self-validates via a bundled deterministic script")
- Restating the same trigger twice in two registers — a verb list, then the same verbs re-quoted
as user phrasings. This is a FAIL, not a suggestion.
**Do not open with an action verb.** "Reviews…", "Analyzes…", "Generates…" was the old house rule
and ADR-0020 deleted it: the opener is `Use when`, matching every skill in this corpus, so one
router reads one shape.
**"Use proactively" is conditional.** Add it only where the runtime should delegate without the
user naming the agent — an agent invoked by name does not need it, and it costs activations
elsewhere when added by reflex. The same conditional governs indirect triggers ("even if the user
doesn't say X"): add one only where the user's natural phrasing genuinely omits the domain word.
**Boundary targets must resolve.** The name after the arrow is checked against real skills under
`plugins/*/.apm/skills/<name>/` and real agents under `plugins/*/.apm/agents/<name>.agent.md`. A
target that does not exist sends the router nowhere. Verify it before writing it — do not invent a
plausible sibling.
**Length.** 250 characters SUGGESTION, 400 characters FAIL, counting the frontmatter value only
with YAML folding resolved. Treat 250 as the target: the SUGGESTION tier is what moves the corpus
average, the FAIL tier only stops outliers.
## Body
Write the body as a direct role instruction, addressed to the agent:
````markdown
You are a <role>. When invoked, <primary action>.
## Inputs
<what the agent is given: files, context, parameters>
## Process
<ordered steps; be explicit where ordering matters>
## Output
<what it produces: format, location, structure>
````
One job per agent. An agent covering two jobs gets delegated to for the wrong one.
**Delegation discipline replaces the word gate.** A plugin/APM agent is a single file with no
sibling `references/` directory: it cannot disclose progressively to itself, so its only way to
stay short is to *invoke* rather than *restate*. A body that transcribes a procedure a skill it
can invoke already owns is an `agent-audit` FAIL, and the fix is one line — "invoke `<skill>`".
- Restating: "To commit, check the message against Conventional Commits: type, scope,
description; header under 100 chars; …"
- Delegating: "Author commits with `git-commits`."
The same holds for a procedure another agent owns. What belongs in the body is what no invocable
skill covers: the agent's role, its boundaries, the order it works in, and the format it returns.
**State a read-only boundary in prose, not only in frontmatter.** `disallowedTools` denies the
tools it names and nothing else — never `Bash`, which an agent with no `tools` field inherits — so
an agent fenced only in frontmatter can still write through a shell redirect.
## Invocation axis
Decide before writing the description whether the agent is model-delegated (the runtime picks it)
or reached only by name (`@agent-<name>`).
Only Copilot's cloud/IDE format expresses that in frontmatter: `disable-model-invocation: true`
requires explicit invocation, and `user-invocable: false` hides an agent from manual invocation.
Both live in `.github/copilot/agents/<name>.md` and are inert in the CLI format. Claude Code has
no equivalent field, and neither does the vendor-neutral plugin/APM file, so at those scopes a
name-invoked agent still needs a description precise enough not to steal delegations — the
boundary clause is doing that work.
## One gate, two measurements
| Gate | SUGGESTION | FAIL | Counts |
|---|---|---|---|
| description | 250 chars | 400 chars | the `description:` value only |
| body (Copilot limit) | 30,000 chars | — | the body only; content past it is truncated silently |
The 30,000-character Copilot ceiling is a runtime truncation limit, not a quality target, and it
applies to a plugin/APM file too — that file compiles into a real Copilot agent downstream. An
agent body long enough to approach it has a delegation defect, not a length problem.