前阵子好几个读者在后台问同一个问题:为什么照着网上教程写的Skill,装进Claude Code或者Codex里就是不好使?要么Agent视而不见,要么跑起来逻辑完全不对。
我仔细看了他们写的Skill,问题几乎都出在同一个地方——把"写一份说明文档"当成了"创建一个Skill"。这个认知偏差如果不纠正,后面怎么写都是白费。
这篇文章我就把自己这一年多来折腾Skill的完整经验整理出来,从底层概念到目录结构、指令写法、上下文占用、依赖管理、实测流程,再到跨平台差异,一条一条捋清楚。适合两类人看:第一类是刚开始接触Skill、想自己动手写的入门者;第二类是已经写过几个但总觉得不够好用、想搞清楚问题在哪的进阶玩家。
1. 先搞明白Skill到底是什么,不然方向就错了
很多教程上来就讲目录结构、Frontmatter怎么写,把Skill当成一个"配置文件"。我觉得这是最大的误导。在你动手写任何文件之前,得先把Skill的本质想清楚。
1.1 Skill、插件、Agent三者不是一回事
这三个概念经常被混着提,实际是完全不同的东西。
Agent是一个能自主规划和执行的智能体。它有对话能力、工具调用能力、记忆能力,能根据目标拆解任务并一步步完成。
**插件(Plugin/Extension)**是一种命令式扩展。它给Agent提供新的工具方法,比如"搜索网页""操作浏览器""执行数据库查询"。Agent在需要的时候主动调用这些方法,插件本身不决定Agent的行为逻辑。
Skill是什么呢?我的理解是:它是一份结构化的专业知识包。它不新增工具能力,而是教Agent"当你面对某类任务时,应该按照什么步骤、什么标准、什么思路去做"。
打个比方:插件是给Agent的"工具箱",Skill是给Agent的"岗位培训手册"。工具箱决定它能用什么,培训手册决定它把事情做成什么样。
1.2 Skill的实际工作流程
你可以观察一下自己用的工具。当你手动在对话里输入一段非常详细的要求,比如:
"你是一个资深数据分析师,拿到数据后先做缺失值检查,再做分布分析,然后按以下步骤输出报告……"
Agent就会按照这段话去执行。Skill做的事情,就是把这段"手动输入"变成"自动加载"。当某个Skill被启用,它的内容会被塞进Agent的上下文里,Agent在相关任务中就会自动遵循里面的指示。
所以结论很直接:
Skill的质量,完全取决于你写的指令文档质量。它本质上是提示词工程的结构化落地。
那些写不好Skill的人,问题根本不在代码,不在格式,而在他们根本不知道要用提示词的思路去写Skill。
2. 目录结构和元信息:第一印象决定成败
现在进入实操层面。虽然不同平台(Claude Code、Codex、OpenClaw等)的Skill目录规范略有差异,但核心结构大体一致。我以目前最常见的规范来讲,你掌握了这套逻辑,到哪个平台都能快速适配。
2.1 标准目录结构长什么样
一个Skill通常是一个独立目录,里面至少包含两个核心文件:
skill-name/ ├── SKILL.md # 指令文档,核心中的核心 ├── scripts/ # 可选,放辅助脚本 │ └── helper.py ├── assets/ # 可选,放参考模板、数据文件 │ └── report_template.md └── references/ # 可选,放详细参考资料 └── api_docs.mdSKILL.md这个名字在很多平台下是约定的默认入口,不要随便改名。有的平台支持自定义入口文件名,但如果你希望Skill具备可移植性,乖乖用SKILL.md最稳妥。
目录命名也要注意。我见过有人把目录命名为"最牛的skill"或者"test_123",这在本地测试没问题,但一旦发布,别人根本看不出这个Skill是干什么的。建议用连字符小写格式:code-reviewer、test-case-generator、>--- name: test-case-generator description: 根据需求文档生成可执行的测试用例,覆盖正常、异常、边界场景 version: 1.0.0 ---
这里最重要的字段是description,因为它是Agent判断"什么时候该启用这个Skill"的依据。但大部分人把description写成了"这是什么",而不是"什么时候用"。
看两个例子对比:
错误写法:
description: 测试用例生成工具正确写法:
description: 当用户需要从需求文档、PRD或功能描述中生成测试用例时使用此Skill。适用于需要覆盖正常流程、异常流程、边界条件的场景。第二个写法好在哪里?它包含了触发条件(用户需要生成测试用例时)、输入来源(需求文档/PRD/功能描述)、适用范围(正常/异常/边界场景)。Agent在阅读描述时,能明确判断"现在该不该启用这个Skill"。
还有一个容易忽略的点:不要在description里塞关键词堆砌。有的平台支持用关键词做触发匹配,但过度堆砌会让描述失去语义清晰度,反而降低匹配准确率。
Frontmatter里的字段千万别加引号加错地方。YAML对缩进和引号敏感,我见过有人在description里用了英文冒号,没加引号,导致解析失败,Skill直接加载不了。长描述里如果包含:,最好给整个值加双引号。
3. 指令文档的内容质量:Skill的上限在这里
SKILL.md的正文部分,是决定Skill是否好用的核心。你可以把它理解成一份给Agent看的标准作业流程(SOP)。但很多人把它写成了产品说明书,这就有问题了。
3.1 从"介绍"到"可执行",差的不是一句话
来看一个反面教材。有人写了一个代码审查Skill,正文长这样:
"你是一个专业的代码审查专家,具备丰富的编程经验。你会仔细审查代码,发现潜在的问题,并给出改进建议。审查内容包括代码质量、安全性、性能、可维护性等。"
这段话有什么问题?全是空洞的描述,没有任何可以让Agent执行的具体指令。什么叫"仔细审查"?什么东西算"潜在问题"?"改进建议"按什么格式输出?
好的审查Skill会这样写:
# 代码审查流程 ## 审查步骤 1. 读取代码文件,分析整体结构和职责划分 2. 逐函数检查逻辑正确性,重点关注: - 边界条件处理(空数组、null、溢出) - 错误处理分支是否完备 - 资源释放是否在finally/with块中完成 3. 检查安全风险: - SQL注入:确认所有用户输入都经过参数化处理 - XSS:检查输出到HTML的内容是否做了转义 - 硬编码密钥:扫描是否有明文密钥或令牌 4. 输出审查报告 ## 报告格式 必须按以下Markdown表格输出: | 严重级别 | 文件/行号 | 问题描述 | 修改建议 | |----------|-----------|----------|----------| 严重级别分为:阻塞(必须修复)、严重(强烈建议)、一般(可选优化)。看到了吗?好的指令有明确的动作(读取、检查、输出)、明确的标准(什么算问题)、明确的格式(表格输出,分级)。Agent看到这样的指令,执行结果才会稳定。
3.2 把"经验判断"转化为"可检查的规则"
写Skill最值钱的部分,是把那些你脑子里的"隐性经验"翻译成Agent能判断的"显性规则"。
比如你写一个日志分析Skill。你的经验是"看到某些特征就说明系统有问题"。那你就得把这些特征写出来:
- 关键词"OutOfMemoryError"、"Connection refused"、"TimeoutException"高频出现,视为严重问题
- 同一IP在5分钟内出现超过50次失败认证记录,视为暴力破解特征
- 日志时间戳出现"gap"且伴随重启记录,可能是发生了崩溃恢复
只要你能把经验具体化,Agent就能表现出"像一个有经验的人"。反过来,如果你的规则含糊不清,Agent就只能给你含糊的结果。
3.3 边界条件和例外情况:这是AGI能力放大的关键
很多Skill失败,不是因为没有主流程,而是没有边界条件处理。
什么是边界条件?就是"当任务不按预期进行时,Agent该怎么办"。
以测试用例生成Skill为例。你写了完整步骤,但没告诉Agent:如果需求文档本身描述模糊怎么办?如果功能涉及外部系统依赖怎么办?如果需求跟现有代码逻辑冲突怎么办?
不写这些,Agent通常会"硬编",生成一套看似完整、实际站不住脚的测试用例。
至少写这几个部分:
- 信息不足时:明确要求Agent先向用户提问,还是基于合理假设并在报告中标注假设
- 目标冲突时:优先级如何排序,比如"安全优先于性能"
- 不可执行时:允许Agent拒绝执行并说明原因,而不是硬编结果
3.4 示例的力量:一个例子顶十句描述
语言模型对具体例子的理解能力,远强于抽象描述。在Skill里放一两个"输入-输出"示例,能让Agent更精准地把握你想要的输出格式和风格。
示例要短小,但要有代表性。比如写一个SQL优化Skill,给一个"优化前-优化后"的对照例子:
## 示例 **优化前(差):** SELECT * FROM orders WHERE DATE(create_time) = '2025-06-01' **优化后(好):** SELECT * FROM orders WHERE create_time >= '2025-06-01 00:00:00' AND create_time < '2025-06-02 00:00:00' **为什么:** 前者的DATE函数导致create_time上的索引失效,全表扫描;后者范围查询可以用到索引。这个例子同时教了Agent两件事:格式上的"好"长什么样,以及背后的原理。比单独写十行"注意索引失效"管用得多。
4. 必须考虑上下文占用:你的Skill会被完整读完
这个章节讲一个被严重低估的问题:上下文窗口预算。
4.1 Skill的加载机制决定了它的"体量"问题
Skill被启用时,它的内容是整体塞进Agent的上下文里的。也就是说,你的Skill每多写1000个token,Agent处理用户任务时能用的上下文就少1000个token。
现在的模型上下文窗口动辄十几万token,但别被这个数字迷惑。实际可用量受两个因素制约:一是多轮对话里历史消息会不断累积,二是Agent需要留出"思考空间"给推理过程。一个动辄五六万token的Skill塞进去,会让主任务的可用空间严重缩水。
所以Skill文件要克制。核心原则是:能用500字说清楚的,绝不用1000字。
4.2 什么时候拆文件,什么时候保持单文件
很多平台支持"引用外部文件"的机制。常见的做法是:
SKILL.md只写核心流程和摘要,把详细参考资料放在references/目录里,在正文中用Read工具指令按需加载。
我把拆分的判断标准总结成一张表:
| 情况 | 建议 |
|---|---|
| 核心流程3000字以内 | 全放SKILL.md,不拆 |
| 详细规则/规范超过2000字 | 拆到references/,在SKILL.md中标注"执行到某步骤时,阅读references/xxx.md" |
| 有可复用的代码片段/脚本 | 拆到scripts/,用命令行方式调用 |
| 有模板类内容(报告模板、代码模板) | 拆到assets/,作为输出参考 |
举个例子。我写过一个"需求文档评审Skill"。SKILL.md里只有评审主流程、评审维度列表、输出格式,大概1500字。但"性能需求评审标准"和"安全需求评审标准"这两块内容很长,塞进SKILL.md会稀释重点。我就把它们放在references/performance-standards.md和references/security-standards.md里,在SKILL.md中写:
当需求涉及性能指标时,先阅读 references/performance-standards.md 中的指标阈值表,再按表进行逐项核对。这样Agent只有在处理性能相关需求时才会去读那份文件,平时不占用上下文。这就是"按需加载"的思路。
4.3 脚本调用是另一个上下文黑洞
如果你的Skill调用了外部脚本,注意:脚本的输出去哪了?在很多实现里,脚本stdout会以"工具调用结果"的形式进入上下文。如果一个脚本输出几千行日志,那等于Skill的"间接上下文开销"远超文件本身。
解决办法:
- 脚本输出做摘要和截断。只输出关键结论、统计信息、命中规则条目,不要把原始数据全量打印。
- 给脚本加参数,比如
--quiet模式或--limit N,控制输出规模。 - 让脚本把详细结果写入文件,只告诉Agent"结果已写入output.json,如需分析请读取该文件"。但要注意,读取文件本身也会占用上下文,还是要控制粒度。
5. 依赖与可移植性:换台机器就废了的Skill没人要
Skill不是写给自己一个人的。一个专业博主写Skill,默认读者可能在任何环境下运行。可移植性是衡量Skill成熟度的重要指标。
5.1 脚本依赖的三层声明
如果Skill带有Python脚本,第一件事就是声明依赖。我在scripts/目录下固定放一个requirements.txt,同时在SKILL.md里写清楚运行环境要求。
更细的做法是三层声明:
- 运行环境:Python版本、Node版本等。写明"要求Python 3.10+",不要含糊写"Python 3"。
- 第三方库:
requirements.txt里逐项列出,并标注大版本。 - 外部命令:比如依赖
git、docker、ffmpeg等系统级命令,必须在SKILL.md里显式说明。
否则,别人下载你的Skill,一跑就报ModuleNotFoundError,第一反应就是删掉。
5.2 路径问题:最常见的翻车现场
脚本里写绝对路径是Skill开发的禁忌。我不知道见过多少人的Skill脚本写死了C:\Users\admin\...或者/home/ubuntu/...,别人一用就废。
正确做法是使用相对路径,并且以Skill目录为基准。在Python里可以这样取:
import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DATA_FILE = os.path.join(BASE_DIR, "..", "assets", "config.json")另外一个经常被忽略的:工作目录不等于Skill目录。Agent执行脚本时,当前工作目录可能在项目根目录,也可能在任意位置。所以脚本内部所有文件路径都必须显式基于脚本自身位置拼出来,不能依赖相对路径相对于当前工作目录。
5.3 坏境变量和密钥:永远不要硬编码
如果脚本需要API密钥、Token之类的东西,不要写死在文件里。正确做法是:
- 使用环境变量:
os.getenv("OPENAI_API_KEY") - 在SKILL.md中写明需要设置哪些环境变量
- 设置合理的默认值(如果匿名可访问的话),并在出错时给出友好提示
我在实测中发现,很多Skill脚本报错时输出一堆看不懂的堆栈,没有提示用户"需要先设置XXX环境变量"。一个负责任的做法是在脚本开头做检查:
if not os.getenv("OPENAI_API_KEY"): print("错误:未检测到 OPENAI_API_KEY 环境变量。请先执行: export OPENAI_API_KEY=sk-xxx") sys.exit(1)这个细节直接决定了你的Skill是"好用"还是"报错劝退"。
6. 平台差异:Claude Code、Codex、OpenClaw各有什么脾气
不同Agent平台的Skill机制不是完全通用的。写之前不搞清楚目标平台,后面一定会出兼容问题。
6.1 主要平台的能力差异对照
我把自己实际测过的几个平台差异点整理一下:
| 对比项 | Claude Code | Codex | OpenClaw |
|---|---|---|---|
| Skill入口文件 | SKILL.md | SKILL.md | SKILL.md |
| Frontmatter字段 | name/description | name/description/version | name/description/version |
| 引用外部文件方式 | 支持Read工具按需读取 | 支持,但注意文件路径必须相对Skill目录 | 部分版本支持,需要加依赖声明 |
| 脚本执行 | 需要显式允许 | 需要显式允许 | 视配置而定 |
| 自动触发粒度 | 描述匹配语义触发 | 描述匹配+手动指定 | 描述匹配+手动指定 |
| 版本控制 | 支持,但非强制 | 推荐声明 | 部分平台要求 |
这里提醒一句:平台更新很快,上面的表格只能代表我写这篇时的状态。实操之前一定要看目标平台的官方文档。但这个表格的底层逻辑是通用的——先确认目标平台支持哪些能力,再据此设计Skill结构。
6.2 兼容性适配的三个实操技巧
如果你想让一个Skill跨平台可用,我有三个建议:
第一,Frontmatter尽量用最小公共集。只用各平台都支持的name和description字段。多出来的字段很可能在某个平台上是未知字段,虽然一般会被忽略,但也不能排除个别平台解析严格导致报错。
第二,外部文件引用用相对路径。在所有平台里,references/api.md这种写法都比/full/path/to/api.md靠谱。后者在换平台后必挂。
第三,脚本层做运行时检测。如果脚本依赖某个只在特定平台才有的环境变量或者目录结构,用代码做兜底。不要假设环境一定长成某个样子。
6.3 平台特有的"隐藏规则"
每个平台都有些文档里不细写的习惯性约束。比如Claude Code里,如果SKILL.md开头不是有效的YAML,整个Skill会被静默跳过,不会报错——你根本不知道它没加载。所以写完第一步就是验证加载是否成功。
再比如Codex对description的语义匹配更看重"触发场景描述",如果你写的是名词性描述,触发率会很低。我通常会在description里加上动词触发词:"当用户需要生成/编写/创建/检查……时使用"。这比只写名词效果明显好。
7. 发布前的实测流程:六步走把问题扼杀在发布前
写完了Skill,别急着丢给朋友或者发到社区。先按下面这套流程过一遍,能帮你省掉之后大量的求助和拒收。
7.1 验证Skill能不能被正确加载
第一步不是测试功能,而是测试加载。建一个最小测试目录,里面放一个测试文件,内容简单到不可能出错:
测试:请用测试用例生成Skill,生成一个针对登录功能的测试用例。如果加载成功,Agent应该表现出遵循了你的指令。如果Agent压根不提测试用例的事,或者回答得很笼统,先检查是不是Skill没被加载。
排查方法:直接问Agent"你现在能看到哪些Skill?"或者查看平台提供的能力列表。Claude Code里可以查看已加载的技能列表;其他平台也都有类似命令。这一步能最快定位加载问题。
7.2 功能实测:用三个不同难度的问题验证
加载确认之后,用三个递增难度的问题测功能:
- 基础场景:一个完全契合Skill目标的标准任务
- 变体场景:一个需要变通才能完成的任务
- 极端场景:一个信息缺失或条件冲突的刁钻任务
比如测试用例生成Skill,三个问题可以是:
基础:请根据这个登录页面的需求说明生成测试用例。 变体:这个需求文档里登录部分的描述不完整,只有用户名密码校验,麻烦你帮我看看应该怎么补测试用例。 极端:功能是"如果登录失败超过5次,则锁定账号30分钟"。请生成覆盖这个规则的测试用例。理想情况下,基础场景应该完美执行;变体场景应该触发你写的"边界条件"处理逻辑(要么提问、要么给假设);极端场景应该验证你的Skill是否覆盖了这类业务规则。
7.3 观察上下文占用情况
这一步很少有人做,但我强烈建议做。开启调试模式,看看加载Skill后上下文的占用数字。如果你的Skill只有一两千token,那问题不大;如果你写了个上万token的庞然大物,想想看值不值得。
一种衡量方法:加载Skill前后的上下文对比。如果差异过大,回到第4章,看看哪些内容可以拆到references里按需加载。
7.4 清理测试痕迹,规范化收尾
Skill测好了之后,做一次收尾:
- 删除测试用文件、临时脚本、调试输出
- 整理目录结构,确保没有散落的无引用文件
- 检查SKILL.md里的示例是否和最终行为一致
- 给
scripts/目录补充requirements.txt和README(如果没有的话) - 更新version字段
7.5 给别人做一次"盲测"
自己测完总觉得没问题,但"自己好用"不等于"别人好用"。找一两个没参与开发的朋友帮你测。
重点观察:
- 他们能不能看懂SKILL.md的指令
- 他们运行脚本时是否遇到环境问题
- 他们在自己的真实任务里,Skill的表现是否稳定
盲测阶段发现的每一个问题,都是留给你的改进机会。
7.6 常见失败模式的根因速查表
发布后被反馈"不好用"的情况,九成落在这几个根因上:
| 症状 | 根因 | 解决方向 |
|---|---|---|
| Skill从未被触发 | description写成了名词描述,没有触发场景 | 改成"当用户需要……时使用" |
| 触发但对任务毫无帮助 | 正文写成了介绍,没有可执行流程 | 按第3章重写正文 |
| 执行到一半方向跑偏 | 边界条件缺失,没有例外处理 | 补充信息不足、目标冲突时的策略 |
| 脚本报错 | 依赖未声明或路径写死 | 补requirements、改相对路径 |
| 上下文不够用 | Skill文件太大 | 拆分到references,按需加载 |
| 输出格式不稳定 | 只说了"输出报告",没给格式模板 | 给出固定模板和示例 |
8. 一点个人体会:Skill是"编码的智慧",不是"编码的规则"
最后聊点感想。
我见过不少人把Skill当成一个"死规则集",觉得只要把规则写全、写细,Agent就会乖乖执行。实测下来,这种做法做出来的Skill笨得很——规则一旦没覆盖到某个情况,Agent就不知道怎么处理。
真正好用的Skill,更像是一份"带思想的SOP"。它不仅仅说"第一步做什么、第二步做什么",还解释"为什么这样做"。当Agent理解了背后的意图,遇到规则没有覆盖到的情况时,它能够根据意图自行推导出合理的处理方式。
这就是我反复强调"例子+理由"比"规则+禁止"更有效的原因。你不能穷尽所有情况,但你可以教会Agent"像有经验的人一样思考"。
还有个小技巧分享给大家:每写完一个Skill,隔两天再读一遍SKILL.md。如果隔两天你还能一眼看懂每个步骤的意思,说明写得够清楚;如果连自己都看不懂了,那Agent肯定也是一头雾水。
Skill这个东西,门槛不高,天花板不低。掌握了这篇文章里讲的这几条核心原则,你写出来的东西至少能脱离"玩具"级别。剩下的,就是在一次次实测和迭代中打磨细功夫了。