Agent 技能调用权限模型:mattpocock-skills 修复跨技能引用违反 user-invoked 不变量实战
【免费下载链接】skillsSkills for Real Engineers. Straight from my .agents directory.项目地址: https://gitcode.com/GitHub_Trending/skills13/skills
本指南以仓库 .changeset/user-invoked-skill-invocation.md 这则变更记录为主体,系统讲解 mattpocock-skills 中「用户调用(user-invoked)」与「模型调用(model-invoked)」两类技能的权限模型、它们之间「没有其他技能能调用一个 user-invoked 技能」的核心不变量,以及一次同时修复 6 处违规跨技能引用(5 个技能的前置条件措辞、1 处 Phase 交接、1 段约定文档)的完整过程。读完你将掌握如何在 agent 技能仓库中正确地设计技能间的调用边界,理解为什么「让用户去运行」与「用 Skill tool 调用」是两条不可混淆的触发路径,并能从源码层面对照验证每条规则的落地方式。
一、从一份 changeset 说起:技能仓库里的变更记录是什么
在深入讨论调用权限之前,先明确这篇文章的「主体文档」到底是什么。仓库根目录下存在一个由@changesets/cli生成的 .changeset 目录,其中除了自动生成的 .changeset/README.md 与全局配置 .changeset/config.json 之外,还散落着一批形如user-invoked-skill-invocation.md、skill-tool-invocation-terminology.md、fix-yaml-frontmatter-colons.md的变更记录文件。它们遵循 Changesets 的约定:每一个未发布的变更,以独立 Markdown 文件记录「改了什么、为什么改」,待发布时由changeset version合并进 CHANGELOG 并完成版本号提升。
package.json 的脚本与依赖印证了这套工作流:
{ "name": "mattpocock-skills", "version": "1.2.3", "scripts": { "changeset": "changeset", "version": "changeset version && node scripts/sync-plugin-version.mjs" }, "devDependencies": { "@changesets/changelog-github": "^0.7.0", "@changesets/cli": "^2.30.0" } }而 .changeset/config.json 则声明了 changelog 生成器指向@changesets/changelog-github并绑定仓库mattpocock/skills、基线分支为main、内部依赖更新级别为patch。因此,每一份 changeset 文件都不仅仅是一行变更说明,而是一条可追溯、可评审、与版本号一一对应的工程决策记录。本文主体user-invoked-skill-invocation.md的 frontmatter 标注了"mattpocock-skills": patch,意味着它是一次补丁级别的行为修复,其正文则是对一次跨技能调用违规缺陷的完整描述。
二、核心概念:user-invoked 与 model-invoked 的二分
仓库的 .agents/invocation.md 是理解这一切的纲领性文档。它开宗明义地指出:仓库里每一个SKILL.md都是一个 skill,而区分它们的唯一坐标轴是调用方(invocation)——谁能够触达这个技能。
- User-invoked(用户调用):只能由人类输入其名字来触发。在 Claude Code 一侧通过 frontmatter 的
disable-model-invocation: true声明;在 Codex 一侧则通过同目录agents/openai.yaml中的policy.allow_implicit_invocation: false声明。这类技能的description是面向人类的,只写一句话摘要供浏览斜杠命令的人阅读,并要求剥离「当用户说……时使用」这类触发器清单。 - Model-invoked(模型调用):模型或用户都可以触发,这是默认形态。无需
disable-model-invocation,agents/openai.yaml中也不需要policy块。其description是面向模型的,保留丰富的触发措辞(「当用户想要……、提到……、要求……」),以便自动调用机制生效。
以仓库中两个典型技能为例。用户调用的 skills/engineering/setup-matt-pocock-skills/SKILL.md frontmatter 形如:
--- name: setup-matt-pocock-skills description: "Configure this repo for the engineering skills: set up its issue tracker, triage label vocabulary, and domain doc layout. Run once before first use of the other engineering skills." disable-model-invocation: true ---其配对的 skills/engineering/setup-matt-pocock-skills/agents/openai.yaml 则是:
interface: display_name: "Setup Matt Pocock Skills" short_description: "Configure a repo for the skills" policy: allow_implicit_invocation: false而模型调用的 skills/engineering/diagnosing-bugs/SKILL.md 与 skills/engineering/code-review/SKILL.md 的 frontmatter 中都没有disable-model-invocation,其 agents/openai.yaml 也只有interface元数据、没有policy块。.agents/invocation.md明确要求这两处保持同步:一个技能要么在两个 harness 中都是 user-invoked,要么都是 model-invoked。
仓库还给出了一条判断某技能是否应保持 model-invoked 的检验准则:"could the model usefully reach for this autonomously?"(模型自主触达它是否有用)。复用(reuse)是提取技能的理由,但不是判断调用方式的准则。
三、核心不变量:没有其他技能能调用一个 user-invoked 技能
.agents/invocation.md对两类技能的关系给出了最关键的约束(这正是本次变更要捍卫的不变量):
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.
译文:每个 harness 都以各自的方式把 user-invoked 技能排除在模型的触达范围之外,因此除了人类,没有任何东西能触发它:没有其他技能可以调用它。一个 user-invoked 技能可以调用 model-invoked 技能,但它永远无法触达另一个 user-invoked 技能。
这个不变量的工程意义在于:user-invoked 技能的语义是「带配置、带确认、带人机对话的交互流程」,例如setup-matt-pocock-skills需要探查仓库、向用户确认、再由用户决定写入哪些文件(见其 Process 章节)。如果模型可以在后台静默调用它,这类需要人类拍板的流程就会被绕过或卡死,配置结果也会与用户意图脱节。因此「只能由人触发」不只是权限约束,更是交互语义的保证。
四、缺陷的由来:前置条件被改写成了不可执行的 Skill tool 调用
本次 changeset 所修复的缺陷,源于一次引入跨技能调用约定的重构(文中记为 PR #878),以及一处「约定与不变量没有互相校验」的疏漏。
事情的经过是这样的:
to-spec、wayfinder、to-tickets、triage、code-review五个技能原本都带有一条前置条件,措辞为「……如果没有,请先运行/setup-matt-pocock-skills」("...run/setup-matt-pocock-skillsif not")。这是一种把操作交给用户/模型的散文式说明。- PR #878 引入了「通过 Skill tool 显式调用技能」的约定,并把这五处前置条件机械地改写成了字面指令
Call the Skill tool with "setup-matt-pocock-skills"。 - 但
setup-matt-pocock-skills是一个user-invoked技能。按照第三节的不变量,无论调用方本身是 user-invoked 还是 model-invoked,任何技能都不能通过 Skill tool 调用它。于是这五处改写全部变成了「注定失败的调用」——模型会尝试调用一个它根本无权触发的工具。 - 更糟的是,这个错误一次感染了 6 个调用点:5 处技能前置条件 + 1 处
diagnosing-bugs的阶段交接。changeset 原文对此的归因是:PR #878 引入「Dependencies between them」这一节时,没有把它与八行之前就已写下的 user-invoked / model-invoked 不变量对齐,这个缺口正是缺陷扩散到 6 处而非 1 处的大部分原因。
这揭示了一个值得所有技能作者警惕的规律:当新的调用约定被引入时,必须回头校验它是否与既有的权限不变量一致。约定与不变量分处两份文档、相隔数行,就足以让一次本应局部的改写演变成全仓库范围的违规。
五、修复一:五个技能的前置条件改由「告知用户」触发
changeset 对这一组修复的描述是:
Reworded all five as instructions for the agent to tell the human to run it instead.
即:不再让模型去调用setup-matt-pocock-skills,而是让 Agent告诉人类去运行它。这与.agents/invocation.md规定的措辞完全一致——前置条件指向 user-invoked 技能时,应写成「告诉用户运行/setup-matt-pocock-skills」("tell the user to run ..."),绝不写成 Skill tool 调用。
在仓库当前状态中可以逐一核对这五处修复后的真实文本:
| 技能 | 文件与位置 | 修复后措辞要点 |
|---|---|---|
| to-spec | skills/engineering/to-spec/SKILL.md | "The issue tracker and triage label vocabulary should have been provided to you. If not, tell the user to run/setup-matt-pocock-skills." |
| to-tickets | skills/engineering/to-tickets/SKILL.md | 同样的「If not, tell the user to run/setup-matt-pocock-skills」句式 |
| triage | skills/engineering/triage/SKILL.md | 在标签映射缺失时,"If not, tell the user to run/setup-matt-pocock-skills" |
| wayfinder | skills/engineering/wayfinder/SKILL.md | 在 issue tracker 未提供时,"If not, tell the user to run/setup-matt-pocock-skills" |
| code-review | skills/engineering/code-review/SKILL.md | "Ifdocs/agents/issue-tracker.mdis missing, tell the user to run/setup-matt-pocock-skills." |
注意一个细节:这五个技能本身并不都是 user-invoked——例如code-review是 model-invoked(frontmatter 无disable-model-invocation)。但这不影响修复的必要性:不变量约束的是「被调用方」而非「调用方」。哪怕调用方是模型可自主调用的技能,只要它想调用的是 user-invoked 的setup-matt-pocock-skills,这条路就是死的。修复的落点是触发路径——从「模型去调用工具」切换到「模型转告用户去执行」。
从措辞学的角度看,to-spec、wayfinder、to-tickets、triage这几个技能本身也是 user-invoked(frontmatter 均含disable-model-invocation: true),它们的前置条件中出现「让用户运行/setup-matt-pocock-skills」语义上完全自洽:用户正在交互式地使用技能,顺手运行一次配置技能再自然不过。而 model-invoked 的code-review同样选择转告用户,是因为docs/agents/issue-tracker.md缺失属于需要人类介入的环境问题,而不是模型能自行修复的。
六、修复二:diagnosing-bugs 移除 Phase 6 的手递手交接
第二类修复对象是diagnosing-bugs。changeset 描述了一个非常典型的失败场景:
diagnosing-bugs's Phase 6 post-mortem hand off toimprove-codebase-architecture(also user-invoked) the same way, from an autonomous, often-unattended bug-fixing flow with no human in the loop to catch the failed call.
diagnosing-bugs是一个 model-invoked 技能,定位是「无人值守」的自动排障流程(SKILL.md 明确写着 "A discipline for hard bugs",用于 hard bugs 与性能回归的诊断循环)。它的 Phase 6 原本会把「事后复盘(post-mortem)」交接给improve-codebase-architecture——而后者同样是 user-invoked 技能(frontmatter 含disable-model-invocation: true)。
这条交接链在三重意义上是注定失败的:
- 权限上:model-invoked 的
diagnosing-bugs无权调用 user-invoked 的improve-codebase-architecture; - 流程上:这是一个「没有人在循环中(no human in the loop)」的自动排障流程,即使模型试图转告用户,也往往无人响应,失败的调用只能静默丢弃;
- 价值上:changeset 指出这个交接「在实践中极少真正触发」("since it rarely fired in practice")。
因此修复策略是直接删除而非软化("Removed the hand-off outright rather than softening it")。删除之后,Phase 6 只剩「Cleanup(清理)」这一项内容。对照当前仓库,skills/engineering/diagnosing-bugs/SKILL.md 的 Phase 6 确实就是纯粹的机械式清理清单:
Phase 6: Cleanup
Required before declaring done:
- Original repro no longer reproduces (re-run the Phase 1 loop)
- Regression test passes (or absence of seam is documented)
- All
[DEBUG-...]instrumentation removed (grepthe prefix)- Throwaway prototypes deleted (or moved to a clearly-marked debug location)
- The hypothesis that turned out correct is stated in the commit / PR message, so the next debugger learns
changeset 特意强调「机械清单未动」("the mechanical checklist is untouched")——说明这次修复是外科手术式的:只切除不合法的交接,保留排障流程真正有价值的部分。这份清单本身也呼应了技能前文的方法论:Phase 4 要求给每条调试日志打上唯一前缀(如[DEBUG-a4f2]),Phase 6 则用一次grep回收全部标记日志;Phase 5 若发现「不存在正确的 seam」要把该结论作为发现记录下来,Phase 6 则负责把正确假设写进 commit/PR 消息传给下一位调试者。这提醒我们:修复调用链时,要区分「流程本身的机械步骤」与「流程间的跨技能耦合」,只动后者。
七、修复三:为 .agents/invocation.md 补充 carve-out 段落
第三类修复落在约定文档本身。changeset 写道:
Added a carve-out paragraph to
.agents/invocation.md's "Dependencies between them" section: theCall the Skill tool with "name"convention only applies when the named skill is model-invoked. This is the section PR #878 introduced without reconciling it against the user-invoked/model-invoked invariant stated eight lines above it; the gap is most of why this bug reached six call sites instead of one.
对照当前 .agents/invocation.md 的「Dependencies between them」一节(L14-L22),可以完整还原这次补充:
- 该节先规定:技能间的依赖必须表达为对Skill tool 的显式调用(
Call the Skill tool with "grilling"),而不是../other-skill/FILE.md这类深层跨目录引用,也不是留给模型自行解读的裸/skill式提及。点名工具是为了提高触发命中率(大多数 harness 把技能调用暴露为模型可调的工具);去掉前导/则是为了保持 harness 中立——技能名本身不再隐含任何特定 CLI 的触发语法。 - 接着规定:Skill tool一次只接受一个技能,需要两个技能就写成两次调用(
Call the Skill tool twice, for "grilling" and "domain-modeling")。 - 最后一段即本次新增的carve-out(例外条款),原文为:
This whole convention only holds when the named skill ismodel-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.
译文要点:整套 Skill tool 调用约定仅在被点名的技能是 model-invoked 时才成立。user-invoked 技能永远无法以这种方式被触达,哪怕只是把它点名给 Skill tool 也不行。当某一步的前置条件是 user-invoked 技能时,应将其表达为让人类去执行的指令:「告诉用户运行/setup-matt-pocock-skills」,绝不要写成 Skill tool 调用。
值得注意的是.agents/invocation.md还顺带界定了一个边界:这套约定针对的是操作性指令(技能自身的步骤要求 Agent 立即去运行另一个技能);而像ask-matt或分桶 README 中那种「点名技能供人类挑选」的路由性散文并不构成调用,可以继续保留/skill形式的纯标签写法。这让「何时用 Skill tool 调用、何时只做标签引用」有了清晰的可判定标准。
八、从仓库全景验证调用约定的落地
修复完成后,仓库中「Skill tool 调用只指向 model-invoked 技能」的约定可以从多处引用交叉验证:
- skills/engineering/grill-with-docs/SKILL.md 写着 "Call the Skill tool twice, for "grilling" and "domain-modeling""——一次调用携带一个技能名,符合「一调用一技能」规则;
- skills/engineering/wayfinder/SKILL.md 在「命名目的地」步骤同样使用 "Call the Skill tool twice, for "grilling" and "domain-modeling"" 的写法;
- skills/engineering/improve-codebase-architecture/SKILL.md 使用 "Call the Skill tool with "codebase-design"" 获取架构词汇表;
- skills/productivity/grill-me/SKILL.md 与 skills/in-progress/retro/SKILL.md 也遵循同样的显式调用句式。
与之形成对照的是,凡是前置条件涉及 user-invoked 的setup-matt-pocock-skills,全部技能(含 ask-matt/SKILL.md 中"run before your first engineering flow"的提示语)都只做「转告用户」式的表述,而没有一处残留 Skill tool 调用。也就是说,本次 changeset 修复后的仓库处于一个自洽状态:调用约定的适用范围与被调用技能的权限类型严格一致。
值得一提的是,同目录另一份 changeset .changeset/skill-tool-invocation-terminology.md 记录了这次约定本身的由来——把跨技能调用从散文式的/skill提法统一为显式 Skill tool 指令(覆盖code-review、diagnosing-bugs、grill-with-docs、grill-me、improve-codebase-architecture、tdd、to-spec、to-tickets、triage、wayfinder共 10 个技能),并说明散文式点名「无法可靠地让技能被加载」是grill-with-docs被反馈最多的问题根源。两份 changeset 放在一起,恰好构成一个完整的闭环:先确立新约定(skill-tool-invocation-terminology),再修补新约定与旧不变量之间的冲突(user-invoked-skill-invocation)。这正是一个成熟技能仓库维护「约定文档与权限不变量」关系的真实样本。
九、工程启示:技能仓库的权限设计与变更管理
围绕这份 changeset,可以提炼出几条对任何 agent 技能仓库(乃至任何「工具/技能可被调用」的系统)都适用的工程经验:
- 权限不变量必须写在纲领文档里,且调用约定要与它对齐。
.agents/invocation.md用两段话分别定义了「user-invoked 不可被其他技能调用」和「跨技能依赖用 Skill tool 表达」,本次缺陷的全部根源就在于新约定段落没有回头校验八行前的不变量。约定文档的每一处新增,都应该触发一次「与既有不变量一致性」的复查。 - 让用户去运行,与用工具去调用,是两条不可混淆的触发路径。前置条件指向 user-invoked 技能时,标准句式是 "tell the user to run
/xxx";指向 model-invoked 技能时,标准句式是Call the Skill tool with "xxx"。混用会让模型拿到一个必然失败的调用指令。 - 无人值守流程中,不要依赖需要人介入的交接。
diagnosing-bugs的例子说明:当流程「no human in the loop」时,指向 user-invoked 技能的交接链不仅非法,而且在实践中极难触发;与其软化措辞,不如评估其真实价值后直接切除,只保留机械清单。改动越外科手术式,回归风险越低。 - changeset 是变更评审的第一现场。这类文件让「改了什么、为什么改、修的是哪个 issue(本仓库记作 Fixes #453)」成为可被 Agent 与 LLM 检索的结构化记录。它与 .changeset/config.json、package.json 中的 changeset 脚本共同构成一套完整的版本化变更管理链路,也让后来的维护者能像阅读本文一样,从一行
"mattpocock-skills": patch出发,还原一次跨 6 个调用点的权限缺陷修复全貌。
如果你正在维护自己的 agent 技能集,可以直接对照本仓库的 .agents/invocation.md 检查三件事:你的技能是否都明确标注了调用方式?跨技能引用是否统一为 Skill tool 调用且只指向 model-invoked 技能?所有指向 user-invoked 技能的前置条件,是否都写成了「告诉用户去运行」?
【免费下载链接】skillsSkills for Real Engineers. Straight from my .agents directory.项目地址: https://gitcode.com/GitHub_Trending/skills13/skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考