skill 装好了,不等于 skill 被读了。一个 skill 一辈子只和模型见三次面:装好那刻、触发那刻、用到某个文件那刻。三套规矩各不相同。把它们摸清,"怎么写 skill"才从手感变成工程。
按惯例声明:文中"代码评审 skill""E2E 测试 skill"是我的自研 skill,名字做了脱敏;tdd 是公开技能,原样保留。案例数字来自本机实测,预算数字出自官方文档。
我自己手搓过一批 skill,开发的、测试的、运行的、审查的都有,迭代了很多版,有的效果不是很好。这个系列想把"效果不好"变成可以诊断的事,这是第一篇:先搞清楚 skill 到底是怎么被模型"看"见的。
上下文是唯一的硬约束
模型的上下文按 token 计费,而且每一轮对话都在付。所以"见面"不是修辞:见一次面,占一份上下文;常常见面,就常常付费。后面三套见面规矩,全部从这一条推出来。
在 skill 出现之前,让模型记住一堆规矩有两条路,各撞一堵墙。
一条路是全写进系统提示(CLAUDE.md、自定义指令)。好处是模型一定看得见;代价是每一轮对话都付全额,哪怕这次任务只是改个错别字。300 行规范贴进去,就是 300 行的税,次次交。
另一条是封装成工具或 MCP 服务。调用精确,不占叙述性上下文;但工具签名只装得下"能做什么",装不下工作流知识:什么时候该用,按什么步骤,做到什么程度算好。这些只能写成话,而话没有地方放。
skill 卡在中间。Anthropic 把它定义为"组织成文件夹的指令、脚本和资源":知识以文档形式存在,按需进上下文;确定性操作写成脚本,执行而不占上下文。官方的比喻是,给 agent 写 skill 像给新员工准备入职手册:手册常备,但没人把它背下来。
| 第一次见面 · 装好那刻 名字和描述常驻系统提示,清单约占上下文 1% |
|---|
| ↓ |
| 第二次见面 · 触发那刻 SKILL.md 正文整份载入 |
| ↓ |
| 第三次见面 · 用到那刻 捆绑文件用到才读,脚本只执行不加载 |
第一次见面:装好那刻
装好的那一刻,模型只读两样东西:frontmatter 里的名字和描述。这两行从此常驻系统提示,你后面十轮对话里它都在场。
这份常驻不免费,但也不贵:所有装好的 skill 清单加起来,约占模型上下文的 1%。超出预算时,最少被调用的 skill 先被丢掉描述,你的 skill 就这么"装死"了,而且不报错。
描述还有一条硬线:超过 1536 字符,清单里直接截断。这条预算动真格,超了就砍,没有警告。
所以第一次见面的规矩只有一条:用最少的字,把"什么时候用我"说清楚。反面教材是我那个 E2E 测试 skill:描述 253 字,前大半是"用户等价红线""主辅双轨""四区断言"这类行话,真正说触发范围的那句缩在句尾。模型扫清单是扫,不是精读,埋在句尾的触发词约等于没有。它后来怎么改,第二篇专门讲。
第二次见面:触发那刻
某个任务真的和描述对上了,模型把整份 SKILL.md 正文载入上下文。第二次见面是一次性的:整份进,不拆页。
正文的官方建议是 500 行以内。是建议,不是硬执行。机器只截断描述,正文超载的惩罚是间接的:塞得越满,和当前任务无关的部分越多,模型真正照做的比例越低。
正文该装什么?流程、规则、判断标准,凡是做这件事时必须知道的。不该装什么?细节的细节。大段参考规范、成套示例、模板全集,这些属于第三次见面。
第三次见面:用到那刻
SKILL.md 正文里可以按名引用同目录的其他文件:参考文档、模板、脚本。这些文件不在装好那刻进上下文,也不跟正文整份走。模型用到哪份,才读哪份。这就是"目录、章节、附录"的类比:附录再厚,平时不占书包。
脚本在这个层里待遇不同:被执行,而非被加载。模型决定跑一个脚本时,拿走的是运行结果,脚本源码本身不进上下文。一个 300 行的转换脚本,对上下文的常驻贡献是零。
这里有个容易翻车的判定:正文里没点名的文件,是死文件吗?我拿两个自己的 skill 逐环验证过。代码评审 skill 和 E2E 测试 skill,正文零提及的文件一共 11 个;顺着引用链走,正文点名编排器,编排器点名各阶段文件,阶段文件再点名参考文档,11 个全部可达。真正死的只有一个 readme.txt:没有任何文本指向它,模型没有理由打开它,0 上下文,也 0 价值。
所以死活的判定标准不是"正文有没有点名",而是"模型从它看得见的链路能不能走到"。逐环 grep 一遍就有答案。
一本实账:tdd
拿公开的 tdd skill 算一笔账:
- 第一次见面:frontmatter 描述,约 160 字符。常驻,每轮在场。
- 第二次见面:SKILL.md 正文,38 行 / 3.6 KB。触发后整份载入。
- 第三次见面:tests.md + mocking.md,3.8 KB。正文引用,用到才读。
整个 skill 7.4 KB,常驻的只有那 160 字,约 2%。其余 98% 全部处于"不用不付钱"的状态。
账不止这一本,连复习都有预算:长对话被自动压缩后,系统只把最近用过的 skill 正文前 5000 token 附回上下文,且所有 skill 合计不超过 25000。
渐进式披露听起来像设计理念,落到账本上就是几个除法。
四步镜头:把模型变成体检单
把上面的规矩折成一套可执行的检查,四步,给任何一个 skill 做结构体检:
- 只读描述,预判触发:只看那一行,说出它该在什么任务里被触发,再写出一个"绝不该触发"的反例。写不出反例,就是缺陷。(验第一次见面)
- 数正文行数:离 500 行多远;超出的部分,哪段该降级成第三次见面的文件。(验第二次见面)
- 列子文件与正文的点名清单:哪些文件被按名引用,哪些零提及。(验第三层的门)
- 沿引用链 grep:零提及的文件,从正文点名的文件出发逐环找,走不到的才是死文件。两个注意:死文件之间的互引不算数,得从正文点得到的文件起算;脚本里提到的文件名也不算,脚本源码不进上下文,模型看不见它引用了谁。(验第三层的链)
| 加载层 | 镜头步骤 | 在验什么 |
|---|---|---|
| 装好那刻(常驻清单) | 第 1 步 | "何时用我"有没有说进常驻那一行 |
| 触发那刻(正文) | 第 2 步 | 正文是否在替第三层扛细节 |
| 用到那刻(捆绑文件) | 第 3、4 步 | 每个文件是否沿模型可见的链路可达 |
| ① | 读描述·预判触发 只看那一行,写出"绝不该触发"的反例 |
|---|---|
| ↓ | |
| ② | 数正文行数 离 500 行多远,超出的降级到第三次见面 |
| ↓ | |
| ③ | 列子文件点名清单 哪些被正文按名引用,哪些零提及 |
| ↓ | |
| ④ | 沿引用链 grep 从点名文件起算,走不到的才是死文件 |
动手试一次:输入是你自己的任意一个 skill 目录,执行就是上面四步,预期产出一张四行体检单,每行一个判定加一个证据出处(行数、点名行号、grep 命中)。
带走三样
- 三次见面模型:装好那刻,只有名字和描述常驻;触发那刻,正文整份进;用到那刻,文档才读,脚本只执行不加载。
- 四个预算数字:清单约占上下文 1%;描述 1536 字符硬截断;正文 500 行是建议;压缩后重附每个 skill 5000 token、合计 25000。
- 死活判定:未点名 ≠ 不可达,沿引用链走得到才算活;无人指向的文件,0 上下文也 0 价值。
临走三问
- 同事把 300 行规范全文贴进 CLAUDE.md,你把同样的内容做成某个 skill 的参考文件。今天下午你们各自只改了一个错别字。这两份内容,分别花了多少上下文?
- 你在 skill 目录里放了一个 400 行的数据转换脚本,同事担心"这玩意会不会把上下文撑爆"。一句话跟他讲清楚脚本和参考文档在这件事上的区别。
- 你评审一个 skill,发现 rules.md 在 SKILL.md 正文里零提及,但正文点名的 orchestrator.md 里有 6 处指向它。它是死文件吗?你怎么验证?
三道都答对了,这一课才算真的带走。