diff --git a/CONTEXT.md b/CONTEXT.md index 62ad8ec..282603c 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -48,7 +48,31 @@ _Avoid_: agent hygiene A reusable slash command defined as a `SKILL.md` file following the [Agent Skills open standard](https://agentskills.io), authored at `plugins//.apm/skills//SKILL.md`. -_Avoid_: command, prompt, macro +_Avoid_: command, macro; and "prompt" for a skill — a **Prompt** is a different artifact + +**Prompt**: +A single-intent, user-triggered message with parameters, authored as +`plugins//.apm/prompts/.prompt.md` — the text a user would otherwise type repeatedly. +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 a house rule, stricter than apm, which frames a prompt as a full workflow. +_Avoid_: command (the Claude-side deployed form), workflow, macro + +**Instruction**: +A scoped rule authored as `plugins//.apm/instructions/.instructions.md`, applied when +the agent touches files matching its `applyTo` glob. Omitting `applyTo` makes it always-on in every +session of every repo that installs the package — a legitimate way for a package to ship guidance to +consumers, but a deliberate choice, never a default. A rule for this repo alone belongs in +**AGENTS.md**, not in an instruction. +_Avoid_: rule (the Claude-side deployed form under `.claude/rules/`), guideline, standard + +**Hook**: +A runtime callback a harness fires inside its own tool loop, authored as one JSON file per concern +under `plugins//.apm/hooks/` in apm's canonical shape — nested entries, PascalCase events, +`${PLUGIN_ROOT}` script paths — which apm renders per target. Reach is narrowed in the package's +`apm.yml` `targets:`, never by filename. The last resort among apm primitives: procedure belongs in +a **Skill**, and a hook is only for "this must always happen at this event". +_Avoid_: trigger, callback script (the script is the hook's payload, not the hook) **apm package**: The deployable unit apm builds and installs — one or more skills, agents, hooks, commands, and MCP @@ -58,6 +82,13 @@ _Avoid_: bundle, module, source tree; and bare "plugin" for the *installable art ADR-0024 is an apm package and not a Claude Code plugin. "Plugin" stays correct as a modifier in the repo's settled compounds — **Plugin marketplace**, "plugin units", `plugins/`. +**apm primitive**: +Any content type authored under `plugins//.apm/` — skills, agents, hooks, instructions, and +prompts. Skills and agents each have their own author skill; `primitive-author` covers the other +three, so its name is narrower in practice than the term. `factory-audit` audits all five. +_Avoid_: component, asset, artifact (unqualified); bare "primitive" when only the three +non-skill, non-agent types are meant — say "hook, instruction, or prompt" + **Output profile**: A named ecosystem format `apm pack` can compile the marketplace manifest into, declared per profile under root `apm.yml`'s `marketplace.outputs:`. apm defines `claude` and `codex`; each writes to its @@ -193,3 +224,8 @@ _Avoid_: namespace, category an audit running in the same context as the work it checks shares that work's blind spots. The recheck belongs to `factory-audit`, not to `forge`, which routes only to the author skills and never to an audit. +- "prompt" meant both apm's `.prompt.md` primitive and, loosely, any slash command or a skill — resolved: + a **Prompt** is the `.prompt.md` primitive under the house rule above. apm calls a prompt "a + callable program for an LLM", but on Claude it deploys as a model-invocable command with fewer + frontmatter keys than a skill, and Codex receives nothing. A fat prompt is a worse skill on every + harness, so the procedure goes in the skill and the prompt only steers it. diff --git a/docs/adr/0029-prompts-are-thin-user-triggered-steering-messages.md b/docs/adr/0029-prompts-are-thin-user-triggered-steering-messages.md new file mode 100644 index 0000000..017a69f --- /dev/null +++ b/docs/adr/0029-prompts-are-thin-user-triggered-steering-messages.md @@ -0,0 +1,62 @@ +# 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.