Claude Code Skill 系统:懒加载的 Agent 行动说明

6 阅读7分钟

Claude Code Skill 系统:懒加载的 Agent 行动说明

摘要

Skill 是 Claude Code 中 懒加载的 Agent 行动说明文件。它本质上是一段提示词增强,通过用户调用(/skill-name)或 LLM 主动调用(SkillTool)触发,在运行时注入到 Agent 的上下文中,指导 Agent 执行特定任务。

核心特征:

  • 懒加载:仅在需要时加载内容,不占用初始上下文
  • 文件组合:Markdown(提示词) + 脚本(可执行逻辑)
  • 两种生效方式:Inline(直接注入)与 Fork(子 Agent)
  • LLM 可调用:通过 SkillTool 让 Agent 自主决定何时使用

与 Tool 懒加载的对比:

  • Tool:通过 ToolSearch 动态加载工具 schema,用于执行操作
  • Skill:通过命令触发动态注入提示词,用于改变 Agent 行为

1. SKILL 的本质:懒加载的行动说明

1.1 什么是"懒加载"

Claude Code 的系统提示词有 token 限制,无法在初始阶段加载所有能力。Skill 通过 按需加载 解决这个问题:

启动时:
  系统提示词: [核心工具] + [SkillTool 的工具定义]
  ↓
  Token 占用:~50k tokens

用户调用 /skill-name:
  系统提示词: [核心工具] + [SkillTool] + [skill-name 的 Markdown 内容]
  ↓
  Token 占用:~50k + skill-name 内容

关键机制: Skill 的完整内容(Markdown Body)不在初始提示词中,仅在触发时注入。

1.2 什么是"行动说明"

Skill 不是工具(Tool),而是 指导 Agent 如何行动的说明文档

概念作用示例
Tool执行特定操作Read(file_path) → 返回文件内容
Skill改变 Agent 行为模式/tech-blog-writer → Agent 按照技术博客写作规范工作

类比:

  • Tool = 函数调用(read_file(path)
  • Skill = 上下文切换(你现在是一位技术博客专家...

1.3 触发时机

Skill 有两种触发方式:

1.3.1 用户手动调用
$ claude "/commit"

执行流程:

  1. 解析命令 /commit
  2. 查找名为 commit 的 Skill
  3. 读取该 Skill 的 SKILL.md 文件
  4. 将 Markdown 内容注入对话
1.3.2 LLM 主动调用(通过 SkillTool)
User: 写一篇关于 Rust 所有权的技术博客

Agent: (内部推理)
       - 检测到 tech-blog-writer Skill
       - when_to_use: "当用户需要撰写技术博客时"
       - 匹配当前任务 → 调用

       [调用 SkillTool(skill: "tech-blog-writer", args: "Rust 所有权")]

区别:

  • 手动调用:用户明确指定
  • 主动调用:Agent 根据 when_to_use 字段判断

2. SKILL 的组成:Markdown + 脚本

2.1 文件结构

一个完整的 Skill 目录可能包含:

~/.claude/skills/example-skill/
├── SKILL.md           ← 必需:主文件(Frontmatter + Markdown)
├── helper.py          ← 可选:辅助脚本
├── config.json        ← 可选:配置文件
└── templates/         ← 可选:模板目录
    └── template.md

核心文件: SKILL.md 是唯一必需文件,其他均为辅助资源。

2.2 SKILL.md 的结构

---
name: example-skill
description: 示例 Skill
when_to_use: 当用户需要示例功能时
execution_context: fork
allowed_tools:
  - Read
  - Bash
shell:
  command: python
  args: ["helper.py"]
---

# Example Skill

这是 Skill 的主体内容,当 Skill 被调用时,这段文本会被 Agent 看到。

## 使用方法
...

## 示例
...

两部分:

  1. YAML Frontmatter--- 包裹)

    • 元数据配置
    • 执行参数
    • 脚本调用配置
  2. Markdown Body

    • Agent 的行动指南
    • 任务描述
    • 示例和模板

2.3 Frontmatter 关键字段

基本字段
字段类型必需说明
namestringSkill 标识符(命令名称)
descriptionstring简短描述(显示在 SkillTool 中)
when_to_usestring-使用时机说明(指导 LLM 何时调用)
执行控制
字段类型说明
execution_context'fork'设为 fork 时使用子 Agent 执行
allowed_toolsstring[]Fork 模式下的工具白名单
model'sonnet' | 'opus' | 'haiku'指定使用的模型
脚本调用
字段类型说明
shellobject脚本执行配置(见下文)
hooksobject钩子配置(事件触发)
条件激活
字段类型说明
pathsstring | string[]路径模式,匹配时才激活此 Skill

2.4 脚本文件

Skill 可以包含可执行脚本,用于:

  • 数据处理
  • 外部工具调用
  • 模板渲染
  • 复杂计算

支持的脚本语言: Python、JavaScript、Shell、任何可执行文件


3. 生效机制:Markdown 和脚本如何工作

3.1 Markdown 的生效机制

Markdown 内容是 Skill 的核心,它通过 注入 Agent 上下文 生效。

3.1.1 Inline 模式(默认)

机制: 将 Markdown Body 直接添加到对话历史

// src/tools/SkillTool/SkillTool.ts (简化)
async callInlineSkill(skill: Command, args?: string) {
  // 1. 读取 SKILL.md
  const content = await fs.readFile(skill.filePath, 'utf-8')

  // 2. 提取 Markdown Body(去除 Frontmatter)
  const body = extractMarkdownBody(content)

  // 3. 注入到对话
  this.messages.push({
    role: 'user',
    content: `<command-name>/${skill.name}</command-name>\n\n${body}\n\n${args || ''}`
  })

  // 4. Agent 在下一轮回复时会看到这段内容
}

流程图:

Before Skill                After /example-skill
┌─────────────────┐        ┌─────────────────┐
 User: 帮我写代码          User: 帮我写代码 
├─────────────────┤        ├─────────────────┤
 Agent: 好的...            Agent: 好的...   
└─────────────────┘        ├─────────────────┤
                            User: /example-skill│
                                                
                            # Example Skill     │
                            你现在是代码专家... 
                            ...                 
                           └─────────────────┘

                            Agent 下一轮回复会基于新增的指令

特点:

  • 零延迟:无需额外 API 调用
  • 上下文共享:Skill 可以引用之前的对话内容
  • Token 占用:Markdown 内容会占用主对话窗口
3.1.2 Fork 模式

机制: 启动独立的子 Agent,Markdown 作为其系统提示词

// src/tools/SkillTool/SkillTool.ts (简化)
async callForkSkill(skill: Command, args?: string) {
  // 1. 读取 Skill 内容
  const body = extractMarkdownBody(await fs.readFile(skill.filePath))

  // 2. 构建子 Agent 的消息
  const forkMessages = [
    {
      role: 'user',
      content: `${body}\n\n${args || ''}`
    }
  ]

  // 3. 启动子 Agent(独立 API 调用)
  const result = await this.apiClient.createMessage({
    model: skill.model || this.defaultModel,
    messages: forkMessages,
    tools: this.filterTools(skill.allowedTools), // 仅限白名单工具
    max_tokens: 4096
  })

  // 4. 返回子 Agent 的输出
  return result.content[0].text
}

流程图:

Main Agent                              Fork Agent
┌─────────────────┐
 User: 分析代码    
├─────────────────┤
 Agent: 调用Skill 
 Skill(name:     
   "analyzer")   │───────────────────→┌─────────────────┐
                                      System:         
                                      # Analyzer      │
                                      你是代码分析专家 
                                      ...             
                                     ├─────────────────┤
                                      (执行分析任务)   
                                      - Read files    
                                      - Grep patterns 
                                     └─────────────────┘
                 │←───────────────────返回分析结果
├─────────────────┤
 分析结果: ...    
└─────────────────┘

特点:

  • 隔离执行:不污染主对话上下文
  • 工具限制:通过 allowed_tools 提升安全性
  • 无历史访问:子 Agent 看不到主对话的历史
  • 额外延迟:需要独立 API 调用(~2-5 秒)
3.1.3 对比表
维度InlineFork
注入位置主对话历史子 Agent 独立上下文
API 调用0 次(复用主对话)1 次(独立调用)
历史可见✅ 看到完整对话❌ 仅看到 Skill 内容
工具访问继承所有工具仅限 allowed_tools
Token 计费算入主对话独立计费

3.2 脚本的生效机制

脚本文件通过三种方式生效:

3.2.1 通过 shell 字段直接执行

配置:

---
name: data-processor
shell:
  command: python
  args: ["process.py", "--input", "data.json"]
  timeout: 30000
---

执行流程:

// src/tools/SkillTool/SkillTool.ts
if (skill.shell) {
  // 在 Skill 目录中执行脚本
  const result = await exec({
    command: skill.shell.command,
    args: skill.shell.args,
    cwd: path.dirname(skill.filePath), // Skill 所在目录
    timeout: skill.shell.timeout || 10000
  })

  // 脚本输出作为 Skill 的结果
  return result.stdout
}

特点:

  • 脚本在 Skill 目录 中执行(可访问辅助文件)
  • 标准输出作为 Skill 的返回值
  • 适合:数据处理、外部工具调用、模板渲染
3.2.2 通过 hooks 字段响应事件

配置:

---
name: auto-format
hooks:
  pre-commit:
    command: python
    args: ["format.py"]
---

执行时机:

  • pre-commit:提交前自动运行
  • post-edit:文件编辑后运行
  • on-error:遇到错误时运行

详见 Claude Code 的 hooks 系统文档。

3.2.3 通过 Markdown 指令让 Agent 调用

SKILL.md 内容:

---
name: code-generator
execution_context: fork
allowed_tools:
  - Bash
  - Write
---

# Code Generator

当用户请求生成代码时:

1. 使用以下命令生成代码:
   ```bash
   python generator.py --template $TEMPLATE_NAME
  1. 将输出写入文件

**执行流程:**
1. Fork Agent 读取上述 Markdown
2. Agent 根据指令调用 `Bash` 工具
3. Bash 工具执行 `python generator.py ...`
4. Agent 获取输出并继续处理

**特点:**
- 脚本不是直接执行,而是通过 **Agent 的工具调用** 执行
- 需要 `allowed_tools` 包含 `Bash`
- 适合:需要 Agent 理解脚本输出并做后续处理的场景

#### 3.2.4 三种方式对比

| 方式 | 执行者 | 适用场景 | 配置 |
|------|--------|----------|------|
| **`shell` 字段** | 系统直接执行 | 纯数据处理、外部工具调用 | `shell: {command, args}` |
| **`hooks` 字段** | 事件触发执行 | 自动化任务(格式化、检查) | `hooks: {event: {command}}` |
| **Markdown 指令** | Agent 通过 Bash 工具执行 | 需要 Agent 理解输出的场景 | `allowed_tools: [Bash]` |

---

## 4. SkillTool:LLM 的主动调用

### 4.1 SkillTool 是什么

SkillTool 是一个 **Claude Code 工具**,暴露给 LLM 调用,用于动态加载 Skill。

**工具定义:**

```typescript
{
  name: "Skill",
  description: `Execute a skill within the main conversation

Available skills:
- commit: Create a git commit with Claude's help
- review-pr: Review a pull request
- tech-blog-writer: 撰写技术博客文章 - 当用户请求撰写技术博客时使用

...`,
  parameters: {
    skill: { type: "string", description: "The skill name" },
    args: { type: "string", description: "Optional arguments" }
  }
}

关键点:

  • SkillTool 的 description 包含所有可用 Skill 的列表
  • 每个 Skill 的 when_to_use 字段会被附加到描述中
  • LLM 根据这些描述判断何时调用哪个 Skill

4.2 动态描述生成

SkillTool 的描述是 动态生成 的,每次加载 Skill 时更新:

// src/tools/SkillTool/SkillTool.ts (简化)
function buildSkillToolDescription(skills: Command[]): string {
  let desc = "Execute a skill within the main conversation\n\n"
  desc += "Available skills:\n"

  for (const skill of skills) {
    const whenToUse = skill.whenToUse ? ` - ${skill.whenToUse}` : ""
    desc += `- ${skill.name}: ${skill.description}${whenToUse}\n`
  }

  return desc
}

// 示例输出:
// Available skills:
// - commit: Create git commits - 当用户需要提交代码时使用
// - tech-blog-writer: 撰写技术博客 - 当用户请求撰写技术博客时使用

机制:

  1. 系统启动时加载所有 Skill(仅读取 Frontmatter,不读取 Body)
  2. 根据 Skill 列表生成 SkillTool 的描述
  3. SkillTool 作为普通工具出现在 LLM 的工具列表中

4.3 调用流程

4.3.1 Inline Skill 的调用
1. User: "写一篇技术博客"

2. LLM: (内部推理)
   - 检测到 tech-blog-writer Skill
   - when_to_use: "当用户请求撰写技术博客时"
   - 决定调用

3. LLM 生成工具调用:
   Skill({
     skill: "tech-blog-writer",
     args: "写一篇技术博客"
   })

4. SkillTool 执行:
   a. 查找 tech-blog-writer Skill
   b. 读取 SKILL.md 的 Markdown Body
   c. 注入到对话历史:
      messages.push({
        role: 'user',
        content: '<command-name>/tech-blog-writer</command-name>\n\n[Markdown Body]\n\n写一篇技术博客'
      })

5. LLM 继续处理(看到注入的 Skill 内容):
   "根据技术博客写作规范,我将..."
4.3.2 Fork Skill 的调用
1. User: "分析这个项目的代码结构"

2. LLM: (内部推理)
   - 检测到 code-analyzer Skill
   - execution_context: fork
   - 决定调用

3. LLM 生成工具调用:
   Skill({
     skill: "code-analyzer",
     args: "分析代码结构"
   })

4. SkillTool 执行:
   a. 检测到 execution_context: fork
   b. 启动子 Agent:
      - System: [code-analyzer 的 Markdown Body]
      - User: "分析代码结构"
      - Tools: [allowed_tools 中指定的工具]
   c. 等待子 Agent 完成
   d. 返回子 Agent 的输出

5. LLM 收到工具返回值(子 Agent 的分析结果):
   "根据分析结果..."

4.4 两种加载方式的选择

SkillTool 根据 execution_context 字段自动选择模式:

// src/tools/SkillTool/SkillTool.ts
async call(params: { skill: string; args?: string }): Promise<string> {
  const skill = this.findSkill(params.skill)

  if (skill.executionContext === 'fork') {
    return await this.callForkSkill(skill, params.args)
  } else {
    return await this.callInlineSkill(skill, params.args)
  }
}

何时使用 Fork:

---
execution_context: fork
allowed_tools:
  - Read
  - Grep
---
  • Skill 需要调用工具(Read、Bash 等)
  • 需要限制工具访问权限(安全考虑)
  • 任务可独立完成(不依赖主对话历史)

何时使用 Inline:

---
# 不设置 execution_context(默认 inline)
---
  • Skill 是纯文本指令(无需工具调用)
  • 需要引用主对话的上下文
  • 对延迟敏感

5. 对比:SKILL vs Tool 的懒加载机制

5.1 Tool 的懒加载(ToolSearch)

机制: 通过 ToolSearch 工具动态加载工具 schema

启动时:
  Tools = [Read, Edit, Write, Bash, ..., ToolSearch]
  ↓
  延迟的工具(Deferred Tools): [NotebookEdit, WebSearch, ...]
  → 仅名称出现在 <system-reminder> 中

Agent 需要使用 NotebookEdit:
  1. Agent: ToolSearch({query: "notebook jupyter"})
  2. ToolSearch 返回: <functions>
                       <function>{"name": "NotebookEdit", "parameters": {...}}</function>
                     </functions>
  3. Agent: NotebookEdit({notebook_path: "...", ...})

详细流程(参考 TOOL_SYSTEM_CN.md):

Round 1:
  User: "编辑 Jupyter notebook"

Round 2:
  Agent: 我需要 NotebookEdit 工具
         ToolSearch({query: "select:NotebookEdit"})
  → 返回 NotebookEdit 的完整 JSONSchema

Round 3:
  Agent: NotebookEdit({notebook_path: "...", new_source: "..."})
  → 执行操作

关键特性:

  • 延迟对象:工具(可调用对象)
  • 加载触发:Agent 调用 ToolSearch
  • 加载内容:工具的 JSONSchema 定义
  • 加载后:工具可被 Agent 调用
  • KV Cache 保护:通过 tool_reference 机制避免缓存失效

5.2 Skill 的懒加载(SkillTool)

机制: 通过 Skill 工具动态注入 Markdown 内容

启动时:
  Tools = [Read, Edit, ..., Skill]
  Skill 描述中包含所有可用 Skill 的名称和简介

Agent 需要使用 tech-blog-writer:
  1. Agent: Skill({skill: "tech-blog-writer", args: "..."})
  2. SkillTool 读取 SKILL.md 的 Markdown Body
  3. 注入到对话 / 启动 Fork Agent
  4. Agent 根据注入的内容调整行为

详细流程:

Round 1:
  User: "写一篇技术博客"

Round 2:
  Agent: 我检测到需要使用 tech-blog-writer Skill
         Skill({skill: "tech-blog-writer", args: "写一篇技术博客"})

  → Inline 模式:
    注入 Markdown 到对话历史
    messages.push({role: 'user', content: '# Tech Blog Writer\n...'})

  → Fork 模式:
    启动子 Agent,Markdown 作为系统提示词

Round 3:
  Agent: 根据 Skill 的指导完成任务

关键特性:

  • 延迟对象:提示词内容(行为指南)
  • 加载触发:Agent 调用 Skill 工具 或 用户输入 /skill-name
  • 加载内容:Skill 的 Markdown Body
  • 加载后:Agent 的行为被修改
  • KV Cache 影响
    • Inline 模式:追加内容,不破坏缓存
    • Fork 模式:独立上下文,无影响

5.3 共同点

维度Tool 懒加载Skill 懒加载
目的延迟加载工具 schema延迟加载提示词
触发方式LLM 调用专用工具LLM 调用专用工具或用户命令
初始提示词占用仅工具名称(~10 tokens/工具)仅 Skill 名称 + 简介(~20 tokens/Skill)
KV Cache 友好✅(通过 tool_reference)✅(追加式注入)
动态发现通过关键词搜索通过 when_to_use 匹配

5.4 差异点

维度Tool 懒加载Skill 懒加载
延迟的内容JSONSchema(参数定义)Markdown(自然语言指令)
加载后的作用工具变为 可调用Agent 行为被 重新指导
是否执行操作✅(工具执行具体操作)❌(Skill 仅提供指导,操作仍通过工具)
是否需要参数 schema✅(严格的类型定义)❌(自然语言参数)
内容来源代码中定义(TypeScript)文件系统(Markdown 文件)
用户可扩展❌(需修改源码)✅(添加 Markdown 文件即可)

5.5 协同工作

Tool 和 Skill 经常协同工作:

示例:tech-blog-writer Skill

---
name: tech-blog-writer
execution_context: fork
allowed_tools:
  - Read       使用 Tool 读取代码
  - Grep       使用 Tool 搜索代码
  - Write      使用 Tool 写入文章
---

# Tech Blog Writer

你是技术博客专家。写作流程:

1. 使用 Read 工具读取项目代码
2. 使用 Grep 工具搜索关键实现
3. 分析代码并撰写文章
4. 使用 Write 工具保存文章

执行流程:

Main Agent:
  Skill({skill: "tech-blog-writer"}) ← 懒加载 Skill

Fork Agent:
  → 读到 Skill 的指导:"使用 Read 工具..."Read({file_path: "..."})         ← 使用 Tool(已预加载)
  → Grep({pattern: "..."})           ← 使用 Tool(已预加载)
  → Write({file_path: "..."})        ← 使用 Tool(已预加载)

关系:

  • Skill 定义"做什么"(任务目标和流程)
  • Tool 提供"怎么做"(具体操作能力)

6. Skill 的加载源与优先级

Skill 从 5 个来源加载:

优先级来源路径说明
1Managed~/.claude/skills/官方管理的 Skill
2User~/.claude/skills/用户自定义的全局 Skill
3Project<project>/.claude/skills/项目级 Skill
4Bundled代码内置核心 Skill(/commit, /review-pr)
5MCPMCP 服务器远程 Skill

去重规则: 高优先级来源覆盖低优先级来源(基于 Skill 名称)

条件激活: 通过 paths 字段实现路径匹配

---
name: cargo-helper
paths:
  - "**/Cargo.toml"
  - "**/*.rs"
---

仅在存在 Cargo.toml.rs 文件的项目中激活。


7. 实战案例

7.1 最简 Skill:test-skill

---
name: test-skill
description: Test skill for debugging
---

# Test Skill

When invoked, respond with "Test skill loaded successfully!"

调用:

$ claude "/test-skill"

执行:

  1. SkillTool 读取 SKILL.md
  2. 注入 Markdown Body 到对话
  3. Agent 读取指令:"respond with..."
  4. Agent 输出:"Test skill loaded successfully!"

模式: Inline(无 execution_context

7.2 Fork Skill:tech-blog-writer

---
name: tech-blog-writer
description: 撰写技术博客文章
when_to_use: 当用户请求撰写技术博客时使用
execution_context: fork
allowed_tools:
  - Read
  - Grep
  - Glob
  - Write
model: sonnet
---

# Tech Blog Writer

你是技术博客专家。写作流程:

1. 使用 Read/Grep 分析项目代码
2. 按照以下结构撰写:
   - 摘要
   - 引言
   - 核心机制(带代码示例)
   - 实战案例
   - 设计权衡
   - 总结
3. 使用 Write 保存文章

调用:

User: 写一篇关于 Speculation 的技术博客

LLM: (自动检测并调用)
     Skill({skill: "tech-blog-writer", args: "Speculation"})

执行:

  1. SkillTool 检测到 execution_context: fork
  2. 启动子 Agent,Markdown 作为系统提示词
  3. 子 Agent 使用 Read/Grep 读取代码
  4. 子 Agent 撰写文章
  5. 子 Agent 使用 Write 保存文章
  6. 返回结果给主 Agent

模式: Fork(需要工具调用 + 隔离执行)

7.3 脚本驱动 Skill:data-processor

---
name: data-processor
description: 处理 JSON 数据
shell:
  command: python
  args: ["process.py"]
---

# Data Processor

This skill processes JSON data using the `process.py` script.

调用:

$ claude "/data-processor"

执行:

  1. SkillTool 检测到 shell 字段
  2. 在 Skill 目录执行:python process.py
  3. 获取脚本的标准输出
  4. 输出作为 Skill 的结果返回

模式: 脚本直接执行(不经过 Agent)


8. 总结

8.1 核心概念

概念定义
Skill懒加载的 Agent 行动说明文件
懒加载仅在触发时注入内容,不占用初始上下文
Inline将 Markdown 注入主对话历史
Fork启动子 Agent,Markdown 作为系统提示词
SkillToolLLM 可调用的工具,用于动态加载 Skill

8.2 Skill vs Tool

维度SkillTool
本质行为指南可执行函数
内容自然语言(Markdown)函数签名(JSONSchema)
作用改变 Agent 的工作方式执行具体操作
扩展性用户可自由添加需修改源码

8.3 何时使用 Skill

使用 Skill 当:

  • 需要 Agent 按照特定风格/流程工作
  • 任务需要多步骤指导(但每步用现有 Tool 完成)
  • 希望复用某种行为模式(如"技术博客写作"、"代码审查")
  • 需要针对特定项目类型提供专门指导

不使用 Skill 当:

  • 需要执行新的原子操作(应该开发新 Tool)
  • 任务是一次性的(直接在对话中描述即可)

8.4 设计理念

Skill 系统体现了 Claude Code 的 能力分层 设计:

Layer 3: Skill(任务模式层)
          指导如何组合
Layer 2: Tool(操作能力层)
          提供原子操作
Layer 1: Model(推理引擎层)
  • Model 提供通用智能
  • Tool 提供具体能力
  • Skill 提供任务模板

附录 A:Frontmatter 完整字段

字段类型必需默认值说明
namestring-Skill 标识符
descriptionstring-简短描述
when_to_usestring--使用时机说明
execution_context'fork'--设为 fork 使用子 Agent
allowed_toolsstring[]-[]Fork 模式工具白名单
modelstring-继承指定模型
effortstring-'medium'预算控制
shellobject--脚本执行配置
hooksobject--钩子配置
pathsstring[]--条件激活路径
user-invocableboolean-true用户是否可手动调用
argument_hintstring--参数提示
versionstring--版本号

附录 B:相关代码文件

文件说明
src/skills/loadSkillsDir.tsSkill 加载核心逻辑
src/tools/SkillTool/SkillTool.tsSkillTool 实现
src/skills/bundled/*.ts内置 Skill 定义
~/.claude/skills/用户 Skill 目录