Agent Skills 三层加载
在技能标准出现之前,想让同一套工作流在三个编码助手里都能用,得维护三份文件:一份 CLAUDE.md、一份 AGENTS.md、一份 .cursorrules。三份格式、三套路径,改动一次要同步三次,然后它们就开始各自漂移。
现在这个成本可以省掉。一份 SKILL.md 能在几十个 Agent 产品里用——写一次,到处加载。
1. 一个技能就是一个文件夹
最小形态只有一个文件:
release-notes/
└── SKILL.md # 必须叫这个名字,大小写敏感
完整形态可以带三类可选目录:
release-notes/
├── SKILL.md
├── scripts/ # 可执行代码,Agent 能直接跑
│ └── collect-commits.sh
├── references/ # 需要时才读的文档
│ └── FORMAT.md
└── assets/ # 模板、示例、静态资源
└── template.md
只有 SKILL.md 是必需的,其余都是可选的、按需加载的。
1.1 frontmatter 只有两个必填字段
---
name: release-notes
description: 从 git 提交记录生成发布说明,按类型分组并标注破坏性变更。用于发版、写 changelog 或整理本周改动时。
---
就这两个。其余字段(license、compatibility、metadata、allowed-tools)全是可选的,而且规范要求合规实现忽略不认识的前端字段——这正是它能跨产品移植的原因:核心足够小,扩展不破坏兼容。
2. name 字段的约束
约束不多但都很硬:
- 1–64 字符
- 只能包含小写字母、数字和连字符
- 不能以连字符开头或结尾,不能有连续连字符
- 必须与父目录名完全一致
最后一条是最常见的加载失败原因。目录叫 Release-Notes 而 name 写 release-notes,或者改名目录忘了改 name,技能直接不加载,而且通常不会给明确报错。
不合格的例子:PDF-Processing(大写)、-pdf(开头连字符)、pdf--processing(连续连字符)。
3. description 是唯一的触发器
这是整个格式里最需要花心思的一行,它是设计的核心:name 和 description 是启动时唯一会被读入上下文的东西,Agent 靠它判断"这个任务要不要用这个技能"。
它要同时说清两件事——做什么,以及什么时候用。
# 差:太泛,Agent 不知道何时该用
description: 处理 PDF。
# 好:说清能力 + 触发场景 + 关键词
description: 从 PDF 提取文本和表格、填写表单、合并多个文件。当用户提到 PDF、表单、文档提取,或需要处理上传的 PDF 时使用。
上限 1024 字符,一般写 1–3 句就够。写的时候想一个问题:用户会用哪些词描述这个任务? 把这些词放进去。Agent 匹配的是语义,但具体的关键词能显著提高命中率。
描述写得含糊,技能就等于不存在——它会被静静地跳过,没有任何提示。
3.1 描述之间要能区分
技能库大了之后会出现"该选哪个"的问题。如果两个技能的 description 高度重叠,Agent 会选错或者都选。
写完一批技能之后,建议把它们的所有 description 列成一列读一遍,看能不能仅凭描述判断该用哪个。如果连人都分不清,Agent 更分不清。
4. 三层渐进加载
规范把加载分成三级,这个设计决定了技能库的上下文成本。
4.1 发现(启动时)
启动时扫描技能目录,只加载每个技能的 name 和 description,每个约 100 token。这时候 Agent 知道有哪些技能可用,但不知道具体内容。
所以挂几十个技能的成本是可接受的——未激活的技能几乎不占上下文。这是它相对于"把所有规则写进一个巨大的 CLAUDE.md"最大的优势:后者无论用不用,全文都在上下文里。
4.2 激活(命中时)
当用户点名某个技能,或 Agent 判断某个技能与当前任务相关时,才把 SKILL.md 正文整体读进来。
规范建议正文控制在 500 行、约 5000 token 以内。超出部分应该拆到 references/ 里去。
这里有个容易忽略的点:正文是一次性全部载入的。一个 3000 行的 SKILL.md 每次激活都吃掉这么多上下文,而其中大部分内容这次可能用不上。
4.3 资源(按需)
scripts/、references/、assets/ 里的文件只在明确需要时才读。规范还建议引用保持一层深度——从 SKILL.md 直接指向 references/FORMAT.md,而不要让 FORMAT.md 再引用更深一层的文件。链式引用在按需加载模型下很容易断。
# SKILL.md 里的引用写法(相对技能根目录)
详细的字段说明见 [格式参考](references/FORMAT.md)。
字段校验跑 scripts/validate.py。
5. 正文该写什么
正文没有格式限制,但有几条从实践里来的经验。
5.1 写可执行的步骤,不写原则
"要保持代码简洁"这类原则,模型本来就知道,写进去只是占上下文。有价值的是它不知道的东西:这个仓库的发布流程是什么、先跑哪个脚本、输出要落到哪个文件、遇到冲突怎么处理。
判断标准:这句话删掉之后,Agent 会不会做错? 不会,就删掉。
5.2 长内容拆到 references
SKILL.md 保留主干流程,细节放 references/:
REFERENCE.md—— 详细技术参考FORMS.md—— 表单模板、结构化格式- 按领域拆的文件 ——
billing.md、auth.md
拆细一点更好:Agent 按需加载,小文件意味着每次只吃需要的那部分。
5.3 脚本要自带错误信息
scripts/ 里的脚本是给 Agent 跑的,不是给人跑的。所以:
- 依赖要写清楚,不能假设环境里有
- 失败时给出可行动的错误信息("缺少环境变量 GITHUB_TOKEN" 而不是 "error: undefined")
- 边界情况别崩,返回非零退出码和说明
Agent 会读错误信息并尝试修复。一句好的错误信息能省掉三次失败的尝试。
6. 各家放在不同的目录
格式统一了,但发现路径没有统一。这是目前移植技能时唯一要处理的事:
| 产品 | 项目级 | 全局 |
|---|---|---|
| Claude Code | .claude/skills/ | ~/.claude/skills/ |
| GitHub Copilot | .github/skills/ | ~/.copilot/skills/ |
| Cursor | .cursor/skills/ | ~/.cursor/skills/ |
| Gemini CLI | .gemini/skills/ | ~/.gemini/skills/ |
| Codex | .agents/skills/ | 用户级路径 |
| OpenCode | .opencode/skills/ | ~/.config/opencode/skills/ |
项目级与全局级的优先级,规范客户端实现指南建议 project-over-user(同名冲突时项目级生效)——但注意各产品实际行为并不统一:Claude Code 的顺序是 enterprise > personal > project,恰好相反。写通用规则时留一句"以具体客户端为准",别把一种实现当成标准。
实践做法:保留一份权威副本,其余用符号链接或安装命令同步。不要把同一份 SKILL.md 复制六份——那又回到三份文件各自漂移的老问题了。有 CLI 工具支持把一份源安装到任意 Agent 的项目级或全局级,用 copy 或 symlink 模式。
链接之前先校验 frontmatter,避免一个格式错误的 name 被复制到六个目录里。
7. 和 MCP、和系统提示词怎么分工
三者解决的不是同一件事,容易混:
- MCP 给的是能力——能调什么工具、读什么资源(见《MCP 的工具、资源和提示模板》)。没有 MCP,Agent 没有手段。
- Skill 给的是做法——拿到这些手段之后,按什么流程做。没有 Skill,Agent 有能力但每次都要重新摸索。
- System Prompt 给的是约束——什么不能做、什么必须遵守(见《System Prompt 写的是约束》)。
一个粗略的分法:跨任务都要守的规矩放系统提示词;某个特定任务才需要的流程放技能;完成任务需要的外部能力放 MCP。
技能相对系统提示词的优势是按需加载——系统提示词里的每一句都在每一轮请求里付费,技能只有命中时才付费。这跟《上下文是有限预算》里说的是同一件事:把不总是需要的东西从常驻上下文里挪出去。
8. 几个会踩的坑
- 文件名必须是 SKILL.md,Linux 和 macOS 上大小写敏感,写成
skill.md不生效 - name 与目录名不一致导致静默跳过
- description 写成"是什么"而不是"什么时候用",命中率极低
- 正文过长,每次激活都吃掉大量上下文,且会挤掉真正相关的内容
- 引用链超过一层,按需加载时断掉
- 复制多份而不是链接一份,改动不同步
最后一条最隐蔽:它会安静地工作几个月,然后在某次改动之后,两个 Agent 表现出不一致的行为,而你查不出原因。
Skills 与规则沉淀到一定规模之后,会自然出现一个问题:这套东西到底有没有用。该看哪些数、哪些数会被自己骗到,见《AI 研发基建怎么度量》。