Skill和模型的三次见面

69 阅读8分钟

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 做结构体检:

  1. 只读描述,预判触发:只看那一行,说出它该在什么任务里被触发,再写出一个"绝不该触发"的反例。写不出反例,就是缺陷。(验第一次见面)
  2. 数正文行数:离 500 行多远;超出的部分,哪段该降级成第三次见面的文件。(验第二次见面)
  3. 列子文件与正文的点名清单:哪些文件被按名引用,哪些零提及。(验第三层的门)
  4. 沿引用链 grep:零提及的文件,从正文点名的文件出发逐环找,走不到的才是死文件。两个注意:死文件之间的互引不算数,得从正文点得到的文件起算;脚本里提到的文件名也不算,脚本源码不进上下文,模型看不见它引用了谁。(验第三层的链)
加载层镜头步骤在验什么
装好那刻(常驻清单)第 1 步"何时用我"有没有说进常驻那一行
触发那刻(正文)第 2 步正文是否在替第三层扛细节
用到那刻(捆绑文件)第 3、4 步每个文件是否沿模型可见的链路可达
①读描述·预判触发 只看那一行,写出"绝不该触发"的反例
↓
②数正文行数 离 500 行多远,超出的降级到第三次见面
↓
③列子文件点名清单 哪些被正文按名引用,哪些零提及
↓
④沿引用链 grep 从点名文件起算,走不到的才是死文件

动手试一次:输入是你自己的任意一个 skill 目录,执行就是上面四步,预期产出一张四行体检单,每行一个判定加一个证据出处(行数、点名行号、grep 命中)。

带走三样

  1. 三次见面模型:装好那刻,只有名字和描述常驻;触发那刻,正文整份进;用到那刻,文档才读,脚本只执行不加载。
  2. 四个预算数字:清单约占上下文 1%;描述 1536 字符硬截断;正文 500 行是建议;压缩后重附每个 skill 5000 token、合计 25000。
  3. 死活判定:未点名 ≠ 不可达,沿引用链走得到才算活;无人指向的文件,0 上下文也 0 价值。

临走三问

  1. 同事把 300 行规范全文贴进 CLAUDE.md,你把同样的内容做成某个 skill 的参考文件。今天下午你们各自只改了一个错别字。这两份内容,分别花了多少上下文?
  2. 你在 skill 目录里放了一个 400 行的数据转换脚本,同事担心"这玩意会不会把上下文撑爆"。一句话跟他讲清楚脚本和参考文档在这件事上的区别。
  3. 你评审一个 skill,发现 rules.md 在 SKILL.md 正文里零提及,但正文点名的 orchestrator.md 里有 6 处指向它。它是死文件吗?你怎么验证?

三道都答对了,这一课才算真的带走。