--- source_keys: - agentskills-spec - agentskills-best-practices --- # Body Discipline Reference Upstream source: agentskills.io — skill-authoring, best-practices. House contract: the context budget. ## The core test For every sentence in the body, ask: **"Would the agent get this wrong without this instruction?"** If no — cut it. The agent already knows it from general training. Adding it wastes tokens and dilutes the signal of what matters. ## What the body is for The body carries the **decision procedure only**: ordered steps, decision branches, gates, and which reference to load when. Include content the agent lacks: - Project-specific conventions and domain procedures it cannot infer - Non-obvious edge cases and environment-specific gotchas - The specific tools or sequences to use — not the full range of options - One default per decision point with one escape hatch Move to `references/`, behind an explicit "If X, read `references/.md`" trigger — the literal conditional form, never a generic pointer. Write the real filename in the skill under audit; the angle brackets are a placeholder here, and a literal `references/file.md` in a body is an ERROR from the gate because no such file exists on disk. **A dispatch table satisfies this requirement on its own.** A table row already pairs a condition with a target, which is exactly what the literal form encodes; restating each row underneath as a prose conditional duplicates the routing in the one body whose whole purpose is to be short. Where a body dispatches, audit the table for condition/target completeness and stop there — do not require the conditional form as well. The literal form is what a body needs when it loads a reference *without* a dispatch table: a single mid-procedure deepening, an escape hatch, an error path. Move: - Lookup tables and spec restatements - Output schemas, templates and example blocks - Rationale and justification prose - Anything only one branch of the procedure ever reaches Do not include at all: - Concepts the agent already knows (what JSON is, how HTTP works, what a CSV is) - Exhaustive option lists — pick a default; the agent does not benefit from choosing - Steps the agent handles independently — over-specifying leads to unproductive paths - Restatements of the description, which is already in context ## Two length families, measured differently Do not conflate these, and do not report them as one finding. | Gate | SUGGESTION | FAIL | Counts | |---|---|---|---| | Body budget (house) | 600 words | 900 words | the **body only** — everything after the frontmatter's closing `---` | | Spec conformance (agentskills.io) | — | 2,770 words / 500 lines | the **whole file**, frontmatter included | The 2,770-word ceiling is a token-conformance backstop calibrated to the densest prose in the corpus; it says nothing about quality and a file can sit a thousand words inside it while failing the body budget. The 900-word ceiling is the quality gate: a body is loaded into the caller's live context and competes with the conversation already there. `validate.sh` reports both. Cite whichever one actually fired. A word count cannot detect the defect it stands in for. Treat both numbers as backstops to the dispatch rule and the Gotchas constraint below, never as a substitute for them. ## Dispatch is mandatory at two or more mutually exclusive flows If a skill handles two or more flows that a single invocation cannot both take — separate subcommands, separate input types, separate lifecycle stages — the body carries a **dispatch table** plus the gates common to every branch, and each flow lives in its own self-contained `references/` file. Inlining all of them is a FAIL regardless of word count, because every invocation then pays for every branch it did not take. The reference shape in this repo is `apm-workflow`: a **294-word body** dispatching to 3,154 words of references across five mutually exclusive flows. Its whole-file count is 348 words — cite 294 when calibrating a body, or the conflation this section warns against reappears in the finding itself. The 3,154 counts the five flow files only; `references/sources.md` is a provenance record and is never loaded at runtime, so counting it inflates the dispatched total. ### What earns the wiring exemption A dispatch table earns the exemption above on its properties, not on which skill it appears in. Audit any dispatching body against these four: - Every flow the skill handles has a row, and every row names a target file that exists on disk. - Each row pairs a condition the agent can evaluate from the request with exactly one target. A row keyed on a literal slash invocation fails this: a model-invoked activation never produces that string, so the routing silently falls to whatever else the row carries. - One line after the table tells the agent to read the file its row matched, and only that one. - The gates every branch needs sit in the body, not inside one flow's file — see the reachability precondition below. A table missing any of the four is not exempt, and the literal-conditional requirement applies to it as written. The exemption covers the wiring form only: every other rule in this file applies to a dispatching skill exactly as it applies to any other. ## Gotchas sections The highest-value construct in a body, and the easiest to fill with noise. A Gotcha must state a fact that **contradicts a reasonable default** — something the agent gets wrong precisely by acting sensibly. ```markdown ## Gotchas - The `users` table uses soft deletes. Always include `WHERE deleted_at IS NULL`. - User ID is `user_id` in the database, `uid` in auth, `accountId` in billing. Same value. ``` Constraints: - **More than five entries is a SUGGESTION** — five is the guideline, not a ceiling. Past five, the section is usually a summary of the body rather than a set of traps, and the agent stops reading it as a warning. It stays advisory because whether a given gotcha earns its place is judgment; `validate.sh` emits it through `suggest()` and the run still exits 0. - **A Gotcha that paraphrases a step in the body below it is a FAIL.** It has no independent content, and it teaches the agent that Gotchas can be skimmed because the real instruction is coming. This one is the auditor's call — no script detects it. The Fix is conditional: delete the Gotcha only if the surviving copy is reachable from every branch that needs it — see the reachability precondition below. - **A Gotchas section exceeding 25% of the body is a SUGGESTION** — the body has been inverted into a preamble. Same tier and same reasoning as the entry count, and independent of it: either can fire without the other. - Place the section near the top. A gotcha read after the mistake is worthless, which is also why Gotchas is the one construct exempt from moving to `references/`. Worked negative example — **`git-commits` v0.1.2 at commit `5e23250`, a fixed pre-retrofit snapshot, not the current file.** The live skill is v0.1.3 and matches none of the citations below; they are quoted as they stood in that snapshot, and are not to be refreshed against `HEAD`. The snapshot is reachable only from a checkout of the authoring repo — an installed plugin cache holds no git history and no such path — so read the citations below as quoted rather than going to look for the file. From a checkout: ```text git show 5e23250:/.apm/skills/git-commits/SKILL.md ``` That body carried twelve Gotchas, four of which restated content already below them or already in the description: | Gotcha | Restates | |---|---| | `:31` "SemVer mapping is not optional" | the description | | `:32` "Confirmation gates are mandatory for destructive operations" | step 9 at `:52` | | `:33` "Never skip hooks with `--no-verify`" | step 9 at `:52` | | `:36` "Never commit secrets" | step 2 at `:45` | All four are FAILs under the paraphrase rule. The entry count and the section's share of the body (387 of 1,102 words, 35%) are two further SUGGESTIONs on top — the script reports both, and neither fails the run on its own. What makes this worth auditing directly is that the four paraphrase FAILs pass every word gate there is; only reading the construct finds them. ### The paraphrase rule has a reachability precondition **A Gotcha that restates a step may be deleted only when the surviving copy is reachable from every branch that needs it.** In a dispatch body it usually is not: each flow file is loaded alone, so a step in one is invisible to an invocation that took another branch. When the restated rule is a safety gate more than one flow needs, the Fix is to **move it into the body's common-gates section**, never to drop it in favour of the per-flow copy. Row four is the case that proves it. Following the rule literally, the retrofit deleted the always-loaded secrets Gotcha and kept step 2 of `references/create-commit.md` — but `git-commits` dispatches to exactly one flow file, and `references/rewrite-history.md` stages changes and runs `--amend`, which commits newly staged content exactly as a fresh commit does. A grep for `secret` across the skill in that state returned one hit, on a path two of three branches never reach: that branch could commit a credential with no check anywhere in its loaded context, against this repo's governance hard prohibition. v0.1.3 carries the rule as gate 2 of "Gates on every flow" instead. So check reachability before writing the Fix. Rows one to three are unaffected — the description is loaded on every invocation, and confirmation is likewise a common gate rather than a per-flow step. ## Calibrating control **Be prescriptive** when operations are fragile, consistency matters, or a specific sequence must be followed: ```markdown Run exactly: \`\`\`bash python scripts/migrate.py --verify --backup \`\`\` Do not modify the command or add additional flags. ``` **Give freedom** when multiple approaches are valid. Explaining *why* outperforms rigid directives — agents make better decisions when they understand the purpose. ## Defaults not menus Never present a list of equivalent options — pick one and mention the alternative briefly: ```markdown # Too many options Use pypdf, pdfplumber, PyMuPDF, or pdf2image... # Default with escape hatch Use pdfplumber for text extraction. For scanned PDFs requiring OCR, use pdf2image instead. ``` The FAIL and SUGGESTION criteria for this dimension live in `references/finding-criteria.md`, which Step 3 loads on every run.