技能系统:Agent 的训练手册
如果说 Agent Loop 是 Agent 的心跳,工具系统是它的双手,那技能系统就是一本训练手册。
有了手,Agent 能读写文件、执行命令;但"能做什么"不等于"知道怎么做"。Git 提交规范、代码审查清单、部署流程——这些领域知识不能靠模型自己猜,需要被注入。
最直接的办法是把所有知识塞进系统提示。但过多技能或导致上下文巨量消耗,且大部分跟当前任务无关。
技能系统的解法是按需加载:
系统提示只放名称和描述,需要时再加载完整内容。用到什么,注入什么。
第一阶段:系统提示(提供所有技能的名称及描述)
┌──────────────────────────────────────┐
│ Skills available: │
│ - git-workflow: Git 工作流规范 │ ~100 tokens/skill
│ - code-review: 代码审查清单 │
│ - deploy: 部署流程指南 │
└──────────────────────────────────────┘
第二阶段:按需加载(用到才给,高价值)
┌──────────────────────────────────────┐
│ 完整的技能内容... │ ~2000 tokens/skill
│ 步骤 1:检查分支命名规范 │
│ 步骤 2:验证 commit message 格式 │
│ ... │
└──────────────────────────────────────┘六层技能来源:谁说了算
Claude Code 的技能来自六个层级,按加载优先级排列(优先级从高到底):
| 优先级 | 层级 | 存放位置 | 作用范围 |
|---|---|---|---|
| 1 | 内置级 | 编译进二进制 | 开箱即用的核心技能 |
| 2 | 管理策略级 | 系统级目录 | 企业版,企业管理员下发 |
| 3 | 用户级 | ~/.claude/skills/ | 跨所有项目的个人偏好 |
| 4 | 项目级 | .claude/skills/ | 仅当前项目生效 |
| 5 | 插件级 | 插件目录 | 插件自带的能力 |
| 6 | MCP 级 | MCP 服务器 | 外部服务提供的远程技能 |
启动时,系统按上述顺序依次加载并去重。 需要注意:去重是基于文件路径,不是技能名称。不同来源提供的同名技能可以共存,只有同一个文件通过符号链接等方式出现在多个目录时才会去重。
技能生命周期:一个技能从加载到执行的全过程
技能不是"加载一次就完事"。它经历四个阶段:启动时进入名单、操作文件时被发现、更新技能列表、模型选中时执行完整内容。
第一阶段:启动时进入名单
Claude Code 启动时,扫描各来源目录,合并去重。技能按 paths 字段分成两类:
- 无条件技能:没有
paths字段,立即可用 - 有条件技能:带
paths字段(如paths: ["src/**/*.ts"]),进入待命池
只有无条件技能注入系统提示,每轮对话开头都出现——但只有新增技能才会发送,已发送的不重复注入。列表预算约 8000 字符(上下文窗口的 1%),内置技能优先保留完整描述,其余按剩余空间均分。
第二阶段:操作文件时发现新技能
启动扫描只覆盖已知目录。某些子项目的 .claude/skills/ 不在其中。当模型操作文件(Read/Write/Edit)时,系统沿文件路径向上查找 .claude/skills/——走到哪,发现到哪。被 .gitignore 忽略的目录自动跳过。
新发现的技能同样按 paths 分类:无条件的直接加入可用列表,有条件的进入待命池。
同时检查待命池中的条件技能:如果操作的文件路径匹配某个技能的 paths 规则,该技能从待命池移入可用列表,后续对话中模型能看到它。一旦激活就永久有效——即使后续操作的文件不再匹配,也不会消失。
第三阶段:更新技能列表
每轮对话开始时,系统检查可用技能列表是否有变化——新发现的、新激活的、已发送过的都不再重复。新增技能注入一条系统提醒消息:
The following skills are available for use with the Skill tool:
- ts-checker: TypeScript 代码审查第四阶段:模型选中,执行完整内容
模型从技能列表中选择技能,调用 SkillTool,传入技能名和参数。工具验证技能是否存在、用户是否授权后,按两种模式执行:
Inline(默认):技能内容直接注入当前对话,共享上下文和 token 预算。适合轻量任务。
Fork:在独立子代理中运行(Subagents 章节会介绍),独立上下文和 token 预算。适合长时间任务。
执行过程中通过参数替换获取用户传入的值:
$ARGUMENTS→ 完整参数字符串$1,$2→ 位置参数- 命名参数 → frontmatter 定义
arguments: file_path时,$file_path映射到对应值
替换顺序:命名参数 → 位置参数 → 完整参数 → 无占位符时追加末尾。
技能可限定允许的工具列表(allowed-tools)。代码审查技能只需读取和搜索,不需要写入和执行——最小权限原则。只包含只读工具的技能自动放行。
写一本自己的训练手册
技能格式:一个目录 + 一个 SKILL.md 文件。
Frontmatter 配置项
SKILL.md 的 YAML frontmatter 支持以下字段:
| 字段 | 必需 | 描述 |
|---|---|---|
name | 否 | Skill 列表中的显示名称。默认为目录名 |
description | 推荐 | 功能说明和使用场景。Claude 用它决定何时调用。省略则取正文第一段 |
when_to_use | 否 | 何时调用的额外上下文(触发短语、示例请求),追加到 description 后,合并展示于技能列表中 |
argument-hint | 否 | 在输入框键入斜杠命令时,会作为浅灰色的占位符显示,用于提示该命令期望的参数内容 |
arguments | 否 | 命名位置参数,用于正文中的 $name 替换。按顺序映射到参数位置 |
disable-model-invocation | 否 | true 阻止 Claude 自动加载,仅限 /name 手动触发。默认 false |
user-invocable | 否 | false 从 / 菜单隐藏,用于背景知识类技能。默认 true |
allowed-tools | 否 | 技能激活时可免确认使用的工具列表 |
model | 否 | 覆盖当前轮次的模型,如 sonnet、haiku、inherit(保持当前) |
effort | 否 | 覆盖工作力度:low、medium、high、xhigh、max |
context | 否 | 设为 fork 在独立子代理中运行 |
agent | 否 | context: fork 时使用的子代理类型,比如 Explore、Plan |
hooks | 否 | 限定于此技能生命周期的钩子 |
paths | 否 | Glob 模式,仅在处理匹配文件时激活 |
shell | 否 | 内联命令使用的 shell,bash(默认)或 powershell |
完整示例
<!-- ~/.claude/skills/gen-api/SKILL.md -->
---
description: 生成 REST API 端点的完整代码骨架
when_to_use: 需要创建新的 API 端点时
arguments: method resource
argument-hint: "[GET|POST|PUT|DELETE] [resource-name]"
allowed-tools:
- Read
- Write
- Grep
- Glob
---
为资源 $resource 创建 $method 方法的 REST API 实现。
基于项目既有模式,生成以下文件:
1. 路由定义
2. 请求/响应类型
3. 控制器逻辑
4. 基本测试参数按位置映射,顺序必须严格:
/gen-api POST users → $method=POST, $resource=users ✓
/gen-api users POST → $method=users, $resource=POST ✗(参数错位)调用方式:
- 手动触发:输入
/gen-api POST users - 自动触发:对模型说"帮我生成一个创建用户的 API",模型识别意图后自动调用该技能
参数不足 arguments 声明数量时,多余变量替换为空字符串。未使用 $name 占位符的多余参数会追加到内容末尾。
三条核心原则
一个技能只做一件事。 超过 200 行就该拆分。"全栈开发"技能不如"API 生成"、"组件生成"、"测试生成"三个独立技能。
描述要精确。 description 是模型选择技能的依据。模糊描述导致选错。"代码审查"比"帮助改进代码"好得多。
用条件技能减少噪音。 加 paths 字段,只在操作匹配文件时激活。20 个技能不是问题,20 个同时出现在列表里才是。