【译】Claude Agent Skills:从第一性原理深入拆解

162 阅读22分钟

原文地址:leehanchung.github.io/blogs/2025/…

Claude 的 Agent Skills 系统是一个基于提示词的高级“元工具”架构,通过注入专用指令扩展 LLM 能力。与传统的函数调用或代码执行不同,skills 通过提示扩展上下文修改来改变 Claude 处理后续请求的方式,而无需运行可执行代码。

本文从第一性原理拆解 Claude 的 Agent Skills 系统,记录这个名为“Skill”的工具如何作为元工具,把领域专用提示注入对话上下文。我们将以 skill-creatorinternal-comms 这两个技能为例,走一遍完整生命周期,从文件解析、API 请求结构到 Claude 的决策过程。

Claude Agent Skills 概览

Claude 用 Skills 来改进特定任务的表现。Skills 是包含说明、脚本和资源的文件夹,Claude 可在需要时加载。Claude 用声明式、基于提示的系统来发现和调用技能。模型(Claude)根据系统提示中呈现的文本描述决定是否调用 skills代码层面没有算法化的技能选择或 AI 意图检测,决策完全发生在 Claude 的推理中,依据提供的技能描述。

Skills 不是可执行代码。它们不会跑 Python 或 JavaScript,也没有 HTTP 服务器或函数调用,也不是硬编码在 Claude 的 system prompt 里。Skills 存在于 API 请求的另一部分。

那它们是什么?Skills 是把领域专用指令注入对话上下文的提示模板。技能调用时,会同时修改对话上下文(插入指令提示)和执行上下文(更改工具权限、可能切换模型)。技能不直接执行动作,而是展开为详细提示,让 Claude 为解决特定问题做好准备。每个技能会作为 Claude 所见工具 schema 的动态补充出现。

当用户发起请求时,Claude 接收三样东西:用户消息、可用工具(Read、Write、Bash 等),以及 Skill 工具。Skill 工具的描述包含所有可用技能的格式化列表,合并了它们的 namedescription 等字段。Claude 读完列表后,用自身的语言理解把你的意图与技能描述匹配。如果你说“帮我做一个日志技能”,Claude 会看到 internal-comms 的描述(“当用户想写符合自己公司风格的内部沟通”),识别匹配后,以 command: "internal-comms" 调用 Skill 工具。

术语说明:

  • Skill 工具(大写 S)= 管理所有技能的元工具。它与 Read、Write、Bash 等一起出现在 Claude 的 tools 数组中。
  • skills(小写 s)= 具体技能,如 pdfskill-creatorinternal-commsSkill 工具加载的就是这些专用指令模板。

下图直观展示了 Claude 使用 skills 的方式。

Claude Skill Flowchart

技能选择机制在代码层面没有算法路由或意图分类。Claude Code 不用 embeddings、分类器或模式匹配来决定调用哪个技能。系统只是把所有可用技能格式化成文本,嵌进 Skill 工具的提示里,然后让 Claude 的语言模型自行决策。纯靠 LLM 推理——没有正则、没有关键词匹配、没有基于 ML 的意图检测。决策发生在 Claude 的 transformer 前向计算里,而不是应用代码中。

当 Claude 调用技能时,系统会执行简单流程:加载 markdown 文件(SKILL.md),展开成详细指令,把这些指令作为新的用户消息注入对话上下文,修改执行上下文(允许的工具、模型选择),然后在这个强化环境中继续对话。这与传统工具完全不同,传统工具执行并返回结果;技能则是让 Claude 做好准备,而不是直接解决问题。

构建 Agent Skills

下面通过 Anthropic 技能仓库中的 skill-creator 作为案例,介绍如何构建技能。提醒:agent skills 是包含说明、脚本和资源的有序文件夹,智能体可以动态发现并加载,从而在特定任务上表现更好。Skills 将你的专业知识打包成 Claude 可组合的资源,把通用智能体变成符合你需求的专用智能体。

关键信息: Skill = 提示模板 + 对话上下文注入 + 执行上下文修改 + 可选数据文件与 Python 脚本

每个 Skill 都定义在名为 SKILL.md(大小写不敏感)的 markdown 文件中,可选在 /scripts/references/assets 下打包额外文件。这些文件可以是 Python 脚本、shell 脚本、字体、模板等。以 skill-creator 为例,它包含 SILL.md、许可证 LICENSE.txt,以及放在 /scripts 下的几个 Python 脚本。skill-creator 没有 /references/assets

skill-creator package

技能会从多个来源被发现和加载。Claude Code 会扫描用户设置(~/.config/claude/skills/)、项目设置(.claude/skills/)、插件提供的技能和内置技能来构建可用列表。对于 Claude Desktop,可以像下图这样上传自定义技能。

Claude Desktop Skill

注意: 构建 Skills 最重要的概念是渐进式披露——先呈现刚好足够的信息,让智能体决定下一步,再按需披露更多细节。对 agent skills 而言:

  1. 披露 Frontmatter:最小信息(name、description、license)
  2. 如果选择该 skill,加载 SKILL.md:全面但聚焦
  3. 执行技能时,再加载辅助资源、参考资料、脚本

编写 SKILL.md

SKILL.md 是技能提示的核心,分两部分:frontmatter 与正文。frontmatter 配置技能“如何运行”(权限、模型、元数据),正文告诉 Claude “要做什么”。Frontmatter 是写在 YAML 里的 markdown 头部。

┌─────────────────────────────────────┐
│ 1. YAML Frontmatter (Metadata)      │ ← Configuration
│    ---                              │
│    name: skill-name                 │
│    description: Brief overview      │
│    allowed-tools: "Bash, Read"      │
│    version: 1.0.0                   │
│    ---                              │
├─────────────────────────────────────┤
│ 2. Markdown Content (Instructions)  │ ← Prompt for Claude
│                                     │
│    Purpose explanation              │
│    Detailed instructions            │
│    Examples and guidelines          │
│    Step-by-step procedures          │
└─────────────────────────────────────┘

Frontmatter

frontmatter 包含控制 Claude 如何发现和使用技能的元数据。以下是 skill-creator 的示例:

---
name: skill-creator
description: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
license: Complete terms in LICENSE.txt
---

逐个来看字段:

Claude Skills Frontmatter

name(必填)

即技能名称。name 会作为 Skill 工具的 command 使用。

skillname 会作为 Skill 工具里的 command

description(必填)

description 提供技能做什么的简述,是 Claude 判断何时调用技能的主要信号。示例里明确写了 “This skill should be used when users want to create a new skill”——这种清晰、面向动作的描述有助于 Claude 把用户意图与技能能力匹配。

系统会自动在描述后追加来源信息(如 "(plugin:skills)"),用于区分多个来源的同名技能。

when_to_use(未文档化——可能废弃或预备功能)

⚠️ 重要提示: when_to_use 在代码中大量出现,但未在任何官方文档中说明。该字段可能:

  • 正在废弃
  • 内部/实验功能,尚未正式支持
  • 规划中的未发布功能

建议: 依赖详细的 description 即可。在正式技能中避免使用 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 中写清使用指南,如上例。

license(可选)

许可证说明。

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 中文模型(IYKYK)。

model: "claude-opus-4-20250514"  # 指定模型
model: "inherit"                 # 继承当前会话模型(默认)

versiondisable-model-invocationmode(可选)

Skills 支持三个可选字段用于版本与调用控制。version(如 “1.0.0”)是元数据,用于跟踪技能版本,会从 frontmatter 解析,主要用于文档与技能管理。

disable-model-invocation(布尔值)会阻止 Claude 通过 Skill 工具自动调用该技能。设为 true 时,技能会从列表中排除,只能通过用户手动输入 `/skill-name` 调用,适合危险操作、配置命令或需要显式控制的交互流程。

mode(布尔值)用于把技能分类为“模式命令”,能修改 Claude 的行为或上下文。设为 true 时,该技能会在技能列表顶部的 “Mode Commands” 专区展示(与常规工具技能分开),适用于 debug-mode、expert-mode、review-mode 等需要建立特定运行语境或流程的技能。

SKILL.md 提示内容

frontmatter 之后是 markdown 正文,即 Claude 在调用 skill 时收到的实际提示。在这里定义技能的行为、指令与流程。编写有效技能提示的关键是保持聚焦,并使用渐进式披露:核心指令放在 SKILL.md,详细内容引用外部文件。

推荐结构:

---
# Frontmatter here
---

# [Brief Purpose Statement - 1-2 sentences]

## Overview
[What this skill does, when to use it, what it provides]

## Prerequisites
[Required tools, files, or context]

## Instructions
## Instructions

### Step 1: [First Action]
[Imperative instructions]
[Examples if needed]

### Step 2: [Next Action]
[Imperative instructions]

### Step 3: [Final Action]
[Imperative instructions]

## Output Format
[How to structure results]

## Error Handling
[What to do when things fail]

## Examples
[Concrete usage examples]

## Resources
[Reference scripts/, references/, assets/ if bundled]

skill-creator 为例,它的指令列出了构建技能的每个步骤:

## Skill Creation Process

### Step 1: Understanding the Skill with Concrete Examples
### Step 2: Planning the Reusable Skill Contents
### Step 3: Initializing the Skill
### Step 4: Edit the Skill
### Step 5: Packaging a Skill

当 Claude 调用这个技能时,会收到整段提示作为新指令,并自动在路径前加上基目录。{baseDir} 变量解析为技能安装目录,允许 Claude 用 Read 工具加载参考文件:Read({baseDir}/scripts/init_skill.py)。这种方式让主提示保持精简,同时在需要时访问详细文档。

提示内容最佳实践:

  • 控制在 5,000 词(约 800 行)以内,避免上下文过载
  • 使用祈使语气(“Analyze code for…”),而非第二人称(“You should…”)
  • 详尽内容放外部文件,通过引用加载,不要全部内嵌
  • 使用 {baseDir} 指定路径,避免硬编码绝对路径如 /home/user/project/
❌ Read /home/user/project/config.json
✅ Read {baseDir}/config.json

技能调用时,Claude 只能使用 allowed-tools 中的工具;如果 frontmatter 指定了模型,也会覆盖会话模型。技能的基目录自动提供,方便访问打包资源。

随技能打包资源

当 SKILL.md 旁边打包资源时,Skills 会更强大。标准目录结构通常是:

my-skill/
├── SKILL.md              # 核心提示与指令
├── scripts/              # 可执行 Python/Bash 脚本
├── references/           # 读入上下文的文档
└── assets/               # 模板与二进制文件

为何要打包资源? 让 SKILL.md 保持精简(< 5,000 词)可避免上下文窗口过载。打包资源可提供详细文档、自动化脚本、模板,而不胀大主提示。Claude 只在需要时加载,靠渐进式披露管理上下文。

scripts/ 目录

放可执行代码,Claude 通过 Bash 工具运行——自动化脚本、数据处理器、验证器或代码生成器等需要确定性逻辑的内容。

示例:skill-creator 的 SKILL.md 像这样引用脚本:

When creating a new skill from scratch, always run the `init_skill.py` script. The script conveniently generates a new template skill directory that automatically includes everything a skill requires, making the skill creation process much more efficient and reliable.

Usage:

```scripts/init_skill.py <skill-name> --path <output-directory>```

The script:
  - Creates the skill directory at the specified path
  - Generates a SKILL.md template with proper frontmatter and TODO placeholders
  - Creates example resource directories: scripts/, references/, and assets/
  - Adds example files in each directory that can be customized or deleted

Claude 看到指令后,会执行 python {baseDir}/scripts/init_skill.py{baseDir} 自动解析为技能安装路径,让技能可在不同环境中移植。

scripts/ 适用于: 复杂多步操作、数据转换、API 交互,或任何比自然语言更适合用代码表达的精确逻辑。

references/ 目录

存放 Claude 需要读入上下文的文档:markdown、JSON schema、配置模板或任何完成任务所需的文本资料。

示例:mcp-creator 的 SKILL.md 如下引用:

#### 1.4 Study Framework Documentation

**Load and read the following reference files:**

- **MCP Best Practices**: [📋 View Best Practices](./reference/mcp_best_practices.md) - Core guidelines for all MCP servers

**For Python implementations, also load:**
- **Python SDK Documentation**: Use WebFetch to load `https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md`
- [🐍 Python Implementation Guide](./reference/python_mcp_server.md) - Python-specific best practices and examples

**For Node/TypeScript implementations, also load:**
- **TypeScript SDK Documentation**: Use WebFetch to load `https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/main/README.md`
- [⚡ TypeScript Implementation Guide](./reference/node_mcp_server.md) - Node/TypeScript-specific best practices and examples

Claude 执行指令时,用 Read 工具读取 Read({baseDir}/references/mcp_best_practices.md),将内容加载进上下文,在不让 SKILL.md 臃肿的前提下获得详细信息。

references/ 适用于: 详细文档、大型模式库、检查清单、API schema,或任何对任务必需但过长的文本。

assets/ 目录

放模板和二进制文件,Claude 只按路径引用,不读入上下文。比如 HTML 模板、CSS、图片、配置样板或字体。

在 SKILL.md 中:

Use the template at {baseDir}/assets/report-template.html as the report structure.
Reference the architecture diagram at {baseDir}/assets/diagram.png.

Claude 看到路径但不会读取内容,而是复制模板、填充占位符,或在输出中引用路径。

assets/ 适用于: HTML/CSS 模板、图片、二进制文件、配置模板,或任何按路径操作而非读入上下文的文件。

references/assets/ 的关键区别:

  • references/:通过 Read 工具读入上下文的文本内容
  • assets/:仅按路径引用,不读入上下文

这关系到上下文管理:10KB 的 markdown 放在 references/,加载时会占用上下文 token;同样大小的 HTML 模板放在 assets/ 则不会,Claude 只知道路径存在。

最佳实践: 路径始终用 {baseDir},不要硬编码绝对路径,使技能可在不同用户环境、项目目录和安装位置中移植。

常见技能模式

像所有工程问题一样,理解常见模式有助于设计有效技能。以下是工具集成与流程设计中最有用的模式。

模式 1:脚本自动化

场景: 需要多条命令或确定性逻辑的复杂操作。

把计算任务交给 scripts/ 里的 Python 或 Bash。技能提示告诉 Claude 执行脚本并处理输出。

Claude Skill Script Automation

SKILL.md 示例:

Run scripts/analyzer.py on the target directory:

`python {baseDir}/scripts/analyzer.py --path "$USER_PATH" --output report.json`

Parse the generated `report.json` and present findings.

需要的工具:

allowed-tools: "Bash(python {baseDir}/scripts/*:*), Read, Write"

模式 2:Read - Process - Write

场景: 文件转换和数据处理。

最简单的模式——读入、转换、写出。适用于格式转换、数据清洗、报告生成。

Claude Skill Read Process Write

SKILL.md 示例:

## Processing Workflow
1. Read input file using Read tool
2. Parse content according to format
3. Transform data following specifications
4. Write output using Write tool
5. Report completion with summary

需要的工具:

allowed-tools: "Read, Write"

模式 3:Search - Analyze - Report

场景: 代码库分析与模式检测。

用 Grep 搜索模式,读取匹配文件获取上下文,分析后生成结构化报告。或搜索企业数据仓库,分析得到信息并汇报。

Claude Skill Search Analyze Report

SKILL.md 示例:

## Analysis Process
1. Use Grep to find relevant code patterns
2. Read each matched file
3. Analyze for vulnerabilities
4. Generate structured report

需要的工具:

allowed-tools: "Grep, Read"

模式 4:命令链执行

场景: 有依赖的多步操作。

按顺序执行命令,每步依赖前一步成功。常见于 CI/CD 类流程。

Claude Skill Command Chain Execution

SKILL.md 示例:

Execute analysis pipeline:
npm install && npm run lint && npm test

Report results from each stage.

需要的工具:

allowed-tools: "Bash(npm install:*), Bash(npm run:*), Read"

高阶模式

向导式多步流程

场景: 需要在每步获取用户输入的复杂流程。

把复杂任务拆成独立步骤,每个阶段都要明确用户确认。适用于安装向导、配置工具或引导式流程。

SKILL.md 示例:

## Workflow

### Step 1: Initial Setup
1. Ask user for project type
2. Validate prerequisites exist
3. Create base configuration
Wait for user confirmation before proceeding.

### Step 2: Configuration
1. Present configuration options
2. Ask user to choose settings
3. Generate config file
Wait for user confirmation before proceeding.

### Step 3: Initialization
1. Run initialization scripts
2. Verify setup successful
3. Report results

基于模板的生成

场景: 使用 assets/ 中的模板生成结构化输出。

加载模板,填入用户提供或生成的数据并写出结果。常见于报告生成、样板代码创建或文档生成。

SKILL.md 示例:

## Generation Process
1. Read template from {baseDir}/assets/template.html
2. Parse user requirements
3. Fill template placeholders:
   -  → user-provided name
   -  → generated summary
   -  → current date
4. Write filled template to output file
5. Report completion

迭代式精炼

场景: 需要多轮递进分析的流程。

先广泛扫描,再对识别出的议题逐步深入。用于代码审查、安全审计或质量分析。

SKILL.md 示例:

## Iterative Analysis

### Pass 1: Broad Scan
1. Search entire codebase for patterns
2. Identify high-level issues
3. Categorize findings

### Pass 2: Deep Analysis
For each high-level issue:
1. Read full file context
2. Analyze root cause
3. Determine severity

### Pass 3: Recommendation
For each finding:
1. Research best practices
2. Generate specific fix
3. Estimate effort

Present final report with all findings and recommendations.

上下文聚合

场景: 汇总多来源信息以建立完整理解。

从不同文件和工具收集数据,整合成连贯描述。适用于项目概要、依赖分析或影响评估。

SKILL.md 示例:

## Context Gathering
1. Read project README.md for overview
2. Analyze package.json for dependencies
3. Grep codebase for specific patterns
4. Check git history for recent changes
5. Synthesize findings into coherent summary

Agent Skills 内部架构

概览和构建流程讲完后,来看技能在底层如何工作。技能系统通过一个名为 Skill 的元工具来运行,它作为所有技能的容器和分发器。这种设计让技能在实现与目的上都与传统工具有根本区别。

Skill 工具是管理所有技能的元工具

Skills 对象设计

传统工具如 ReadBashWrite 执行独立动作并立即返回结果。技能不同:它们不是直接动作,而是把专用指令注入对话历史,并动态修改 Claude 的执行环境。这通过两条用户消息实现——一条包含用户可见的元数据,另一条包含完整的技能提示,对 UI 隐藏但会发送给 Claude——同时修改智能体上下文以变更权限、切换模型、调整思考 token 参数,持续整个技能使用期间。

Claude Skill Execution Flow

复杂、引导型流程

复杂度很高。普通工具的消息交换很简单——助手工具调用,然后用户结果。技能会注入多条消息,在动态修改的上下文里运行,并带来大量 token 开销,以提供指导 Claude 行为的专用指令。

理解 Skill 元工具的工作方式有助于把握整个系统机制。来看其结构:

Pd = {
  name: "Skill",  // 工具名常量:$N = "Skill"

  inputSchema: {
    command: string  // 例如 "pdf"、"skill-creator"
  },

  outputSchema: {
    success: boolean,
    commandName: string
  },

  // 🔑 关键字段:生成技能列表
  prompt: async () => fN2(),

  // 校验与执行
  validateInput: async (input, context) => { /* 5 个错误码 */ },
  checkPermissions: async (input, context) => { /* allow/deny/ask */ },
  call: async *(input, context) => { /* 产生消息 + 上下文修改 */ }
}

prompt 字段是 Skill 工具与 Read、Bash 等静态描述工具的区别所在。Skill 工具用动态提示生成器,在运行时聚合所有可用技能的名称和描述来构建自身的描述,实现渐进式披露——系统仅把技能名称和描述(来自 frontmatter)作为最小元数据放入 Claude 的初始上下文,让模型决定哪个技能匹配当前意图。完整的技能提示只在 Claude 选定技能后加载,既避免上下文膨胀,又保持可发现性。

async function fN2() {
  let A = await atA(),
    {
      modeCommands: B,
      limitedRegularCommands: Q
    } = vN2(A),
    G = [...B, ...Q].map((W) => W.userFacingName()).join(", ");
  l(`Skills and commands included in Skill tool: ${G}`);
  let Z = A.length - B.length,
    Y = nS6(B),
    J = aS6(Q, Z);
  return `Execute a skill within the main conversation

<skills_instructions>
When users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.

How to use skills:
- Invoke skills using this tool with the skill name only (no arguments)
- When you invoke a skill, you will see <command-message>The "{name}" skill is loading</command-message>
- The skill's prompt will expand and provide detailed instructions on how to complete the task
- Examples:
  - \`command: "pdf"\` - invoke the pdf skill
  - \`command: "xlsx"\` - invoke the xlsx skill
  - \`command: "ms-office-suite:pdf"\` - invoke using fully qualified name

Important:
- Only use skills listed in <available_skills> below
- Do not invoke a skill that is already running
- Do not use this tool for built-in CLI commands (like /help, /clear, etc.)
</skills_instructions>

<available_skills>
${Y}${J}
</available_skills>
`;
}

与某些助手把工具放在 system prompt 中不同,Claude 的agent skills 不在 system prompt 里。它们作为 Skill 工具描述的一部分存在于 tools 数组中。单个技能的名称体现在 Skill 元工具输入 schema 的 command 字段里。为便于理解,下面是实际 API 请求结构:

{
  "model": "claude-sonnet-4-5-20250929",
  "system": "You are Claude Code, Anthropic's official CLI...",  // ← system prompt
  "messages": [
    {"role": "user", "content": "Help me create a new skill"},
    // ... conversation history
  ],
  "tools": [  // ← 发送给 Claude 的 tools 数组
    {
      "name": "Skill",  // ← 元工具
      "description": "Execute a skill...\n\n<skills_instructions>...\n\n<available_skills>\n...",
      "input_schema": {
        "type": "object",
        "properties": {
          "command": {
            "type": "string",
            "description": "The skill name (no arguments)"  // ← 单个技能名称
          }
        }
      }
    },
    {
      "name": "Bash",
      "description": "Execute bash commands...",
      // ...
    },
    {
      "name": "Read",
      // ...
    }
    // ... other tools
  ]
}

<available_skills> 部分位于 Skill 工具描述中,每次 API 请求都会重新生成。系统会动态聚合当前加载的技能(用户与项目配置、插件提供、内置技能),并受到默认 15,000 字符的 token 预算限制。这迫使技能作者写出精炼描述,确保工具描述不会压垮模型上下文窗口。

技能对话与执行上下文注入设计

大多数 LLM API 支持 role: "system" 消息来承载 system prompt。事实上,OpenAI 的 ChatGPT 会把默认工具放在 system prompt 里,包括 bio(记忆)、automations(任务调度)、canmore(画布控制)、img_gen(图像生成)、file_searchpythonweb 等,工具提示占了 system prompt 的约 90% token。虽然有用,但若工具/技能很多,这很低效。

然而 system 消息语义与技能需求不符。system 消息设置全局上下文,影响整段对话,权重高于用户指令。

技能需要的是临时、作用域明确的行为。skill-creator 只应影响“创建技能”相关任务,而不是让 Claude 在剩余会话都变成 PDF 专家。使用 role: "user" 并设置 isMeta: true,能让技能提示作为用户输入呈现给 Claude,保持临时性并局限于当前交互。技能结束后,对话与执行上下文会恢复,不留残余修改。

普通工具如 ReadWriteBash 的通信模式很简单:调用时发送路径,返回文件内容,继续工作。用户在记录中看到 “Claude used the Read tool” 就够了——工具做了一件事,返回结果,交互结束。技能完全不同。它们不执行离散动作、返回结果,而是注入全面指令集,改变 Claude 推理与处理任务的方式。这带来普通工具没有的设计挑战:一方面需要给用户透明度,显示正在运行哪些技能、在做什么;另一方面 Claude 需要详细、可能很长的指令才能正确执行。如果在聊天记录里展示完整技能提示,UI 会被几千字的内部指令淹没;若完全隐藏技能激活,用户又不知道系统在做什么。解决方案是把这两条沟通渠道分为可见性不同的独立消息。

技能系统通过每条消息的 isMeta 标志控制它是否显示在 UI 中。isMeta: 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>The "pdf" skill is loading</command-message>
<command-name>pdf</command-name>
<command-args>report.pdf</command-args>

这条消息刻意保持简短(通常 50-200 字符)。XML 标签便于前端做特殊渲染、校验 <command-message> 是否存在,并为会话记录技能执行的审计轨迹。由于 isMeta 默认 false,这条元数据天然可见。

技能提示消息则相反。它加载 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 技能提示可能包含:

You are a PDF processing specialist.

Your task is to extract text from PDF documents using the pdftotext tool.

## Process

1. Validate the PDF file exists
2. Run pdftotext command to extract text
3. Read the output file
4. Present the extracted text to the user

## Tools Available

You have access to:
- Bash(pdftotext:*) - For running pdftotext command
- Read - For reading extracted text
- Write - For saving results if needed

## Output Format

Present the extracted text clearly formatted.

Base directory: /path/to/skill
User arguments: 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
    })
  ] : [])
];

附件消息可携带诊断信息、文件引用或补充上下文。权限消息仅在 frontmatter 指定 allowed-tools 或请求模型覆盖时出现,为运行时环境提供元数据。这样模块化组合允许每条消息各司其职,并可根据技能配置选择性加入,把基础的两条消息模式扩展到更复杂场景,同时依靠 isMeta 控制可见性。

为什么不用单条消息?

单条消息会陷入两难。设为 isMeta: false,整条消息可见,几千字的 AI 指令会塞满聊天记录;设为 isMeta: true,则完全看不到技能激活或参数,缺乏透明度。用户要么被淹没,要么毫无可见性。

两条消息的分离,通过不同的 isMeta 值解决了透明度与清爽度的冲突:消息 1(可见)提供状态与透明度;消息 2(隐藏)给 Claude 详细指令。这样既透明又不打扰用户体验。

代码路径也不同:元数据消息会解析 <command-message> 标签、校验并格式化供 UI 展示;技能提示消息直接发送给 API,不做解析或校验——它是纯粹给 Claude 的指令内容。合并二者会迫使一条消息同时服务两种受众、两种处理流程,违反单一职责。

案例研究:执行生命周期

理解了内部架构,接下来以假设的 pdf 技能走一遍完整流程,看看用户说 “Extract text from report.pdf” 时发生了什么。

Claude Skill Execution Flow

阶段 1:发现与加载(启动)

Claude Code 启动时会扫描技能:

async function getAllCommands() {
  // 并行加载所有来源
  let [userCommands, skillsAndPlugins, pluginCommands, builtins] =
    await Promise.all([
      loadUserCommands(),      // ~/.claude/commands/
      loadSkills(),            // .claude/skills/ + plugins
      loadPluginCommands(),    // 插件定义命令
      getBuiltinCommands()     // 内置命令
    ]);

  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: "Extract text from PDF documents (plugin:document-tools)",
  whenToUse: "When user wants to extract or process text from PDF files",
  allowedTools: ["Bash(pdftotext:*)", "Read", "Write"],
  model: undefined,  // 继承会话模型
  isSkill: true,
  disableModelInvocation: false,
  promptContent: "You are a PDF processing specialist...",
  // ... 其他字段
}

阶段 2:第 1 轮——用户请求与技能选择

用户发送请求:“Extract text from report.pdf”。Claude 收到此消息以及 tools 数组中的 Skill 工具。在 Claude 决定调用 pdf 技能前,系统需要把可用技能呈现在 Skill 工具描述中。

技能过滤与呈现

并非所有加载的技能都会显示在 Skill 工具里。技能必须在 frontmatter 中包含 descriptionwhen_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)  // ← 必须至少有一个!
  );
}

技能格式化

每个技能会被格式化到 <available_skills> 部分。比如 pdf 会变成
"pdf": Extract text from PDF documents - When user wants to extract or process text from PDF files

function formatSkill(skill) {
  let name = skill.name;
  let description = skill.whenToUse
    ? `${skill.description} - ${skill.whenToUse}`
    : skill.description;

  return `"${name}": ${description}`;
}

Claude 的决策

当用户提示 “Extract text from report.pdf” 时,Claude 读到包含 Skill 工具的 API 请求和 <available_skills>,并这样推理(假设,因为我们看不到思考链):

内部推理:
- 用户想“从 report.pdf 提取文本”
- 这是 PDF 处理任务
- 查看可用技能...
- "pdf": Extract text from PDF documents - When user wants to extract or process text from PDF files
- 匹配!用户要提取 PDF 文本
- 决策:调用 Skill 工具,command="pdf"

注意,没有算法匹配:无词法、无语义、无搜索。纯 LLM 推理,依据技能描述决策。随后 Claude 返回工具调用:

{
  "type": "tool_use",
  "id": "toolu_123abc",
  "name": "Skill",
  "input": {
    "command": "pdf"
  }
}

阶段 3:Skill 工具执行

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:不是 prompt 类型
  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: "Blocked by permission rules" };
    }
  }

  // 检查允许规则
  for (const [pattern, rule] of getAllowRules(permContext)) {
    if (matches(skillName, pattern)) {
      return { behavior: "allow" };
    }
  }

  // 默认:询问用户
  return { behavior: "ask", message: `Execute skill: ${skillName}` };
}

假设没有规则,用户会被提示 “Execute skill: pdf?”
用户批准 ✓

步骤 3:加载技能文件并生成执行上下文修改

校验与权限通过后,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()}" skill is loading</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” 部分。

阶段 4:发送 API(第 1 轮结束)

系统构建发送给 Anthropic API 的完整消息数组,包括对话历史与新注入的技能消息:

// 第 1 轮发送给 API 的完整 messages
{
  model: "claude-sonnet-4-5-20250929",
  messages: [
    {
      role: "user",
      content: "Extract text from report.pdf"
    },
    {
      role: "assistant",
      content: [
        {
          type: "tool_use",
          id: "toolu_123abc",
          name: "Skill",
          input: { command: "pdf" }
        }
      ]
    },
    {
      role: "user",
      content: "<command-message>The \"pdf\" skill is loading</command-message>\n<command-name>pdf</command-name>"
      // isMeta: false(默认)→ UI 可见
    },
    {
      role: "user",
      content: "You are a PDF processing specialist...\n\n## Process\n1. Validate PDF exists\n2. Run pdftotext...",
      isMeta: true  // 对 UI 隐藏,发送给 API
    },
    {
      role: "user",
      content: {
        type: "command_permissions",
        allowedTools: ["Bash(pdftotext:*)", "Read", "Write"],
        model: undefined
      }
    }
  ]
}

Turn 1 Completion

上面展示了目前完成的部分。执行上下文修改已应用,预先允许 Bash(pdftotext:*)ReadWrite 等后续调用。请求发送给 Anthropic API。至此技能执行完毕。如果是普通工具,这里就结束了;但技能不同:它只是注入了对话与执行上下文。现在还需要在这些上下文下再次调用 Claude,完成用户请求!

阶段 5:Bash 工具执行(带技能上下文的工具调用!)

Claude 收到带注入上下文的 API 响应。技能提示已经改变了 Claude 的行为,为其提供:

  • PDF 处理专用指令(对话上下文)
  • 预授权的 Bash(pdftotext:*)ReadWrite 工具(执行上下文)
  • 清晰的流程(对话上下文)

Claude 处理上下文并按 pdf 技能流程执行:

I'll extract text from report.pdf. Let me process the file.

[Following pdf skill's instructions]
1. Validate that report.pdf exists
2. Run pdftotext command to extract text
3. Read the output file
4. Present the extracted text to you

Claude 使用 Bash 工具(已预授权,无需再问):

{
  "type": "tool_use",
  "id": "toolu_456def",
  "name": "Bash",
  "input": {
    "command": "pdftotext report.pdf output.txt",
    "description": "Extract text from PDF using pdftotext"
  }
}

Bash 工具成功执行并返回结果。随后 Claude 用 Read 工具读取输出文件,再把提取的文本呈现给用户。技能通过注入指令与修改执行上下文,成功引导 Claude 完成了 PDF 提取流程。


结论:心智模型回顾

Claude Code 中的技能是通过元工具架构实现的基于提示的对话与执行上下文修改器

要点:

  1. 技能是 SKILL.md 中的提示模板,不是可执行代码
  2. Skill 工具(大写 S)是 tools 数组中的元工具,用来管理各个技能,而不是放在 system prompt
  3. 技能通过注入 isMeta: true 的指令提示来修改对话上下文
  4. 技能通过更改工具权限、模型选择来修改执行上下文
  5. 选择依靠 LLM 推理,非算法匹配
  6. 工具权限限定在技能执行范围内,通过执行上下文修改实现
  7. 每次调用技能会注入两条用户消息——一条用户可见元数据,一条对 UI 隐藏的指令

优雅之处: 把专用知识视为修改对话上下文的提示修改执行上下文的权限,而非直接执行的代码,让 Claude Code 获得了灵活、安全、可组合的能力,这在传统函数调用中很难做到。


References

@article{
    leehanchung_bullshit_jobs,
    author = {Lee, Hanchung},
    title = {Claude Agent Skills: A First Principles Deep Dive},
    year = {2025},
    month = {10},
    day = {26},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2025/10/26/claude-skills-deep-dive/}
}