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:
@@ -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.
|
||||
Reference in New Issue
Block a user