系列第二篇。知道 Skill 是什么,和真的把它写对,中间差得其实很远。很多人第一次写 Skill,文件看起来像模像样:有 frontmatter、有步骤、有格式说明,甚至还加了几个卫星文件。结果放进工具里一跑,不触发,或者触发了也不按你想的来。
我自己后来回头看,问题通常不在"写得不够认真",而在于一开始用错了写法。有人把 Skill 写成百科全书,有人把它写成项目说明书,也有人把它写成一堆正确但没有立场的建议。这篇想解决的不是"怎么把文档写漂亮",而是:怎么把它写成一个 Agent 真会照着走的东西。
〇、先建立一个判断标准
写 Skill 之前先想清楚一件事:怎么算"好"?
一个好的 Skill 满足三个条件:
- 触发得准:该用它的时候,Agent 能想起来用它;不该用的时候不会误触发。
- 执行得稳:Agent 照着它做,十次有九次走的是同一条路、得到同样质量的结果。
- 活得久:项目代码改了、文件挪了,Skill 不用跟着改。
下面所有的技巧,都是在为这三个目标服务。
一、解剖一个 Skill:它只有两个部分
任何 Skill 本质上只有两部分:门牌 和正文。
markdown
---
name: pdf-extract
description: 从 PDF 提取文本和表格、填写表单、合并文档。当用户提到 PDF、表单或文档提取时使用。
---
↑↑↑ 这是门牌(frontmatter)
下面全是正文......
门牌(description)是整个 Skill 最重要的一句话
这里有个新手必须知道的机制:Agent 平时不读你的 Skill 正文。 它启动时只把所有已安装 Skill 的 description 装进脑子。用户提需求时,它扫一眼这些 description,觉得哪个匹配,才去读那个 Skill 的全文。
这个机制叫渐进式披露,目的是省上下文------你装了 50 个 Skill,不能让 50 篇全文天天占着 Agent 的脑容量。
所以 description 是一份合同:它是 Agent 决定"用不用你"的唯一依据。写法有固定公式:
csharp
第一句:它能干什么。
第二句:Use when ...(什么时候用:具体的关键词、场景、文件类型)。
对比一下:
坏:帮助处理文档。
好:从 PDF 提取文本和表格、填写表单、合并文档。
当用户提到 PDF、表单或文档提取时使用。
坏的那个,Agent 没有任何办法把它和其他文档类 Skill 区分开------等于没装。很多人的 Skill "不生效",问题九成出在 description 太模糊。
正文:把"做什么"和"背景知识"分开写
正文的最佳实践是双层结构:
- 行动层 :一步一步的指令,全部用祈使句------"先做 X"、"然后检查 Y"、"在 Z 完成前不要继续"。这是 Agent 要执行的部分。
- 参考层:背景知识、格式说明、依赖声明,用陈述句。这是 Agent 执行时需要查阅的部分。
为什么要分开?因为混在一起时,Agent 会把背景知识误当成指令去执行,或者把关键指令当成背景扫过去。人类读混排的文档也会犯同样的错------AI 只是放大了这个问题。
一个简洁的双层模板:
markdown
---
name: my-skill
description: 一句话说清能力。Use when [具体触发条件]。
---
## 怎么做(行动层,祈使句)
**第一步 ------ 收集需求**
问用户 X、Y、Z 三个问题,一次问一个。
**第二步 ------ 执行**
...
在测试全绿之前,不要进入第三步。 ← 关卡(Gate)
**第三步 ------ 验收**
...
## 参考资料(参考层,陈述句)
- 输出格式见 FORMAT.md
- 本 Skill 依赖项目根目录存在 CONTEXT.md,没有则先提示用户创建
二、我最常看到的三个写坏方式
1. 一上来就写太长
这不是洁癖,是机制约束。Skill 全文会被塞进 Agent 的上下文窗口,越长越贵、越长越容易被"稀释"(重要指令淹没在废话里)。(写多了就有自己优化的经验了,不要怕写不出优雅的 skill)。
内容超了怎么办?拆卫星文件:
perl
my-skill/
├── SKILL.md # 100 行以内的主入口(必须)
├── FORMAT.md # 输出格式详细说明(需要时才读)
├── EXAMPLES.md # 示例(需要时才读)
└── scripts/ # 确定性操作的脚本(需要时才执行)
SKILL.md 里只留一句引用:"输出格式见 FORMAT.md"。Agent 走到那一步、真的需要格式时,才会去读它------这是渐进式披露在 Skill 内部的第二次应用。
但注意:默认不拆。只有满足以下任一条件才拆卫星文件:
- 内容确实超出 100 行预算
- 内容属于明显不同的领域(比如财务 schema vs 销售 schema)
- 某块内容很少被用到(高级功能)
新手最常见的错误是反过来:为一个 30 行就能说清的 Skill 建了五个文件。单个精炼的 SKILL.md 是简单技能的正确形态。
2. 写得太客气,像没说一样
对比两种写法:
arduino
温和:"你可以考虑先写测试,也可以先写实现,看情况。"
有立场:"禁止先写完所有测试再写所有实现(这叫水平切片,是反模式)。
必须一个测试 → 一段实现 → 下一个测试,垂直推进。"
模型在模糊指令下会回归自己的默认行为------而你写 Skill 的初衷恰恰是要矫正默认行为。所以好的 Skill 都很"凶":
- 点名反模式:明确写出"不要做什么",最好给反模式起个名字("水平切片"),名字让概念可以被引用和识别
- 设关卡(Gate):用二元条件卡住流程------"复现不出来之前,禁止改代码"。注意是"禁止",不是"建议尽量"
- 给每个立场配理由:一句"为什么"能显著提高模型的遵守率,因为它能把规则泛化到你没列举的情况
3. 写死路径,文件一挪就废
arduino
脆弱:"打开 src/utils/triage.ts,修改第 42 行的 handleLabel 函数"
耐久:"找到处理 triage 标签的函数(搜索关键词 handleLabel),
它接受一个 label 字符串并返回新状态"
文件会改名,行号几天就过期,但接口、行为、类型签名能活很久。判断标准很简单:如果明天有人把项目文件全部重新组织一遍,你的 Skill 还成立吗?
同理:Skill 里不要写绝对路径(你的 Skill 可能被装到别人机器的不同位置)、不要写"当前日期"这种时效信息。
这三个问题是我最先看的。除此之外,还有两条很常用但不一定每次都展开讲的判断:
- 确定性的事交给脚本,不要交给模型。 如果只是校验 JSON、批量重命名文件、格式转换,就写进
scripts/,让 Agent 去跑。 - 依赖要分清硬软。 没有它就一定会出错的,写成硬依赖;有了更好、没有也能跑的,写成软依赖。
这两条我平时会在改第二版、第三版的时候补进去。第一版 Skill 能跑起来,比一开始就把所有规则写全更重要。
三、怎么划分:一个 Skill 该多大?
这是被问得最多的问题。答案借用 Unix 哲学:
一个 Skill 只做一件事,把它做好。
按"动作"划分,不按"领域"划分
bash
按领域:一个巨大的 "前端开发 Skill"(里面塞了组件规范、测试、部署、性能优化......)
按动作:/tdd(测试驱动开发)、/diagnose(诊断 bug)、/triage(分类 issue)
判断粒度是否合适的试金石:你能用一个动词短语命名它吗?
mattpocock/skills 里的技能全部通过这个测试:tdd、triage、diagnose、to-issues(拆成 issue)、zoom-out(拉远视角)、grill-me(拷问我)。每个名字就是一个动作。
我平时怎么判断该拆还是该并
如果 description 里老出现"和""以及",而且两边的触发场景根本不是一回事,我通常就会拆。
如果正文已经长成两条几乎不相交的流程分支,我也会拆。因为这时 Agent 读起来已经不是"一个动作有几步",而像"两件事硬塞在一个文件里"。
反过来,如果两个 Skill 总是一起被触发,其中一个还只有十来行、全部作用只是给另一个铺垫,那大概率是拆过头了,应该并回去。
让 Skill 之间能"接力"------可组合性
好的 Skill 库不是孤岛集合,而是工作流网络:A 的输出刻意设计成 B 的输入。
mattpocock 的技能链是个绝佳示范:
bash
/grill-me(拷问需求)→ /to-prd(生成 PRD)→ /to-issues(拆成 issue)
→ /tdd(逐个实现)→ /diagnose(出 bug 时诊断)
每个技能独立可用,串起来就是一条完整的工程流水线。实现可组合的手法很朴素:
- 在 A 的文档里写明"本技能的输出可直接交给 /B 消费"
- 共享格式文件:两个 Skill 引用同一个 FORMAT.md,而不是各自定义一遍
四、Agent(子代理)的划分:什么时候不该用 Skill,该用 Agent?
Skill 和子 Agent 经常被混为一谈,其实分工很清晰:
| Skill | 子 Agent | |
|---|---|---|
| 本质 | 一份说明书 | 一个独立工人(带独立上下文) |
| 改变什么 | 当前 Agent 的行为方式 | 任务的执行结构 |
| 成本 | 几乎为零(读一个文件) | 高(新开一个会话) |
三种情况值得派子 Agent:
- 脏活隔离:要读 200 个文件做调研,但主对话只需要三段结论。派子 Agent 去读,免得撑爆主对话的上下文。
- 并行:几个互不依赖的任务(配合 worktree 隔离,见第一篇)。
- 角色对立:审查者不能是写代码的本人。Code review、安全审计要用独立 Agent,否则就是"自己改自己的作业"。
反过来,这些情况不要用子 Agent:任务只有两三步、需要和用户来回对话、需要主对话的完整历史。开销不值得。
最佳实践是把两者结合:用 Skill 来定义"什么时候派子 Agent、给它什么指令"。Skill 是剧本,子 Agent 是按剧本上场的演员。
五、我现在会怎么从零写一个 Skill
如果让我今天从头写一个新 Skill,我通常不会想着"把完整方法论全部落进去",而是先按下面这个顺序做:
第 1 步 ------ 回答四个问题
- 这个 Skill 完成什么任务?(一句话,必须是动词短语)
- 什么应该触发它?(关键词、场景、文件类型------这直接变成 description 的后半句)
- 它属于哪类?(工程类 / 生产力类 / 杂项------影响你对它的质量要求)
- 需要脚本吗?还是纯指令就够?
第 2 步 ------ 写 description
先写门牌再写正文。如果 description 写不清,说明你自己还没想清楚这个 Skill 的边界------回到第 1 步。
第 3 步 ------ 写行动层
祈使句,分阶段,阶段之间放关卡。问自己:模型最可能在哪一步偷懒抄近路?在那里设一个"禁止"。
第 4 步 ------ 写参考层 + 按需拆卫星文件
依赖声明(硬/软)、格式说明、与其他 Skill 的组合关系。超过 100 行才拆。
第 5 步 ------ 跑质量检查清单
- description 包含 "Use when..." 触发条件
- SKILL.md 在 100 行以内
- 行动层全是祈使句,关卡是二元的("禁止 X"而非"尽量避免 X")
- 没有文件路径、行号、时效性信息
- 反模式有名字、立场有理由
- 卫星文件都被 SKILL.md 引用了(没有"孤儿文件")
第 6 步 ------ 实测和迭代
开一个全新会话(避免上下文污染),用自然语言提一个应该触发它的需求。观察:触发了吗?步骤走全了吗?关卡卡住了吗?没有就回去改 description 或加关卡。
Skill 是养出来的,不是一次写成的。 每次 Agent 在某个 Skill 上跑偏,都是一次免费的需求反馈------把跑偏的原因写回 Skill 里,它就比昨天更稳一点,所以不要吝啬对你的 Skill 库进行修改, 调整。
六、新手最常踩的五个坑
- 把 Skill 当百科全书写:塞满所有相关知识。→ Skill 是 SOP 不是教材,只写"做这件事需要的"。
- description 写成自夸:"强大的 PDF 处理能力" → Agent 不吃营销话术,它要的是触发条件。
- 全是建议没有禁令:模型在模糊地带永远选省事的路。该禁止就禁止。
- 在 Skill 里硬编码自己机器的路径:换台机器就废。
- 写完从来不测:不开新会话实测的 Skill,等于没写。
我自己现在最在意的两件事
写 Skill 的时候,我现在最先盯的不是"格式标准不标准",而是两件更实际的事:
- 它会不会被想起来。 也就是 description 到底写得准不准。
- 它会不会在关键地方拦住模型。 也就是 Gate 到底设得狠不狠。
很多看起来很专业的 Skill,问题其实都出在这两处。要么 description 太虚,像没装一样;要么正文太温和,模型一路顺着自己的惯性就滑过去了。
写在最后
写 Skill 这件事,对我来说越来越像带新人。你不是在写一篇"解释我多懂"的文档,而是在写一份别人真能照着做的手册。区别只在于,现在这个"别人"是 Agent,而且它会在每次会话里真的去执行你写下来的东西。
下一篇我不打算继续讲抽象原则,而是直接拆一份已经被很多人反复验证过的技能库:从 mattpocock/skills 我们能学到什么。