如何编写自己的 Agent Skill:SKILL.md 与测试
从代理连续两次处理错误的真实故障开始,掌握 SKILL.md 结构、决定技能触发时机的 description 写法,以及如何用同一请求验证技能是否生效。
从一次真实故障编写自己的 agent skill
编写自己的 agent skill 的最佳方法,是从一次真实故障中提炼。找到 coding agent 连续两次处理错误的任务,记录你两次输入的修正内容,然后将该修正保存为 agent 可自行加载的 SKILL.md 文件。之后只需处理机制问题:文件布局,以及决定 skill 是否会触发的那一行配置。
顺序很重要。凭空编写的 skill 记录的是你从未遇到过的问题,而且会在每次会话中占用上下文。根据你亲眼观察到的故障提炼出的 skill 自带测试:再次提出相同请求,检查 agent 这次是否能正确处理。如果你还不熟悉这种格式,请先阅读什么是 agent skill,以及 agent 如何加载它,然后回来编写一个。
从一个代理连续两次出错的任务开始
一次可能是偶然。两次就是模式,而模式值得记录到文件中。
下面是在真实服务器上反复出现的一类故障。你要求代理向 nginx 添加一个反向代理配置块。它修改了 /etc/nginx/conf.d/app.conf,然后运行 sudo systemctl restart nginx。由于修改中有拼写错误,nginx 拒绝启动,网站在你修复之前一直不可用:
nginx: [emerg] unknown directive "proxy_pas" in /etc/nginx/conf.d/app.conf:12
Job for nginx.service failed because the control process exited with error code.你在聊天中纠正了它。再次修改服务前,先使用 sudo nginx -t 测试配置,然后使用 reload 应用修改,不要使用 restart。一周后,在另一个任务中又出现了同样的错误。第二次就是信号。
趁故障还在眼前,记下两件事:你输入的请求,以及你给出的纠正内容,并保留你使用的原话。这两行就是 skill。请求说明触发条件需要匹配什么。纠正内容就是完整正文。
Anthropic 自己的编写指南也将这一点放在首位。先在没有 skill 的情况下,让代理执行具有代表性的任务,记录它出错的位置,然后编写能够修复这些错误的最少指令。故障就是规范,因此如果一个 skill 无法追溯到某个具体故障,通常就说明没有人真正需要它。
如需查看同一提炼过程的完整示例,可以先从头到尾阅读 Ponytail 将一个反复出现的故障——代理改写的内容远超你的要求——提炼为一个 skill,再编写自己的 skill。
技能的结构
技能是一个目录,其中包含一个必需文件。
.claude/skills/nginx-config-changes/
├── SKILL.md
├── reference/
│ └── proxy-headers.md
└── scripts/
└── check-and-reload.shSKILL.md 以 frontmatter 块开头。这是使用 YAML 编写的几个设置项(Docker Compose 文件也使用相同的配置格式),位于 --- 标记之间,后面是 Markdown 格式的说明。以下是上文故障对应的完整技能。
---
name: nginx-config-changes
description: Tests and reloads nginx safely after a config edit. Use when editing files under /etc/nginx, adding a server block or a reverse proxy, or changing a TLS certificate path.
---
## Rules
Run `sudo nginx -t` after every edit under `/etc/nginx`. Do not touch the service until it prints `test is successful`.
Apply the change with `sudo systemctl reload nginx`. Never use `restart`. A reload keeps the running workers serving traffic until the new config parses, so a broken config leaves the site up. A restart stops nginx first, so a broken config takes the site down.
If `nginx -t` fails, fix the file and test again. Never reload a config that failed the test.
For the proxy header defaults this project expects, see [reference/proxy-headers.md](reference/proxy-headers.md).该文件不到 20 行,但已经是一个完整的技能。各部分如下:
name:最多 64 个字符,只能使用小写字母、数字和连字符,且不能包含claude或anthropic。对于个人技能或项目技能,它只是显示标签。您输入的命令来自目录名,因此此技能的命令名是/nginx-config-changes。description:说明技能的功能和使用时机,最多 1,024 个字符。这一行发挥实际作用,下一节只讨论这一点。- 正文:技能实际触发后才加载的说明。
reference/:代理按需读取的其他文件。通过SKILL.md链接这些文件,并将链接深度保持为一级,因为被另一个引用文件引用的文件通常只能被部分读取。scripts/:代理执行而不是读取的文件。只有这些文件的输出会占用上下文,因此 300 行的脚本也不会带来太大开销。
当它要纠正的行为足够顽固、需要专门结构时,技能才会扩展为完整布局;unlazy 技能将这部分空间用于深度树、一组 gate 文件和 PLAN.md 契约,防止代理在大段工作分支尚未处理时就宣布任务已完成。
目录的位置决定谁可以使用该技能。
.claude/skills/<name>/SKILL.md:位于仓库中,仅适用于该项目,并会随仓库一起提供给所有克隆该仓库的用户。~/.claude/skills/<name>/SKILL.md:适用于您计算机上的所有项目,但其他人的计算机无法使用。<plugin>/skills/<name>/SKILL.md:随插件一起提供,在启用该插件的所有位置都可用。
使用 mkdir -p .claude/skills/nginx-config-changes 创建技能并写入文件。Claude Code 会监控这些目录,因此编辑现有技能后,修改会在当前会话中生效。如果会话启动时顶层 skills 目录尚不存在,则创建该目录后需要重启,因为会话启动时没有可监控的目录。
description 字段是文件中投入产出比最高的一行
启动时,代理会将每个可用 skill 的 name 和 description 加载到上下文中,但不会加载正文。当请求到达时,这一行是判断该 skill 是否相关的全部依据。因此,正文即使写得很好,只要 description 含义模糊,正文就不会被读取。
使用第三人称编写 description。Tests and reloads nginx safely 是有效写法。I can help you with nginx 无效,因为该文本会注入系统提示词,第一人称读起来像是模型在描述自己。
description 需要包含两项内容:skill 的功能,以及适用条件。将最重要的使用场景放在前面,因为 Claude Code 会在 1,536 个字符处截断列表项。还可以使用可选的 when_to_use 字段补充触发短语和示例请求;该字段会追加到 description 后面,但仍受同一字符上限限制。
然后使用你实际会输入的词。description: Helps with nginx 不会匹配任何内容,因为没有人会输入“helps with”。上面的版本明确列出了 /etc/nginx、server block、reverse proxy 和 TLS (transport layer security) certificate path,这基本涵盖了应触发该 skill 的请求词汇。
可以这样测试 description:将这一行和你准备输入的请求一起提供给一个从未看过正文的人,然后询问他该 skill 是否适用。如果他无法判断,模型也无法判断。
保持正文精简,因为它会一直占用上下文
调用技能时,渲染后的内容会作为一条消息加入对话,并在本次会话的剩余时间内保留。Claude Code 后续轮次不会重新读取该文件。您写入的每一行都会在整个会话中产生开销,而不只影响一次回答。
Anthropic 建议将 SKILL.md 控制在 500 行以内,并将详细内容移到单独的文件中。上下文压缩说明了这个数字并非随意设定。对话经过摘要以释放上下文后,Claude Code 会重新附加每个技能最近一次调用的内容,每个技能最多保留前 5,000 个令牌,并从最近调用的技能开始填充总计 25,000 个令牌的预算。较长的技能会在中途被截断。多个较长的技能还会互相挤出上下文。
因此,只写模型尚不了解的内容。模型知道 nginx 是什么,也知道反向代理的作用。模型不知道您关于 reload 超过 restart 时的内部规则,而该规则正是此文件存在的唯一原因。
如果技能要求代理运行随附的脚本,请使用 ${CLAUDE_SKILL_DIR} 指定路径,以便脚本无论安装在何处都能正确解析;同时预先批准相同的命令,避免运行时因权限提示而中断。
---
name: nginx-config-changes
description: Tests and reloads nginx safely after a config edit. Use when editing files under /etc/nginx, adding a server block or a reverse proxy, or changing a TLS certificate path.
allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/check-and-reload.sh *)
---该授权仅涵盖调用技能的当前轮次;当您发送下一条消息时,授权会清除,因此不会静默变成永久权限。
如何证明技能已触发
观察技能加载只能说明代理找到了它,不能说明回答已经发生变化。两项都要检查,并且要在新会话中检查,因为编写技能的会话已经保留了编写过程中提供的所有信息。这些残留上下文会掩盖文件中的缺陷。
- 在项目中使用
claude启动新会话。 - 按照日常工作时的方式,用自己的话输入请求,不要提及技能名称。
- 观察技能是否被调用。如果技能没有触发,请修改描述。此时还不是技能正文的问题。
- 使用
/nginx-config-changes手动调用技能进行对照。如果手动调用时行为正确,而通过请求调用时行为错误,则说明问题在触发条件,而不是指令内容。 - 关闭技能后运行相同的请求,并比较两次回答。在
/skills菜单中选中该技能,按Space将其状态切换为off,然后按Enter保存。这会向.claude/settings.local.json写入skillOverrides条目;完成后再次按Space,可将其切换回on。 - 编写几个不应触发该技能的请求,并确认技能对这些请求保持静默。
如需自动执行这一循环,请从官方市场安装 skill-creator 插件。
/plugin marketplace add anthropics/claude-plugins-official
/plugin install skill-creator@claude-plugins-official如果安装输出显示 Run /reload-plugins to activate.,请运行该命令。然后要求 Claude 按名称评估你的技能。该插件会将测试用例存储在技能目录中的 evals/evals.json 内,并在各自独立的子代理中运行每个用例,因此每次运行都会使用干净的上下文。随后,它会生成启用技能与未启用技能的对比结果。这才是有效指标:以技能消耗的 token 数和时间为基准,衡量通过率提升。
技能也可以携带自己的验证流程,而不是交给单独的评估运行处理。例如,Old Coder 技能会让代理返回一份证据报告,供你自行重新运行。
故障模式:技能从不触发
您输入请求后,代理仍执行原来的错误操作,而且没有出现技能行。请按以下顺序排查。
- 描述说明了技能的作用,但从未说明何时使用该技能,因此您的请求中没有任何内容与其匹配。
- 描述避开了您输入的词。如果您说的是“nginx”,描述中也必须写明 nginx。
- 前置元数据中设置了
disable-model-invocation: true。这会让描述完全不进入模型上下文,技能只能由您通过/name调用。 - 前置元数据中的
pathsglob 会将激活范围限制为匹配的文件,而您正在处理的文件不匹配。 - 技能位于起始目录下的嵌套
.claude/skills/目录中。只有代理读取或编辑该子目录中的文件后,才会加载其中的技能;在此之前,该技能完全不可用。
故障模式:技能会持续触发
相反的问题是描述范围过宽,导致技能在无关任务上触发。“处理服务器时使用”几乎匹配服务器代码库中的所有请求。这样,技能正文会加载到它无法帮助的任务中,并在本次会话的其余时间里一直保留在上下文中。
将描述范围缩小到实际相关的条件,并列出它适用的文件或命令。如果技能仅适用于特定文件,请添加 paths glob。对于部署或提交等具有副作用的操作,请设置 disable-model-invocation: true,并使用 /name 手动调用它。这样,代理就不会自行判断现在是否适合执行部署。
失败模式:这条技能应放入规则文件
像 CLAUDE.md 或 AGENTS.md 这样的规则文件会在每次会话开始时加载,并应用于每项任务。技能正文只会在技能触发时加载。决定因素是适用频率。对仓库中每项任务都适用的事实(例如使用的包管理器)应放入规则文件。只适用于少量任务的流程(例如上面的 nginx 规则)应放入技能中。这样,在没有人编辑 nginx 的日子里不会产生额外开销。
真正的问题是把同一条指令同时放在两个位置。两份副本会逐渐产生差异;当代理执行错误操作时,您无法判断它遵循了哪一份。每条指令都应明确归属一个位置。如果规则已经准确地放在一个位置,却仍然被跳过,这是另一个问题。在将其移入技能并寄希望于迁移能解决问题之前,值得先检查被忽略指令背后的机制。技能、MCP 服务器和规则文件之间的边界说明了更复杂的情况,包括正确答案是使用 MCP(model context protocol)服务器,由它向代理提供新工具,而不是新指令的情况。
技能经实际工作验证后再共享
一项经一周实际工作验证仍然有效的技能,才值得提交到版本库。.claude/skills/ 中的项目技能会像代码一样接受审查,并随代码仓库一起提供,因此队友克隆仓库后无需额外设置即可获得你的修正。无需复制粘贴即可在仓库之间移动技能是另一个问题,详见如何在仓库之间共享代理技能。
需要注意可移植性。Claude Code 接受大量 frontmatter 字段,但 Agent Skills 标准只允许以下 6 个字段:name、description、license、compatibility、metadata 和 allowed-tools。如果 frontmatter 中包含其他字段,就将技能上传到 claude.ai 或将其打包用于 Skills API,处理会直接失败,而不是忽略该字段:
Unexpected key(s) in SKILL.md frontmatter: argument-hint. Allowed properties are: allowed-tools, compatibility, description, license, metadata, name仅使用这 6 个字段,同一个文件就能在 Claude Code 以及其他读取该标准的环境中加载。文件能否加载仍决定了它可以执行的操作,因为Cowork 在 Anthropic 沙箱中运行,而 Claude Code 在您自己的计算机或 VPS 上运行,所以前面的 nginx 技能值得带到队友的检出目录中,但在无法访问服务器的沙箱中没有用处。编写能够适应其他模型的技能指令本身是另一项工作,详见编写适用于任何模型的技能。
FAQ
SKILL.md 文件应有多长?
控制在 500 行以内,并且大多数有用的技能应明显短于这个长度。技能调用后,其正文会进入对话,并在本次会话的剩余时间内保留。因此,每一行都会反复产生开销,而不是只产生一次开销。将较长的参考资料移到技能目录中的独立文件内,再从 SKILL.md 链接到这些文件,深度保持为一层,这样代理只会在需要时读取它们。捆绑的脚本会直接执行,而不是被读取,因此只产生其输出的开销。
为什么我的技能始终不会触发?
通常原因是描述,因为模型做出决定时,只有技能描述位于上下文中。确保描述说明何时使用该技能,而不只是说明它的功能,并包含您在请求中实际输入的词语。如果描述看起来没有问题,请检查 frontmatter 中是否存在 disable-model-invocation: true。该字段会让模型完全看不到技能。还要检查是否存在 paths glob,因为它可能将技能限制为您当前未操作的文件。另一个原因是技能位于起始目录下的嵌套 .claude/skills/ 目录中:只有代理读取或编辑该子目录中的文件后,技能才会加载。
这是应该写成技能,还是写进规则文件的一行?
先确认它适用于多少任务。规则文件会在每次会话中加载,因此其中应保存适用于所有任务的事实,例如包管理器或分支命名约定。技能只会在触发时加载,因此适合保存仅适用于少量任务的操作流程。不要在两个位置写入相同的指令,否则两份内容会逐渐不一致,您也无法判断代理遵循了哪一份。
如何确认技能确实有帮助?
将其与基线进行比较。收集一些真实请求,在技能可用的新会话中分别运行这些请求,然后从 /skills 菜单中关闭技能,再次运行这些请求,并排比较两组回答。使用新会话很重要,因为编写技能时所在的对话仍包含您的说明,这会让不完整的文件看起来已经完整。skill-creator 插件会为您执行这项比较,并在令牌开销旁报告通过率。
可以在其他代理中使用同一个 SKILL.md 吗?
可以,前提是只使用 Agent Skills 标准定义的字段:name、description、license、compatibility、metadata 和 allowed-tools。Claude Code 接受更多字段,还支持其他工具无法执行的正文功能,例如注入 shell 命令。使用标准之外的字段上传技能会失败,并明确列出允许的属性。因此,应尽早决定技能是仅供 Claude Code 使用,还是需要在不同工具之间迁移。