如何为你的智能体添加技能支持#

为 AI 智能体或开发工具添加 Agent Skills 支持的指南。

本指南将深入探讨如何为 AI 智能体或开发工具添加 Agent Skills(智能体技能)支持。它涵盖了技能的完整生命周期:发现技能、告知模型相关技能、将内容加载到上下文中,以及确保这些内容在长时间会话中持续有效。

无论你的智能体架构如何,核心集成逻辑都是一致的。具体的实现细节会根据以下两个因素而有所不同:

  • 技能存储在哪里? 本地运行的智能体可以扫描用户文件系统的技能目录。而云端托管或沙箱化的智能体则需要替代的发现机制——例如 API、远程注册中心或捆绑的静态资产。
  • 模型如何访问技能内容? 如果模型具备文件读取能力,它可以直接读取 SKILL.md 文件。否则,你需要提供专用的工具,或者通过编程方式将技能内容注入提示词。

本指南在这些差异产生影响的地方进行了说明。你不需要支持所有场景——只需遵循适合你智能体需求的路径即可。

前提条件:熟悉 Agent Skills 规范,该规范定义了 SKILL.md 的文件格式、Frontmatter 字段和目录约定。

核心原则:渐进式披露#

每一个兼容技能的智能体都遵循相同的“三层加载”策略:

层级加载内容何时加载Token 消耗
1. 目录技能名称 + 描述会话开始时每个技能约 50-100 tokens
2. 指令SKILL.md 的完整正文技能被激活时推荐 < 5000 tokens
3. 资源脚本、引用、资产当指令中显式引用它们时视具体资源而定

模型从一开始就能看到目录,因此它知道有哪些可用技能。当模型判断某个技能相关时,它会加载完整的指令。如果这些指令中引用了辅助文件,模型会根据需要单独加载它们。

这种策略能保持基础上下文精简,同时让模型可以按需获取专业知识。即使一个智能体安装了 20 个技能,它也不需要预先承担 20 套完整指令的 Token 成本——只需为给定对话中实际用到的技能付费。

步骤 1:发现技能#

在会话启动时,寻找所有可用技能并加载其元数据。

扫描位置#

扫描哪些目录取决于智能体的环境。大多数本地运行的智能体至少会扫描两个范围:

  • 项目级别(相对于当前工作目录):特定于某个项目或仓库的技能。
  • 用户级别(相对于用户主目录):跨所有项目对该用户可用的技能。

除此之外,还可以包含其他范围——例如管理员部署的组织级技能,或者随智能体本身捆绑的技能。

在每个范围内,建议同时扫描客户端特定目录和通用的 .agents/skills/ 约定路径

范围路径用途
项目<project>/.<your-client>/skills/你的客户端原生路径
项目<project>/.agents/skills/跨客户端互操作
用户~/.<your-client>/skills/你的客户端原生路径
用户~/.agents/skills/跨客户端互操作

.agents/skills/ 路径已成为跨客户端技能共享的广泛共识。虽然 Agent Skills 规范并没强制要求技能目录的存放位置(它只定义了目录内部的内容),但扫描 .agents/skills/ 意味着其他兼容客户端安装的技能对你的客户端也是可见的,反之亦然。

为了更好的兼容性,一些实现还会扫描 .claude/skills/(项目级和用户级),因为许多现有技能是安装在那里的。其他可选位置还包括向上追溯至 Git 根目录的父目录(对 Monorepo 很有用)、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 的技能),可以选择“先到先得”或“后到先得”——只要保持一致即可。发生冲突时建议记录一条警告,以便用户知道某个技能被覆盖了。

信任考量#

项目级技能来源于正在开发的仓库,而仓库可能是不可信的(例如新克隆的开源项目)。考虑在加载项目级技能前进行一种“信任检查”——只有当用户将该项目文件夹标记为受信任时才加载技能。这可以防止恶意仓库静默地向智能体上下文中注入指令。

云端托管和沙箱化智能体#

如果你的智能体在容器或远程服务器上运行,它将无法访问用户的本地文件系统。此时发现机制需要根据技能范围有所调整:

  • 项目级技能 通常最容易处理。如果智能体在克隆的仓库上操作(即使是在沙箱内),项目级技能随代码一起存在,可以直接从仓库目录树中扫描。
  • 用户级和组织级技能 在沙箱中不存在。你需要从外部来源预置它们——例如,克隆一个配置库、通过智能体设置接受技能 URL 或安装包,或者允许用户通过 Web 界面上传技能目录。
  • 内置技能 可以作为静态资产封装在智能体的部署包中,使其在每个会话中无需外部获取即可使用。

一旦技能对智能体可用,后续生命周期——解析、披露、激活——的处理方式都是相同的。

步骤 2:解析 SKILL.md 文件#

对于每个发现的 SKILL.md,提取其元数据和正文内容。

Frontmatter 提取#

一个 SKILL.md 文件分为两部分:位于 --- 限定符之间的 YAML Frontmatter,以及结束限定符之后的 Markdown 正文。解析步骤如下:

  1. 寻找文件开头的第一个 --- 以及随后的第二个 ---
  2. 解析之间的 YAML 块。提取必填字段 namedescription,以及其他可选字段。
  3. 结束限定符 --- 之后的所有内容,经过去除首尾空白后,即为该技能的正文内容

完整的 Frontmatter 字段及约束请参见规范

处理格式错误的 YAML#

为其他客户端编写的技能文件可能包含技术上无效的 YAML,但它们的解析器可能碰巧接受了。最常见的问题是在未加引号的值中包含冒号:

# 技术上无效的 YAML —— 冒号会破坏标准解析
description: Use this skill when: the user asks about PDFs

考虑增加一个回退机制,在重试解析前尝试将此类值包裹在引号中或转换为 YAML 块标量。这能以极小的成本提高跨客户端的兼容性。

宽容校验#

对发现的问题发出警告,但在可能的情况下仍加载该技能:

  • 名称与父目录名称不匹配 → 发出警告,仍加载。
  • 名称超过 64 个字符 → 发出警告,仍加载。
  • 描述缺失或为空 → 跳过该技能(描述对“披露”至关重要),并记录错误。
  • YAML 格式完全无法解析 → 跳过该技能,并记录错误。

记录诊断信息,以便能够向用户展示(如通过 debug 命令、日志文件或 UI),但不要因为一些小的格式问题就封杀整个技能。

规范name 字段定义了严格约束(需匹配目录名、字符集限制、长度限制)。上述“宽容方法”有意放宽了这些限制,目的是提高与为其他客户端编写的技能的兼容性。

存储内容#

每个技能记录至少需要存储三个字段:

字段描述
name来自 Frontmatter
description来自 Frontmatter
locationSKILL.md 文件的绝对路径

将这些记录存储在以 name 为键的内存 Map 中,以便在激活时快速查找。

你也可以在发现阶段就加载并存储正文内容(Frontmatter 之后的内容),或者在收到激活指令时才按需读取。预先存储能让激活更迅速;按需读取则能节省内存消耗,并能实时获取文件的最新修改。

技能的基础目录location 的父目录)在后续解析相对路径和枚举资源时会用到——需要时从 location 派生即可。

步骤 3:向模型披露可用技能#

告知模型关于可用技能的信息,但不要加载它们的全部内容。这是 渐进式披露 的第一层。

构建技能目录#

对于每个发现的技能,在适合你技术栈的结构化格式(XML、JSON 或列表均可)中包含 namedescription,以及可选的 locationSKILL.md 的路径):

<available_skills>
  <skill>
    <name>pdf-processing</name>
    <description>提取 PDF 文本、填写表单、合并文件。在处理 PDF 时使用。</description>
    <location>/home/user/.agents/skills/pdf-processing/SKILL.md</location>
  </skill>
  <skill>
    <name>data-analysis</name>
    <description>分析数据集、生成图表并创建摘要报告。</description>
    <location>/home/user/project/.agents/skills/data-analysis/SKILL.md</location>
  </skill>
</available_skills>

location 字段有两个用途:它支持“文件读取激活”模式(见步骤 4),并为模型提供了一个基准路径,用于解析技能正文中的相对引用(如 scripts/evaluate.py)提供基准路径。如果你的专用激活工具会在结果中包含技能路径(见结构化包装),则可以从目录中省略 location。否则,请保留它。

每个技能大约会增加 50-100 个 tokens。即使安装了几十个技能,目录依然会保持非常小巧。

放置位置#

常见的方案有两种:

  • 系统提示词 (System Prompt) 章节:将目录作为系统提示词中的一个独立部分,并在前面加上如何使用技能的简短说明。这是最简单的方法,适用于任何具备读取文件工具的模型。
  • 工具描述 (Tool Description):将目录嵌入到专用激活工具的描述中(见步骤 4)。这能保持系统提示词清爽,并将“发现”与“激活”逻辑自然地解耦。

两种方案都可行。系统提示词法更简单、兼容性更广;工具描述法在有专用激活工具时更显优雅。

行为指令#

在目录旁边包含一段简短的指令块,告诉模型如何以及何时使用技能。具体的措辞取决于你支持的激活机制:

方案 A:如果模型通过读取文件激活技能:

以下技能为特定任务提供了专业指令。
当任务与技能的描述匹配时,在继续操作前,请使用你的文件读取工具加载
所列位置处的 SKILL.md。
当技能中引用相对路径时,请相对于技能目录(SKILL.md 的父目录)进行解析,
并在工具调用中使用绝对路径。

方案 B:如果模型通过专用工具激活技能:

以下技能为特定任务提供了专业指令。
当任务与技能的描述匹配时,请调用 activate_skill 工具
并传入对应的技能名称,以加载其完整指令。

保持这些指令简洁。目标只是告诉模型技能已经存在以及如何获取它们——技能内容本身加载后会接管详细的行为指导。

过滤机制#

某些技能应当从模型可见的目录中排除。常见原因包括:

  • 用户在设置中明确禁用了该技能。
  • 权限系统拒绝了对该技能的访问。
  • 该技能通过 frontmatter(如 disable-model-invocation: true)主动选择了退出模型调用。

直接从目录中移除这些被过滤的技能,而不要在列出它们后再在激活时报错。这可以防止模型浪费轮次去尝试加载一个无法使用的工具。

当没有可用技能时#

如果没发现任何技能,请完全省略上述所有章节。不要展示空的 <available_skills/> 块或注册一个没有任何选项的技能工具——这只会让模型感到困惑。

步骤 4:激活技能#

当模型或用户选择了一个技能,将其完整指令加载到对话上下文中。这是 渐进式披露 的第二层。

模型驱动激活#

大多数实现方案都依赖于模型的“主观判断”作为激活机制,而不是在客户端硬编码规则匹配。模型读取目录,判断某个技能相关,并主动加载它。

具体的实现方式有两种:

  • 文件读取激活:模型调用其标准的文件读取工具,读取目录中提供的 SKILL.md 路径。无需额外开发——只要智能体有读文件能力即可。
  • 专用工具激活:注册一个特定的工具(如 activate_skill),该工具接收技能名称并返回其原始内容。当模型不能直接读文件时必须用此种方式,即使模型能读文件,这种方式也很有好处。其优势包括:
    • 内容控制:可以决定返回什么内容(例如是否屏蔽 frontmatter)。
    • 结构化识别:可以用标签包裹内容,方便后续上下文管理。
    • 枚举资源:在返回指令的同时,可以列出关联的资源文件。
    • 安全审计:方便强制执行权限检查或记录激活日志。

如果使用专用工具激活,请将 name 参数限制在已发现的技能名称内(例如通过 Enum 枚举)。这能有效防止模型“幻听”不存在的技能。如果没有可用技能,就不要注册此工具。

用户显式激活#

用户也应当能直接激活技能,而不必等待模型决定。最常见的模式是 Slash 命令或提及语法(如 /skill-name$skill-name),这些输入会被客户端拦截并直接注入对应的内容。具体的语法随你定,关键是由客户端处理查找和注入逻辑。

为用户提供一个自动补完的小组件(在用户输入时提示可用技能)会极大地提升这一功能的发现率。

模型接收的内容#

关于指令内容的呈现,有两种主流选择:

  • 全量文件:模型看到包含 Frontmatter 的完整 Markdown。这在文件读取模式下是自然的。某些前置字段(如 compatibility 硬件/环境要求)可能对模型的决策依然有用。
  • 纯正文(去除 Frontmatter):客户端解析并剥离 YAML,仅返回 Markdown 核心指令。大多数采用专用激活工具的实现都倾向于这种更干净的做法。

实践表明,这两种方式都能正常工作。

结构化包装#

如果你采用了专用激活工具,建议将返回的内容包装在可辨识的标签中。例如:

<skill_content name="pdf-processing">
# PDF 处理

## 何时使用此技能
当用户需要处理 PDF 时...

[SKILL.md 剩余内容]

技能根目录: /home/user/.agents/skills/pdf-processing
本技能内的相对路径均相对于该目录解析。

<skill_resources>
  <file>scripts/extract.py</file>
  <file>scripts/merge.py</file>
  <file>references/pdf-spec.md</file>
</skill_resources>
</skill_content>

这种结构化包装的好处:

  • 模型能明确区分“技能指令”与“普通对话”。
  • 客户端在进行上下文压缩(步骤 5)时能轻松识别并特殊处理。
  • 探测关联资源但不强制加载。

枚举配套资源#

当激活工具返回指令时,它可以一并列出目录下的配套资源(脚本、文档、资产),但不要自动读取它们。模型会在需要时,根据指令的引导,使用其文件读取工具按需加载特定的资源。

如果目录极其庞大,建议对列表长度进行截断,并加注“可能未完全显示”。

权限白名单机制#

如果你的智能体有管控文件访问的权限系统,请记得将技能目录加入白名单。否则,每当模型想读取一个配套的脚本或参考文档时,都会弹出一个权限确认框,极大地干扰了自动化体验。

步骤 5:管理随时间变化的技能上下文#

技能加载进来后,需要确保它们在整个会话中都能发挥作用。

防止在上下文压缩中由于误删而失效#

当对话由于太长而不得不截断或总结时,请务必保护技能内容不被清理。技能指令是持久性的行为准则——一旦在会话中途丢失,智能体的表现通常会大幅退化,且往往不会报错,只会表现为“变笨了”。

常用手段:

  • 将激活工具的输出标记为“不可清理”。
  • 利用结构化标签(见步骤 4)在压缩算法中识别出技能板块并强制保留。

去重确认#

记录当前会话中已经激活的技能清单。如果模型或用户尝试再次加载已经存在于上下文中的技能,可以忽略本次请求,以节省上下文空间并防止指令冗余。

子智能体委派(可选)#

这是一种进阶模式。技能指令不再注入给主模型,而是启用一个全新的、专注的子智能体会话,并将技能加载给它。子智能体完成任务后,仅将执行结果和摘要返回给主会话。这种方式能有效地隔离复杂任务的上下文干扰。