Skill的实现方式:让 Agent 学会“开挂“
Skill的实现方式:让 Agent 学会"开挂"
用 Claude Code 写代码时间长了,你会发现一个现象:有时候它表现得像一个经验丰富的老手,对你的项目结构、编码习惯、甚至某些专业领域的知识了如指掌;有时候又像个新手,什么都不懂,需要你从头解释。
区别在哪?在于它有没有加载对应的 Skill。
Skill 其实就是一份提前写好的提示词,在特定时机注入到 Claude 的上下文中,告诉它"你现在要扮演一个擅长做某某领域的专家"。就像给一个聪明但什么都不知道的新员工一份详细的岗位手册,他看完就知道该怎么干活了。
Skill 长什么样
一个 Skill 本质上就是一个 Markdown 文件,放在特定目录下。Claude Code 在启动时会扫描这些目录,把找到的 Skill 注册到系统中。
最简单的 Skill 结构是这样的:
my-skill/
SKILL.md
SKILL.md 就是 Skill 的全部内容。来看一个最基础的例子:
---
name: code-review
description: 审查代码变更,检查潜在问题
---
你是一个严格的代码审查员。审查代码时重点关注:
1. 安全漏洞(SQL 注入、XSS、硬编码密钥)
2. 性能问题(N+1 查询、未优化的循环)
3. 代码风格(命名规范、函数长度)
输出格式:
- 用中文回复
- 每个问题标注严重程度: 高 / 中 / 低
- 给出具体的修改建议
开头用 --- 包裹的部分是 frontmatter,定义了 Skill 的元信息:名字和描述。描述很重要,Claude Code 靠它来判断什么时候该加载这个 Skill。
下面的内容就是注入到上下文中的指令。你写什么,Claude 就会遵循什么。
Skill 放在哪里
Claude Code 会从多个位置扫描 Skill:
~/.claude/skills/ # 用户级,全局生效
<project>/.claude/skills/ # 项目级,仅当前项目生效
用户级的 Skill 适合放通用能力,比如代码审查、文档生成;项目级的 Skill 适合放项目专属的知识,比如"我们项目用的是 Vue 3 + TypeScript,组件放 src/components 下"。
每个 Skill 就是一个文件夹,里面放 SKILL.md:
~/.claude/skills/
code-review/
SKILL.md
doc-generator/
SKILL.md
api-design/
SKILL.md
Skill 是怎么被加载的
这是最关键的部分。回到开头那个问题:如果把所有规范文档全塞进 system prompt,6500 行的 prompt 每次调用都带着,99% 的内容和当前任务无关,白白烧 token。
Skill 的解决方案是两层设计——目录层和内容层分开加载:
| 层 | 注入位置 | 时机 | 代价 |
|---|---|---|---|
| 目录 | system prompt | 启动时扫描 skills/ | ~100 tokens/skill,每轮都带 |
| 内容 | tool_result | Agent 调用 load_skill 时 | ~2000 tokens/skill,按需 |
第一层:启动时注入目录
Claude Code 启动时,harness 会扫描 skills/ 目录,解析每个 SKILL.md 的 YAML frontmatter(name、description),存入一个注册表 SKILL_REGISTRY。然后从注册表生成目录,注入 system prompt。
Agent 每轮都能看到"我有哪些技能可用",但只看到名字和描述,不花额外 API 调用。
SKILL_REGISTRY: dict[str, dict] = {}
def _scan_skills():
for d in sorted(SKILLS_DIR.iterdir()):
if not d.is_dir():
continue
manifest = d / "SKILL.md"
if manifest.exists():
raw = manifest.read_text()
meta, body = _parse_frontmatter(raw)
name = meta.get("name", d.name)
desc = meta.get("description", raw.split("\n")[0].lstrip("#").strip())
SKILL_REGISTRY[name] = {"name": name, "description": desc, "content": raw}
def build_system() -> str:
catalog = "\n".join(f"- **{s['name']}**: {s['description']}" for s in SKILL_REGISTRY.values())
return f"You are a coding agent. Skills available:\n{catalog}\nUse load_skill to get full details when needed."
这里有个安全细节:注册表是启动时填充的,后续通过名字查找,不走文件路径,没有路径遍历风险。
第二层:load_skill 按需加载内容
Agent 看到目录后,决定"我需要 code-review 这个 Skill",于是调用 load_skill("code-review") 工具。这时候才把 SKILL.md 的完整内容读出来,通过 tool_result 注入当前 messages。
TOOLS.append({
"name": "load_skill",
"description": "Load a skill by name to get full instructions",
"input_schema": {
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"]
}
})
TOOL_HANDLERS["load_skill"] = load_skill
def load_skill(name: str) -> str:
skill = SKILL_REGISTRY.get(name)
if not skill:
return f"Skill not found: {name}"
return skill["content"]
注意一个关键区别:Skill 内容不是 system prompt 的一部分,它作为一次工具调用的结果进入 messages。后续轮次会随对话历史一起携带,直到上下文被压缩或会话结束。
用伪代码来描述整个流程:
启动时:
扫描 skills/ → 解析 frontmatter → 存入 SKILL_REGISTRY → 生成目录 → 注入 system prompt
运行时:
用户: "帮我 review 代码"
↓
Agent 看到目录里有 code-review Skill
↓
Agent 调用 load_skill("code-review")
↓
tool_result 返回 SKILL.md 完整内容
↓
Agent 基于 Skill 指令执行任务
这个设计和上下文压缩(compact)自然衔接:按需加载解决了"不该提前带的不要带",compact 解决"该丢的怎么丢"。两层配合,让 Agent 的上下文始终保持在最精简的状态。
Skill 和普通 Prompt 有什么区别
你可能会想:这不就是把一段 prompt 塞进上下文吗?我自己复制粘贴一段指令进去不也一样?
从效果上看确实类似,但 Skill 有几个关键优势:
1. 按需加载,不浪费 token
你有一百个 Skill,但每次对话只用到一两个。两层设计确保目录层始终轻量(~100 tokens/skill),内容层只在需要时才通过工具调用加载(~2000 tokens/skill)。如果全塞进 system prompt,上下文窗口直接就被吃掉了。
2. 声明式的,不需要写代码
Skill 不是插件系统,不需要实现接口、注册回调。你只需要写 Markdown,描述你想要的行为就行。门槛低到任何会写字的人都能创建 Skill。
3. 可以调用工具
Skill 里可以声明它需要使用哪些工具,以及如何使用。比如一个"数据库迁移" Skill 可以指定使用 bash 工具执行 prisma migrate 命令。
4. 版本管理和共享
Skill 就是文件,可以放在 Git 仓库里做版本管理。团队共享一套 Skill,每个人的行为就统一了。
来看一个真实场景
假设你团队有一套代码规范,每次 review 代码都要重复说一遍:
我们用 TypeScript strict 模式
函数命名用 camelCase,组件命名用 PascalCase
API 返回值统一用 { code, data, message } 格式
错误处理统一用 try-catch,不用 .catch()
每次都复制粘贴这些规则,烦不烦?
写成 Skill 就一劳永逸了:
---
name: team-code-review
description: 按照团队规范审查代码变更
---
你是一个严格的代码审查员,审查时遵循以下团队规范:
## TypeScript 规范
- 使用 strict 模式
- 禁止使用 any,必须定义具体类型
- 接口以 I 开头,如 IUserData
## 命名规范
- 函数/变量:camelCase
- 组件/类:PascalCase
- 常量:UPPER_SNAKE_CASE
- 文件名:kebab-case
## API 规范
- 返回值统一格式:{ code: number, data: T, message: string }
- 错误处理统一使用 try-catch
- 不使用 .catch() 链式调用
## 审查输出格式
1. 列出所有不符合规范的地方
2. 每条标注:文件名 + 行号 + 问题描述 + 修改建议
3. 最后给一个总体评价
把这个文件放到 ~/.claude/skills/team-code-review/SKILL.md,以后说"帮我 review 代码",Claude 就会自动按照你们团队的规范来审查,不用你再重复那些规则了。
Skill 的触发方式
Skill 的触发有两种方式:
自动触发:Agent 在推理过程中看到 system prompt 里的 Skill 目录,根据用户消息和 Skill 描述自行判断是否需要加载。如果匹配上了,就调用 load_skill 工具获取完整内容。比如用户说"帮我 review 一下这个 PR",Agent 看到目录里有一个 description 是"审查代码变更"的 Skill,就会主动调用 load_skill("code-review")。
手动触发:在 Claude Code 中输入 /skill-name 可以强制加载某个 Skill。比如 /code-review 会直接加载代码审查 Skill,不管当前消息是什么。
手动触发适合那些不容易被自动匹配的场景。比如你有一个 Skill 是"按照公司模板生成周报",自动匹配很难判断什么时候该触发,但手动 /weekly-report 就很明确。
一个更复杂的例子
Skill 不只能写审查规则,还能定义完整的工作流程。来看一个生成 API 文档的 Skill:
---
name: api-doc-generator
description: 根据代码自动生成 API 接口文档
---
你是一个 API 文档生成专家。当用户要求生成 API 文档时,按以下流程执行:
## 第一步:扫描项目
- 使用 Glob 工具查找所有路由文件(routes/、api/、controllers/)
- 使用 Read 工具读取每个路由文件的内容
## 第二步:提取接口信息
对每个接口提取:
- HTTP 方法和路径
- 请求参数(query、body、path params)
- 响应格式
- 认证要求(是否需要 token)
## 第三步:生成文档
使用 Markdown 格式输出,每个接口包含:
- 接口名称和描述
- 请求示例(curl 格式)
- 响应示例(JSON 格式)
- 错误码说明
## 第四步:保存文件
将生成的文档保存到 docs/api.md
这个 Skill 不只定义了"输出什么",还定义了"怎么做"——先扫描、再提取、再生成、再保存。Claude 会严格按照这个流程执行,就像一个有标准作业流程的员工。
Skill 和 CLAUDE.md 的关系
你可能还听说过 CLAUDE.md,它和 Skill 有什么区别?
CLAUDE.md 是项目级的全局指令,每次对话都会加载。适合放项目的基本信息:技术栈、目录结构、构建命令这些。
Skill 是按需加载的能力包,只在需要的时候才注入。适合放专业领域的工作流和规则。
两者可以配合使用。CLAUDE.md 告诉 Claude “这个项目是什么”,Skill 告诉 Claude “在特定场景下该怎么做”。
CLAUDE.md(始终加载)
"这是一个 Vue 3 + TypeScript 项目,使用 pnpm 管理依赖"
Skill(按需加载)
"当用户要求 review 代码时,按照团队规范审查"
"当用户要求生成文档时,按照标准模板输出"
小结
Skill 的核心思想就一句话:用到的时候才加载,别全塞 prompt 里。
传统的做法是把所有规范文档塞进 system prompt,每次调用都带着,不管有没有用。Skill 的两层设计解决了这个问题:目录层始终轻量,内容层按需通过工具调用注入。从软件工程的角度看,这就是懒加载思想——和前端的路由懒加载、后端的依赖注入是同一个思路。
Claude Code 的 Skill 机制没有用什么复杂的技术,没有编译器、没有运行时、没有沙箱。它就是一个 Markdown 文件加一套两层加载策略。但正是这种简洁,让它变得极其灵活——你不需要会写代码,只需要会写字,就能给 Agent 扩展能力。
注:本文参考了 GitHub 开源学习项目:learn-claude-code。这是一个非常优秀的学习资源,推荐读者结合项目内容一同学习。
更多推荐



所有评论(0)