2.4 Skill System¶
本节摘要
从State Management角度看,Skill 系统回答的核心问题是:Agent 在每一步决策时,能「看到」哪些可复用能力、什么时候加载它们、加载后怎么影响 Context? 本节按「是什么 → 从哪来 → LLM 看到什么 → 何时激活 → 怎么执行」的主线展开。
一、Skill 是什么:定义与元数据¶
每个 Skill 是一个 SKILL.md 文件——Markdown 正文 + YAML frontmatter 元数据。
Skill 完整元数据¶
YAML frontmatter 中可声明的字段(源自 SAFE_SKILL_PROPERTIES 白名单,SkillTool.ts:875-908):
| 字段 | 说明 | LLM 是否可见 |
|---|---|---|
name |
Skill 名称(必填) | ✅ 注入 listing |
description |
功能描述(必填) | ✅ 注入 listing |
whenToUse |
使用场景提示 | ✅ 拼接到 description 后 |
aliases |
别名列表 | ❌ 仅用于命令查找 |
argumentHint |
参数提示 | ❌ |
paths |
路径触发 glob | ❌ 框架用于自动激活 |
context |
执行模式:fork / 不设即 inline |
❌ 框架控制执行隔离 |
model |
模型覆盖(如 opus) |
❌ 运行时覆盖主模型 |
effort |
推理档位覆盖 | ❌ 运行时覆盖 effort 级别 |
disableModelInvocation |
禁止模型主动调用 | ❌ 设为 true 则不出现在 listing 中 |
userInvocable |
是否允许用户手动调用 | ❌ |
source |
来源标记(bundled/plugin/...) | ❌ 内部追踪 |
loadedFrom |
加载路径类型(skills/commands/mcp) | ❌ 内部追踪 |
pluginInfo |
插件元信息 | ❌ 内部追踪 |
agent |
关联的 Agent 定义 | ❌ Fork 执行时使用 |
Skill 示例¶
---
name: react-testing
description: React 组件测试最佳实践
whenToUse: "用户编写或修改 React 组件测试时"
paths:
- "src/components/**/*.test.tsx"
context: fork
model: sonnet
---
# React 组件测试指南
在编写 React 组件测试时,遵循以下最佳实践...
关键设计:LLM 只看到 name、description、whenToUse 三个字段的组合摘要。其余所有字段都是框架内部元数据,控制激活时机、执行方式、运行时覆盖等。正文内容也是按需加载——LLM 通过 SkillTool 调用后才读取全文。
二、Skill 从哪来¶
Skill 从 5 个来源并行加载,高优先级同名覆盖低优先级:
| 优先级 | 来源 | 路径 | 谁来写 |
|---|---|---|---|
| 1(最高) | managed | 组织级管理目录 | IT 管理员 |
| 2 | user | ~/.claude/skills/ |
用户个人 |
| 3 | project | <project>/.claude/skills/ |
团队共享 |
| 4 | additional | --add-dir 额外指定 |
命令行参数 |
| 5(最低) | legacy | 遗留 commands 兼容层 | 历史兼容 |
去重策略:realpath 解析符号链接,确保同一物理文件不重复加载。
此外还有内置 Skill(skills/bundled/),如 remember(Memory 管理)和 skillify(将 Session Memory 转化为可复用 Skill)——它们在预算计算中享有特权。
三、LLM 看到什么:何时提供、提供什么¶
Skill 信息通过三个通道在不同时机到达 LLM,各自携带不同粒度的信息。
通道 1:系统提示 session_guidance 段 — 使用指导¶
在系统提示的动态区域(getSessionSpecificGuidanceSection),当 Skill 列表非空且 SkillTool 可用时,注入一条指导性说明:
/<skill-name>(e.g., /commit) is shorthand for users to invoke a user-invocable skill. When executed, the skill gets expanded to a full prompt. Use the Skill tool to execute them. IMPORTANT: Only use Skill for skills listed in its user-invocable skills section - do not guess or use built-in CLI commands.
这段文本不包含任何 Skill 列表——它只告诉 LLM「有 Skill 可用,用 SkillTool 调用」。
通道 2:Attachment 系统 — Skill 列表(<system-reminder> 包裹)¶
Skill 列表通过 Claude Code 的 Attachment 系统 注入对话。Attachment 是 system prompt 和对话消息之外的第三条状态注入通道,每 turn 动态计算 30+ 类Context 信息,以 <system-reminder> 标签包裹的 user message 形式注入。
getSkillListingAttachments()(attachments.ts:2661)计算 skill_listing attachment:
- 获取所有 Skill(本地 + MCP)
- 通过
sentSkillNamesMap 追踪已发送的 Skill——只发送增量(Delta 语义) - 用
formatCommandsWithinBudget在预算内格式化
LLM 最终看到的格式:
<system-reminder>
The following skills are available for use with the Skill tool:
- react-testing: React 组件测试最佳实践 - 用户编写或修改 React 组件测试时
- commit: Generate a git commit message and commit changes
</system-reminder>
每个 Skill 以一行文本呈现:- {name}: {description} - {whenToUse},合并后截断到 250 字符。
关于 Attachment 系统的完整分析
Attachment 系统的触发时机、序列化机制、增量更新策略、以及对Context/Memory/工具/Sub-agent 各组件的影响,详见 2.6 Attachment 系统。
通道 3:SkillTool 工具定义 — 调用方式¶
SkillTool 作为一个普通工具注册(SkillTool.ts),其 prompt 告诉 LLM 调用格式:
skill: "commit" # 调用 commit skill
skill: "commit", args: "-m 'Fix bug'" # 带参数调用
skill: "ms-office-suite:pdf" # 完全限定名调用
三个通道的完整信息流¶
graph TD
A["会话启动"] --> B["系统提示 session_guidance\n「有 Skill 可用,用 SkillTool」\n仅指导文本"]
A --> C["SkillTool prompt\n调用格式说明\n作为 tool 定义的一部分"]
A --> D["system-reminder\nSkill 列表\n- name: description"]
D --> E["LLM 扫描列表\n判断相关性"]
E --> F["调用 SkillTool\nskill='react-testing'"]
F --> G["加载 SKILL.md 全文\n注入对话 Context"]
预算控制¶
listing 总量受 1% Context Window的预算约束:
| 常量 | 值 | 说明 |
|---|---|---|
SKILL_BUDGET_CONTEXT_PERCENT |
0.01 | 预算 = 窗口 × 1% |
DEFAULT_CHAR_BUDGET |
8,000 字符 | 回退值(≈ 2K tokens) |
MAX_LISTING_DESC_CHARS |
250 字符 | 单条 description 上限 |
formatCommandsWithinBudget 的三级降级策略(源码 prompt.ts:70-171):
| 级别 | 条件 | 处理 |
|---|---|---|
| Full | 总长度 ≤ 预算 | 所有 Skill 展示完整 name: description |
| Truncated | 超预算 | Bundled 保留完整;其余均摊剩余空间,description 被截断 |
| Names-only | 均摊后单条 < 20 字符 | Bundled 保留完整;其余只展示 name,无 description |
Bundled Skill 永不截断——remember、skillify 等核心能力的描述始终完整展示。
四、Skill 何时激活¶
Skill 的「激活」指其完整内容进入 LLM 可感知范围(不只是 listing 中的一行摘要)。三种方式:
方式 1:模型主动选择(默认)¶
LLM 根据 <system-reminder> 中的 listing 判断相关性,调用 SkillTool 加载全文。disableModelInvocation: true 的 Skill 不出现在 listing 中,模型无法主动选择。
方式 2:路径触发(自动激活)¶
frontmatter paths 声明 gitignore 风格 glob,文件操作触及匹配路径时自动激活——即使模型没有主动选择。这解决了:LLM 可能不知道某个 Skill 存在,但操作触及相关文件时应该获得指导。
方式 3:用户手动触发¶
用户通过 /skill-name 命令显式激活。
动态发现:向上遍历¶
文件操作进入新目录时,从该目录向上遍历到项目根,搜索每一层的 .claude/skills/。monorepo 中不同子包可有独立 Skill,仅在操作该子包时才被发现。
五、Skill 怎么执行:对 Context 的影响¶
Skill 激活后,其全文被注入对话。执行方式由 context 字段决定,对 Context状态的影响截然不同:
| 维度 | Inline(默认) | Fork(context: 'fork') |
|---|---|---|
| Context | 输出留在主对话中 | 在隔离子 Agent 中执行 |
| Context 占用 | 高——所有中间步骤占用主窗口 | 低——只有最终摘要返回 |
| Prompt Cache | 直接使用 | 共享父级静态前缀 |
| 运行时覆盖 | model/effort 通过 contextModifier 覆盖主模型 |
model/effort 传入 runAgent |
| 适用场景 | 简短 Skill | 复杂多步骤 Skill |
Inline 模式的 contextModifier¶
Inline Skill 执行后会返回一个 contextModifier(SkillTool.ts:775-838),可以修改后续的运行时 Context:
| 修改项 | 条件 | 效果 |
|---|---|---|
| 工具权限 | Skill 声明了 allowedTools |
将指定工具加入 alwaysAllowRules,自动放行 |
| 模型覆盖 | Skill 声明了 model |
覆盖 mainLoopModel(保留 [1m] 后缀防止窗口降级) |
| 推理档位 | Skill 声明了 effort |
覆盖 effortValue |
这意味着一个 Skill 不仅注入指导内容,还能修改后续对话的运行时参数——例如一个"深度分析"Skill 可以将模型切换到 opus 并提高 effort。
关键文件索引¶
| 文件 | 职责 |
|---|---|
src/tools/SkillTool/prompt.ts |
预算计算 + listing 格式化 + 三级降级策略 |
src/tools/SkillTool/SkillTool.ts |
执行入口 + contextModifier + 权限检查 |
src/constants/prompts.ts |
session_guidance 中的 Skill 使用指导 |
src/commands.ts |
getSkillToolCommands listing 过滤 + getSlashCommandToolSkills |
src/skills/loadSkillsDir.ts |
五层加载 + 路径触发 + 向上遍历发现 + 去重 |
src/skills/bundled/*.ts |
内置 Skill(remember、skillify) |