Agent Skill 编写最佳实践: 上下文预算、触发工程与可证伪的验证
Note
为什么一个精心写好的 skill 会从来不被触发, 而一个写得普通的 skill 却总被误触发? 决定这件事的, 甚至不是它的正文. 更反直觉的是: 命名不合法、描述缺失、正文超预算 —— 这些失败没有一个会报错. skill 只是安静地从目录里消失, 而模型既看不到错误, 也分不清"不存在"与"写错了". 那么, 一份让模型真正用得上的 skill, 究竟该把什么写在哪里?
0x00 背景
大多数人写 skill 的方式是这样的: 把知道的都写进去, 写长了就删一点, 看起来完整就交差. 于是最常见的结果是两种 —— 要么这个 skill 永远不会被触发, 要么它把上下文窗口占满而收益极低.
这两种失败有一个共同的、隐蔽的特征: 它们都不会报错. 名字写错、触发条件写在正文里、正文长到超出预算 —— 没有任何机制会告诉你. skill 只是安静地不工作, 或者安静地拖慢每一次对话.
这篇文章假设你已经知道 skill 是什么, 直接谈怎么做才是对的. 它回答四个进阶问题:
- 一份 skill 的上下文预算到底是多少, 超了该压还是该拆?
- 为什么精心写的 skill 从来不被触发? 触发是由什么决定的?
- 内容该放在哪一层? 有没有可判定的分工标准?
- 怎么证明你的 skill 真的有用, 而不是自我感觉良好?
0x01 核心结论
-
渐进式披露不是设计风格, 是有量化预算的工程约束. 元数据约 100 词、正文少于 500 行 / 5000 token、第三级基本无上限. 这三个数字是所有决策的锚点.
-
超长不是"压缩"问题, 是"分层"问题. 有文件系统时, 一个 skill 能携带的上下文总量实际上没有上限 —— 因为脚本可以被执行而不必读进上下文, 参考资料只在需要时才加载. 所以正确动作是按"是否总是需要"把内容分到第二级和第三级, 而不是把第二级从 15 万 token 压到 1.5 万. 后者是错的: 它没有回答"这些字该不该在这一层".
-
触发完全由
description决定, 而且"什么时候用"必须写在描述里. 名称和描述是模型唯一用来判断要不要加载的输入. 正文只在触发之后才加载 —— 所以正文里的 "When to Use" 段永远不会被读到. 新手最自然的写法恰好就是这一种, 结果 skill 永不触发. -
"简洁"的判据不是字数, 是提问. 默认假设是模型已经足够聪明; 每写一段都要问"模型真的需要这段解释吗""这段的 token 成本值得吗". 能被强模型自行推导的内容, 其 token 成本是纯亏损 —— 因为它挤占的是公共资源.
-
分工有一个可判定的标准: 自由度. 脆弱易错、必须一致的流程 → 低自由度(可执行脚本); 多种做法都行、依赖判断的 → 高自由度(文字指引). 这把"什么该外化"从审美问题变成了工程判断.
-
"什么时候用"和"怎么做"必须分居两处: 前者只能进描述, 后者才进正文. 混在一起是最高频的结构性错误.
-
验证必须可证伪. 让被测 agent 不知道自己在被测, 传原始产物而不是你的结论, 不给预期答案. 如果只有在被测 agent 看到泄漏的上下文时才能成功, 那就不能相信这个结果. 这是"skill 变好了"唯一站得住的判据.
-
规则必须可执行, 不能靠记. 因为上述所有失败的特征都是不报错. 把预算和字段契约变成校验脚本, 是唯一能在实践中守住这条线的方式.
0x02 关键细节
一、上下文预算: 三个数字决定一切
官方规范把 skill 的加载分成三级. 理解这张表的重点不是"分三层", 而是每层有预算, 且预算的约束力不同:
| 级别 | 内容 | 何时进上下文 | 预算 | 约束力 |
|---|---|---|---|---|
| 第一级 | name + description | 所有已安装 skill 在启动时预载 | 约 100 词 | 应极力压缩 |
| 第二级 | SKILL.md 正文 | 触发时整篇读入 | 少于 5000 token 且少于 500 行 | 硬约束 |
| 第三级 | references/ scripts/ assets/ | 按需读取 | 基本无上限 | 无 |
第一级的成本是乘性的. 它不是一个 skill 花 100 词, 而是每个装上的 skill 都要花 100 词, 而且它常驻在每一次对话里. 所以描述要精确, 不要堆砌.
第二级的成本是整篇的. 这一点经常被忽略: 模型不会"只读相关章节" —— 一旦触发, 整份正文都会进上下文. 所以正文里放"只在 5% 场景才需要"的内容, 等于让另外 95% 的场景为它付钱. 这正是 500 行预算存在的理由.
第三级之所以无上限, 是因为它的费用是按需支付的. 官方原话说得很清楚: 有文件系统和代码执行工具的 agent, 在处理具体任务时不需要把整个 skill 读进上下文窗口, 所以一个 skill 能携带的上下文总量实际上没有上限.
于是拆分成为唯一正确的解法. 判据很直白: 这段内容是不是每次触发都需要?
- 是 → 留在正文
- 否 → 外移到
references/, 并在正文里说明什么时候去读它
一个提升拆分收益的关键洞察: 如果第三级里几个文件是互斥场景(比如云部署里的多家云文档), 那么拆开能真正降低单次消耗 —— 因为模型只会加载用户实际选中的那一份. 反之, 如果拆出来的文件每次都要一起读, 那拆分只增加了跳转成本.
二、触发工程: 为什么你的 skill 从不被触发
这是实践中最高的失败率, 也是最少被理解的.
机制: 启动时, 所有已安装 skill 的 name 和 description 被预载进系统提示词. 模型只看这两个字段决定当前任务该不该加载某个 skill. 正文在触发之前完全不可见.
推论一: "什么时候用"的信息只能写在描述里.
官方给出的强规则是: 把所有"什么时候用"的信息放进描述, 不要放在正文. 理由就是上面的机制 —— 正文里的触发信息永远不会被读到.
反模式 A(最高频): 触发条件写在正文
---
name: my-skill
description: 帮我处理文档
---
# My Skill
## When to Use This Skill <- 永远不会被读到
当用户需要处理 docx 时使用...
模型看到的是 帮我处理文档. 它没有任何依据判断"用户现在要不要用这个 skill", 于是永远不触发.
反模式 B: 描述只写"做什么", 不写"什么时候用"
同样是静默失效. 不是被拒绝, 而是从不出现.
正确构造公式:
[做什么: 具体的、含关键名词的能力清单] + [什么时候用: 触发场景 + 用户可能说的原话]
对照示例:
# 差: 太短, 没有触发信息, 没有关键词
description: Helps with PDFs.
# 好: 能力具体, 含关键词, 且明确列出触发场景
description: Extracts text and tables from PDF files, fills PDF forms, and
merges multiple PDFs. Use when working with PDF documents or when the user
mentions PDFs, forms, or document extraction.
推论二: 描述里的"用户可能说的原话"是真正的触发器.
模型是在做语义匹配, 所以描述里包含用户实际会用的词至关重要. 写抽象名词(如"文档处理")不如写具体名词(如 "docx"、"表单"、"合并")加上真实说法.
推论三: 描述有长度上限, 但不要浪费.
规范限制描述在 1024 字符内. 实践中常见的问题反而是太短, 而不是太长 —— 短描述省下的几十个 token, 换来的是 skill 完全不工作, 性价比极差.
三、内容分层的判据: 自由度
"什么该写进正文、什么该写成脚本", 官方给了一个很好用的判断框架 —— 按任务的脆弱程度决定具体程度.
打个比方: 窄桥加悬崖需要护栏(低自由度), 开阔原野允许多条路(高自由度).
| 自由度 | 形式 | 适用场景 |
|---|---|---|
| 高 | 纯文字指令 | 多种做法都可行、依赖上下文判断、靠启发式 |
| 中 | 伪代码或带参数的脚本 | 有偏好做法、允许一定变化、配置影响行为 |
| 低 | 具体脚本、极少参数 | 操作脆弱易错、必须一致、必须按固定顺序 |
为什么这个框架有用: 它把"要不要写脚本"从个人偏好变成了风险判断. 如果一个步骤做错代价高且难以自查, 就应该固化成脚本(它同时也省 token —— 可执行而不必读入上下文). 如果一件事本来就有多种合理做法, 硬写成固定脚本反而降低效果.
三类第三级资源的职责边界(这点最容易混淆):
| 目录 | 定义 | 是否进上下文 | 典型内容 |
|---|---|---|---|
scripts/ | 可执行代码 | 否 | 反复重写的代码、需要确定性可靠的步骤 |
references/ | 供查阅的文档 | 是(按需) | schema、API 文档、规范、长示例 |
assets/ | 产出用资源 | 否 | 模板、图标、字体、boilerplate |
references/ 与 assets/ 的分界就是是否进上下文. 把模板文件放进 references/ 会让模型试图去"读"它; 把参考文档放进 assets/ 则会让模型读不到.
四、正文怎么写: 几条硬规则
1. 用祈使句直接下指令.
好: When the user asks for X, always do Y first.
差: The agent should consider doing Y.
2. 信息只能存一份.
正文和 references/ 之间不能重复. 官方明确要求: 信息要么在正文, 要么在参考文件, 不能两边都有. 原则是正文只留核心流程与选择指引, 细节、schema、长示例移到参考文件.
3. 每个第三级文件都必须被引用, 并说明何时读.
拆出去了但正文里没提, 等于这个文件不存在 —— 模型不知道它在那里.
4. 引用保持一层深, 不要链式嵌套.
官方原文要求: 文件引用保持从 SKILL.md 出发一层深, 所有参考文件都应直接从 SKILL.md 链出, 避免深层嵌套的引用链. 理由是链式引用会让模型逐步丢失方向.
5. 长参考文件要给检索提示与目录.
超过约 100 行的参考文件, 应在开头放目录; 超过一万词的文件, 应在正文里给出检索模式(如 grep 关键词), 否则模型无法定位到正确部分.
6. 不要放给人看的文档.
官方明确禁止在 skill 目录里放 README.md、CHANGELOG.md、INSTALLATION_GUIDE.md、QUICK_REFERENCE.md 之类. 理由: skill 目录是给 agent 用的, 额外文档只增加混乱. 这一条和很多人的本能相反 —— 我们习惯了给项目配 README, 但 skill 不是项目.
五、命名: 看似琐碎, 实则会导致静默失败
规范的约束比想象中严格:
- 不超过 64 字符
- 只允许小写字母、数字、连字符
- 不能以连字符开头或结尾
- 不能有连续连字符(如
my--skill无效) - 必须与父目录名完全一致
最后一条最容易踩: 目录叫 hx-archify 而 name 写 archify, 在不做校验的宿主里会直接失败. 命名风格上, 官方建议优先用动词开头的短语描述动作, 需要时用工具名做前缀(如 gh-address-comments).
六、验证: 怎么证明 skill 真的有用
这是整篇最有方法论价值的部分, 也是最少人做的.
核心要求: 让被测 agent 不知道自己在被测. 把它当作一个接到任务的普通 agent.
正确: Use $skill-name at <path> to solve <真实任务>
错误: 请审查 <path> 这个 skill, 假装有用户来问你...
为什么不能"请求审查": 一旦你告诉它这是测试, 它就会带着元认知去读 skill —— 会主动补全缺失信息、会按你的暗示行动. 这时你测到的是"它能不能挑出问题", 而不是"这个 skill 能不能指导工作".
污染控制清单:
- 用新鲜会话做独立验证(同一会话里的上下文会残留)
- 传原始产物(任务输入、日志、diff), 不要传你的结论
- 不要给预期答案、怀疑的 bug、打算怎么修
- 每轮迭代后从源产物重建上下文
- 清理上一轮留下的产物, 避免跨轮污染
- 只给最少的任务局部上下文
判定规则(最关键的一句):
如果只有在被测 agent 看到泄漏的上下文时才能成功, 那就先收紧 skill 或收紧测试设置 —— 不能相信这个结果.
这句话的价值在于它把"效果好"变成了可证伪的命题. 只看"任务成功了"是不够的, 必须确认成功不是来自信息泄漏.
什么时候做: 当 skill 较复杂、或包含难以自查的步骤时. 简单的 skill 可以只做真实使用后的观察迭代.
七、把规则变成代码: 为什么必须可执行
前面所有失败 —— 描述太短、触发信息写错位置、正文超预算、命名不合法、混入 README —— 有一个共同特征: 全部不报错.
而宿主的实现会放大这个问题. 一个值得记住的观察是: 主流宿主的 skill 加载器在遇到不合规条目时, 常见行为是丢弃并只写一条日志 —— 也就是说, 格式违规在模型侧完全静默. 名字不合法、描述缺失、布尔值写错, 结果只是这个 skill 从目录里消失, 而模型既看不到错误, 也无法区分"这个 skill 不存在"和"这个 skill 写错了". 有的实现甚至在文档里明确承认: 模型目录收不到任何逐条诊断信息.
这就是为什么"我写完了, 看起来没问题"这个判断不可靠 —— 你没有任何反馈信号. 校验必须由你自己在交付前补上, 而不是等宿主告诉你.
所以正确做法是把规范变成校验脚本, 至少覆盖:
| 检查项 | 依据 |
|---|---|
| 命名合法 + 与目录名一致 | 规范(强制) |
| 描述非空、长度合规、含触发信息 | 规范 + 官方建议 |
| 正文行数在预算内 | 规范 + 官方建议 |
| 正文里没有 "When to Use" 段 | 官方建议(信息只应在描述) |
| 没有 README / CHANGELOG 等杂项文件 | 官方建议(明确禁止) |
| 第三级文件全部被正文引用 | 官方建议 |
| 引用不超过一层深 | 规范(强制) |
| 大参考文件有检索提示 | 官方建议 |
一个来自实践的经验: 写完校验器后, 先拿已有的 skill 跑一遍. 我在做这件事时, 校验器立刻抓出了两类问题 —— 它自己在正文里写了一处触发段(正是它警告别人的反模式), 以及把工具链产物目录(如编译缓存目录)误报成"未引用文件". 前者是真实违规, 后者是需要加白名单的误报. 两类问题肉眼都很难发现, 这恰好证明了可执行校验的必要性.
顺带一个判断校验器质量的标准: 它应该能抓出你刚写的 skill 里至少一个问题. 如果一个校验器跑遍你所有 skill 都全绿, 大概率是它检查得太浅, 而不是你写得完美.
八、完整工作流
把前面的原则串成一个可执行顺序:
-
先收具体用例. 不要从抽象描述开始. 必须问清: 用户会说什么话才会用到它(→ 直接决定描述)? 典型任务长什么样(→ 决定是否需要脚本)? 有没有需要反复重写的代码、需要查的资料、产出要复用的文件?
-
先写第三级, 后写第二级. 先落
scripts/references/assets/, 最后写SKILL.md. 因为正文的职责是索引与指路, 得先知道有哪些东西才写得出来. 如果写了脚本, 必须真的跑一遍确认它能工作. -
写 frontmatter. 名称符合命名规则并与目录名一致; 描述用"做什么 + 什么时候用"的公式, 塞进真实关键词.
-
写正文. 祈使句; 每段自问"模型真的需要吗"; 每个第三级文件都被引用并说明何时读; 不在正文放触发信息.
-
跑校验. 修所有错误. 这一步不能跳.
-
forward-test. 复杂 skill 用新鲜、不知情的 agent 验证, 遵守上面的污染控制.
-
真实使用后迭代. 观察它在实际任务里哪里卡住、哪里被误触发、哪里被忽略. 这比一次性写完更有效 —— 也更能发现"它真正需要什么上下文", 而不是靠事先猜.
0x03 拓展升华展望
把这篇的原则再往上收一层, 会发现它们并不是 skill 独有的: 任何写给模型看的指令, 都同时受上下文预算和触发条件的约束. skill 只是把这两件事第一次变成了可以量化、可以校验的工程指标.
事实层面 … 三级预算是公开的量化约束: 元数据约 100 词、正文少于 500 行 / 5000 token、第三级基本无上限; 触发完全由 name 与 description 决定, 正文里的触发信息永远不会被读到; 而主流宿主的加载器在遇到不合规条目时通常只丢弃并写一条日志, 模型侧完全静默. 这些行为都可以被外部观察到, 也正是可执行校验之所以必要的原因.
个人判断 … 我认为接下来会分化的不是"写 skill 的技巧", 而是谁来承担校验: 只要格式违规依然静默, 把规范交给模型记忆就一定会漏. 更可能的走向是校验前移 —— 在交付前、在 CI 里、在导入时各有一道, 而写作本身反而会变得更轻, 因为作者不必再记住所有规则. 另一个判断是, 随着模型变强, "描述写什么"的重要性还会继续上升: 正文可以越写越薄, 但描述一旦写偏, 这个 skill 就再也不会出现 —— 而它不会报错.
0x04 参考来源
- Anthropic — Equipping agents for the real world with Agent Skills — https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills (官方工程博客; 渐进式披露的原始论述、四条编写准则与完整作者工作流)
- Agent Skills 开放标准规范 — https://github.com/agentskills/agentskills/blob/main/docs/specification.mdx (字段约束、命名规则、三级预算与官方校验器)
- anthropics/skills 官方仓库 — https://github.com/anthropics/skills (官方 skill 集合; 含驱动生产文档能力的复杂样例, 可作复杂度上限参照)
- Agent Skills 标准站点 — https://agentskills.io (跨平台便携性说明与规范入口)
关联阅读
- SkillOpt: 把 skill 文档当参数来训练 — 自动优化 skill 的尝试, 以及它在规范与可复现性上的边界
- 自维护可插拔记忆层设计 — 另一类"让 agent 自己积累知识"的设计
- DSH 预设提示词全解 — 宿主侧提示词如何组织

