如何为你的 Agent 添加 Skills 支持
本指南将介绍如何为 AI Agent 或开发工具添加 Agent Skills 支持。它涵盖了完整的生命周期:发现 Skills、告知模型这些 Skills 的存在、将它们的内容加载到上下文中,以及随着时间的推移保持这些内容的有效性。
无论你的 Agent 架构如何,核心集成方式都是相同的。实现细节因以下两个因素而异:
- Skills 存放在哪里? 本地运行的 Agent 可以扫描用户文件系统中的 Skill 目录。云端托管或沙盒环境中的 Agent 则需要替代的发现机制——API、远程注册表或捆绑的资产。
- 模型如何访问 Skill 内容? 如果模型具备文件读取能力,它可以直接读取
SKILL.md文件。否则,你需要提供一个专用工具,或者通过编程方式将 Skill 内容注入到提示词(prompt)中。
本指南会指出这些差异在何处产生影响。你不需要支持所有场景——只需选择适合你 Agent 的路径即可。
先决条件:熟悉 Agent Skills 规范,该规范定义了 SKILL.md 文件格式、Frontmatter 字段以及目录约定。
核心原则:渐进式披露
每个兼容 Skills 的 Agent 都遵循相同的三层加载策略:
| 层级 | 加载内容 | 加载时机 | Token 消耗 |
|---|---|---|---|
| 1. 目录 (Catalog) | 名称 + 描述 | 会话开始时 | 每个 Skill 约 50-100 tokens |
| 2. 指令 (Instructions) | 完整的 SKILL.md 正文 | 当 Skill 被激活时 | <5000 tokens(推荐) |
| 3. 资源 (Resources) | 脚本、参考资料、资产 | 当指令引用它们时 | 视情况而定 |
模型从一开始就能看到目录,因此它知道有哪些可用的 Skills。当它认为某个 Skill 相关时,就会加载完整的指令。如果这些指令引用了支持文件,模型会根据需要逐个加载它们。
这保持了基础上下文的精简,同时让模型能够按需访问专业知识。一个安装了 20 个 Skills 的 Agent 不需要预先支付 20 套完整指令的 token 成本——只需支付在特定对话中实际使用的那些。
第 1 步:发现 Skills
在会话启动时,查找所有可用的 Skills 并加载它们的元数据。
扫描位置
扫描哪些目录取决于你的 Agent 环境。大多数本地运行的 Agent 至少扫描两个作用域:
- 项目级(相对于工作目录):特定于某个项目或代码库的 Skills。
- 用户级(相对于主目录):对特定用户的所有项目都可用的 Skills。
其他作用域也是可能的——例如,由管理员部署的组织级 Skills,或与 Agent 本身捆绑的 Skills。合适的作用域集合取决于你的 Agent 的部署模型。
在每个作用域内,考虑同时扫描客户端特定目录和 .agents/skills/ 约定目录:
| 作用域 | 路径 | 目的 |
|---|---|---|
| 项目级 | <project>/.<your-client>/skills/ | 你的客户端的原生位置 |
| 项目级 | <project>/.agents/skills/ | 跨客户端互操作性 |
| 用户级 | ~/.<your-client>/skills/ | 你的客户端的原生位置 |
| 用户级 | ~/.agents/skills/ | 跨客户端互操作性 |
.agents/skills/ 路径已成为跨客户端 Skill 共享的广泛采用的约定。虽然 Agent Skills 规范没有强制规定 Skill 目录的存放位置(它只定义了里面的内容),但扫描 .agents/skills/ 意味着其他兼容客户端安装的 Skills 会自动对你的客户端可见,反之亦然。
出于实用兼容性的考虑,一些实现也会扫描 .claude/skills/(包括项目级和用户级),因为许多现有的 Skills 都安装在那里。其他额外的位置包括直到 git 根目录的祖先目录(对 monorepo 很有用)、XDG 配置目录以及用户配置的路径。
扫描什么内容
在每个 Skills 目录中,寻找包含确切命名为 SKILL.md 文件的子目录:
~/.agents/skills/
├── pdf-processing/
│ ├── SKILL.md ← 已发现
│ └── scripts/
│ └── extract.py
├── data-analysis/
│ └── SKILL.md ← 已发现
└── README.md ← 已忽略(不是 skill 目录)
实用的扫描规则:
- 跳过不会包含 Skills 的目录,例如
.git/和node_modules/ - 可选择遵循
.gitignore以避免扫描构建产物 - 设置合理的边界(例如,最大深度 4-6 层,最多 2000 个目录),以防止在大型目录树中发生失控扫描
处理名称冲突
当两个 Skills 共享相同的 name 时,应用确定性的优先级规则。
现有实现中普遍的约定是:项目级 Skills 覆盖用户级 Skills。
在同一作用域内(例如,在 <project>/.agents/skills/ 和 <project>/.<your-client>/skills/ 下都找到了名为 code-review 的 Skills),采用最先发现或最后发现的原则都是可以接受的——选择一种并保持一致。当发生冲突时记录一条警告,以便用户知道某个 Skill 被遮蔽(shadowed)了。
信任考量
项目级 Skills 来自正在处理的代码库,这可能是不受信任的(例如,一个刚克隆的开源项目)。考虑将项目级 Skill 的加载置于信任检查的控制之下——只有在用户将项目文件夹标记为受信任时才加载它们。这可以防止不受信任的代码库将指令静默注入到 Agent 的上下文中。
云端托管和沙盒 Agent
如果你的 Agent 运行在容器或远程服务器中,它将无法访问用户的本地文件系统。发现机制需要根据 Skill 作用域的不同而有所区别:
- 项目级 Skills 通常是最简单的情况。如果 Agent 在克隆的代码库上运行(即使在沙盒内),项目级 Skills 会随代码一起移动,并且可以从代码库的目录树中扫描到。
- 用户级和组织级 Skills 在沙盒中不存在。你需要从外部源提供它们——例如,克隆一个配置库、通过 Agent 的设置接受 Skill URL 或包,或者让用户通过 Web UI 上传 Skill 目录。
- 内置 Skills 可以作为静态资产打包在 Agent 的部署产物中,使其在每个会话中都可用,而无需外部获取。
一旦 Agent 可以获取到 Skills,生命周期的其余部分——解析、披露、激活——的工作方式都是相同的。
第 2 步:解析 SKILL.md 文件
对于每个发现的 SKILL.md,提取元数据和正文内容。
提取 Frontmatter
一个 SKILL.md 文件包含两部分:位于 --- 分隔符之间的 YAML Frontmatter,以及闭合分隔符之后的 Markdown 正文。解析步骤如下:
- 找到文件开头的起始
---以及其后的闭合---。 - 解析它们之间的 YAML 块。提取
name和description(必填),以及任何可选字段。 - 闭合
---之后的所有内容(去除首尾空白)即为该 Skill 的正文内容。
有关完整的 Frontmatter 字段集及其约束,请参阅规范。
处理格式错误的 YAML
为其他客户端编写的 Skill 文件可能包含技术上无效的 YAML,但它们的解析器碰巧接受了。最常见的问题是包含冒号的未加引号的值:
# 技术上无效的 YAML —— 冒号会破坏解析
description: Use this skill when: the user asks about PDFs
考虑一种后备方案,在重试之前将此类值用引号包裹,或将它们转换为 YAML 块标量。这能以极低的成本提高跨客户端兼容性。
宽松验证
对问题发出警告,但在可能的情况下仍加载该 Skill:
- 名称与父目录名称不匹配 → 警告,仍然加载
- 名称超过 64 个字符 → 警告,仍然加载
- 描述缺失或为空 → 跳过该 Skill(描述对于披露至关重要),记录错误
- YAML 完全无法解析 → 跳过该 Skill,记录错误
记录诊断信息以便向用户展示(在调试命令、日志文件或 UI 中),但不要因为表面问题而阻止 Skill 加载。
规范对 name 字段定义了严格的约束(匹配父目录、字符集、最大长度)。上述宽松的方法故意放宽了这些约束,以提高与为其他客户端编写的 Skills 的兼容性。
存储什么内容
至少,每个 Skill 记录需要三个字段:
| 字段 | 描述 |
|---|---|
name | 来自 Frontmatter |
description | 来自 Frontmatter |
location | SKILL.md 文件的绝对路径 |
将这些内容存储在以 name 为键的内存映射中,以便在激活期间快速查找。
你也可以在发现时存储正文(Frontmatter 之后的 Markdown 内容),或者在激活时从 location 读取它。存储它可以使激活更快;在激活时读取它总体上占用更少的内存,并且可以获取两次激活之间对 Skill 文件的更改。
稍后需要该 Skill 的基础目录(location 的父目录)来解析相对路径并枚举捆绑的资源——在需要时从 location 派生它。
第 3 步:向模型披露可用的 Skills
告诉模型存在哪些 Skills,而不加载它们的完整内容。这是渐进式披露的第 1 层。
构建 Skill 目录
对于每个发现的 Skill,将 name、description 以及可选的 location(SKILL.md 文件的路径)包含在适合你技术栈的任何结构化格式中——XML、JSON 或项目符号列表都可以:
<available_skills>
<skill>
<name>pdf-processing</name>
<description>Extract PDF text, fill forms, merge files. Use when handling PDFs.</description>
<location>/home/user/.agents/skills/pdf-processing/SKILL.md</location>
</skill>
<skill>
<name>data-analysis</name>
<description>Analyze datasets, generate charts, and create summary reports.</description>
<location>/home/user/project/.agents/skills/data-analysis/SKILL.md</location>
</skill>
</available_skills>
location 字段有两个用途:它启用了文件读取激活(参见第 4 步),并为模型提供了一个基础路径,用于解析 Skill 正文中的相对引用(如 scripts/evaluate.py)。如果你的专用激活工具在其结果中提供了 Skill 目录路径(参见第 4 步中的结构化包装),你可以从目录中省略 location。否则,请包含它。
每个 Skill 大约为目录增加 50-100 个 tokens。即使安装了数十个 Skills,目录仍然保持紧凑。
将目录放置在何处
有两种常见的方法:
系统提示词部分:将目录作为带有标签的部分添加到系统提示词中,并在前面加上关于如何使用 Skills 的简短说明。这是最简单的方法,适用于任何可以访问文件读取工具的模型。
工具描述:将目录嵌入到专用 Skill 激活工具的描述中(参见第 4 步)。这保持了系统提示词的整洁,并自然地将发现与激活结合在一起。
两者都有效。系统提示词放置更简单且兼容性更广;当你有专用的激活工具时,工具描述嵌入则更整洁。
行为指令
在目录旁边包含一个简短的指令块,告诉模型如何以及何时使用 Skills。措辞取决于你支持哪种激活机制(参见第 4 步):
如果模型通过读取文件来激活 Skills:
The following skills provide specialized instructions for specific tasks.
When a task matches a skill's description, use your file-read tool to load
the SKILL.md at the listed location before proceeding.
When a skill references relative paths, resolve them against the skill's
directory (the parent of SKILL.md) and use absolute paths in tool calls.
如果模型通过专用工具激活 Skills:
The following skills provide specialized instructions for specific tasks.
When a task matches a skill's description, call the activate_skill tool
with the skill's name to load its full instructions.
保持这些指令简明扼要。目标是告诉模型存在 Skills 以及如何加载它们——一旦加载,Skill 内容本身会提供详细的指令。
过滤
某些 Skills 应该从目录中排除。常见原因包括:
- 用户在设置中禁用了该 Skill
- 权限系统拒绝访问该 Skill
- 该 Skill 已选择退出模型驱动的激活(例如,通过
disable-model-invocation标志)
从目录中完全隐藏被过滤的 Skills,而不是列出它们并在激活时进行拦截。这可以防止模型浪费对话轮次去尝试加载它无法使用的 Skills。
当没有可用的 Skills 时
如果没有发现任何 Skills,请完全省略目录和行为指令。不要显示空的 <available_skills/> 块,也不要注册没有有效选项的 Skill 工具——这会使模型感到困惑。
第 4 步:激活 Skills
当模型或用户选择了一个 Skill 时,将完整的指令传递到对话上下文中。这是渐进式披露的第 2 层。
模型驱动的激活
大多数实现依赖模型自身的判断作为激活机制,而不是在宿主端实现触发器匹配或关键字检测。模型读取目录(来自第 3 步),判断某个 Skill 与当前任务相关,并加载它。
两种实现模式:
文件读取激活:模型使用目录中的 SKILL.md 路径调用其标准的文件读取工具。不需要特殊的基础设施——Agent 现有的文件读取能力就足够了。模型将文件内容作为工具结果接收。当模型具有文件访问权限时,这是最简单的方法。
专用工具激活:注册一个工具(例如 activate_skill),该工具接收 Skill 名称并返回内容。当模型无法直接读取文件时,这是必需的;即使模型可以读取文件,这也是可选的(但很有用)。与原始文件读取相比的优势:
- 控制返回的内容——例如,剥离 YAML Frontmatter 或保留它(参见下面的模型接收的内容)
- 将内容包装在结构化标签中,以便在上下文管理期间进行识别
- 在指令旁边列出捆绑的资源(例如
references/*) - 强制执行权限或提示用户同意
- 跟踪激活以进行分析
如果你使用专用的激活工具,请将 name 参数约束为有效 Skill 名称的集合(例如,作为工具 Schema 中的枚举)。这可以防止模型幻觉出不存在的 Skill 名称。如果没有可用的 Skills,则完全不要注册该工具。
用户显式激活
用户也应该能够直接激活 Skills,而无需等待模型做出决定。最常见的模式是宿主拦截的斜杠命令或提及语法(/skill-name 或 $skill-name)。具体语法由你决定——核心思想是宿主处理查找和注入,因此模型接收 Skill 内容而无需自己采取激活操作。
自动补全小部件(在用户输入时列出可用的 Skills)也可以使其易于发现。
模型接收的内容
当一个 Skill 被激活时,模型会接收该 Skill 的指令。关于该内容的具体形式,有两个选项:
完整文件:模型看到整个 SKILL.md,包括 YAML Frontmatter。这是文件读取激活的自然结果,模型读取的是原始文件。对于专用工具来说,这也是一个有效的选择。Frontmatter 可能包含在激活时有用的字段——例如,compatibility 记录了环境要求,可以为模型如何执行 Skill 的指令提供参考。
仅正文(剥离 Frontmatter):宿主解析并移除 YAML Frontmatter,仅返回 Markdown 指令。在具有专用激活工具的现有实现中,大多数采用这种方法——在发现阶段提取 name 和 description 后剥离 Frontmatter。
这两种方法在实践中都有效。
结构化包装
如果你使用专用的激活工具,考虑将 Skill 内容包装在标识标签中。例如:
<skill_content name="pdf-processing">
# PDF Processing
## When to use this skill
Use this skill when the user needs to work with PDF files...
[rest of SKILL.md body]
Skill directory: /home/user/.agents/skills/pdf-processing
Relative paths in this skill are relative to the skill directory.
<skill_resources>
<file>scripts/extract.py</file>
<file>scripts/merge.py</file>
<file>references/pdf-spec-summary.md</file>
</skill_resources>
</skill_content>
这具有实际的好处:
- 模型可以清楚地区分 Skill 指令与其他对话内容
- 宿主可以在上下文压缩期间识别 Skill 内容(第 5 步)
- 捆绑的资源会呈现给模型,而无需急切加载
列出捆绑的资源
当专用激活工具返回 Skill 内容时,它还可以枚举 Skill 目录中的支持文件(脚本、参考资料、资产)——但它不应该急切地读取它们。当 Skill 的指令引用特定文件时,模型会使用其文件读取工具按需加载它们。
对于大型 Skill 目录,考虑限制列表的长度,并注明它可能是不完整的。
权限白名单
如果你的 Agent 有一个控制文件访问的权限系统,请将 Skill 目录加入白名单,以便模型可以读取捆绑的资源而不会触发用户确认提示。如果没有这个设置,每次引用捆绑的脚本或参考文件都会导致弹出权限对话框,从而打断那些包含 SKILL.md 本身之外资源的 Skills 的工作流。
第 5 步:随着时间的推移管理 Skill 上下文
一旦 Skill 指令进入对话上下文,请在整个会话期间保持它们的有效性。
保护 Skill 内容免受上下文压缩的影响
如果你的 Agent 在上下文窗口填满时会截断或总结较旧的消息,请豁免对 Skill 内容的修剪。Skill 指令是持久的行为指导——在对话中途丢失它们会静默地降低 Agent 的性能,而没有任何可见的错误。模型会继续运行,但缺少了 Skill 提供的专业指令。
常见方法:
- 将 Skill 工具的输出标记为受保护,以便修剪算法跳过它们
- 使用第 4 步中的结构化标签来识别 Skill 内容,并在压缩期间保留它
激活去重
考虑跟踪在当前会话中已激活了哪些 Skills。如果模型(或用户)尝试加载已经存在于上下文中的 Skill,你可以跳过重新注入,以避免相同的指令在对话中多次出现。
子 Agent 委托(可选)
这是一种仅受部分客户端支持的高级模式。与其将 Skill 指令注入到主对话中,不如在一个独立的子 Agent 会话中运行该 Skill。子 Agent 接收 Skill 指令,执行任务,并将工作总结返回给主对话。
当一个 Skill 的工作流足够复杂,能够从专用的、专注的会话中受益时,这种模式非常有用。