Claude 的 Agent Skills 系统代表了一种复杂的基于提示的元工具架构,它通过专门的指令注入来扩展大型语言模型(LLM)的能力。与传统的函数调用或代码执行不同,Skills 通过 提示扩展 和 上下文修改 来修改 Claude 处理后续请求的方式,而无需编写可执行代码。
本文深入探讨了 Claude 的 Agent Skills 系统,从基本原理开始,记录了工具名为“Skill”的元工具如何将特定领域的提示注入到对话上下文中。我们将使用 skill-creator 和 internal-comms 技能作为案例研究,检查从文件解析到 API 请求结构到 Claude 的决策过程的整个生命周期。
Claude 使用 Skills 来提高其执行特定任务的能力。Skills 被定义为包含指令、脚本和资源的文件夹,Claude 可以在需要时加载它们。Claude 使用 声明式、基于提示的系统 来发现和调用技能。AI 模型(Claude)根据其系统提示中呈现的文本描述做出调用 Skills 的决定。没有算法技能选择或 AI 驱动的意图检测 在代码级别。决策完全发生在 Claude 的推理过程中,基于提供的技能描述。
Skills 不是可执行代码。它们不运行 Python 或 JavaScript,也没有 HTTP 服务器或函数调用发生在幕后。它们也没有硬编码到 Claude 的系统提示中。Skills 存在于 API 请求结构的独立部分。
那么,什么是 Skills?Skills 是将特定领域的指令注入到对话上下文中的专用提示模板。当技能被调用时,它修改了对话上下文(通过注入指令提示)和执行上下文(通过更改工具权限和可能切换模型)。与其直接执行操作,技能扩展为详细的提示,准备 Claude 解决特定类型的问题。每个技能都出现在 Claude 见到的工具模式的动态添加中。
当用户发送请求时,Claude 收到三件事情:用户消息、可用的工具(Read、Write、Bash 等)和 Skill 工具。Skill 工具的描述包含每个可用技能的格式化列表,包括其 name、description 和其他字段的组合。Claude 读取此列表,并使用其本机语言理解来匹配用户的意图与技能描述。如果用户说“帮助我创建一个技能用于日志”,Claude 会看到 internal-comms 技能的描述(“当用户想要使用公司喜欢的格式编写内部通信时”),识别匹配,并使用 command: "internal-comms" 调用 Skill 工具。
术语说明: Skill 工具(大写 S)= 管理所有技能的元工具。它出现在 Claude 的工具数组中,旁边是 Read、Write、Bash 等。 skills(小写 s)= 像 pdf、skill-creator、internal-comms 这样的个别技能。这些是 Skill 工具加载的专用指令模板。
以下是对 Skills 如何被 Claude 使用的更直观的表示:

技能选择机制在代码级别没有算法路由或意图分类。Claude 代码不使用嵌入、分类器或模式匹配来决定调用哪个技能。相反,系统将所有可用的技能格式化为嵌入在 Skill 工具提示中的文本描述,并让 Claude 的语言模型做出决定。这是纯粹的 LLM 推理。没有正则表达式、没有关键字匹配、没有基于 ML 的意图检测。决策发生在 Claude 的转换器的前向传递中,而不是在应用代码中。
当 Claude 调用一个技能时,系统遵循一个简单的工作流:它加载一个 Markdown 文件(SKILL.md),将其扩展为详细的指令,将这些指令作为新的用户消息注入到对话上下文中,修改执行上下文(允许的工具、模型选择),并在这种丰富的环境中继续对话。这与传统工具根本不同,传统工具执行并返回结果。技能 准备 Claude 解决问题,而不是直接解决它。
以下是帮助更好地区分工具和技能及其功能的表格:
| 方面 | 传统工具 | 技能 |
|---|---|---|
| 执行模型 | 同步、直接 | 提示扩展 |
| 目的 | 执行特定操作 | 指导复杂工作流 |
| 返回值 | 立即结果 | 对话上下文 + 执行上下文更改 |
| 示例 | Read、Write、Bash | internal-comms、skill-creator |
| 并发性 | 通常安全 | 不是并发安全 |
| 类型 | 各种 | 始终为“提示” |
现在,让我们深入了解如何通过检查 Anthropic 的技能存储库中的 skill-creator 技能 作为案例研究来构建技能。作为提醒,agent Skills 是包含指令、脚本和资源的文件夹,代理可以动态发现和加载它们以更好地执行特定任务。Skills 通过将您的专业知识打包为 Claude 的可组合资源来扩展 Claude 的功能,从而将通用代理转变为适合您的需求的专用代理。
关键见解:技能 = 提示模板 + 对话上下文注入 + 执行上下文修改 + 可选数据文件和 Python 脚本
每个 Skill 都在一个名为 SKILL.md(大小写不敏感)的 Markdown 文件中定义,文件中包含可选的捆绑文件,存储在 /scripts、/references 和 /assets 下。这些捆绑文件可以是 Python 脚本、shell 脚本、字体定义、模板等。使用 skill-creator 作为示例,它包含 SKILL.md、LICENSE.txt 用于许可,以及 /scripts 文件夹中的几个 Python 脚本。skill-creator 没有 /references 或 /assets。

技能从多个来源发现和加载。Claude 代码扫描用户设置(~/.config/claude/skills/)、项目设置(.claude/skills/)、插件提供的技能和内置技能来构建可用技能列表。对于 Claude Desktop,我们可以上传自定义技能,如下所示。

注意:构建技能的最重要概念是渐进式披露 - 只显示足够的信息来帮助代理决定下一步该做什么,然后在它们需要时显示更多详细信息。在技能的情况下, 披露前置内容:最小(名称、描述、许可) 如果选择了技能,则加载 SKILL.md:全面但专注 然后加载帮助资产、参考和脚本,因为技能正在被执行
SKILL.md 是技能提示的核心。它是一个遵循两部分结构的 Markdown 文件 - 前置内容和内容。前置内容配置技能的运行方式(权限、模型、元数据),而 Markdown 内容告诉 Claude 该做什么。前置内容 是 Markdown 文件头部以 YAML 编写的部分。
┌─────────────────────────────────────┐
│ 1. YAML 前置内容(元数据) │ ← 配置
│ --- │
│ 名称:技能名称 │
│ 描述:简要概述 │
│ 允许的工具:“Bash、Read” │
│ 版本:1.0.0 │
│ --- │
├─────────────────────────────────────┤
│ 2. Markdown 内容(指令) │ ← Claude 的提示
│ │
│ 目的说明 │
│ 详细指令 │
│ 示例和指南 │
│ 步骤骤骤程序 │
└─────────────────────────────────────┘
前置内容包含控制 Claude 如何发现和使用技能的元数据。例如,以下是 skill-creator 的前置内容:
---
名称:skill-creator
描述:创建有效技能的指南。此技能应在用户想要创建一个新技能(或更新现有技能)以使用专门的知识、工作流或工具集成来扩展 Claude 的功能时使用。
许可证:LICENSE.txt 中的完整条款
---
让我们逐一介绍前置内容的字段。

自我解释。技能的名称。技能的名称用作 Skill 工具中的命令。
技能的名称用作 Skill 工具中的命令。
描述字段提供了技能的简要摘要。这是 Claude 用于确定何时调用技能的主要信号。在上面的示例中,描述明确指出“此技能应在用户想要创建新技能时使用” - 这种清晰、面向操作的语言有助于 Claude 将用户意图与技能功能匹配。
系统会自动将源信息追加到描述中(例如 (plugin:skills)),这有助于区分来自不同来源的技能,当多个技能被加载时。
重要注意:
when_to_use字段在代码库中被广泛使用,但在任何官方 Anthropic 文档中都没有记录。该字段可能是: 一个被逐步淘汰的弃用功能 一个内部/实验功能,尚未获得官方支持 一个计划功能,尚未发布 推荐:改为使用详细的描述字段。避免在生产技能中使用when_to_use,直到它出现在官方文档中。
尽管它没有被记录下来,但以下是 when_to_use 当前在代码库中如何工作:
函数 formatSkill( 技能) {
let 描述 = 技能.whenToUse ?
` ${技能描述} - ${技能.whenToUse} ` :
技能描述;
返回 `" ${技能名称} ": ${描述} `;
}
当存在时,when_to_use 会使用连字符分隔符追加到描述中。例如:
尽管没有文档说明,但以下是 when_to_use 在代码库中的当前工作原理:
function formatSkill(skill) {
let description = skill.whenToUse ?
` ${ skill . description } - ${ skill . whenToUse } ` :
skill.description;
return `" ${ skill . name } ": ${ description } `;
}
当 when_to_use 存在时,它会被追加到描述中,使用连字符作为分隔符。例如:
"skill-creator": Create well - structured, reusable skills...-When user wants to build a custom skill package with scripts, references, or assets
这个组合字符串是 Claude 在 Skill 工具的提示中看到的内容。然而,由于这种行为没有文档说明,因此它可能会在未来的版本中更改或被删除。更安全的方法是直接在 description 字段中包含使用指南,如上面的 skill-creator 示例所示。
自解释。
allowed-tools 字段定义了技能可以在不需要用户批准的情况下使用的工具,类似于 Claude 的 allowed-tools。
这是一个以逗号分隔的字符串,它被解析为允许的工具名称数组。您可以使用通配符来限定权限,例如 Bash(git:*) 只允许 git 子命令,而 Bash(npm:*) 允许所有 npm 操作。skill-creator 技能使用 "Read,Write,Bash,Glob,Grep,Edit" 来提供广泛的文件和搜索功能。一个常见的错误是列出所有可用的工具,这会创建安全风险并破坏安全模型。
只包括您的技能实际需要的内容 - 如果您只是读写文件,则
"Read,Write"就足够了。
#✅ skill - creator 允许多个工具
allowed - tools: " Read,Write,Bash,Glob,Grep,Edit"
#✅ 只允许特定的 git 命令
allowed - tools: " Bash(git status:*),Bash(git diff:*),Bash(git log:*),Read,Grep"
#✅ 只允许文件操作
allowed - tools: " Read,Write,Edit,Glob,Grep"
#❌ 不必要的表面区域
allowed - tools: " Bash,Read,Write,Edit,Glob,Grep,WebSearch,Task,Agent"
#❌ 不必要的表面区域, 包括所有 npm 命令
allowed - tools: " Bash(npm:*),Read,Write"
model 字段定义了技能可以使用的模型。它默认继承用户会话中的当前模型。对于像代码审查这样的复杂任务,技能可以请求更强大的模型,例如 Claude Opus 或其他 OSS 中文模型。
model: " claude-opus-4-20250514"
# 使用特定的模型
model: " inherit"
# 使用会话的当前模型( 默认)
技能支持三个可选的前置字段,用于版本控制和调用控制。version 字段(例如 version: “1.0.0”)是一个用于跟踪技能版本的元数据字段,主要用于文档和技能管理目的。
disable-model-invocation 字段(布尔值)防止 Claude 自动通过 Skill 工具调用技能。当设置为 true 时,技能将被排除在 Claude 显示的列表中,只能通过 /skill-name 手动调用,使其适合危险操作、配置命令或需要显式用户控制的交互式工作流。
mode 字段(布尔值)将技能分类为“模式命令”,它修改 Claude 的行为或上下文。当设置为 true 时,技能将出现在技能列表顶部的“模式命令”部分(与常规实用技能分开),使其适合调试模式、专家模式或审查模式等技能,这些技能建立特定的操作上下文或工作流。
在前置字段之后是 Markdown 内容 - 实际的提示,当 skill 被调用时,Claude 会看到这个提示。这是您定义 skill 行为、指令和工作流的地方。编写有效的技能提示的关键是保持它们的专注性并使用渐进式披露:在 SKILL.md 中提供核心指令,并引用外部文件以获取详细内容。
以下是一个推荐的内容结构:
-- -
# 前置字段在这里
-- -
#[简要目的声明 - 1 - 2 句]
# # 概述
[这个技能做什么、 什么时候使用它、 它提供什么]
# # 前置条件
[所需的工具、 文件或上下文]
# # 指令
# # # 步骤 1:[第一个操作]
[命令式指令]
[如果需要的示例]
# # # 步骤 2:[下一个操作]
[命令式指令]
# # # 步骤 3:[最终操作]
[命令式指令]
# # 输出格式
[如何结构结果]
# # 错误处理
[当事情失败时该怎么办]
# # 示例
[具体的使用示例]
# # 资源
[如果捆绑, 引用 scripts / 、references / 、assets / ]
例如,skill-creator 技能包含以下指令,指定了创建技能所需的工作流程的每个步骤。
# # 技能创建过程
# # # 步骤 1: 使用具体示例理解技能
# # # 步骤 2: 规划可重用的技能内容
# # # 步骤 3: 初始化技能
# # # 步骤 4: 编辑技能
# # # 步骤 5: 打包技能
当 Claude 调用这个技能时,它会接收到整个提示作为新的指令,带有基本目录路径作为前缀。{baseDir} 变量解析为技能的安装目录,允许 Claude 使用 Read 工具加载引用文件:Read({baseDir}/scripts/init_skill.py)。这种模式使主提示保持简洁,同时提供详细的文档以备需要。
提示内容的最佳实践:
保持在 5,000 个字以内(约 800 行),以避免让上下文感到不知所措
使用命令式语言(“分析代码以...”)而不是第二人称(“您应该分析...”)
引用外部文件以获取详细内容,而不是嵌入所有内容
使用 {baseDir} 作为路径,从不硬编码绝对路径,如 /home/user/project/
❌
Read / home / user / project / config.json✅ Read {
baseDir
}
/config.json
当技能被调用时,Claude 只能访问 allowed-tools 中指定的工具,模型可能会被覆盖,如果在前置字段中指定。技能的基本目录路径会被自动提供,使捆绑的资源可访问。
Skills 在您将支持资源与 SKILL.md 捆绑在一起时变得强大。标准结构使用三个目录,每个目录都有特定的用途:
my - skill / ├──SKILL.md # 核心提示和指令├── scripts / # 可执行的 Python / Bash 脚本├── references / # 加载到上下文中的文档└── assets / # 模板和二进制文件
为什么要捆绑资源? 保持 SKILL.md 简洁(在 5,000 个字以内)可以防止 Claude 的上下文窗口感到不知所措。捆绑的资源允许您提供详细的文档、自动化脚本和模板,而无需使主提示变得臃肿。Claude 只有在需要时才会加载它们,使用渐进式披露。
scripts/ 目录包含 Claude 通过 Bash 工具运行的可执行代码 - 自动化脚本、数据处理器、验证器或代码生成器,它们执行确定性操作。
例如,skill-creator 的 SKILL.md 引用了这样的脚本:
当从头开始创建新技能时, 请始终运行 `init_skill.py`
脚本。 该脚本方便地生成一个新模板技能目录, 该目录自动包含技能所需的所有内容, 使技能创建过程更加高效和可靠。
用法:
``
` scripts/init_skill.py --path `
``
该脚本:
-
在指定路径创建技能目录 -
生成具有适当前置字段和 TODO 占位符的 SKILL.md 模板 -
创建示例资源目录: scripts / 、references / 和 assets /
-在每个目录中添加示例文件, 可以自定义或删除
当 Claude 看到这个指令时,它会执行 python {baseDir}/scripts/init_skill.py。{baseDir} 变量会自动解析为技能的安装路径,使技能在不同环境中可移植。
使用 scripts/ 目录进行 复杂的多步骤操作、数据转换、API 交互或任何需要在代码中而不是自然语言中表达精确逻辑的任务。
references/ 目录存储 Claude 在引用时读入其上下文的文档。这是文本内容 - Markdown 文件、JSON 模式、配置模板或 Claude 完成任务所需的任何文档。
例如,mcp-creator 的 SKILL.md 引用了这样的参考资料:
# # # # 1.4 研究框架文档
**
加载并阅读以下参考文件: **
- ** MCP 最佳实践 **: [📋查看最佳实践](. / reference / mcp_best_practices.md) - 所有 MCP 服务器的核心指南
**
对于 Python 实现, 还加载: **
- ** Python SDK 文档 **: 使用 WebFetch 加载 `https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md` -
[🐍Python 实现指南](. / reference / python_mcp_server.md) - Python 特定的最佳实践和示例
加载和阅读以下参考文件:
对于 Python 实现,还需要加载:
https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md对于 Node/TypeScript 实现,还需要加载:
https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/main/README.md当 Claude 遇到这些指令时,它使用 Read 工具:`Read({baseDir}/references/mcp_best_practices.md)`。内容被加载到 Claude 的上下文中,提供了详细的信息而不使 SKILL.md变得杂乱。
**使用 references/ 目录**存储详细文档、大型模式库、清单、API 模式或任何对于任务必要但对于 SKILL.md来说过于冗长的文本内容。
#### assets/ 目录
`assets/` 目录包含 Claude 引用的模板和二进制文件,但不加载到上下文中。可以将其视为技能的静态资源 - HTML 模板、CSS 文件、图像、配置模板或字体。
在 SKILL.md 中:
使用 {baseDir}/assets/report-template.html 作为报告结构。 引用 {baseDir}/assets/diagram.png 中的架构图。
Claude 看到文件路径,但不读取内容。相反,它可能将模板复制到新位置,填充占位符,或在生成的输出中引用路径。
**使用 assets/ 目录**存储 HTML/CSS 模板、图像、二进制文件、配置模板或任何 Claude 通过路径而不是读取到上下文中的文件。
`references/` 和 `assets/` 之间的关键区别是
- references/ : 通过 Read 工具加载到 Claude 上下文中的文本内容
- assets/ : 只通过路径引用的文件,不加载到上下文中
这种区别对于上下文管理至关重要。`references/` 中的 10KB Markdown 文件在加载时会消耗上下文令牌。`assets/` 中的 10KB HTML 模板不会。Claude 只知道路径存在。
> 最佳实践:始终使用 {baseDir} 作为路径,永远不要硬编码绝对路径。这使得技能可以在用户环境、项目目录和不同的安装中移植。
### 常见技能模式
与所有工程一样,了解常见模式有助于设计有效的技能。以下是工具集成和工作流设计中最有用的模式。
#### 模式 1:脚本自动化
**用例:** 需要多个命令或确定性逻辑的复杂操作。
此模式将计算任务卸载到 `scripts/` 目录中的 Python 或 Bash 脚本中。技能提示告诉 Claude 执行脚本并处理其输出。

**SKILL.md 示例:**
在目标目录上运行 scripts/analyzer.py:
python {baseDir}/scripts/analyzer.py --path "$USER_PATH" --output report.json
解析生成的 report.json 并呈现结果。
**所需工具:**
allowed-tools : " Bash(python {baseDir}/scripts/:), Read, Write"
#### 模式 2:读取 - 处理 - 写入
**用例:** 文件转换和数据处理。
最简单的模式 - 读取输入,根据指令转换它,然后写入输出。适用于格式转换、数据清理或报告生成。

**SKILL.md 示例:**
**所需工具:**
allowed-tools : " Read, Write"
#### 模式 3:搜索 - 分析 - 报告
**用例:** 代码库分析和模式检测。
使用 Grep 搜索代码库中的模式,读取匹配文件以获取上下文,分析结果,并生成结构化报告。或者,搜索企业数据存储以获取数据,分析检索的数据以获取信息,并生成结构化报告。

**SKILL.md 示例:**
**所需工具:**
allowed-tools : " Grep, Read"
#### 模式 4:命令链执行
**用例:** 具有依赖关系的多步骤操作。
执行一系列命令,其中每个步骤依赖于前一个步骤的成功。常用于 CI/CD 类型的工作流。

**SKILL.md 示例:**
执行分析管道: npm install && npm run lint && npm test
报告每个阶段的结果。
**所需工具:**
allowed-tools : " Bash(npm install:), Bash(npm run:), Read"
### 高级模式
#### 向导式多步骤工作流
**用例:** 需要在每个步骤都需要用户输入的复杂过程。
将复杂任务分解为离散步骤,并在每个阶段之间需要用户确认。适用于设置向导、配置工具或引导式过程。
**SKILL.md 示例:**
#### 模板基于生成
**用例:** 从存储在 `assets/` 中的模板创建结构化输出。
加载模板,使用用户提供或生成的数据填充占位符,并写入结果。常用于报告生成、样板代码创建或文档。
**SKILL.md 示例:**
#### 迭代改进
**用例:** 需要多次迭代以增加深度的过程。
首先进行广泛的分析,然后对已识别的问题进行更深入的分析。适用于代码审查、安全审计或质量分析。
**SKILL.md 示例:**
对于每个高级问题:
对于每个发现:
呈现包含所有发现和建议的最终报告。
#### 上下文聚合
**用例:** 将来自多个来源的信息组合起来以构建综合的理解。
从不同的文件和工具中收集数据,并将其合成为一个连贯的图景。适用于项目摘要、依赖分析或影响评估。
**SKILL.md 示例:**
## 代理技能内部架构
在概述和构建过程之后,我们现在可以研究技能实际的工作原理。技能系统通过一种元工具架构运作,其中一个名为 `Skill` 的工具作为所有个别技能的容器和调度器。这种设计从根本上区分了技能和传统工具,无论是实现还是目的。
> Skill 工具是一个元工具,管理所有技能
## 技能对象设计
传统工具如 `Read`、`Bash` 或 `Write` 执行离散的操作并返回立即的结果。技能的工作方式不同。它们不会直接执行操作,而是将专门的指令注入对话历史,并动态修改 Claude 的执行环境。这是通过两个用户消息实现的 - 一个包含元数据的消息对用户可见,另一个包含完整的技能提示的消息对用户不可见,但发送给 Claude - 并通过修改代理的上下文来改变权限、切换模型和调整思考令牌参数来实现的。

| 功能 | 普通工具 | 技能工具 |
| --- | --- | --- |
| 本质 | 直接操作执行器 | 提示注入 + 上下文修改器 |
| 消息角色 | 助手 → 工具使用 用户 → 工具结果 | 助手 → 工具使用 技能 用户 → 工具结果 用户 → 技能提示 ← 注入! |
| 复杂性 | 简单(3-4 条消息) | 复杂(5-10+ 条消息) |
| 上下文 | 静态 | 动态(每回合修改) |
| 持久性 | 工具交互 | 工具交互 + 技能提示 |
| 令牌开销 | 最小(~100 令牌) | 显著(~1,500+ 令牌每回合) |
| 用例 | 简单、直接任务 | 复杂、引导式工作流 |
复杂性很大。普通工具生成简单的消息交换 - 助手工具调用后跟用户结果。技能注入多个消息,在动态修改的上下文中运行,并带有显著的令牌开销,以提供指导 Claude 行为的专门指令。
了解 `Skill` 元工具的工作原理揭示了该系统的机制。让我们来看看它的结构:
Pd = { 名称: " 技能 ",// 工具名称常量:$N = "Skill"
输入模式:{ 命令:字符串 // 例如 "pdf"、"skill-creator" },
输出模式:{ 成功:布尔值, 命令名称:字符串 },
// 🔑 KEY 字段:此字段生成技能列表 提示:异步 () => fN2 (),
### 输入和输出模式 ```json inputSchema: {
command: string // 例如 "pdf"、"skill-creator"
}
,
outputSchema: {
success: boolean,
commandName: string
}
,
// 🔑 关键字段:此字段生成技能列表
prompt: async () => fN2(),
// 验证和执行
validateInput: async (input, context) => {
/* 5 个错误代码 */ },
checkPermissions: async (input, context) => {
/* 允许/拒绝/询问 */ },
call: async * (input, context) => {
/* 生成消息 + 上下文修改器 */ }
}
prompt 字段使得 Skill 工具与其他工具(如 Read 或 Bash)区别开来,这些工具具有静态描述。相反,Skill 工具使用一个动态提示生成器,该生成器通过聚合所有可用技能的名称和描述来构造其描述。这种方法实现了 渐进式披露 —— 系统仅将最小的元数据(技能名称和描述)加载到 Claude 的初始上下文中,为模型提供足够的信息来决定哪个技能与用户的意图相匹配。完整的技能提示仅在 Claude 选择后加载,从而防止上下文膨胀同时保持可发现性。
async function fN2() {
let A = await atA(),
{
modeCommands: B,
limitedRegularCommands: Q
} = vN2(A),
G = [...B, ...Q].map((W) => W.userFacingName()).join(", ");
l(`Skill 工具包含的技能和命令:${G}`);
let Z = A.length - B.length,
Y = nS6(B),
J = aS6(Q, Z);
return `在主对话中执行一个技能
<skills_instructions>
当用户要求您执行任务时,请检查是否有任何可用的技能可以更有效地完成该任务。技能提供专门的能力和领域知识。
如何使用技能:
- 使用技能名称(无参数)调用技能
- 当您调用技能时,您将看到 <command-message>“{name}” 技能正在加载</command-message>
- 技能的提示将展开并提供完成任务的详细说明
- 示例:
- \`命令: "pdf" \` - 调用 pdf 技能
- \`命令: "xlsx" \` - 调用 xlsx 技能
- \`命令: "ms-office-suite:pdf" \` - 使用完全限定名称调用
重要:
- 仅使用以下列出的可用技能
- 不要调用已经运行的技能
- 不要使用此工具执行内置的 CLI 命令(如 /help、/clear 等)
</skills_instructions>
<available_skills>
${Y}${J}
</available_skills>
`;
}
与某些工具不同,这些工具作为特定助手(如 ChatGPT)的系统提示的一部分存在,Claude 代理技能不在系统提示中。它们存在于 tools 数组中,作为 Skill 工具的描述的一部分。个别技能的名称作为 Skill 元工具的输入模式的 command 字段的一部分来表示。为了更好地可视化其结构,以下是实际的 API 请求结构:
{
"model": "claude-sonnet-4-5-20250929",
"system": "您是 Claude Code,Anthropic 的官方 CLI...", // ← 系统提示
"messages": [{
"role": "user",
"content": "帮助我创建一个新技能"
},
// ... 对话历史
],
"tools": [ // ← 发送到 Claude 的工具数组
{
"name": "Skill", // ← 元工具
"description": "执行一个技能... \n\n <skills_instructions>... \n\n <available_skills> \n ...",
"input_schema": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "技能名称(无参数)" // ← 个别技能的名称
}
}
}
},
{
"name": "Bash",
"description": "执行 Bash 命令...",
// ...
},
{
"name": "Read",
// ...
}
// ... 其他工具
]
}
<available_skills> 部分存在于 Skill 工具的描述中,并为每个 API 请求重新生成。系统通过聚合当前加载的技能(来自用户和项目配置、插件提供的技能和任何内置技能)来动态构建此列表,受 15,000 个字符的令牌预算限制。这个预算限制迫使技能作者编写简洁的描述,并确保工具描述不会让模型的上下文窗口感到不知所措。
大多数 LLM API 支持 role: "system" 消息,这些消息可以理论上携带系统提示。事实上,OpenAI 的 ChatGPT 在其系统提示中包含默认工具,包括 bio 用于记忆、automations 用于任务调度、canmore 用于控制画布、img_gen 用于图像生成、file_search、python 和 web 用于互联网搜索。并且,工具提示占据了其系统提示中的大约 90% 的令牌数。这可能很有用,但如果我们有很多工具和/或技能需要加载到上下文中,这种方法效率不高。
然而,系统消息具有不同的语义,使其不适合技能。系统消息设置全局上下文,该上下文在整个对话过程中持续存在,并且具有比用户指令更高的权威性。
技能需要临时的、范围内的行为。skill-creator 技能应该只影响技能创建相关任务,而不是将 Claude 转变为整个会话的永久 PDF 专家。使用 role: "user" 和 isMeta: true 使技能提示出现在 Claude 的用户输入中,保持临时性和局部性。技能完成后,对话将返回正常的对话上下文和执行上下文,而不会有残留的行为修改。
像 Read、Write 或 Bash 这样的正常工具具有简单的通信模式。当 Claude 调用 Read 时,它发送文件路径,接收文件内容,然后继续工作。用户在其记录中看到“Claude 使用了 Read 工具”,这就足够了。该工具执行了一次操作并返回结果,这就是交互的结束。技能以根本不同的方式运作。与其执行离散的操作并返回结果,技能注入全面的指令集,修改 Claude 理解和处理任务的方式。这就产生了一个设计挑战,正常工具从未面临过:用户需要了解哪些技能正在运行以及它们在做什么,而 Claude 需要详细的、可能冗长的指令来正确执行技能。如果用户在聊天记录中看到完整的技能提示,UI 将被成千上万字的内部 AI 指令弄得混乱不堪。如果技能激活完全隐藏,用户将失去对系统正在代表他们做什么的可见性。解决方案需要将这两个通信通道分离为具有不同可见性规则的不同消息。
技能系统使用每个消息上的 isMeta 标志来控制消息是否出现在用户界面中。当 isMeta: false(或省略标志时,默认为 false)时,消息在用户可以看到的对话记录中呈现。当 isMeta: true 时,消息作为 Claude 的对话上下文的一部分发送到 Anthropic API,但永远不会出现在 UI 中。这个简单的布尔标志使得双通道通信成为可能:一个流用于人类用户,另一个流用于 AI 模型。元工具的元提示!
当技能执行时,系统将两个单独的用户消息注入到对话历史中。第一个消息携带技能元数据,isMeta: false 使其对用户可见作为状态指示器。第二个消息携带完整的技能提示,isMeta: true 将其隐藏在 UI 中,同时使其可供 Claude 使用。这种分离解决了透明度与清晰度的权衡,向用户展示发生了什么而不用让他们感到不知所措。
元数据消息使用前端可以解析和适当显示的简洁 XML 结构:
let metadata = [
`<command-message> ${statusMessage} </command-message>`,
`<command-name> ${skillName} </command-name>`,
args ? `<command-args> ${args} </command-args>` : null
].filter(Boolean).join("\n");
// 消息 1:无 isMeta 标志 → 默认为 false → 可见
messages.push({
content: metadata,
autocheckpoint: checkpointFlag
});
例如,当 PDF 技能激活时,用户在其记录中看到一个干净的加载指示器:
<command-message> “pdf” 技能正在加载 </command-message>
<command-name> pdf </command-name>
<command-args> report.pdf </command-args>
此消息故意保持简洁,通常为 50 到 200 个字符。XML 标签使前端能够以特殊格式呈现它,验证正确的 <command-message> 标签的存在,并在会话期间维护已执行技能的审计跟踪。由于 isMeta 标志在省略时默认为 false,因此此元数据自动出现在 UI 中。
技能提示消息采取相反的方法。它加载来自 SKILL.md 的完整内容,可能会用额外的上下文增强它,并显式设置 isMeta: true 以将其隐藏在用户界面中:
let skillPrompt = await skill.getPromptForCommand(args, context);
// 根据需要增强前置/后置内容
let fullPrompt = prependContent.length > 0 || appendContent.length > 0 ?
[...prependContent, ...appendContent, ...skillPrompt] :
skillPrompt;
// 消息 2:显式 isMeta: true → 隐藏
messages.push({
content: fullPrompt,
isMeta: true // 从 UI 中隐藏,发送到 API
});
典型的技能提示运行 500 到 5,000 个字,提供全面指导以转变 Claude 的行为。PDF 技能提示可能包含:
您是 PDF 处理专家。
您的任务是使用 pdftotext 工具从 PDF 文档中提取文本。
## 过程
1. 验证 PDF 文件是否存在
2. 运行 pdftotext 命令以提取文本
3. 读取输出文件
4. 以清晰的格式向用户呈现提取的文本
## 可用工具
您可以使用:
- Bash(pdftotext: * ) - 用于运行 pdftotext 命令
- Read - 用于读取提取的文本
- Write - 如果需要保存结果
## 输出格式
以清晰的格式呈现提取的文本。
您的任务是使用 pdftotext 工具从 PDF 文档中提取文本。
您可以使用以下工具:
以清晰的格式呈现提取的文本。
基目录:/path/to/skill 用户参数:report.pdf
此提示建立任务上下文, 概述工作流程, 指定可用工具, 定义输出格式, 并提供环境特定路径。 带有标题、 列表和代码块的 Markdown 结构有助于 Claude 解析和遵循指令。 使用 `isMeta: true`,
此整个提示发送到 API, 但永远不会混乱用户的转录。
除了核心元数据和技能提示外, 技能还可以注入附加的条件消息用于附件和权限:
let allMessages = [ createMessage ({ content : metadata , autocheckpoint : flag }), // 1. 元数据 createMessage ({ content : skillPrompt , isMeta : true }), // 2. 技能提示 ... attachmentMessages , // 3. 附件(条件) ...( allowedTools . length || skill . model ? [ createPermissionsMessage ({ // 4. 权限(条件) type : " command_permissions " , allowedTools : allowedTools , model : skill . useSmallFastModel ? getFastModel () : skill . model }) ] : []) ];
附件消息可以携带诊断信息、文件引用或补充技能提示的附加上下文。权限消息仅在技能在其前置内容中指定 `allowed-tools` 或请求模型覆盖时出现,提供修改运行时执行环境的元数据。这种模块化的组合允许每个消息具有特定的目的,并根据技能的配置包含或排除,从而在保持相同的可见性控制通过 `isMeta` 标志的同时处理更复杂的场景。
### 为什么使用两个消息而不是一个?
单个消息设计将迫使做出一个不可能的选择。设置 `isMeta: false` 将使整个消息可见,将数千字的 AI 指令转储到用户的聊天记录中。用户将看到类似这样的内容:
┌─────────────────────────────────────────────┐ │ 加载“pdf”技能 │ │ │ │ 您是一名 PDF 处理专家。 │ │ │ │ 任务是使用 pdftotext 工具从 PDF 文档中提取文本。 │ │ │ │ ## 过程 │ │ │ │ 1. 验证 PDF 文件是否存在 │ │ 2. 运行 pdftotext 命令以提取文本 │ │ 3. 读取输出文件 │ │ ... [500 行更多] ... │ └─────────────────────────────────────────────┘
UI变得不可用,充满了 Claude 而非人类的内部实现细节。或者,设置 `isMeta: true` 将隐藏一切,提供对激活的技能或接收到的参数的透明度。用户将无法看到系统代表他们做了什么。
两个消息的分离通过给每个消息一个不同的 `isMeta` 值来解决这个问题。消息 1 中的 `isMeta: false` 提供了面向用户的透明度。消息 2 中的 `isMeta: true` 为 Claude 提供了详细的指令。这种粒度控制实现了透明度而不至于信息过载。
消息还服务于根本不同的受众和目的:
| 方面 | 元数据消息 | 技能提示消息 |
| --- | --- | --- |
| 受众 | 人类用户 | Claude(AI) |
| 目的 | 状态/透明度 | 指令/指导 |
| 长度 | ~50-200 个字符 | ~500-5,000 个字 |
| 格式 | 结构化 XML | 自然语言 Markdown |
| 可见性 | 应该可见 | 应该隐藏 |
| 内容 | “发生了什么?” | “如何做?” |
代码库甚至通过不同的路径处理这些消息。元数据消息经过 ` ` 标签的解析、验证和 UI 显示。技能提示消息直接发送到 API,而无需解析或验证 - 它是 Claude 推理过程中仅有的原始指令内容。将它们结合起来将违反单一责任原则,因为这将迫使一条消息为两个不同的受众服务两个不同的处理管道。
## 案例研究:执行生命周期
现在,我们已经介绍了 Agent Skills 的内部架构,让我们通过检查使用假设的 `pdf` 技能作为案例研究的完整执行流程来了解当用户说“从 report.pdf 中提取文本”时会发生什么。

### 阶段 1:发现和加载(启动)
当 Claude Code 启动时,它会扫描技能:
async function getAllCommands () { // 从所有来源并行加载 let [ userCommands , skillsAndPlugins , pluginCommands , builtins ] = await Promise . all ([ loadUserCommands (), // ~/.claude/commands/ loadSkills (), // .claude/skills/ + plugins loadPluginCommands (), // Plugin-defined commands getBuiltinCommands () // Hardcoded commands ]);
return [... userCommands , ... skillsAndPlugins , ... pluginCommands , ... builtins ] . filter ( cmd => cmd . isEnabled ()); }
// 特定技能加载 async function loadPluginSkills ( plugin ) { // 检查插件是否有技能 if ( ! plugin . skillsPath ) return [];
// 支持两种模式: // 1. skillsPath 中的根 SKILL.md // 2. 带有 SKILL.md 的子目录
const skillFiles = findSkillMdFiles ( plugin . skillsPath ); const skills = [];
for ( const file of skillFiles ) { const content = readFile ( file ); const { frontmatter , markdown } = parseFrontmatter ( content );
skills . push ({
type : " prompt " ,
name : ${ plugin . name } : ${ getSkillName ( file )} ,
description : ${ frontmatter . description } (plugin: ${ plugin . name } ) ,
whenToUse : frontmatter . when_to_use , // ← 注意:下划线!
allowedTools : parseTools ( frontmatter [ ' allowed-tools ' ]),
model : frontmatter . model === " inherit " ? undefined : frontmatter . model ,
isSkill : true ,
promptContent : markdown ,
// ... 其他字段
});
}
return skills ; }
对于 pdf 技能,这将产生:
{ type : " prompt " , name : " pdf " , description : " 从 PDF 文档中提取文本 (plugin:document-tools) " , whenToUse : " 当用户想要从 PDF 文件中提取或处理文本时 " , allowedTools : [ " Bash(pdftotext:*) " , " Read " , " Write " ], model : undefined , // 使用会话模型 isSkill : true , disableModelInvocation : false , promptContent : " 您是一名 PDF 处理专家... " , // ... 其他字段 }
### 阶段 2:第一回合 - 用户请求和技能选择
用户发送请求:“从 report.pdf 中提取文本”。Claude 接收到此消息以及其工具数组中的 Skill 工具。在 Claude 决定调用 pdf 技能之前,系统必须在 Skill 工具的描述中呈现可用的技能。
#### 技能过滤和呈现
并非所有加载的技能都出现在 Skill 工具中。技能必须在其前置内容中具有 `description` 或 `when_to_use`,否则将被过滤掉。过滤标准:
async function getSkillsForSkillTool () { const allCommands = await getAllCommands ();
return allCommands . filter ( cmd => cmd . type === " prompt " && cmd . isSkill === true && ! cmd . disableModelInvocation && ( cmd . source !== " builtin " || cmd . isModeCommand === true ) && ( cmd . hasUserSpecifiedDescription || cmd . whenToUse ) // ← 必须有一个! ); }
#### 技能格式化
每个技能都格式化为 ` ` 部分。例如,我们的假设 `pdf` 技能可以格式化为
`"pdf": 从 PDF 文档中提取文本 - 当用户想要从 PDF 文件中提取或处理文本时`
function formatSkill ( skill ) {
let name = skill . name ;
let description = skill . whenToUse
? ${ skill . description } - ${ skill . whenToUse }
: skill . description ;
return " ${ name } ": ${ description } ;
}
#### Claude 的决策过程
现在,当用户提示:“从 report.pdf 中提取文本”。Claude 接收到 API 请求,带有 Skill 工具,读取 ` ` 部分,并推理(假设,因为我们没有看到推理痕迹):
内部推理:
请注意,这里没有算法匹配。没有词汇匹配。没有语义匹配。没有搜索。这是 Claude 根据技能描述为其决策做出的纯粹的 LLM 推理。一旦完成,Claude 返回工具使用:
{ "type" : "tool_use" , "id" : "toolu_123abc" , "name" : "Skill" , "input" : { "command" : "pdf" } }
### 阶段 3:技能工具执行
Skill 工具现在执行。这对应于序列图中的黄色“SKILL TOOL EXECUTION”框,它在产生结果之前执行验证、权限检查、文件加载和上下文修改。
#### 步骤 1:验证
async validateInput ({ command }, context ) { let skillName = command . trim (). replace ( /^ / / , "" );
// 错误 1:空 if ( ! skillName ) return { result : false , errorCode : 1 };
// 错误 2:未知技能 const allSkills = await getAllCommands (); if ( ! skillExists ( skillName , allSkills )) { return { result : false , errorCode : 2 }; }
// 错误 3:无法加载 const skill = getSkill ( skillName , allSkills ); if ( ! skill ) return { result : false , errorCode : 3 };
// 错误 4:禁用模型调用 if ( skill . disableModelInvocation ) { return { result : false , errorCode : 4 }; }
// 错误 5:不是基于提示的 if ( skill . type !== " prompt " ) { return { result : false , errorCode : 5 }; }
return { result : true }; }
pdf 技能通过所有验证检查
#### 步骤 2:权限检查
async checkPermissions ({ command }, context ) { const skillName = command . trim (). replace ( /^ / / , "" ); const permContext = ( await context . getAppState ()). toolPermissionContext ;
// 检查拒绝规则 for ( const [ pattern , rule ] of getDenyRules ( permContext )) { if ( matches ( skillName , pattern )) { return { behavior : " deny " , message : " 被权限规则阻止 " }; } }
// 检查允许规则 for ( const [ pattern , rule ] of getAllowRules ( permContext )) { if ( matches ( skillName , pattern )) { return { behavior : " allow " }; } }
经过验证和权限审批,Skill 工具加载技能文件并准备执行上下文修改:
async * call({
command
}, context) {
const skillName = command.trim().replace(/^ \/ /, "");
const allSkills = await getAllCommands();
const skill = getSkill(skillName, allSkills);
// 加载技能提示
const promptContent = await skill.getPromptForCommand("", context);
// 生成元数据标签
const metadata = [
`<command-message>The " ${ skill . userFacingName ()} " 技能正在加载</command-message>`,
`<command-name> ${ skill . userFacingName ()} </command-name>`
].join(' \n ');
// 创建消息
const messages = [{
type: " user ",
content: metadata
}, // 对用户可见
{
type: " user ",
content: promptContent,
isMeta: true
}, // 对用户隐藏,对 Claude 可见
// ... 附件、权限
];
// 提取配置
const allowedTools = skill.allowedTools || [];
const modelOverride = skill.model;
// 生成结果并包含执行上下文修改器
yield {
type: " result ",
data: {
success: true,
commandName: skillName
},
newMessages: messages,
// 执行上下文修改函数
contextModifier(context) {
let modified = context;
// 注入允许的工具
if (allowedTools.length > 0) {
modified = {
...modified,
async getAppState() {
const state = await context.getAppState();
return {
...state,
toolPermissionContext: {
...state.toolPermissionContext,
alwaysAllowRules: {
...state.toolPermissionContext.alwaysAllowRules,
command: [
...state.toolPermissionContext.alwaysAllowRules.command || [],
...allowedTools // ← 预先批准这些工具
]
}
}
};
}
};
}
// 覆盖模型
if (modelOverride) {
modified = {
...modified,
options: {
...modified.options,
mainLoopModel: modelOverride
}
};
}
return modified;
}
};
}
Skill 工具生成结果,包含 newMessages(元数据 + 技能提示 + 权限用于会话上下文注入)和 contextModifier(工具权限 + 模型覆盖用于执行上下文修改)。这完成了黄色“SKILL TOOL EXECUTION”框从序列图中。
系统构造完整的消息数组发送到 Anthropic API。这包括会话中的所有消息以及新注入的技能消息:
// 完整的消息数组发送到 API 用于 Turn 1
{
model: " claude-sonnet-4-5-20250929 ",
messages: [{
role: " user ",
content: " 从 report.pdf 中提取文本 "
},
{
role: " assistant ",
content: [{
type: " tool_use ",
id: " toolu_123abc ",
name: " 技能 ",
input: {
command: " pdf "
}
}]
},
{
role: " user ",
content: " <command-message>\" pdf \" 技能正在加载</command-message> \n <command-name>pdf</command-name> "
// isMeta: false(默认)- 对用户在 UI 中可见
},
{
role: " user ",
content: " 您是一名 PDF 处理专家... \n\n ## Process \n 1. 验证 PDF 是否存在 \n 2. 运行 pdftotext... ",
isMeta: true // 对 UI 隐藏,发送到 API
},
{
role: " user ",
content: {
type: " command_permissions ",
allowedTools: [" Bash(pdftotext:*) ", " 读取 ", " 写入 "],
model: undefined
}
}
]
}

上述内容展示了我们到目前为止所做的工作。执行上下文修改器被应用,预先批准 Bash(pdftotext:*)、读取 和 写入 用于随后的工具调用。请求被发送到 Anthropic API。这结束了技能执行。如果这是一个正常的工具,我们已经完成了所有工作。但是,技能是不同的。Agent 技能仅注入了会话上下文和执行上下文。这意味着我们仍需要使用注入的上下文调用 Claude 代理以完成用户的请求!
Claude 接收到 API 响应,包含注入的会话上下文。技能提示已经转变了 Claude 的行为,为其提供了:
专门的 PDF 处理指令(会话上下文)
预先批准的访问 Bash(pdftotext:*)、读取和写入工具(执行上下文)
一个清晰的工作流程来遵循(会话上下文)
Claude 处理上下文并遵循 pdf 技能的工作流程:
我将从 report.pdf 中提取文本。让我处理这个文件。 [遵循 pdf 技能的指令] 1. 验证 report.pdf 是否存在 2. 运行 pdftotext 命令以提取文本 3. 读取输出文件 4. 将提取的文本呈现给您
Claude 使用 Bash 工具(预先批准,无需用户提示):
{
"type": "tool_use",
"id": "toolu_456def",
"name": "Bash",
"input": {
"command": "pdftotext report.pdf output.txt",
"description": "使用 pdftotext 从 PDF 中提取文本"
}
}
Bash 工具执行成功,返回结果。Claude 然后使用读取工具读取输出文件,并将提取的文本呈现给用户。技能成功地引导 Claude 完成了专门的 PDF 提取工作流程,通过注入指令到会话上下文和修改执行上下文以获得工具权限。
Claude Code 中的技能是 基于提示的会话和执行上下文修改器,通过元工具架构工作:
关键要点:
技能是 SKILL.md 文件中的提示模板,而不是可执行代码
Skill 工具(大写 S)是工具数组中的一个元工具,而不是系统提示的一部分
技能通过注入指令提示(通过 isMeta:true 消息)修改会话上下文
技能通过更改工具权限和模型选择修改执行上下文
选择发生通过 LLM 推理,而不是算法匹配
工具权限被限定到技能执行的范围内,通过执行上下文修改
技能在每次调用时注入两个用户消息——一个用于用户可见的元数据,一个用于隐藏的指令发送到 API
优雅的设计: 通过将专门的知识视为 修改会话上下文的提示 和 修改执行上下文的权限,而不是 执行的代码,Claude Code 实现了灵活性、安全性和组合性,这些在传统函数调用中是难以实现的。