Five skills (to-spec, wayfinder, to-tickets, triage, code-review) told the agent to call the Skill tool on setup-matt-pocock-skills as a missing-config precondition. It's user-invoked, so per .agents/invocation.md no other skill — user- or model-invoked — can ever reach it that way. PR #878 turned the old soft "/skill"-style prose into a literal "Call the Skill tool with ..." instruction without checking invocation type, which made this fire more reliably in the wrong direction. Reworded all five as instructions to tell the human to run it instead. diagnosing-bugs had the same problem handing its post-mortem off to improve-codebase-architecture, with no human anywhere in that loop to catch the failed call. Removed the hand-off outright rather than softening it, since it rarely fired in practice; Phase 6 is now "Cleanup" only. Added a carve-out to .agents/invocation.md's "Dependencies between them" section — the section PR #878 introduced — so future "call the Skill tool" instructions can't repeat this against a user-invoked target. Fixes #453.
3.8 KiB
Model-invoked vs user-invoked
Every SKILL.md in this repo is a skill. The one axis that splits them is invocation — who can reach it:
- User-invoked — reachable only by the human typing its name. Set
disable-model-invocation: truein the frontmatter (Claude Code) andpolicy.allow_implicit_invocation: falseinagents/openai.yaml(Codex). Thedescriptionis human-facing: a one-line summary read by a person browsing slash-commands. Strip trigger lists ("Use when the user says…"). - Model-invoked — reachable by model or user. The default: omit
disable-model-invocationand thepolicyblock fromagents/openai.yaml. Thedescriptionis model-facing and keeps rich trigger phrasing ("Use when the user wants…, mentions…, asks for…") so auto-invocation fires. The test for whether a skill should stay model-invoked: could the model usefully reach for this autonomously? (Reuse is the reason to extract a skill, not the test.)
Each harness excludes a user-invoked skill from the model's reach in its own way, so nothing but the human can fire it — no other skill can. A user-invoked skill may invoke model-invoked skills, but it can never reach another user-invoked skill.
Every skill also carries an agents/openai.yaml beside its SKILL.md. It holds Codex UI metadata — interface.display_name and interface.short_description for the skill picker — and, for user-invoked skills, the policy.allow_implicit_invocation: false that pairs with disable-model-invocation. Keep the two in sync: a skill is user-invoked in both harnesses or neither.
Bucket README.mds and the top-level README.md group entries into User-invoked and Model-invoked.
Dependencies between them
Dependencies are expressed as an explicit instruction to call the Skill tool with the named skill (Call the Skill tool with "grilling"), not deep ../other-skill/FILE.md cross-references, and not a bare /skill-style mention left for the model to interpret. Naming the tool is what gets it fired: most harnesses expose skill invocation as a tool the model calls, and spelling that out gets a higher hit rate than dropping a /name into prose and hoping it's read as a command. Dropping the leading / also keeps this harness-neutral rather than less — a skill name on its own carries no assumption about which harness's trigger syntax it belongs to. Shared reference docs live inside the skill that owns them; other skills reach that material by calling the Skill tool with it, not by linking across folders.
This is about operative instructions — a skill's own steps telling the agent to go run another skill right now. Router prose that just names skills for a human to pick from (ask-matt, bucket README.mds) isn't invoking anything, so it keeps /skill-style names as plain labels.
The Skill tool takes one skill per call. A step that needs two skills is two calls, not one call with two names — say so (Call the Skill tool twice, for "grilling" and "domain-modeling"), not "call it with X and Y," which reads as a single call taking both.
This whole convention only holds when the named skill is model-invoked. A user-invoked skill can never be reached this way, full stop — per the invariant above, no other skill can call it, including by naming it to the Skill tool. When a step's precondition is a user-invoked skill (e.g. setup-matt-pocock-skills), phrase it as an instruction for the human to act on — "tell the user to run /setup-matt-pocock-skills" — never as a Skill tool call.
Passive vs active domain work
Merely reading CONTEXT.md for vocabulary is a one-line prose pointer, not the domain-modeling skill. Only the active build/sharpen discipline (challenge terms, edge-case scenarios, write ADRs, update CONTEXT.md inline) is domain-modeling.