写了几十个Skill之后,我总结出这套工程方法:从「触发不了」到「生产可用」
这儿不讲skill和prompt的区别,也不说MCP/A2A是啥子,说这些的文章已经很多了。
本文默认你已经晓得skill是个啥,且已写过几个skill了。
下面只摆一件事:咋把skill写对、写稳、写到能在团队里长期维护。
先分清遇到的是哪一类问题
写过skill的人,大多都卡在过下面两种情况:
- 一是装了等于没装:skill明明在目录里,agent一次都没用过。
- 二是用了等于没用:偶尔触发了,但跑着跑着开始自由发挥,SOP形同虚设。
遇到这些情况,多数人要么把提示词写得更长或语气更强,要么加一堆「必须」「严禁」「ALWAYS」「NEVER」。
但这两个情况是两码事。前者是路由失败,发生在模型决定要不要加载skill之前;后者是执行失败,skill已经加载了,但步骤、工具或验收没被遵循。
一个是找不到,一个是没做到。病因不一样,修法也不一样,混在一起改,是很多skill改不好的原因。
下面就讲一下怎么处理更合适。
一、skill有三层,不是简单的一个文件
很多人的skill就是一坨SKILL.md,触发条件、步骤、参考资料、示例、注意事项,全塞进一个文件。
这种skill短期能用,很快就失控:正文越来越长,关键约束被稀释。
更好的方式是「渐进加载」,把发现、理解、执行拆成三个成本完全不同的层。
| 层级 | 加载时机 | 承载内容 | 设计责任 |
|---|---|---|---|
| L1元数据 | 启动时始终加载 | name、description |
精确发现与路由 |
| L2指令 | 判定相关后才加载 | SKILL.md正文:步骤、约束、示例 |
控制执行行为 |
| L3资源与代码 | 按需加载 | 脚本、模板、参考资料 | 提供事实、确定性与产物 |
这三层是anthropic官方说过的,好处是:常驻上下文,单个skill只有少量token的元数据,主要是在加载之后才付成本。
skill写的时候在哪层偷懒,问题就会堆到哪层:
- 在L1塞正文,元数据膨胀,每项任务都在为没用到的skill付token;
- 在L2塞参考资料,每次触发都读一堆无关信息,关键信息被淹没;
- 在L2用自然语言描述确定性逻辑,模型开始「大致照做」,边界条件开始出错。
下面逐层展开讲一哈子咋个整更合适。
二、L1触发
description是用于路由发现的
2.1 绝大多数「不生效」,问题出在这一行
description是整个skill里最值得花心思写的一行字。它决定了模型在会不会选中这个skill。
最常见的反面教材写法长这样:
# ❌ 反例
name: docs-helper
description: 帮助处理文档和相关内容。
这句话的问题是:它只说了「我大概能干啥」,没说「什么情况下该找我」,也没说「什么情况下别找我」。模型拿着这个描述只能猜。
好的description至少要回答三件事:
- 做什么(能力声明,一句话讲清业务结果)
- 什么时候用(正向触发,最好包含用户的真实说法)
- 什么时候不用(排除条款+该找谁)
# ✅ 正例
name: docx-contract-review
description: |
审查 .docx格式的合同文件,检查缺失条款、主体名称、日期和签章栏是否完整。
使用场景(USE WHEN):输入是一份 .docx 合同,或者用户说「帮我审一下这份合同」
「看看这个协议有没有问题」。
不适用于(DO NOT USE FOR):
- PDF内容提取 → 改用pdf-extract
- 合同修订批注 → 改用docx-redline
- 通用写作建议 → 不适用本skill
2.2 排除条款比触发条款更重要
「不是X时不要触发」比「是X时触发」更能提升路由准确率。因为正向描述天然是模糊的,「review this contract」和「summarize this contract」语义上离得很近,模型很容易误判;但如果明确写了「不适用于:通用摘要总结,改用doc-summarize」,边界就清楚了。
排除条款还有个好处:它能告诉你路由错了该往哪走。写上「改用pdf-extract」,相当于给模型一张路由表,比单纯说「不要用于PDF」有用得多。
2.3 触发描述里应该写「用户的原话」
「使用场景」里列举的那些说法,不是给关键词硬匹配用的,而是弥补模型对真实表达习惯的推断偏差。
用户不会说「请执行灰度发布协调流程」,他会说:
- 「把payments-api灰度到prod」
- 「先放5%流量试试」
- 「1.42.0这个版本先小流量验证一下」
把这些真实说法写进去,路由命中率会有不少提升。
2.4 一个容易踩的物理坑:先确认它「存在」
在改description之前,先确认skill真的被扫描到了。
我遇到过这么个情况:用npx skills add装的skill,实际落到了~/.agents/skills/,而我当时用的claudecode扫描的却是~/.claude/skills/。安装位置和运行时扫描位置不是一回事,用户以为装好了,环境里根本没有。具体目录名会随工具链和版本变,不用死记这两个路径,关键是意识到「装到哪儿」和「运行时扫哪儿」可能是两回事。
所以调试顺序应该从外到内:
物理存在→清单声明→路由选择→正文加载→脚本执行→输出验收
目录都没被扫描的话,改一百遍措辞也不会有任何信号。
2.5 控制同时挂载的数量
skill挂太多会互相抢注意力,误触发率明显上升。经验上,同时挂载控制在20个以内比较稳,按角色或场景打包,不要一股脑全塞进去。
真需要很多的话,就分层:常驻少量高频的,其余按项目或工作区动态装载。
三、L2正文
SKILL.md是地图,不是仓库
3.1 要默认假设「模型已经很聪明」
写skill最容易犯的错,是把模型当小学生,什么都教一遍。
其实只需要写它不知道的。领域惯例、公司内部流程、非常规的踩坑点,这些才是skill正文该承载的内容。「先仔细阅读代码」「确保没有语法错误」这类废话,不但没用,还会稀释真正重要的约束。
3.2 正文的最小有效结构
我常用的骨架是这个:
# 灰度发布协调
## 目标与非目标
协调一次分阶段的生产环境发布。
非目标:不负责集群开通,不负责数据库迁移。
## 前置条件
- 已确认service、image_tag、namespace三个参数;
- 调用方已审批本次生产变更;
- 已存在基线SLO数据。
## 执行步骤
1. 预检:执行./scripts/preflight.sh,任何非零退出码立即停止。
2. 规划:输出目标版本号、灰度比例、回滚阈值。
3. 实施:严格只执行一次声明好的部署命令。
4. 验证:运行冒烟测试,tests/smoke.yaml中的断言必须全部通过。
5. 决策:SLO达标才放量,否则执行回滚并发出审计事件。
## 强制检查项
- 部署操作必须是幂等的;
- 灰度流量比例必须在策略允许范围内;
- 任何密钥都不得回显到输出中。
## 失败与回退
- 仅网络类瞬时故障可重试一次;
- 触发阈值告警时,调用rollback.sh并立即停止;
- 遇到歧义时只提一个聚焦的问题,不要猜测目标服务。
## 示例
输入:「把payments-api 1.42.0灰度到prod-us」
→ 预检 → 放量 5% → 冒烟 → 放量或回滚
反例:
输入:「payments-api为什么这么慢?」
→ 不要调用本skill,应改用k8s-troubleshoot。
几个要点:
- 「非目标」和「前置条件」不能省。前者防止模型过度扩张职责,后者让模型知道「条件不满足时该停下来问」,而不是硬着头皮往下跑。
- 步骤要编号、要有停止条件。「跑一下测试,看看结果,再决定要不要继续」这种写法等于没写。
- 示例和反例成对出现。反例划定边界,优先写最容易混淆的那个相邻skill。
3.3 控制体量
经验值:SKILL.md正文控制在500行以内。超过了就该拆:
- 长篇参考资料拆到
references/,正文里写明「什么时候去读」; - 确定性计算与校验拆到
scripts/; - 输出模板与固定格式拆到
assets/。
anthropic官方的说法是:脚本由agent执行(bash、python、node等均可),只有脚本输出进入上下文;参考资料由skill显式指向,需要时才读取。
3.4 别把ALWAYS/NEVER当万能药
堆砌强指令词是个很常见的误区,它有两个问题:
- 用多了就疲了,模型不会因为写三个
MUST就更听话; - 它只说「不能做什么」没说「为什么」,而模型在遇到边界情况时,恰恰是靠「为什么」来类推的。
更好的写法是规则+原因:
# ❌ 反例
严禁直接向main分支提交代码。
# ✅ 正例
不要直接向main分支提交:每周四会自动从main切出发布分支,
直接提交的代码可能静默错过这一班发布列车。
如果需要紧急修复,请开hotfix分支并通知发布负责人。
给了原因,模型在遇到你没预料到的情况时才能做出正确类推。
四、指令该写多死?按风险定档
我在实践中的经验是:指令的严格程度,应该由出错代价决定,而不是由你的焦虑程度决定。
把所有步骤都锁死,skill会变得脆弱,环境稍有变化就卡住;全部放开,高风险操作又容易出事。
| 风险等级 | 典型场景 | 推荐写法 | 校验方式 |
|---|---|---|---|
| 高 | 数据库迁移、删除操作、生产写入、权限变更 | 精确脚本+强约束+审批门 | 脚本校验+人工确认 |
| 中 | 部署、批量重构、配置变更 | 伪代码+参数化步骤 | dry-run+检查点 |
| 低 | 代码审查、文案润色、信息总结 | 自然语言指令,留出判断空间 | 输出格式约定 |
说白了,就看这一步做错了代价多大:
- 不可逆的,脚本化+审批;
- 可逆但影响面大的,dry-run+检查点;
- 随便改改就行的,给个方向,让它自己发挥。
anthropic在《building effective agents》里也表达过类似的观点:能用预定义代码路径解决的就用workflow,把灵活性留给真正需要模型动态决策的地方。过度agent化本身就是一种反模式。
五、L3护栏
把「应该」变成「检查点」。
这一章里的checks:、side_effects:、on_failure:等字段,不是agent skills官方frontmatter的一部分,当前没有任何运行时会自动解析并执行它们。它们是一份「显式化契约」,把约束写清楚,好让agent照着走、让脚本去落实、让人能审。要让它们真的生效,得在scripts/或正文里实现对应逻辑。
5.1 三成原则
我的经验是:一个成熟的skill,至少30%的约束应该被转成机器可校验的检查点。
自然语言适合承载无法穷举的语义判断;代码更适合格式、数值、存在性、幂等性这些东西。
# ❌ 反例
注意不要泄露密钥,并且要校验输入参数。
# ✅ 正例
checks:
- id: 密钥泄露扫描
run: ./scripts/scan_secrets.sh
on_failure: block # 直接中断
- id: 版本号格式校验
run: ./scripts/semver_check.sh "${image_tag}"
on_failure: block
- id: 幂等性预演
run: ./scripts/dry_run.sh
on_failure: ask_user # 停下来询问
注意on_failure有三种取值,对应三种处置策略:block(直接中断)、ask_user(停下来问)、retry(可重试)。把处置策略也写进去,skill才完整。
5.2 声明副作用契约
如果skill会产生外部副作用,最好把它写清楚:
inputs:
service: string # 服务名
image_tag: semver # 镜像版本
namespace: enum(prod-us, prod-eu)
side_effects:
- 对目标 namespace 执行 kubectl apply
idempotent: true # 是否幂等
dry_run: scripts/dry_run.sh # 预演脚本
rollback: scripts/rollback.sh # 回滚脚本
requires_approval: true # 是否需要人工审批
幂等性这一项特别重要,因为agent可能会在各种情况下重试:网络抖动、工具超时、上下文压缩后重新规划。如果skill不幂等还不给它写清楚,一次重试就可能变成两次部署。
5.3 失败策略要分级
无脑「失败时重试三次」不是好的失败处理策略。
retry:
max: 1
conditions: [网络超时] # 只有瞬时故障才重试
escalate:
conditions: [权限不足, 数据破坏风险] # 结构性错误直接上报
human_approval:
conditions: [生产写入, 回滚操作]
never_skip: [预检, 阈值触发回滚] # 这些步骤永不跳过
重试只适用于「可证明是瞬时的」问题;权限不足、数据破坏风险、未知状态,必须停止或升级,重试并没有nuan用。
5.4 一个隐藏的运行时约束
如果skill里存在等待人工输入、授权确认或外部回调的环节,务必声明它是否能在子agent中使用。
我曾经遇到过一个例子:skill在子agent中被调用时,交互式的授权提示会原样返回给编排器,但当时并没有可供编排器注入回复、恢复子agent会话的原语。结果流程就卡死了。
所以在发布前必须识别skill是否依赖交互式门控,并明确它是「顶层会话专用」还是「可在子agent中使用的」。
六、什么时候该拆,什么时候该合
6.1 拆分标准:看「变化原因」,不看长度
最常见的错误是按文件长度拆分,比如SKILL.md超过500行就砍一刀,基本上都是错的。
更好的是:想砍开的这两部分会不会因为不同的原因而改变?
| 判断维度 | 结论 |
|---|---|
| 触发条件不同 | 拆 |
| 验证标准不同 | 拆 |
| 权限与审批要求不同 | 拆 |
| 输入输出契约不同 | 拆 |
| 只是步骤变长 | 不拆 |
| 仅调用的工具不同,流程相同 | 参数化即可 |
| 输出格式不同但流程一致 | 加模板即可 |
说白了,该不该拆,看触发、验证、权限这些「变化原因」一不一样,而不是看长度。
6.2 skill/tool/RAG的边界
我总结了下面这个表:
| 资产 | 核心责任 | 典型故障 | 治理手段 |
|---|---|---|---|
| skill | 步骤、约束、决策、验收 | 触发错误、步骤漂移、验收缺失 | description、清单、eval |
| tool/function | 操作接口与副作用 | 参数错误、权限不足、异常 | schema、错误码、幂等、日志 |
| RAG/知识库 | 易变事实 | 检索错误、版本陈旧 | 来源元数据、引用校验 |
| memory | 用户与任务状态 | 越权、过期、污染 | 作用域、TTL、写入审批 |
- 「能不能调」出问题找tool,
- 「事实对不对」出问题找RAG,
- 「行为稳不稳」出问题找skill。
顺带说一下MCP:MCP解决的是「连到哪儿」,skill解决的是「怎么用好」。两者可以互补,不是互怼。
七、没有eval,就没有skill迭代
绝大多数人写完skill,跑两次觉得没问题就提交了,三个月后,没人敢改它,因为不知道改了会不会坏。
7.1 四个指标必须分开测
只评价最终答案,会把四类问题混在一起。建议拆成:
| 指标 | 定义 | 诊断的问题 |
|---|---|---|
| 路由准确率 | 正确首选或进入候选的样本数÷路由样本数 | 触发与边界 |
| 步骤遵循率 | 必经步骤被遵循的样本数÷正确路由进该skill的样本数 | SOP表达 |
| 检查点通过率 | 自动检查点通过的试验数÷总试验数 | 硬约束执行 |
| 任务成功率 | 满足最终环境断言的试验数÷总试验数 | 整体结果 |
指标的下降方向,直接指向该改哪里:
- 路由准确率掉了,改
description; - 步骤遵循率掉了,改正文指令;
- 检查点通过率掉了,改脚本或校验逻辑;
- 全都掉、怎么改都救不回来,这个skill该重新设计了。
7.2 一个最小可用的路由测试集
路由是最容易测、也最该先测的。建议每个skill维护20条样本:
| 类型 | 数量 | 目的 |
|---|---|---|
| 典型正例 | 5 | 验证核心任务能触发 |
| 近义改写 | 5 | 验证自然表达漂移下的鲁棒性 |
| 强负例 | 5 | 验证排除条款生效 |
| 相邻技能边界 | 5 | 验证候选之间的区分度 |
「强负例」这一组特别有价值,它测的就是在「不适用于」里写的那些东西到底有没有生效。
7.3 不要只看最终输出
anthropic在讲agent eval时强调过一个关键点:模型说「已预订」,不等于环境里真的存在预订记录。
所以断言要打在环境状态上,而不是模型输出上:
# ❌ 反例:只检查模型说了什么
assert "已放量" in 最终输出
# ✅ 正例:检查环境的真实状态
assert 查询部署("payments-api").版本号 == "1.42.0"
assert 查询SLO("payments-api").错误预算百分比 > 5
另外,每组至少跑3次试验。agent输出有波动,单次结果说明不了问题。
7.4 从「改提示词」转向「读trace」
当不知道skill哪里出问题时,先去看trace,不要凭感觉改措辞。
一条有用的trace至少应该包含:
- skill的id与版本号
- 触发时的全部候选及选中理由
- 正文加载范围
- 脚本调用、参数、输出
- 检查点结果
- 回滚事件
调试流程:
- 复现:固定prompt、模型、工具状态、环境变量;
- 分层重放:先mock工具验证步骤逻辑,再接真实环境验证副作用;
- 差异对比:并排看成功与失败的trace,定位差异出现在路由、参数、分支还是检查点;
- 最小化修复:每次只改一类(description、正文、脚本、数据),然后重跑同一份评测集。
一次改四样,效果好了也不知道是哪样的功劳;效果差了更惨,得再改回来。
八、版本化:让skill可维护
进入团队协作后,skill就不再是「对话附件」了,它是代码资产。
推荐的目录结构:
skills/
release-canary/
SKILL.md # 元数据 + 指令 + 示例
CHANGELOG.md
metadata.schema.json
scripts/
preflight.sh # 预检
dry_run.sh # 预演
rollback.sh # 回滚
tests/
smoke.yaml
eval/
001-灰度发布-正常路径.yaml
002-SLO超阈值-触发回滚.yaml
references/
release-policy.md # 发布策略细则
版本号建议遵循major.minor.patch:
| 变更类型 | 版本规则 | 示例 |
|---|---|---|
| 触发条件或契约变化 | major | 移除自动放量、修改必填输入 |
| 新增步骤或检查 | minor | 增加SLO预算校验 |
| 文案或示例修订 | patch | 调整描述、补充示例 |
CHANGELOG里显式标注破坏性变更:
v2.0.0 - 2026-08-20
- BREAKING: 移除自动放量,调用方必须显式审批
- description: 增加「临时排查调试」场景的排除条款
- checks: 放量前要求校验SLO预算
没有版本和CHANGELOG,就无法把一次失败归因于「这次改坏了」。而归因能力,是持续迭代的前提。
九、一份可以直接抄的检查清单
写完一个skill,发布前过一遍下面这些事项:
触发(L1)
正文(L2)
执行与护栏(L3)
评估
我觉得可以把上面这些项(甚至是本文)写成一个检查skill的skill,哈哈哈!
希望这篇对你有用。如果有不同意见或者踩过别的坑,欢迎在评论区聊。

浙公网安备 33010602011771号