1. Skill 的物理形态
装上某个 Skill,实际发生的事只有一件:磁盘上多了一个文件夹。
.claude/skills/release-notes/
├── SKILL.md
├── references/
│ └── categories.md
└── scripts/
└── collect.pySKILL.md 开头有一段元数据,后面是正文:
---
name: release-notes
description: 把一批合并提交整理成对外发布说明,输出含分类与致谢的 Markdown。
---
# 生成发布说明
正文从这里开始……装完之后,常驻进上下文的只有 name 与 description 两项,合计一两百字节。正文要等模型真的需要它时才读,脚本要等真的执行时才跑。
这就是 Skill 的全部机制:一个文件夹、一份说明、一个按需读取的约定。没有协议、没有服务、没有连接。
1.1 它解决的问题
一个团队把"生成发布说明必须先跑 scripts/changelog.py,手写的格式会漏掉合并提交"这条约定写进了全局提示词。提示词从五千字涨到一万两千字,这条约定被挤到中段,模型开始偶尔遵守、偶尔不遵守。
这不是模型不稳定,是这条约定的可见性下降了。它和另外几十条约定一起,每轮都在争夺注意力。
Skill 的处理方式是把这类只在特定任务里生效的知识移出全局提示词,改成按任务加载。全局提示词只保留对所有任务都成立的内容。
2. Skill 与工具的差别
同一个能力,三种形态。上一篇讲过这条谱系,这里把两种写法并排放。
做法一,专用工具:
deploy_app(service, version, env)
—— 结构化参数,执行稳定,改流程要改代码
做法二,Skill:
SKILL.md 里写六步流程与每步判定条件,
模型读完后用命令执行工具逐步执行
—— 改流程只改一段文字,但每步要模型自己判断两种做法在六个维度上的差别:
| 维度 | 专用工具 | Skill |
|---|---|---|
| 常驻成本 | 每个数百字节到数 KB | 目录一行,约一百字节 |
| 参数传递 | 结构化,受 schema 约束 | 自然语言,需自行处理转义 |
| 谁能改 | 会编程的人,改完要测试部署 | 任何人都能改 |
| 失败方式 | 参数错、权限拒绝 | 步骤理解偏、漏步 |
| 变更成本 | 高 | 低 |
| 适用 | 稳定、参数复杂、有硬约束 | 流程在变、需要判断、步骤固定 |
2.1 参数的差距比想象的严重
Skill 用自然语言描述操作,模型要生成合法的命令行参数,还要处理引号与特殊字符转义。这套转义规则比 JSON 复杂得多,而且在 Linux、macOS、Windows 上各不相同。
于是出现一类 Skill 里的常见做法:让模型把复杂参数以 JSON 写入临时文件,再在命令行里读这个文件。这是绕开转义,不是解决转义——它说明参数复杂到一定程度时,Skill 这种形态本身就不合适。
2.2 容错性反过来
工具的结构如果引号不匹配、花括号缺一个、必填字段缺失,整个 Agent 可能无法运行——错误是全局的。
Skill 的错误通常是局部的:某一步写得不清楚,模型在那一步偏了,其余步骤照常执行。修改也只涉及一段文字。
这个差别在"改得快"与"错得轻"两件事上都有意义,也是 Skill 更适合频繁调整的流程的原因。
2.3 什么时候两者一起用
一个能力既需要判断又步骤固定时,拆开:Skill 写判断原则与流程,工具写可执行步骤。
Skill 里写"根据变更类型选择分类,新增进新增节、修复进修复节",工具里写"按分类渲染 Markdown"。前者需要判断,后者不需要。
3. 一份完整的 SKILL.md
看一份实物比看写法要求有用。下面这份约五百字,资源只给了两个路径。
---
name: release-notes
description: 把一批合并提交整理成对外发布说明,输出含分类与致谢的 Markdown。
whenToUse: 需要为某个版本生成对外公告时;不用于内部技术变更记录。
---
# 生成发布说明
## 何时使用
版本号确定、待发布提交已合并到主分支时。不要用于:
- 内部技术变更记录(那是另一份文件)
- 尚未合并的提交(会让公告与代码不一致)
## 流程
1. 取两个标签之间的提交列表,排除合并提交本身。
2. 按面向用户的类别分组:新增、改进、修复、不兼容变更。
3. 每条写成一句话,说清「变了什么」与「对谁有影响」。
4. 不兼容变更置顶,并写明迁移方式。
5. 致谢外部贡献者,用其提交里的署名。
## 完成标准
- 包含四个分类标题,没有内容的分类省略;
- 每条不超过一句话,不含提交哈希与内部代号;
- 不兼容变更在文首,且每条都写了迁移方式;
- 贡献者署名与提交记录一致。
## 格式示例
以下仅为格式,不含真实内容:
新增
- <功能名>:<对用户的影响>
不兼容变更
- <变更名>:<迁移方式>
## 资源
- 分类规则细化:`references/categories.md`
- 生成脚本:`scripts/collect.py`三处值得注意。
"何时使用"与元数据里的 whenToUse 重复。 这是有意的:目录里那一行用于"要不要加载",正文里这一段用于"加载后要不要继续"。两处的作用不同,重复不算冗余。
完成标准四条都可判定。 "每条不超过一句话"能数,"不含哈希"能查,"置顶"能看位置。换成"语言简练"就无法判定。
格式示例用了占位符。 如果写成"新增:支持批量导出,处理速度提升 3 倍",模型会把"3 倍"当成真实数据引用。占位符与"仅为格式"的声明共同防这件事。
4. 三层披露与体积预算
Skill 内部按"什么时候需要"分三层。
| 层 | 什么时候需要 | 体积 | 里面放什么 |
|---|---|---|---|
| 目录 | 每一轮 | 每条 100–200 字节 | 名称、一句说明、何时使用 |
| 正文 | 命中时 | 通常 2–8 KB | 流程、判定条件、完成标准、注意事项 |
| 资源 | 执行时 | 无硬上限 | 脚本、模板、示例数据 |
设目录有 \(n\) 条、每条说明 \(d\) 字节,常驻成本是:
二十个 Skill、每条 200 字节是 4 KB;每条 500 字节就是 10 KB,且每轮都付。
这个数与正文总量对照才有意义:二十个 Skill 的正文假设平均 4 KB,合计 80 KB。常驻部分只占正文总量的 5%,这就是分层披露的价值——把 80 KB 压成 4 KB,代价是命中时多一次读取。
三层的写法要求不同,因为读者不同。目录的读者是"正在判断该不该加载"的模型,它需要的是匹配信号;正文的读者是"已经决定要做这件事"的模型,它需要的是可执行的步骤;资源的读者是执行环境,它只需要能被调用。
5. 正文写哪四段
正文最常见的毛病是膨胀:把整套流程的每个细节都写进去,命中一次就吃掉几 KB。
判断标准:正文应该让模型能开始工作并知道何时算完成,不必穷举每个分支。
一个量化:正文 8 KB,一次任务里被加载两次,就是 16 KB。这相当于四十个工具声明。
四段的结构。
触发条件。 什么任务下用,什么任务下不用。排除条件同样重要——只写"处理文档"会导致它在所有文档任务里都被加载。
流程。 可执行的步骤。每一步是一个动作加一个判断,不写"仔细分析需求"这类无法执行的话。
完成标准。 可判定。写成"输出包含三节、每节有结论","输出质量好"不构成标准。
示例。 只示范格式,不示范事实。示例里的具体数字、具体结论会被模型当成事实引用,所以要么用占位符,要么显式标注"以下仅为格式示例"。
5.1 分支细节往哪放
正文里出现"如果……则……否则……"的嵌套时,先判断这个分支是否常见。
常见的分支留在正文,因为它会被频繁走到,每次去资源层取一次不划算。少见的分支移到资源层,正文只写"遇到罕见格式时读 references/edge-cases.md"。
判断标准是触发频率,不是重要性。一个很少发生但很关键的分支,也应当放资源层——关键不等于常驻。
6. 目录那一行怎么写
目录说明只回答一个问题:什么任务下该选我。不说怎么实现,不列全部能力。
对比两种写法:
弱:处理文档相关工作。
强:把 Markdown 转成正式的 Word 文档,包含目录、页眉、样式模板。弱写法在"把这份文档转成 Word"这个任务里不会命中,因为它没说 Word。强写法命中,因为它把动作与产物都写了出来。
三个具体要求。
写产物名,不写领域名。 "Word 文档"是产物,"文档相关工作"是领域。产物名能被任务描述里的词直接匹配。
写排除条件。 只写"处理文档"的 Skill 会在所有文档任务里被加载。写上"不用于纯文本转换",能把一部分任务挡在外面。
控制长度。 目录长度直接等于每轮成本。有的实现给说明设了长度上限并在超限时截断,这是必要的防护。
6.1 目录健康的数量指标
判断一个 Skill 库的目录是否健康,看三个数。
| 指标 | 健康值 | 异常含义 |
|---|---|---|
| 单条说明平均长度 | 150 字节以下 | 过高说明有人在往说明里塞正文 |
| 目录总长度 | 8 KB 以下 | 超过就要考虑分组或按需接入 |
| 命中后未使用的比例 | 20% 以下 | 过高说明说明写得太宽 |
第三个指标要统计:加载了某份正文,但该次任务最终没有执行其中任何一步。比例高说明目录说明的匹配信号太泛,把不相关的任务也吸进来了。
7. 元数据与控制字段
除名字与说明,几个字段控制"谁能看到这份 Skill"。
| 字段 | 作用 | 典型用途 |
|---|---|---|
| 何时使用 | 目录里额外一行,补充匹配信号 | 主说明写产物,这行写场景 |
| 禁止模型调用 | 从模型目录与加载器里排除 | 只适合人显式调用的危险操作 |
| 禁止用户调用 | 从用户命令里排除 | 只适合模型按需加载的内部策略 |
第二个字段常被忽略:有些 Skill 只适合人显式调用(例如"重置环境"),让模型自己决定加载它很危险。
第三个字段相反:有些 Skill 只适合模型按需加载,不希望出现在用户的命令菜单里(例如内部的重试策略)。
7.1 可见性不止开关两态
有的实现把可见性做成逐条覆盖,按名字设四档。
| 档位 | 模型可见 | 用户可见 | 说明是否渲染 | 适用 |
|---|---|---|---|---|
| 开 | 是 | 是 | 是 | 常规 Skill |
| 仅名称 | 是 | 是 | 否 | 说明很长但用得频繁 |
| 仅用户调用 | 否 | 是 | 不适用 | 危险操作、需人确认 |
| 关 | 否 | 否 | 不适用 | 临时停用,保留内容 |
第二档最细:Skill 仍对模型可见、可被调用,但目录里只列名字,不渲染说明。省下的是目录体积,代价是模型只能凭名字判断。
这一档说明可见性是一个"模型看到多少"的刻度。目录体积与选择准确率之间的取舍可以按单个 Skill 调,不必全局一刀切。
7.2 跨运行时的一致性
同一个 Skill 常常要同时供给两个运行时,而两个运行时的元数据写法不同。
| 运行时 | 文件 | 字段 | 语义 |
|---|---|---|---|
| 一种 | SKILL.md 的元数据 | 禁止模型调用 | 是否禁止 |
| 另一种 | 附加的 openai.yaml | 允许隐式调用 | 是否允许 |
两者语义相反。这带来一个必然会发生的漂移:改了一个忘了改另一个,同一个 Skill 在一个运行时里是手动专用、在另一个运行时里可以被自动加载。
对付这种漂移,光靠文档要求不够,需要可执行的校验。三条规则。
第一,两个字段都必须出现且都是布尔值。
第二,两者推出的结论必须一致:前者的真等价于后者的假。不一致即报错,并指出两侧各自的值——这种错误靠人肉对照很难看出来。
第三,一个手动专用的 Skill 必须仍然可由用户调用。否则它谁都调不到:模型不许调,人也不能调。这条最容易漏,因为两个标志分别看都合理。
校验的作用不只是防错,它还固定了一件事:手动专用这个概念在两侧必须同时成立。有了这条,新增一个运行时就必须补一份等价映射,不能只实现一半。
8. 发现与裁决顺序
Skill 从哪来、重名怎么算,决定"同一个名字在不同项目里是不是同一件事"。
三类来源,按作用范围从小到大。
| 来源 | 位置 | 特点 |
|---|---|---|
| 项目根 | 仓库内 | 随版本走,团队共享 |
| 自定义根 | 部署级目录 | 不随仓库,跨项目 |
| 用户根 | 个人目录 | 跨项目,个人偏好 |
发现规则是扫描这些根目录的顶层,支持两种形态:目录 bundle(含 SKILL.md)与平铺文件(<name>.md)。刻意不支持嵌套的 **/SKILL.md——嵌套会让扫描成本随目录深度上升,同名冲突的概率也大增。
重名的裁决有一条固定顺序。
- 最近的 scope 层直接胜出(项目层盖过全局层);
- 同层内先比提权字段;
- 再比提供方注册顺序;
- 最后比本地目录顺序。
顺序固定的好处是可预测:同名覆盖不需要读代码就知道谁赢。代价是用户根的同名 Skill 永远盖不过项目根,即使它是更新版本。所以升级一份 Skill 时,改内容要改在对应的层,光在用户根改没用。
9. 失效与变更传导
目录是快照还是实时,决定"新加一份 Skill 要不要重启"。
主流做法是监视这些根目录,新增、改名、删除无需重启即可到达。但监视带来一个新问题:变更如何传导到已加载的内容。
三种传导方式。
| 方式 | 做法 | 优点 | 代价 |
|---|---|---|---|
| 全量失效重取 | 变更后整个缓存失效 | 简单,不会漏 | 每次变更都全量重扫 |
| 增量更新 | 只更新受影响那条 | 便宜 | 改名与删除容易漏 |
| 不传导 | 会话开始时读一次 | 稳定可预测 | 中途改不生效 |
第二种的坑值得单独说。一份 Skill 从 old-name 改名为 new-name,如果增量更新只处理了新增,目录里会同时出现两个名字——旧名字还指向已经不存在的文件,或者读到缓存里的一份旧内容。表现出来是"我改了 Skill 但没生效,还多了个重复的"。
第一种没有这个问题,因为它是全量重取。代价可接受:目录本身不大。
9.1 变更的来源
变更不只来自人改文件。
| 来源 | 例子 | 传导方式 |
|---|---|---|
| 文件变更 | 新增、改名、删除 | 监视目录,失效缓存 |
| 作用范围变更 | 切换工作目录、重组会话 | 缓存键变化 |
| 提供方变更 | 插件加载、注册表更新 | 注册表失效事件 |
第二个来源容易被漏。会话中途切换工作目录后,适用哪些 Skill 会变——因为项目层那一批换了。如果缓存只按"上次扫描时间"做键,切换后读到的仍是旧结果。
9.2 缓存键的选择
| 键 | 正确性 | 命中率 |
|---|---|---|
| 空(不缓存) | 最高 | 最低 |
| 扫描时间 | 依赖文件系统时间戳精度 | 高 |
| 解析后的作用范围链 | 高 | 高 |
第三种是唯一能在"正确"与"命中"之间取得平衡的。思路是:缓存的内容只取决于"哪些根被扫描",而根由作用范围链决定,所以用它做键就能保证正确。
第一种在数量少时是合理选择——不缓存也就没有正确性问题。数量多时必须缓存,此时键的选择变成关键。
10. 版本与覆盖
部署时常见两种需求同时存在:内置一份默认 Skill,允许团队覆盖。
可行设计是"内置默认加团队覆盖,记录基线"。
- 覆盖记录基于哪个内置版本;
- 升级时保留团队的文本;
- 默认变更时在界面上标出"默认已更新"并显示差异;
- 团队可以选择保持、采用新默认、或编辑后保存;
- 没有覆盖的部分自动跟随新默认。
这套机制解决一个具体问题:升级时团队的自定义内容被悄悄覆盖。有了基线记录,系统知道哪部分是团队的、哪部分是默认的。
10.1 覆盖的三种状态
| 状态 | 升级时 | 界面标识 |
|---|---|---|
| 纯内置 | 直接跟随新默认 | 无 |
| 基于内置修改过 | 保留团队文本,标出默认已更新 | 显示差异 |
| 团队自建 | 不受升级影响 | 标为自建 |
第二种是唯一需要人决策的。系统知道哪部分是团队的,因此能给出准确的差异。
如果系统不记录基线,第二种会退化成第一种或第三种——要么升级时覆盖掉团队的修改,要么把团队修改过的当成自建而永远不更新。两种退化都在实践中出现过。
10.2 占位符替换
有的实现支持在正文里写占位符,加载时替换成实际值(例如项目名、当前分支)。
这个机制省去了为每个项目写一份的成本,但引入一类新错误:占位符拼错或变量缺失时,替换结果是一段残缺内容。
拒绝是唯一合理的处理,不能忽略。忽略会让 Skill 带着"当前分支是 "这种残缺内容被加载,模型会尝试理解这段残缺,而理解结果不可控。
保存规则里还有两条同类约束:未知字段直接拒绝(不静默忽略),模板占位符缺失也拒绝。两者都是为了不出现"保存成功了但行为不对"。
11. 生命周期
一份 Skill 写完之后会经历几个阶段,每个阶段的维护动作不同。
| 阶段 | 判定 | 动作 |
|---|---|---|
| 草稿 | 内容未定 | 加禁用字段,避免被误加载 |
| 试用 | 已能命中,完成标准待验证 | 说明保持保守,观察命中率 |
| 稳定 | 命中与完成都正常 | 定期检查正文是否与实现脱节 |
| 待修订 | 出现"按说明做但结果不对" | 对比正文与当前实现 |
| 废弃 | 对应流程已下线 | 移出目录,保留内容供查 |
第一阶段用控制字段控制可见性,不删文件,理由是草稿还需要被自己看到。
第四阶段最容易被忽略。判据是"按 Skill 做完之后有没有返工",不靠有没有人抱怨。返工率高说明正文与实际流程已经不一致。
第五阶段的关键是"移出目录但保留内容"。直接删掉会让历史任务失去引用;留在目录里又会让模型继续加载。控制字段里的"禁止模型调用"正好对应这个需求。
12. 什么时候不该写成 Skill
Skill 不是万能的。四种情况下它不合适。
硬约束。 权限、路径范围、必须成对的操作。这些拦不住——一份说明只能劝阻,代码能拦住。这类内容应当做成工具或运行时检查。
参数复杂。 嵌套对象、多字段联合校验、大量特殊字符。前面讲过,Skill 要靠命令行传参,转义规则比 JSON 复杂得多。参数复杂到要"写文件再读文件"时,说明形态选错了。
使用频率极高。 每次任务都要走的几步,加载正文的成本会反复支付。这类操作做成工具,一次定义长期使用。
不加载就会出错的内容。 这是最容易犯的错。把"改完代码要跑测试"从全局提示词移进 Skill,看起来省了每轮成本,但引入一个新风险:如果这份 Skill 在该任务里没被加载,模型就不会跑测试。
判断标准是:被移动的内容必须是"不加载也不会造成错误"的。"改完代码要跑测试"不满足,因为不跑就出错。它可以留在全局提示词,或者用程序约束(提交前强制跑测试)来保证。
反过来,从 Skill 往工具或全局提示词移动时,判据是"这段内容是否需要模型逐字阅读"。脚本需要执行,不需要逐字读;流程需要逐字读。
13. 失败模式
六类,各有症状与修法。
目录膨胀。 症状:目录涨到十几 KB,模型开始漏选。成因:什么都往里放。修法:把分支细节移到资源层,或拆成两份。
说明太宽。 症状:几乎每个任务都被加载,但多数时候无关。成因:说明写得太泛("处理文档""辅助开发")。修法:加排除条件,写产物名不写领域名。
正文与脚本不一致。 症状:说"运行某脚本并确保通过",而脚本已改行为。成因:脚本与正文分开维护。修法:把脚本的输出格式写进完成标准,让不一致能被发现。
压缩后不再加载。 症状:长任务里正文被摘要掉,后续步骤不再遵守。成因:加载后被当成普通消息,压缩时一起被摘要。修法:压缩后重新注入,或把关键约束提升到全局提示词。
改名残留。 症状:改名后目录出现新旧两个名字。成因:增量更新只处理新增。修法:改为全量重取。
手动专用却谁都调不到。 症状:模型不加载、用户命令里也没有。成因:两个禁用字段同时为真。修法:加跨运行时一致性校验。
第四类在多 Agent 与长会话场景里最常见,也最难发现——任务表面还在推进,只是不再遵守某些约定。
一个检测方式:在长任务中后期问一句"你现在遵守哪些约定",看它能否说出那条被压缩掉的。说不出来说明已经被摘要掉了。