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