--- source_keys: - claude-code-subagents-docs - github-custom-agents-configuration --- # The agent description and body contract House contract. The counts and the boundary targets are enforced by `factory-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 -> .` Add one only where a near-miss agent or skill could steal delegations. Banned from a description; move it to the body — an agent is a single file with no `references/` directory to move it to: - 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.** The opener is `Use when`, matching every skill in this corpus, so one router reads one shape. **"Use proactively" is Claude Code-only, and conditional even there.** The phrase steers the Claude Code runtime to offer an agent unprompted and does nothing anywhere else, so where it may appear depends on the file: | File | Rule | |---|---| | Claude Code `.md` (project/user scope) | Allowed. 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. | | Copilot `.agent.md` (project/user scope) | **Never.** Inert there, and `KyberforgeCopilot.ProactivePhrase` grades it a hard FAIL. | | Vendor-neutral `.apm/agents/.agent.md` (plugin/APM scope) | **Never.** Same Vale rule, same hard FAIL — the file matches the `**/*.agent.md` glob, and it compiles to a real Copilot agent downstream. | A pair whose Claude Code half carries the phrase and whose Copilot half omits it is correct, not inconsistent: `factory-audit` checks that both halves describe the same job, not that they match word for word. Indirect triggers ("even if the user doesn't say X") take a similar conditional at every scope: add one only where the user's natural phrasing genuinely omits the domain word. **Boundary targets must resolve, and the notation decides how hard the gate bites.** Route notation — `/name`, or any arrow form (`-> name`, `` -> `name` ``) — is checked unconditionally: an unresolved target there is a blocking ERROR. The prose form ("do not use for X, use `y` instead") is only a SUGGESTION by default, because a bare hyphenated word in a boundary clause is as likely to be a tool, a file format or an English compound as a route. It is promoted to a blocking ERROR only when a second target in the same sentence *does* resolve, which corroborates that the name was meant as a route. So a typo does **not** dangle equally either way — write the arrow when you want the target checked. Targets resolve against a universe built by walking up **from the agent file itself**: the nearest ancestor holding `plugins/*/.apm/{skills,agents}` (or, failing that, the nearest ancestor holding `.git`) contributes every skill and agent under `/plugins/*/`, plus the agent's own apm package and the packages that package declares in `apm.yml` under `dependencies.apm`. A sibling plugin in the same monorepo therefore resolves; a skill in an unrelated repo does not. A target outside that universe sends the router nowhere — a blocking failure in arrow or `/name` form, and in prose form only a SUGGESTION nobody is forced to act on, which is the worse outcome because it ships. Verify it before writing it — do not invent a plausible sibling. **Never let a hyphenated routing target wrap across lines in a folded `>` scalar.** YAML folding replaces the newline with a space, so `gitea-labels-` at the end of one line and `milestones` at the start of the next fold into `gitea-labels- milestones`. The gate then reads the target as `gitea-labels`, finds no such skill, and reports it dangling — nothing in the source lines looks wrong. Reflow so the whole name sits on one line. The same applies to any backticked skill or agent name anywhere in a description. That universe is the apm marketplace and stops there. A **host built-in is not a routing target**: `/compact`, `/clear` and `/init` are Claude Code slash commands with no counterpart in Copilot CLI or Codex, and `.apm/` source compiles for all three, so routing to one is a portability defect. The gate is right to fail it and there is no allowlist. If a built-in genuinely needs mentioning, write it un-slashed — ``the `compact` built-in`` — which makes no routing claim and is not checked. **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 . When invoked, . ## Inputs ## Process ## Output ## Errors ```` Four required elements: **inputs expected, process steps, output format, error handling.** The last is the one that gets dropped, and dropping it is not neutral: an agent given a malformed input and no instruction invents a recovery, and a subagent's invented recovery is invisible to the caller until the output is wrong. Say explicitly whether the agent stops and reports, or degrades to a named fallback. 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 a `factory-audit` FAIL, and the fix is one line — "invoke ``". - 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-`). 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/.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.