# Prompts are thin, user-triggered steering messages; procedure belongs in a skill **Status: accepted (2026-09-28).** Refs #94. Sets the house rule that `primitive-author` enforces when it writes a `.prompt.md`, and that `factory-audit` checks in its prompt flow. A **Prompt** (the term is in `CONTEXT.md`) is a single-intent, user-triggered message with parameters. It is the text a user would otherwise type again and again. It carries no procedure beyond steering existing skills or agents by name. Once it holds reusable know-how, bundled files, or anything the model should find on its own, it is a **Skill** in the wrong container. This is stricter than apm. apm frames a prompt as a program: its docs state that "a prompt is a program for an LLM" (microsoft.github.io/apm/concepts/what-is-apm/, "Secure by default"; the same sentence is in the apm-cli 0.28.0 package README), and 0.28.0 scaffolds one as a numbered-steps workflow (`apm_cli/workflow/discovery.py`). On every harness this repo targets, a prompt with a full workflow in it is a worse skill: - **Claude Code.** Custom commands have been merged into skills. "Both create `/deploy` and work the same way", and both are model-invocable by default (code.claude.com/docs/en/skills.md). apm 0.28.0 keeps only `description`, `allowed-tools`, `model`, `argument-hint` and `input` for Claude (`_PRESERVED_COMMAND_KEYS`) and drops `disable-model-invocation`. A deployed prompt is therefore a model-visible skill with fewer frontmatter keys, and it cannot be made user-only. - **Copilot / VS Code.** "Prompt files are deprecated for Agent Host sessions and aren't loaded by Agent Host", and VS Code offers a migration that converts existing prompt files to agent skills (code.visualstudio.com/docs/agent-customization/prompt-files). They still load in the Local agent, which that page says will be removed in a future release. - **Codex.** Codex receives no prompts at all. The one job a prompt does better than a skill is a short, parameterised "do this now, using X and Y" message, where `input:` → `$arguments` is the whole point. ## Description contract A prompt's `description` is one plain, human-facing sentence that names the skills or agents it steers. For example: "Review the current PR with `gitea-prs` and `factory-audit`, then summarise." It has no "Use when…" trigger clause and no `Not X -> Y` boundary clause. This is the same shape `factory-audit` already applies to `disable-model-invocation: true` skills. Without a trigger clause, the router has little reason to pick the prompt over the skills it wraps. So when the model routes, it tends to reach the real capability. **Unverified:** how strongly Claude avoids routing to a description with no trigger clause. The contract lowers the chance of the model invoking the prompt, but does not prevent it. If it does happen, the cost is bounded: the prompt is a thin wrapper that calls the right skills anyway. ## Enforcement - **`primitive-author`.** The prompt reference opens with a boundary gate. A request that carries procedure is redirected to `skill-author`. - **`factory-audit`, script checks.** - `description` is present and non-empty (FAIL). - It is 250 characters or fewer (SUGGESTION). - It has no "Use when" trigger clause (SUGGESTION). - It has no `Not X -> Y` boundary clause (SUGGESTION). - **`factory-audit`, judgment step.** A body that clearly carries reusable procedure is a FAIL. A borderline body is a SUGGESTION. There is deliberately no line-count or heading heuristic: the call is made by reading the content, because any threshold misfires. ## Consequences - **`factory-audit` gains a prompt mode.** `*.prompt.md` is a Step 0 dispatch row with its own `references/prompt-flow.md`, and `scripts/lib-checks-primitive.sh` carries the three description checks above plus the judgment step (ADR-0025, amendment 2026-09-28). A prompt that was fine under apm's framing — a trigger clause, a boundary clause, a numbered workflow — now draws findings. - **`primitive-author` refuses procedure-bearing prompts.** Its prompt reference opens with the boundary gate, so a "make me a command" request that carries reusable know-how is redirected to `skill-author` before any file is written. That makes skill-vs-prompt a checked boundary rather than an authoring preference. - **Codex gets no prompts from this repo.** apm deploys none to Codex, so anything a prompt steers must also be reachable there through the skill it names. Keeping procedure in the skill is what makes that true; a fat prompt would be content Codex users silently never see. - **Reversing this is cheap in files, not in routing.** No prompt exists in the repo yet, so the rule constrains new work only. Loosening it later means re-deciding the description contract, and every prompt written under it would need a trigger clause added. ## Considered options - **Fat workflow prompts as peers of skills (rejected).** This follows apm's framing. But every "make me a command" request becomes a coin flip between two near-identical containers. The prompt also carries worse metadata on Claude and does not arrive on Codex. - **The full ADR-0020 description contract for prompts (rejected).** A trigger clause and a boundary clause would make prompts route well. That actively invites the model to invoke the prompt, which contradicts "user-triggered". - **Skill-by-default with prompts as a grudging exception (superseded during the grill).** This framed a prompt as a weaker skill competing for the same job. Giving it a distinct role, a thin caller over skills, is a boundary that can be checked, which "prefer skills" is not.