最近在折腾 AI agent 项目,"agent-skills" 这个关键词几乎天天都要碰。圈子里聊得最多的就是:到底什么样的技能才叫一个合格的 skill?为什么同样的模型,有人调出来像个得力的实习生,有人调出来就像个只会复读的聊天机器人?差异往往就藏在 skills 的设计与实现上。
如果你正准备入坑 agent 开发,或者已经在用 Claude、Codex 这类编码智能体,想给它们装上更顺手的"手脚",这篇文章应该能帮你少走不少弯路。我会从第一性原理出发,拆解 agent skills 到底是什么、怎么设计、怎么实现,再附上完整实操案例和排坑记录,内容偏实战,尽量说人话。
1. agent skills 的本质:先搞清楚它解决了什么问题
1.1 从"工具"到"技能"的认知升级
很多人第一次接触 agent 时,最先听到的概念是 tool(工具),比如"搜索工具""计算器工具""数据库查询工具"。工具解决的是"单次、确定性"的操作——输入几个参数,返回一个结果。但真实世界的任务远没有这么简单。比如"帮我把这个项目的依赖升级到最新版本",这背后是一连串动作:先读 package.json、查当前版本、了解 breaking changes、改代码、跑测试、解决冲突、再跑测试……如果每一步都要 agent 自己临场发挥、现场拼接,结果往往很随机,今天能用明天就翻车。
这就是 skills 存在的意义。一个 skill 不是单个动作,而是把"完成某类任务的方法论"封装成一个可复用、可版本管理、可分享的单元。它包含了触发场景、执行步骤、避坑指南、示例代码,甚至允许 agent 在执行过程中动态调整策略。
我的理解是:tools 是乐高积木块,skills 是说明书加成套积木的组合包。积木块再多,没有图纸,新手也拼不出像样的东西;而 skills 就是那张图纸。
1.2 为什么现在 skills 这么火
最近"Claude agent skills"相关的讨论热度很高,GitHub 上各种 skill 仓库如雨后春笋。核心原因有三点,我逐一拆开讲。
第一,模型能力变强之后,瓶颈从"能不能理解"转移到了"有没有好的工作方法"。大模型的推理能力已经相当能打,但你给它一套混乱的执行框架,它也发挥不出应有水平。相反,一个精心设计的 skill 相当于把资深工程师的"肌肉记忆"喂给了 agent,质量和稳定性都会有明显提升。
第二,agent 的应用场景在快速拓宽。从写代码、改 bug 到数据分析、运维排查、内容创作,每个场景都有自己的专业套路。skills 提供了一种标准化的封装方式,让不同场景的 know-how 可以被沉淀、被分享、被复用。
第三,生态在快速形成。现在除了 Claude 官方的 skills 机制,Codex 有 codex skills,GitHub 有 skills 市场,国内也有不少第三方工作台在推自己的 skills 平台。这种生态效应让"会写 skills"逐渐变成 agent 开发者的基本功。
1.3 skills 和 tools、prompts 的边界在哪
这是新手最容易混淆的地方。我直接给一个对比表,看完基本就清楚了。
| 维度 | Tools | Prompts | Skills |
|---|---|---|---|
| 粒度 | 单个动作 | 指令文本 | 方法集合 |
| 状态 | 无状态,每次独立 | 无状态 | 可以有阶段、有记忆 |
| 复用性 | 低,需要编排 | 中,复制粘贴 | 高,可安装可分发 |
| 触发方式 | 显式调用 | 上下文引导 | 按需自动加载 |
| 包含内容 | 函数+Schema | 提示词 | 说明+脚本+示例+校验 |
写到这我要强调一个观点:不要试图用 skills 替代一切。简单、稳定、单一职责的操作,用 tools 反而更干净;跨步骤、多决策点、需要专业经验的任务,才值得封装成 skill。拿生活类比,开灯用开关(tool),但装修一套房子得靠施工方案和验收标准(skill),后者才需要沉淀成文档和流程。
2. skill 的架构拆解:一个合格技能的内部结构
2.1 SKILL.md:一切的核心入口
目前主流 agent 框架对 skill 的抽象高度一致:一个目录,里面至少有一个 SKILL.md 文件,负责让 agent"读懂"这个技能是干什么的、什么时候该用、怎么用。
SKILL.md 不是传统意义上写给人类看的 README,它更像是 agent 的"任务简报"。我见过写得好的 SKILL.md,几乎都有这几个特征:
- 开篇用两三句话讲清楚这个 skill 适用的任务类型,以及不适用的情况,避免 agent 误触发。
- 用清晰的层级标题组织"执行步骤",不是让人读着舒服,而是让 agent 的注意力能快速定位到当前阶段。
- 包含具体的质量标准和完成定义,比如"测试覆盖率不得低于 80%""所有构造函数必须显式声明"。
- 嵌入失败回退策略,告诉 agent 当步骤 A 失败时,是重试、降级还是向用户求助。
有一个常见误区是:把 SKILL.md 写成一本操作手册,翻到第三屏才能看到核心指令。agent 的上下文窗口虽然越来越大,但注意力永远是稀缺资源,信息密度低、通篇废话的 SKILL.md 会显著拉低执行质量。我在实际项目中的经验是,一份优秀的 SKILL.md 控制在 150~300 行以内,能用列表绝不用长段落,每一步都对应一个可验证的结果。
2.2 辅助脚本和资源:让技能真正"能干活"
光有说明文档还不够,真正落地的 skill 通常还包含:
- 脚本文件,比如 Python、Shell 脚本,负责自动化执行具体步骤。
- 模板文件,比如代码脚手架模板、汇报文档模板。
- 配置文件,用于声明依赖、权限、执行环境。
- 测试用例,供 agent 在完成实现后自行验证。
为什么需要这些"实体文件"?因为 agent 在推理过程中最大的短板是执行效率。一个生成 commit message 的 skill,如果每一步都靠模型现场推理,生成 100 条 commit message 的时间和 token 成本都很可观。但如果 skill 里内置一个脚本,先把 diff 抓出来,再批量调用模型或规则引擎处理,效率和稳定性都会上一个量级。
当然,辅助脚本不是越多越好。脚本越复杂,维护成本越高,出问题时排查难度也越大。我个人的原则是:能用十行 shell 解决的,不写五十行 Python;能用单个脚本搞定的,不拆成四个文件。
2.3 触发与加载机制:让技能在恰当的时机出现
skills 和传统工具最大的使用体验差异在于触发方式。传统工具是 agent 在每一步主动去"检索工具列表";而现代 skill 机制(尤其在 Claude 和 Codex 的实现里)倾向于把 skill 的元信息注入系统提示词或采用"按需加载"策略。
按需加载意味着:agent 在运行过程中遇到一个任务,先判断"这像不像某个 skill 擅长处理的问题",如果匹配,再读取完整的 SKILL.md 和资源文件,然后开始执行。这种方式大幅节约了上下文窗口,但也给 skill 的"元信息描述"提出了更高要求——描述写得模糊,agent 就无法建立匹配;描述写得过于宽泛,又容易误触发。
这里有一个非常实际的调试技巧:每次测试 skill 的时候,留意 agent 到底读取了哪些文件、在哪个环节决定启用这个 skill。如果每次都差一步才触发,问题大概率出在 SKILL.md 的"适用场景"描述上,而不是执行逻辑本身。
2.4 版本与依赖管理:skills 也是要长期维护的代码
我在团队里经常说一句话:skill 写得出来是一回事,养得活是另一回事。很多 skill 刚开始效果惊艳,跑两周就越来越笨,原因往往是:
- 内部脚本依赖了外部服务,但 API 升级了没人同步。
- SKILL.md 里的示例代码和当前项目的最佳实践脱节了。
- skill 针对的框架版本变了大版本,但说明文档没更新。
所以,如果你有多个项目共用一套 skills,强烈建议用 Git 仓储管理,给每个 skill 打上版本号,在 SKILL.md 里声明适用的依赖版本范围。这个动作多做一步,后面能省下大量排查时间。
3. 从零到一:手把手实现一个真实可用的 skill
3.1 选场景:为什么我选了"前端组件代码审查"
光讲理论容易飘,我拿一个实际做过的 skill 举例:一个用于前端 React 组件代码审查的 skill。选这个场景有几个考虑:
- 任务复杂度适中,既有确定性检查(比如 Hook 依赖数组),又有开放性判断(比如组件拆分是否合理)。
- 结果可验证,审查结论可以对照资深前端工程师的手工审查结果来评估。
- 具备通用性,很多人都在写 React,分享出来大家都能用。
你也可以按这个标准去选自己的第一个 skill 场景:不太简单、也不过于复杂,结果容易验收,且是自己反复要做的任务。第一个 skill 千万别选"帮我做架构设计""全面代码审计"这种又大又虚的,否则很容易在调试阶段就失去信心。
3.2 定义 SKILL.md 的内容结构
先看这个 skill 的目录结构:
react-component-review/ ├── SKILL.md ├── scripts/ │ └── check_hooks.py ├── rules/ │ └── react_rules.json └── examples/ ├── bad_component.jsx └── good_component.jsxSKILL.md 的核心内容,我会这样设计:
# React Component Review ## Description Review React function components for code quality, focusing on hooks usage, state management, performance pitfalls, and accessibility issues. Use this skill when: - User asks to review a React component - User requests code quality assessment for frontend code - A pull request includes React component changes Do NOT use when: - Code is not React/JavaScript/TypeScript related - User only wants formatting fixes (use linting tools) ## Workflow 1. Understand the component's purpose and props - Read the component source code - Identify the component's responsibilities 2. Check hooks rules - Verify hook call order and conditions - Verify dependency arrays of useEffect/useCallback/useMemo - Look for missing cleanup functions 3. Check state and props handling - Identify unnecessary state duplication - Look for props drilling and suggest context if needed 4. Check performance patterns - Look for inline functions/objects in render - Check unnecessary re-renders 5. Check accessibility - Validate semantic HTML usage - Check aria attributes and keyboard support 6. Summarize findings into a report - Severity: critical/warning/suggestion - Include code snippets and concrete fix suggestions ## Standards - Critical: bugs, data loss, security risks - Warning: performance issues, maintainability concerns - Suggestion: style and best practice improvements这个设计遵循了我前面说的原则:描述区先给触发条件,明确"何时用何时不用";流程区用有序列表把审查步骤拆成可执行的阶段;标准区解决"什么叫做好"的评判问题。整个文件控制在 100 行左右,信息密度足够高。
3.3 辅助脚本:让检查结果可量化
在辅助脚本这一层,我写了一个check_hooks.py,用 AST 解析 React 组件的 Hook 调用情况。
import ast import sys import json class HookVisitor(ast.NodeVisitor): def __init__(self): self.hook_calls = [] self.in_component = False def visit_FunctionDef(self, node): # 组件名以大写字母开头的函数视为组件 if node.name[0].isupper() and node.args.args: self.in_component = True self.generic_visit(node) self.in_component = False def visit_Call(self, node): if isinstance(node.func, ast.Name): name = node.func.id if name.startswith('use') and name[3].isupper(): self.hook_calls.append({ 'name': name, 'line': node.lineno, 'args_count': len(node.args), 'has_kw_args': bool(node.keywords) }) def main(filepath): with open(filepath, 'r', encoding='utf-8') as f: source = f.read() tree = ast.parse(source) visitor = HookVisitor() visitor.visit(tree) result = { 'file': filepath, 'hook_calls': visitor.hook_calls, 'count': len(visitor.hook_calls) } print(json.dumps(result, indent=2, ensure_ascii=False)) if __name__ == '__main__': main(sys.argv[1])这个脚本本身很简单,但它做了一件很关键的事:把模型靠模糊推理容易漏掉的"客观事实"(比如这个文件里到底有几个 useEffect、每个在哪个行号)先精确抓出来,然后 agent 只需要在这个事实基础上做专业判断,推理负担大大降低。
AST 解析还有一点好处,不区分"组件内"和"组件外"的 Hook 逻辑才是真正考验水平的地方。比如 React 官方规则要求 Hooks 必须无条件、无循环地在组件顶层调用,这些规则仅靠人工 review 容易累,脚本可以辅助标记,然后把标记结果交给 agent 进行语义判断。
3.4 编写 rules 配置:把最佳实践沉淀下来
光检查 Hook 调用还不够。我在react_rules.json里沉淀了团队的最佳实践,这段配置驱动审查规则的方式,对 agent 来说非常有效。
{ "strict": [ { "rule": "hooks-dependency-completeness", "severity": "critical", "check": "useEffect/useCallback/useMemo dependency array must include all external variables used inside" }, { "rule": "hooks-no-conditional", "severity": "critical", "check": "React hooks must not be called inside conditions, loops, or nested functions" }, { "rule": "state-initialization-from-props", "severity": "warning", "check": "initializing useState directly from props without a key reset pattern indicates duplicated state" } ], "suggestions": [ { "rule": "use-object-memo", "severity": "suggestion", "check": "consider using useMemo or memo to prevent unnecessary re-renders" } ] }写规则的时候要特别注意表达方式:规则必须是可以被"验证"的,而不是形而上的口号。比如"提高代码质量"这种规则,agent 读完无法转化为行动;而"依赖数组必须包含内部使用到的所有外部变量",则可以作为一条明确的校验项,agent 在审查时能照此逐条对照。
3.5 裁剪示例:让 agent 有样板可循
examples/bad_component.jsx和examples/good_component.jsx的作用是提供正反样本,帮助 agent 校准"好的审查结论"长什么样。
坏样本里我故意埋了几个典型问题:
import React, { useState, useEffect } from 'react'; export function UserProfile({ userId, user }) { const [name, setName] = useState(user?.name || ''); useEffect(() => { fetch(`/api/users/${userId}`) .then(res => res.json()) .then(data => setName(data.name)); }, []); if (user?.role === 'admin') { const [unused, setUnused] = useState(false); } return ( <div> <h2>{name}</h2> <button onClick={() => setName('')}>Clear</button> </div> ); }好样本会把同样的需求用规范方式实现一遍:useState 传函数初始化、useEffect 依赖数组补全、不要在条件分支里调用 Hook、给按钮加 type 属性等等。样本的目的不是给 agent 抄答案,而是让它在审查时有一把"尺子",对照尺子判断待审代码的偏离程度。
4. 实战:把 skill 接入 agent 并完成一次完整审查
4.1 安装与注册 skill
不同的 agent 框架注册方式略有差异。以 Claude 系的 skill 机制为例,通常是把 skill 目录放到~/.claude/skills/下,或通过配置文件声明路径;Codex 则更倾向于用仓库内.codex/skills目录来管理项目级技能。无论哪种方式,核心步骤一致:
- 创建 skill 目录,命名采用短横线命名法,方便路径匹配。
- 在目录内放置 SKILL.md 作为入口。
- 通过 agent 的命令行或配置命令,指定 skills 目录。
- 触发一次会话,检查 agent 是否识别到该 skill。
有一个细节值得注意:很多人在本地调试时忘了重启 agent 会话,导致新加的 skill 一直"不生效"。"我加了 skill 为什么没反应",这个问题的排查优先级,第一步永远是确认 agent 是不是真的加载了最新配置,而不是去改 SKILL.md 内容。你可以直接问 agent:"你当前可用的 skills 有哪些?",如果能列出你刚添加的项,说明加载链路是通的。
4.2 准备待审查代码
实际的审查任务不会只针对一个组件,而是一个目录或多个文件。为了验证 skill 的泛化能力,我通常至少准备三个文件:
- 一个相对规范的组件,预期输出应该是"少量建议"。
- 一个问题明显的中型组件,预期输出应该同时包含 critical 和 warning。
- 一个使用高阶 API(自定义 Hooks、context、memo)的复杂组件,用来测试 skill 的深度。
在 CLI 场景下,直接对 agent 说"请用 react-component-review 技能审查 src/components/UserProfile.tsx"。在带有工作台的场景下,界面一般会显示当前启用的 skill 列表,直接勾选后发起任务即可。
4.3 执行与观察:agent 的处理流程
实际运行时,agent 的行为大致分三个阶段:
第一阶段是信息收集。它先打开SKILL.md读取审查流程,然后读取被审文件,同时运行check_hooks.py拿到 Hook 统计结果。你会看到 agent 在对话里输出类似"正在检测 hooks 调用模式"的过程信息。
第二阶段是逐条对照规则。agent 会把代码切成几个关注点,对照react_rules.json中每条规则逐项打分。效果好的 skill,会让 agent 每确认一条规则都给出对应的行号或代码摘录,而不是空泛地说"这里有问题"。
第三阶段是生成报告。好的实现会用 markdown 表格组织问题清单,按严重程度排序,并给出可落地的修改建议。我见到过的优秀输出,甚至会顺带把修改后的代码片段完整贴出来,并说明为什么这样改。
4.4 审查报告长什么样
这个 skill 的典型输出大概长这样:
| 严重级别 | 问题 | 位置 | 建议 |
|---|---|---|---|
| Critical | useEffect 依赖数组缺少 userId,闭包捕获了过期的值 | UserProfile.tsx:12 | 改为[userId] |
| Critical | Hooks 不能在条件分支中调用 | UserProfile.tsx:18 | 将 useState 移到组件顶层 |
| Warning | useState 直接从 props 初始化,父组件更新时状态不同步 | UserProfile.tsx:6 | 使用 key 重置或受控组件 |
| Suggestion | 内联箭头函数导致按钮组件每次渲染都重新创建 | UserProfile.tsx:24 | useCallback 或提取具名函数 |
我实测下来,这个报告的准确率和一致性,比"让 agent 直接审查但不引导流程"要高不少。尤其是"position"这一栏,AST 脚本提供的客观事实帮了大忙,agent 极少再把问题定位到错误的行号上。
5. 常见问题与排查技巧实录
5.1 skill 不触发或总是误触发
这是我最常被问到的问题。症状是:agent 明明装了 skill,但遇到对应的任务却像没看见一样,或者八竿子打不着的问题也把它拉出来用。
排查思路很固定。先检查SKILL.md里的 Description 措辞。我在写 Description 时有个经验:不要只用几个抽象名词描述任务,要尽可能写清触发场景。比如"review React components"就不如"use this skill when user asks to review a React function component, especially regarding hooks, state, and performance issues"更有效。模型对"when"从句的理解能力比对纯关键词的理解能力要强得多。
误触发的问题,往往出在"何时不要使用"区域写得不够用力。不要害怕把限制条件写得具体甚至苛刻,宁可少触发,也不要让它总在不合适的场景浪费上下文。因为 agent 一旦加载了一个错误的 skill,就会带着偏颇的框架去处理任务,结果往往会更糟。
5.2 辅助脚本执行报错
脚本报错是 skill 实战里几乎必然遇到的事情。常见的有三种类型:
- 环境依赖缺失,比如系统没有 Python 3 或缺少某些 pip 包。
- 路径问题,agent 有时用相对路径打开文件,但工作目录和 skill 脚本不在同一层。
- 文件编码问题,Windows 下源码文件可能是 GBK 编码,Python 默认按 UTF-8 处理就会崩。
我在设计 skill 脚本时通常会加一层"防御式报错":脚本启动时先打印cwd和文件路径,遇到解析错误用 try 捕获并输出友好提示,而不是直接抛堆栈。这样可以大大降低 agent 在沙箱里反复猜测的成本。
另外,如果你用一个 skill 的本意是"让团队的普通工程师也能用",尽量把脚本依赖收敛到最基础环境。能用系统自带命令完成的,就不要引入第三方库。每多一个依赖,你的 skill 在其他机器上跑不起来的概率就会高一分。
5.3 同一份 skill,两次执行结果差异很大
这个问题的根源往往不是 skill 本身,而是模型的随机性和上下文干扰。即使设置了温度参数,长链路的 agent 任务也会因为中间推理路径的分叉,导致最终结果出现漂移。
我采取的应对策略有两个。
第一,在 SKILL.md 里尽量把"完成标准"写死。比如这个 skill 审查的是 React 组件,就明确"你必须在报告的末尾输出一份检查清单,逐项勾选已完成的项目"。这相当于给 agent 的推理过程加了一个锚点,让它不容易半路跑飞。
第二,利用脚本输出的客观数据作为硬约束。AST 脚本输出的 hook 列表、行号信息,agent 可以视作"不可置疑的地面真相"。它只能在这个事实基础上做进一步判断,而不能自行想象"大概这边有个 useEffect"。这大幅抑制了结果的随机性。
5.4 问题排查速查表
| 症状 | 高频原因 | 首选排查动作 |
|---|---|---|
| skill 未加载 | 配置目录错误 | 询问 agent 列出当前 skills |
| skill 不触发 | Description 太抽象 | 加上具体 when 场景描述 |
| skill 误触发 | 排除条件不足 | 在 SKILL.md 写明 Do NOT use when |
| 脚本无法运行 | 环境依赖缺失 | 在 skill 目录加 install 脚本 |
| 结果不稳定 | 标准不明确 | 增加完成清单和脚本硬约束 |
| 输出格式混乱 | SKILL.md 缺少输出示例 | 增加 Expected Output 示例段 |
| 上下文消耗过大 | SKILL.md 冗长 | 精简到 300 行以内,拆分多文件 |
5.5 一个很容易被忽略的点:上下文窗口的"预算"管理
很多开发者给 skill 塞了很多内容,包括历史经验、知识库链接、背景信息,但 agent 每次执行任务时能装的上下文是有限的,你不能什么都往里塞。
我在设计 skill 的时候会做一个"上下文预算规划":假设 agent 的可用上下文是 200K tokens,那么 skill 本身加载最多不超过 20K tokens,剩下的给代码文件、给推理过程、给用户对话留足空间。如果 skill 内容超过这个预算,就拆成"核心版"和"扩展版",默认只加载核心版,当 agent 判断需要更深的知识时,再读取扩展文件。
这个思路是向文档系统学的。就像写技术文档不能把全套 API 手册都放在首页,skill 的设计同样讲究"渐进式披露"——先给 agent 最少必要信息,让它的推理专注于当前阶段,再在需要时暴露更深层的知识。
6. 写在后面的一点个人体会
从更宏观的角度看,skills 真正值得关注的价值,是把人的经验注入到 agent 的执行框架里。我在做 react-component-review 这个 skill 时,最深的感触是:写 SKILL.md 的过程,本质上是一个"把自己的审查经验结构化"的过程。以前我看代码靠的是大脑里模糊的模式匹配,现在必须把它转译成明确的步骤、规则、标准,这个转译过程本身就倒逼我把方法论想得更清楚。
如果你准备开始写自己的第一个 skill,我的建议是:从你自己最能拿出手的那类任务入手。比如你是写文案的,就做一个文案审校 skill;你是做运维的,就做一个日志排查 skill;你是做测试的,就做一个测试用例设计 skill。技能本身虽然技术含量不高,但因为你足够懂这个领域,你写出来的流程和标准会比 AI 自己摸索出来的靠谱得多。
在你要把这个 skill 分享出去之前,再检查三件事:第一,SKILL.md 是否能在三分钟内读完并理解触发条件和执行步骤;第二,辅助脚本是否在干净环境里能跑通;第三,是否给了一份"预期输出示例"。这三件事做到位,你的 skill 才算真正具备了被其他人或模型顺畅使用的基础。