如何写一个好的 skill 让你的效率加倍
如何写一个好的 skill
全文核心是三句话:
-
结构服务于内容,
-
激活优于存储
-
结构可复用,内容禁止预制
一、skill 的最初形态:一个文件就够了
在我们聊 "上下文"、“薄壳”、"Harness" 这些概念之前,先看看 skill 最开始的样子 —— 一个 markdown 文件,几条行为准则,仅此而已。
参考项目:forrestchang/andrej-karpathy-skills(基于 Andrej Karpathy 总结的 LLM 编码反模式)
单文件 skill 长什么样
Andrej Karpathy 的整个项目就 6 个文件、859 行。核心是一个 67 行的 SKILL.md,里面只有四条行为准则:
目录结构就这么简单:
skills/karpathy-guidelines/
└── SKILL.md ← 全部内容都在这里
没有 rules/,因为四条准则放在 SKILL.md 里就够了
没有 workflows/,因为这个 skill 不绑定特定任务流程
没有 references/,因为一个外部链接就够了
这不是偷懒,是对架构复杂度的准确判断:结构服务于内容,而不是用结构撑完整性。
设计一:原则 + 检验句,而不是原则 + 解释
大多数 skill 里面的规则是这样写的:
保持简洁。 写解决问题所需的最少代码。
Karpathy Skills 的写法是:
简洁优先。 解决问题所需的最少代码,不写投机性的功能。
问自己:“资深工程师会觉得这过度复杂吗?” 如果是,就简化。
再看一条:
精准修改。 只改必须改的,只清理你自己造成的乱。
检验标准:每一行改动都应该能直接追溯到用户的请求。
区别在哪?
前者是声明 —— 告诉 Agent"应该这样"。
后者是检验句 —— 给 Agent 一个可以在执行后自我验证的具体问题。
推荐格式
原则名称
一句话描述原则。
检验:[一个可以跑的命令 / 一个可以问自己的具体问题]
设计二:代码行为层面的 ❌ / ✅ 对比
请求是"修复空 email 导致的崩溃":
❌ Agent 实际会做的:
def validate_user(user_data):
+ """Validate user data.""" # 加了 docstring(没被要求)
+ email = user_data.get('email', '').strip()
- if not user_data.get('email'):
+ if not email:
raise ValueError("Email required")
- if '@' not in user_data['email']:
+ if '@' not in email or '.' not in email.split('@')[1]: # "顺手"加强校验
raise ValueError("Invalid email")
+ if len(username) < 3: # 没被要求的 username 校验
+ raise ValueError("Username too short")
✅ 应该只做的改动:
def validate_user(user_data):
- if not user_data.get('email'):
+ email = user_data.get('email', '')
+ if not email or not email.strip():
raise ValueError("Email required")
# 其他代码保持原样
docstring、更严格的邮箱校验、username 长度限制 —— 这种过度设计在 agent 的开发过程中特别多见。但在一个只需要修 bug 的 PR 里加进去,就是 Surgical Changes 原则的违反。
这类反模式 Agent 最容易踩,因为 "看起来都对"。通用的 "禁止写烂代码" 提示根本拦不住它,因为 Agent 不知道什么样子是烂代码 , 必须用真实的例子提醒他。
设计三:按需引入结构复杂度
设计 skill 结构前先回答三个问题:
- 这个 skill 有多少个不同主题的内容?
- 绑定了多少种任务流程?
- 预期会随项目演进持续更新吗?
三个都是“否” → 单文件 skill 就是最优解。
按需引入复杂度,不要一开始就摆全套架构。 一上来就搭完整目录(rules/ + workflows/ + references/ + 多 harness 薄壳……)会制造 "这个项目很完整" 的错觉,但每个子目录里只有一两行占位符,维护成本反而更高。
Karpathy Skills 是一个静态的行为提示,不是自我更新的知识系统。它从一开始就知道自己要做什么,所以结构刚好够用。
但一旦出现以下信号之一,单文件就撑不住了:
- 多主题:SKILL.md 开始出现 "### X 相关"、"### Y 相关" 的分节
- 任务路由:不同类型的任务需要读不同的规则(加 Controller 和修 bug 读的不是同一套)
- 需要沉淀教训:同样的坑第二次踩,但没有地方记录它
- 多人协作 / 多项目复用:规则开始有变体,需要分文件管理
这些信号出现 → 就该进入文件夹化 skill 的阶段。
二、文件夹化 Skill
当单文件撑不住 —— 主题 ≥ 3、任务路由复杂、需要沉淀教训 ——skill 就该从一个文件裂成一个文件夹。2000 行的 SKILL.md 不是 "内容丰富",是 Agent 每次都要读完整本书。
Skill 是一个文件夹,可以包含 Markdown、脚本、资产、数据、配置等。Agent 会自主发现和使用其中的所有内容。
把它想成一个小型项目,而不是一份文档。
⚠️ 这个非常重要。
如果是一个很小的 skill,用单文件没问题(见第一章的 Karpathy 例子);但是博主之前公司有一个 2000 字的 md,AI 根本读不到…… 而且拓展性极差,要了博主半条老命,这也是这篇文章和项目的初衷。
skills/<name>/
├── SKILL.md # 入口:路由表 + 优先级
├── rules/ # 长期约束
├── workflows/ # 步骤流程
├── references/ # 背景资料:架构、坑点、索引
│ └── gotchas.md # 已知的坑(通常是最高价值内容)
├── docs/ # 可选:提示词、报告
└── scripts/ # 可选:辅助脚本、脚手架工具
Skill 文件夹能放什么

Anthropic 的关键洞察: 让 Agent 把时间花在组合和编排上,而非从头写样板代码。Skill 文件夹里的脚本和可复用资产会显著降低 Agent 的出错率
如果把所有内容都混在一起会怎样?Agent 会在 3000 行约束里翻找检查清单,一个 "规则" 文件里藏着流程步骤 —— 浪费 token,维护也变噩梦。
文件内容严格分离

边缘情况分类(按形式决定目标,不按内容)
有些内容既是解释性的又容易违反(如 "输入验证的坑"),按形式决定:
“你必须做 X”(指令性的) → rules/
“小心 X”(警告性的) → references/gotchas.md
第 1 步、第 2 步、第 3 步(流程性的) → workflows/
判断窍门:当迷茫的时候,挠一挠自己的后脑勺问自己:
“我能做 X 吗?”→ rules;
“这个坑怎么避?”→ references;
“我现在该做什么?”→ workflows。
文件大小参考值

一个良好的 skill,如果单个文件太大,不可避免会导致 Agent 无法读到正确的内容;所以在一定情况下自动拆分或合并规则文件是必须的。但也不是一定要触发:如果大于标准但都是同一个模块的,那么也不应该拆分。
行数是信号,不是命令。 超标触发评估,而非自动拆分 —— 同一个模块的内容即使超过 300 行也不应该硬拆。
三、让 skill 谦虚
同一会话里用着用着 Agent 突然 "变蠢"—— 明明第一轮还按规则走,第三轮开始凭感觉写代码,读过的 SKILL.md 规则全忘了。这不是模型笨,是你少了一道强制再读的钩子。
过度自信的 agent:
[第 1 轮] 用户:"帮我修一下 UserService 里的空指针 bug"
→ Agent 读 SKILL.md
→ 匹配 Common Tasks 的 "Fix bug" 路由
→ 读 rules/coding-standards.md + rules/project-rules.md
→ 按 workflows/fix-bug.md 流程修好 ✓
[第 2 轮] 用户:"顺便加个导出 Excel 的接口"
→ Agent:"我已经知道这个项目的规则了"
→ 跳过 SKILL.md
→ 直接开写 Controller
问题:
- 新任务匹配的是 "Add Controller" 路由,要读的是 rules/backend-rules.md
- 这个文件里有一条 gotcha:"导出接口必须走 async 队列,直接响应会超时"
- Agent 没读到,写了同步接口
- 测试通过(小数据),生产炸(大数据)
- 两小时定位之后发现:规则一直在那里,只是 Agent 没读
为什么跳过了?
- 跨任务没重走路由:第 1 轮记住了 "Fix bug 路由",误以为等于 "所有任务的路由"
- 上下文可能已悄悄压缩:第 3 轮的时候 /compact 早就跑过,SKILL.md 早就不在 context 里了,Agent 只凭残留摘要干活
这不是 skill 内容的问题,是 harness 没给 Agent 重读触发。
三层强制再读(本项目的实际做法)
光写一句 "请每次重读 SKILL.md" 不管用 —— 第一轮能记住,第十轮压缩后指令早就没了。必须结构化地多层冗余:

为什么要三层冗余?因为每一层都可能被压缩器丢掉,留给你的是下一层。最坏情况下只剩薄壳 —— 这就是为什么第六章说 "Red Flags 必须塞进薄壳而不是只写在 workflow 里"。
嘴硬的 Agent
光有机制还不够,压力下 Agent 会自己编借口绕过。本项目的 workflows/update-rules.md § Rationalizations to Reject 就是一张从真实失败里抄来的借口表:
例:

硬约束:这张表只能从真实失败里抄,不能凭空想象。
一条原则,一个检验
沿用第一章的 "原则 + 检验句" 格式 (强烈建议):
## Session Discipline(同会话多任务必须重走路由)
每个新任务——即使是同一会话的第 N 轮——必须重读 SKILL.md、重新匹配
Common Tasks 路由、重读该路由列出的所有必读文件。
检验:问自己"这次任务我读的文件和 Common Tasks 里对应路由列的完全一致吗?"
如果有任何差异(少读 / 多读 / 凭记忆),立即回头重走路由。
四、skill 的三要素
你精心写了一份 Prompt,措辞严谨,逻辑清晰,甚至还加了示例。但 Skill 跑起来之后,模型要么 "触发不了",要么 "触发了却不按规范做",要么 "今天好用,明天又乱来"。问题出在哪?
如果你想做好一个 AI Skill,你需要同时想清楚三件事:Prompt、Context、Harness。它们分别解决三个完全不同维度的问题,缺任何一个,Skill 都只是 "半成品"。
Prompt —— 定义做什么
Prompt 是你给模型的指令书。但在 Skill 体系里,Prompt 其实分为两个层次。
层次一:Description(触发描述)
非常重要!!!
skill 里面最重要的就是 description 了,否则命中都命中不了!!!
description 是写在 SKILL.md,是模型判断 "要不要调用这个 Skill" 的最重要依据。
范例:
---
name: docx-writer
description: >
创建专业 Word 文档。当用户提到 .docx、Word 文档、
报告模板、正式文件时,必须使用此技能,即使用户
没有明确说"帮我做 Word 文档"。
---
Description 相当于 Skill 的 "门牌号"—— 写得模糊,模型就找不到门;写得太窄,该触发的时候触发不了。
反直觉的设计:模型天然倾向 undertrigger(保守激活),所以 description 要主动覆盖用户可能的各种表达方式。
层次二:Body(执行指令)
这是 SKILL.md 的正文部分,告诉 Claude 具体怎么执行 —— 步骤顺序、输出格式、注意事项、边界条件。
## 输出格式
始终使用以下模板结构:
# [文档标题]
## 执行摘要
## 关键发现
## 建议与下一步
写好 Body 的三个关键原则:
- 用祈使句,而不是 "你应该……"。「读取文件」比「你应该先读取文件」更直接有效。
- 解释 "为什么",而不只是 "做什么"。让模型理解背后的逻辑,它才能在边缘情况下做出合理判断。
- 控制长度,SKILL.md 正文建议 500 行以内。超出就拆分为引用文件,按需加载。
Context —— 决定知道多少
这是最容易被忽视的一环。
Context 是模型在生成回答时能 "看到" 的所有信息。你的 Prompt 写得再好,如果模型在执行时 "看不到" 它,一切都是零。
三级渐进式加载机制
Skill 系统用 "Progressive Disclosure(渐进式披露)" 来管理 Context,分三个层级:

这个设计解决了一个根本矛盾:信息越多越好,但 Context 窗口是有限的。
解法是:只把 "始终需要" 的信息放在顶层,把 "可能需要" 的信息放在引用文件里,让模型在需要时再去读。
典型目录结构
my-skill/
├── SKILL.md ← 第 1 + 2 级
└── references/
├── aws.md ← 第 3 级,部署到 AWS 时才读
├── gcp.md ← 第 3 级,部署到 GCP 时才读
└── azure.md ← 第 3 级,部署到 Azure 时才读
agent 只读取当前任务相关的引用文件,而不是把所有内容都塞进 Context。这样既保证了信息完整,又不浪费窗口资源。
Context 设计的三个常见问题
- Context 太少:模型看不到规范,行为随意发挥
- Context 太大:超出窗口,后面的指令被静默忽略
- Context 设计混乱:无关信息干扰模型的判断,导致输出不稳定
这也是为什么本项目把 rules /workflows/references 严格分开 —— 不是形式主义,是为了让每个任务只加载最小必要集合。
Harness —— 验证好不好用
Harness 是一层常被低估的结构:很多失稳问题的根因不是模型,是 harness 没给它正确的拦截和重试机制。
很多人写完 Skill 就直接上线,出了问题才去猜 "是哪里写错了"。Harness 就是让这个过程变得有据可查 —— 你改了什么,变好了还是变差了,一目了然。
对应到 skill 里,Harness 做三件事:
1. 结构性拦截(防失控)
Prompt 里写一百遍 "必须做 AAR" 都会被 "就这一次" 绕过。结构性拦截需要:
薄壳里的 Red Flags STOP 块(见第六章 6.3)—— 把 "就这一次跳过" 前置拦截
workflows/update-rules.md 里的 Rationalizations 表(见第八章 8.3)—— 把 Agent 的真实借口抄进文件
SessionStart hook(见第七章)—— 压缩后自动重新注入 SKILL.md
这三者叠加才能扛住长会话的压力。
2. 自动化验证(防漏项)
templates/skill/scripts/smoke-test.sh 做 48 项自检:结构、行数、占位符残留、路由完整性、Cursor 一致性、薄壳一致性。见第十五章。
人类特别不擅长手动检查 48 项 —— 脚本能抓住 80% 的 "遗忘型错误"。
3. 真实压力测试(防纸面合规)
templates/skill/scripts/test-trigger.sh 会从 Common Tasks 里生成真实用户可能说的提示词,用来测 description 的触发率 —— 单独读一遍 SKILL.md 觉得没问题,跑 test-trigger.sh 才发现一半的触发短语命中不了。
跳不过 "自己看" 这一步。 模型判断不了 "读起来顺不顺",让 AI 自动改 prompt 最后会改成自我安慰。真实输出必须人眼看。
三者缺一不可

很多开发者把 90% 精力放在 Prompt 上,跑不对又只调 Prompt,从不审视 Context 设计,也没有 Harness 客观衡量 "改好了还是改坏了"。
Prompt 定行为,Context 给视野,Harness 做质检。 三者缺一,skill 都只是 "半成品"——
缺 Prompt → 激活率低 / 行为漂移
缺 Context → 规则写了读不到
缺 Harness → 今天好用明天乱来,出错也不知道
五、SKILL.md:导航中心
SKILL.md 不是百科全书,是目录。Agent 每次任务都要读它,所以它必须短、必须只讲 "读什么 / 什么时候读"—— 而不是 "这个 skill 有哪些规则"。
SKILL.md 的四个核心板块
SKILL.md 应该很短(<= 100 行),只负责告诉 Agent 读什么、什么时候读。
---
name: {{NAME}}
description: > (触发条件,见 5.1)
primary: true
---
# {{NAME}}
{{SUMMARY}}
## Always Read ← 每次任务都读(2-3 个文件)
## Session Discipline ← 多任务会话的强制再读(见第三章)
## Common Tasks ← 按任务类型路由
## Known Gotchas ← 最关键坑点 + 指向 references/gotchas.md
## Core Principles ← 项目特有原则(每条带 ✓ Check)
一个 skill 里面的文件最重要的是什么呢?name?version?description?还是下面的内容? —— 答案一定是 description。
Description = 触发条件
description 字段是 Agent 决定 "要不要激活这个 Skill" 的依据。它不是摘要,是触发条件。
# ❌ 错误 —— Agent 无法匹配
description: API development helper
# ✅ 正确 —— 明确触发短语 + 激活条件
description: >
This skill should be used when the user asks to "add a new API endpoint",
"write controller logic", "fix a backend bug", or "add a database migration".
Activate when the task involves REST routes, request validation,
service layer logic, or MyBatis mapper changes.
质量检查:

一个 Description 写不好的 Skill,等同于不存在。
⚠️ 如果你有 Cursor 注册入口 .cursor/skills/
两层路由:Always Read + Common Tasks
第一层 —— Always Read(每次任务都读,2–3 个文件封顶):
## Always Read
1. `rules/project-rules.md`
2. `rules/coding-standards.md`
放什么?只放 "任何任务都必须遵守" 的约束 —— 项目通用规则、编码规范。领域特定规则(backend /frontend/db)绝对不放这里,应该让 Common Tasks 按需路由。
第二层 —— Common Tasks(按任务类型路由):
## Common Tasks
- Add Controller → read `rules/backend-rules.md` + follow `workflows/add-controller.md`
- Fix bug → read task-relevant `rules/*.md` + follow `workflows/fix-bug.md`; ref: `references/gotchas.md`
- Multi-subtask / long autonomous run (≥ 3 independent subtasks) → follow `workflows/subagent-driven.md`
- **Other / unlisted task** → read `rules/project-rules.md` + `rules/coding-standards.md`, then match by workflow filename. If no match, proceed with Always Read rules.
规则:
- 每条必须列精确文件路径,不能只写 “follow the workflow”
- Common Tasks 控制在 5–10 条;超出按领域分组(frontend tasks /backend tasks /ops tasks)
- 必须有 “Other /unlisted task” 兜底条目 —— 没兜底 = 不在列表里的任务 Agent 会乱跑
- 必须有 multi-subtask 路由指向 workflows/subagent-driven.md
附录 1、踩坑清单
-
让 Agent 每次实时生成脚手架 → 它会漏段。改成 cp -R templates/ + sed
-
把 "具体业务 spec 示例" 预制进 templates/ → 下游会抄例子不写自己的。让 FILL 标记逼它思考
-
Rationalizations 表凭空扩写 → 稀释真实借口的压力值。只能从真实失败抄
-
把 Auto-Triggers 只写在 workflow 里,不写进薄壳 → 压缩后薄壳是最后防线,薄壳丢了就全丢了
-
薄壳坚持 ≤15 行不肯扩到 ≤60 → 加上 Red Flags + Auto-Triggers 15 行写不下,硬压导致协议碎片化
-
多 harness 项目没有 GEMINI.md/ Copilot 入口 → 这些 harness 读不到你的 skill, 等于没有
本文来自博客园,作者:自律即自由-,转载请注明原文链接:https://www.cnblogs.com/deyo/p/19906065

浙公网安备 33010602011771号