Agent Skill 的 description 怎么写?275 个样本里 94.5% 漏了关键一句

0 阅读11分钟

Agent Skill 的 description 怎么写?275 个样本里 94.5% 漏了关键一句

更新时间:2026-10-22 | 本文所有数字都可复现,脚本和完整数据在文末

结论先说

  1. Agent Skill 被加载时,host 只读 description,不读 SKILL.md 正文。 正文写得再好,在「要不要加载」这一步是看不见的。
  2. 只写「我能做什么」,等于把所有语义相近的场景都变成候选。 结果是误激活,而且不报错。
  3. 解法只有一句:在 description 里写明「什么时候不该用」。 写在正文里拦不住,因为 host 决定加载时根本不读正文。
  4. 我扫了本机 275 个已安装 skill,只有 15 个(5.5%)写了这句。 厂商 curated 包是 21.1%,社区是 4.3%。
  5. 改这一句的成本约 1 分钟。 它减少的是一类特定故障:agent 安静地做错事,然后报告成功。

一、起因:一次镜像不同步

我的仓库里有一份 skill,存了两个位置:

plugins/<name>/skills/<skill>/   ← canonical,改这里
skills/<skill>/                  ← 镜像,skills.sh 和手动安装读这里

为什么两份?因为 Claude 插件市场要求 plugins/<name>/skills/<skill>/ 这个结构,而 npx skills 和手动 cp -r 的人读的是仓库根目录的 skills/。两边都得有。

于是就有了一个每次发版都会犯的错:改完 canonical,忘了同步镜像。

结果不是报错,是装了的人拿到旧版本,而且没人知道。这种 bug 最贵——它不报警,只是慢慢让所有人跑在不同的代码上。

我想把这个流程沉淀下来,让它变成一件不用记的事。这个决定后面引出了整篇文章。

二、host 是怎么决定要不要加载一个 skill 的

先明确机制:host 分两级加载,第一级能看到的只有 name 和 description。

以 Claude Code、Codex、Cursor 为例:

阶段host 做什么此时能看到的内容
1. 索引扫描所有已安装 skill 的 frontmattername、description
2. 匹配判断哪个 description 与当前对话相关同上,仅此两项
3. 加载命中后才读取 SKILL.md 正文正文、references/、scripts/

第 2 步是分水岭:正文在这一步不存在。

所以「我的 skill 写得很详细但不生效」,问题几乎都在第 2 步,不在正文。

三、只写「我能做什么」会发生什么

所有语义相近的场景都会成为候选,误激活不会报错,只会安静地做错事。

最直接的做法是对 AI 说「把我们同步镜像的流程写成一个 skill」,然后你会得到类似这样的东西:

  1. 检查 canonical 目录和镜像目录是否一致
  2. 如果不一致,将 canonical 的内容复制到镜像
  3. 验证同步结果

读起来没问题,但它不能执行。「检查是否一致」用什么检查?diff -r 吗?它会因为 .gitignore 里的文件报一堆噪音。「验证同步结果」验证到什么程度算通过?

你在教它做事,但没给它任何可执行的证据。

更贵的是边界问题。如果 description 只写「同步 skill 目录」,下面这些请求都会命中它:

用户可能说实际该路由到
"Sync my fork with upstream."git 操作,不是镜像同步
"Copy this directory to another drive."文件复制,不是发版流程
"Publish the skill to skills.sh."发布流程,不是镜像同步

第三种最危险。用户说要发布,agent 很勤快地去做了镜像同步,然后回复「已完成」。

看起来做了事,实际做错了事,而且不报错。 这类错误比「不会做」贵得多——不会做至少会停下来问你,做错了会安静地把结果交给你。

四、换成从证据里抽,不从记忆里编

可靠写法的共同点是:每一句都能追到一个真实执行过的命令和真实输出。

这个仓库里真实存在的东西是:

证据来源
scripts/sync_mirror.py --check 退出码 1 = 有漂移脚本实现
退出码 0 = 已同步脚本实现
第一次跑会创建镜像并打印源/目标路径本次会话输出
第二次跑打印 mirror is in sync本次会话输出
--check 在漂移时列出具体差异文件脚本实现

于是流程变成三步,每一步都有退出条件:

python scripts/sync_mirror.py --check     # 退出 0 或 1,1 时列出漂移文件
python scripts/sync_mirror.py             # 镜像
python scripts/sync_mirror.py --check     # 必须打印 "mirror is in sync"

写不出证据的步骤,就不写。 这是「从证据里抽」和「凭记忆描述」的分界线。

这个 skill 最后是 103 行、约 890 tokens。

五、275 个已安装 skill 的实测:只有 5.5% 写了边界

我写了个脚本,扫本机所有 host 目录下已安装的 SKILL.md,看 description 里有没有任何形式的负向触发(Do not use / Not for / Avoid using / Not intended for):

SKILL.md 文件数:     374
去重后唯一 skill:   275
有 description:     275
含负向触发:          15
负向触发率:         5.5%

374 个文件去重成 275 个,是因为同一个 skill 常被同时装进 .claude/skills、.agents/skills、.cursor/skills、.codex/skills。不去重会让装得最广的几个 skill 被反复计数,把结果带偏。

94.5% 的 skill,没有告诉 agent「什么时候不该用我」。

按来源拆开,差距明显:

来源数量写了负向触发比例
微软 Azure 系列(厂商 curated)19421.1%
社区与其他256114.3%

厂商包比社区规范约 5 倍。但这个「规范」也只是相对而言——即便精心维护的 Azure 包,也有 79% 的 skill 没写边界。

样本边界必须说清:这是一台 Windows 机器上的安装记录,主要构成是几个公开合集(Anthropic 官方示例、微软 Azure 包、superpowers、gstack)加零散安装,不是随机抽样,也不是全网普查。你在自己机器上重跑,数字会变。但 21.1% 对 4.3% 这个量级的差距,不太可能来自样本偶然。

六、description 怎么写:一个可直接套的模板

description 要装三件事:做什么、什么时候用、什么时候不用。

description: >-
  <动词开头,一句话说清做什么>.
  Use when <场景 1>, <场景 2>, <场景 3>.
  Do not use for <最容易被误判的相邻任务> (use <那个 skill> instead),
  or <另一种边界情况>.

逐行说明:

行作用判断标准
第一行说清职责动词开头,一句话,不含形容词
Use when正向触发场景具体到能分辨,不写「相关任务」
Do not use for负向触发写出你最怕被误用的那个场景

反例与正例:

# 反例:所有 sync 相关请求都会命中
description: Sync skill directories.

# 正例:镜像与发布被明确分开
description: >-
  Mirror a canonical skill directory into its published copy.
  Use when SKILL.md or references changed and a release tag is imminent.
  Do not use for publishing (use publish-skill instead),
  or for repositories that keep only one copy.

关键是最后那句。想不出边界,通常意味着这个 skill 的职责还不够窄。

七、怎么验证边界写对了:触发测试

至少要 3 条正向 + 3 条负向,每条都有明确的预期行为。

类型用户可能说预期
正向"The skill directory drifted again, mirror it."激活
正向"I changed references/, sync the published copy."激活
正向"About to tag a release, keep the mirror current."激活
负向"Sync my fork with upstream."不激活
负向"Copy this directory to another drive."不激活
负向"Publish the skill to skills.sh."不激活

每条都要有可判定的预期,不能写「表现正常」。负向测试的价值在于:没有它们,sync 和 mirror 这两个词会把所有东西都吸进来。

八、8 道门禁:那个 skill 的完整打分

写完之后我没有直接发布,而是拿一套评分卡逐项打分,每项 0/1/2 分,满分 16:

#门禁得分依据
1触发精度2/23 个正向触发 + 1 个排除项
2渐进披露2/2103 行,没有内联参考资料
3Token 预算2/2~890 tokens,远低于 5k 核心预算
4可执行性2/25 步,每步都有命令和退出条件
5证据锚定2/2来自 sync_mirror.py 和两次真实运行
6安全2/2不删源数据;只写镜像;无密钥、无网络安装
7可移植性2/2路径从仓库根发现,无绝对路径
8可证明性2/25 个正向 + 3 个负向触发;dry run 跑了两次

16/16,可以发。

但注意第 1 道和第 8 道门都在说同一件事——触发。这两道门才是大部分 skill 真正失败的地方,而不是正文。

九、这套流程在哪

上面提到的门禁、打分卡、触发测试模板和统计脚本,我整理成了一个开源仓库:

skill-forge — github.com/YaBoom/skil…

里面具体有什么:

内容路径用来做什么
Skill 本体plugins/skill-forge/skills/skill-forge/把已完成的工作流蒸馏成 skill
103 行完整示例examples/sync-skill-mirror/本文第四节引用的那个 skill 的全部源码
8 道门禁打分记录docs/dry-run-scorecard.md16/16 的逐项依据,可对照
统计脚本scripts/survey_triggers.py复现 275 / 15 / 5.5% 这三个数
确定性检查脚本scripts/skill_audit.py检查行数、token 估算、负向触发缺失、绝对路径
英文版长文docs/boundary.html完整数据集和门禁表,可直接引用

安装(Claude Code):

/plugin marketplace add YaBoom/skill-forge
/plugin install skill-forge@skill-forge-marketplace

任意 Agent Skills host:

npx skills add YaBoom/skill-forge -s skill-forge -g

MIT 协议。不想安装也能用:统计脚本和检查脚本可以直接 clone 下来跑,本文所有数字都能这样复核。

十、常见问题(FAQ)

Q1:什么是 Agent Skill?

Agent Skill 是一个目录,核心是 SKILL.md:frontmatter 里写 name 和 description,正文写步骤,旁边可带 references/、scripts/、assets/。Claude Code、Codex、Cursor 这类 host 会读取它,作为可复用的工作方法加载进上下文。

Q2:Agent Skill 和 Prompt、MCP、Tool 有什么区别?

类型是什么是否落盘加载方式
Prompt一次性对话指令否每轮手动提供
Tool / MCP可调用的能力接口服务端定义按需调用
Skill可复用的工作方法与判断标准是,SKILL.md按 description 匹配后加载

三者不冲突。一个 skill 里可以调用 MCP 工具,它负责的是「什么时候调、按什么顺序调、调完怎么验证」。

Q3:description 写多长合适?

长到能装下三件事:做什么、什么时候用、什么时候不用。没有固定字数限制,但第三件事通常需要一整句,这也是带负向触发的 description 普遍比不带的更长的原因。

Q4:为什么把「不要用我」写在正文里没用?

因为 host 决定是否加载时只读 description,正文在这一步不可见。写在正文里的排除说明,只有在该 skill 已经被加载之后才会被读到——那时已经误激活了。

Q5:怎么写触发测试?

两组:正向 3 条以上(用户会怎么说法,应该激活),负向 1 条以上、最好 3 条(用户会怎么说法,但不该激活)。每条都要有明确预期,不能写「表现正常」。

Q6:有没有工具能自动检查?

有一个确定性检查脚本,能查出负向触发缺失、绝对路径、token 超预算这类可判定问题:

python plugins/skill-forge/skills/skill-forge/scripts/skill_audit.py \
  plugins/skill-forge/skills/skill-forge --repo .

退出码 0 表示没有阻断性问题。需要人工判断的部分(触发精度、可证明性)仍需自己看。

Q7:5.5% 这个数字怎么来的?

扫描本机所有 host 目录下的 SKILL.md,按目录名去重,检查 description 是否包含 Do not use、Not for、Avoid using、Not intended for。脚本公开,可在自己机器上重跑,数字会不同。

Q8:skill 应该写多长?

本文那个例子是 103 行、约 890 tokens。经验值是把核心 SKILL.md 控制在 5k tokens 以内,超出的内容拆到 references/,正文只留一句「需要时读哪个文件」——这叫渐进披露,避免每次加载都吃掉全部上下文。

十一、4 条可以立刻改的

  1. 在 description 末尾加一句 Do not use for ...。 具体到能分辨的场景,不写「不适用于无关任务」。
  2. 至少写一条负向触发测试。 想不出反向场景,说明职责还不够窄。
  3. 给每一步写死完成条件。 不是「确认同步成功」,而是「--check 打印 mirror is in sync」。
  4. 数字优先于形容词。 ~890 tokens 比「很轻量」有用,退出码 1 比「检查失败」有用。

数据来源

内容位置
统计脚本scripts/survey_triggers.py
8 道门禁打分记录docs/dry-run-scorecard.md
103 行完整示例examples/sync-skill-mirror/
仓库github.com/YaBoom/skil…
英文版(含完整数据集与门禁表)yaboom.github.io/skill-forge…

本文所有数字均来自上述公开文件,可逐条核对。样本为单机安装记录,非随机抽样,重跑结果会变化。


如果只改一件事:改 description 里的负向触发。它便宜、见效快,而且是唯一一条能直接减少 agent「做错事」的改动。