跳转至

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 只看到 namedescriptionwhenToUse 三个字段的组合摘要。其余所有字段都是框架内部元数据,控制激活时机、执行方式、运行时覆盖等。正文内容也是按需加载——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 解析符号链接,确保同一物理文件不重复加载。

此外还有内置 Skillskills/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:

  1. 获取所有 Skill(本地 + MCP)
  2. 通过 sentSkillNames Map 追踪已发送的 Skill——只发送增量(Delta 语义)
  3. 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 永不截断——rememberskillify 等核心能力的描述始终完整展示。


四、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 执行后会返回一个 contextModifierSkillTool.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)