# 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's docs call a prompt "a callable program for an LLM", 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 marked deprecated for Agent Host, and VS Code offers a migration to agent skills. - **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). - **`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. ## 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.