Skill 工程化设计与失效防御

一句话定位:写 Skill 的难点不是”规则怎么写”,而是**“写好的规则在第 40 轮对话时还在不在”。本文讲四件可迁移的事——把原则写成可自检的问句**、用薄壳对抗上下文压缩、用激活优于存储判断知识有没有真生效、用 2/3 门槛决定什么值得记录。适合已经在用 Claude Code / Cursor 写过 skill、但发现”规则明明写了 Agent 却不遵守”的人。

💬 大白话:你给 AI 写了一份工作手册,它第一次照着做,做到第五次就开始凭感觉干活了。不是它变笨了,是手册从它眼前消失了——这篇讲怎么让手册一直在它眼前。


目录


1. 靶心问题:规则一直在,只是 Agent 没读

大多数讲 Skill 的材料停在”怎么写规则”。这套方法论回答的是另一个问题:为什么写好的 skill 用着用着就失效了。

一个典型故障链:

[第 1 轮] 用户:"修一下 UserService 的空指针 bug"
  → Agent 读 SKILL.md → 匹配到 "Fix bug" 路由
  → 读 rules/coding-standards.md → 按流程修好 ✓

[第 2 轮] 用户:"顺便加个导出 Excel 的接口"
  → Agent:"我已经知道这个项目的规则了" → 跳过 SKILL.md → 直接开写

问题:
- 新任务本该匹配 "Add Controller" 路由,要读的是 rules/backend-rules.md
- 那个文件里有一条坑:"导出接口必须走 async 队列,直接响应会超时"
- Agent 没读到,写了同步接口
- 测试通过(小数据),生产炸(大数据),两小时后才定位

结论:规则一直在那里,只是 Agent 没读。

两个根因:

  1. 跨任务没重走路由 —— 第 1 轮记住的是”修 bug 的路由”,Agent 误以为等于”所有任务的路由”
  2. 上下文已被悄悄压缩 —— /compact(自动压缩,把早期对话摘要化后替换原文)早就跑过,SKILL.md 根本不在上下文里了,Agent 只凭残留摘要干活

第 2 条是关键:这不是 skill 内容的问题,是 harness 没给 Agent 重读触发。(harness = 包在模型外面的那层工程系统,见 Harness工程与Agent解剖)


2. 核心结论

问题结论
Skill 失效的主因是什么不是规则写得差,是规则在长会话里从上下文消失了
原则该怎么写写成执行后能自问的检验句,不是”应该如何”的声明
怎么防压缩把路由表结构化内联进入口文件(薄壳)——表格比自然语言更抗压缩
知识记在哪算数记在任务必经路径上才算,躺在 references/ 里不算
什么值得记录过 2/3 门槛:可重复 + 代价高 + 代码里看不出来
一句话记忆激活优于存储

3. 声明 vs 检验句:让原则带上”钩子”

这是成本最低、收益最高的一条——改几行字就能用。

对比两种写法:

写法 A(声明)写法 B(检验句)
保持简洁。 写解决问题所需的最少代码。简洁优先。 问自己:“资深工程师会觉得这过度复杂吗?” 如果是,就简化。
只改必须改的。检验标准:每一行改动都应该能直接追溯到用户的请求。

为什么 B 更有效:

声明只能在生成前抽象地”记住”,生成后根本没有钩子去触发验证。检验句给了 Agent 一个执行完可以真的去问自己的具体问题——然后根据答案决定要不要回滚某些行。

可以直接套用的格式:

## 原则名称
 
一句话描述原则。
 
检验:[一个可以跑的命令 / 一个可以问自己的具体问题]

3.1 配套:用真实反例,而不是明显错误

同源的一条经验是——给 Agent 看的反例,不能是明显的错误(内存泄漏、SQL 注入、死循环这些它本来就知道),而要是**“看起来合理但时机错了”的改动**:

请求是"修复空 email 导致的崩溃":
 
❌ Agent 实际会做的:
+     """Validate user data."""              # 加了 docstring(没被要求)
-     if '@' not in user_data['email']:
+     if '@' not in email or '.' not in email.split('@')[1]:  # "顺手"加强校验
+     if len(username) < 3:                  # 没被要求的 username 校验
 
✅ 应该只做的改动:
-     if not user_data.get('email'):
+     email = user_data.get('email', '')
+     if not email or not email.strip():
      # 其他代码保持原样

docstring、更严格的校验、长度限制——单看每条都”对”,但在一个只需要修 bug 的改动里加进去,就是范围失控。

通用的”禁止写烂代码”提示拦不住这类问题,因为 Agent 不知道什么样子算烂,必须用真实例子指出来。


4. 薄壳:对抗上下文压缩的结构化冗余

💬 薄壳(thin shell) = 在每个工具的入口文件(CLAUDE.md / AGENTS.md / .cursor/rules/* 等)里放的一小段内容,把最关键的路由信息直接内联进去,而不是写”去读某某文件”。

4.1 为什么”去读 SKILL.md”这句话不管用

场景:CLAUDE.md 里写了一句
      "formal docs live under skills/, read skills/*/SKILL.md first"

对话进行到第 40 轮 → 触发 /compact → 上下文被压缩成摘要
→ 用户开启新任务"加个分页功能"
→ Agent 根据摘要里残留的模糊记忆直接写代码
→ SKILL.md 已经不在上下文里,所有约束全部丢失
→ 输出看起来仍然合理,用户察觉不到

根因判断(这是全套方法里最关键的一条工程直觉):

自然语言指令(“去读 X”)在上下文压缩时被当成普通描述丢掉;但结构化的表格、清单会被保留更多。

4.2 薄壳的三块内容(建议 ≤ 60 行)

## Quick Routing (survives context truncation)
 
| Task | Required reads | Workflow |
|------|---------------|----------|
| Fix bug | `rules/project-rules.md` | `workflows/fix-bug.md` |
| Multi-subtask (≥3 独立子任务) | `rules/project-rules.md` | `workflows/subagent-driven.md` |
| Other | `rules/project-rules.md` | Check `workflows/` for closest match |
 
## Auto-Triggers
 
- **New task in same session** → re-read `SKILL.md`, re-match route, re-read
  all required files. "I already read it" is not valid — context compresses.
- Before declaring any non-trivial task complete → run Task Closure Protocol
 
## Red Flags — STOP
 
"Just this once I'll skip the AAR" → stop.

三块各自的职责:

块作用硬性要求
Quick Routing压缩后 Agent 查”这次该读哪些文件”的唯一线索必须有 Other 兜底行;每条列精确文件路径
Auto-Triggers事件 → 动作映射;核心是”同会话新任务必须重走路由""我前面读过了”必须被明确宣告为无效理由
Red Flags — STOP把”就这一次跳过”类借口前置拦截必须放薄壳里——压缩后它是最后一道防线

注意标题里那句 (survives context truncation)——标题本身就在向 Agent 解释这块存在的理由。

4.3 反例:只写指针的薄壳为什么坏事

# CLAUDE.md
Please read skills/my-skill/SKILL.md before starting any task.
It has all the rules and workflows you need.

短会话能用,长会话必挂:/compact 后这句自然语言被摘要掉,Agent 没有路由表可查,而用户察觉不到——输出看起来合理,只是少了所有约束。


5. 机制层:四道防线 + 子 Agent 隔离

本节讲的都是不依赖模型自觉的机制:前半是对抗规则丢失的四道防线(§ 5.1–5.2),后半是对抗上下文污染的子 Agent 隔离协议(§ 5.3)。

单靠任何一层都扛不住”长会话 + 多任务 + 多次压缩”的真实工作流。四层各管一段:

层对抗什么机制生效位置
Session Discipline同会话多任务不重走路由SKILL.md 里的强制再读条款模型自觉
薄壳 Auto-Triggers/compact 后 SKILL.md 消失结构化表格内联入口文件模型自觉(但抗压缩)
SessionStart hook/clear 把上下文擦干净客户端在 startup/clear/compact 事件上自动重注入 SKILL.md机制强制
PreToolUse hookAgent 要改核心规则文件脚本先拿到调用参数,exit 2 直接取消这次 Edit机制强制

为什么要冗余?因为每一层都可能被压缩器丢掉,留给你的是下一层。

5.1 前两层是”自觉”,后两层是”物理拦截”

这个区分很重要。前两层依赖模型愿意遵守,后两层不依赖:

# PreToolUse 闸的核心逻辑(简化)
if [[ 编辑会让文件变长 && (超出上限 || 没有真实失败证据) ]]; then
  echo "BLOCKED: 超过上限 / 没有证据" >&2
  exit 2    # ← 非 0 退出码让客户端直接取消这次 Edit
fi
exit 0      # 放行

Agent 根本没机会写到文件里,它只看到”Edit 被 hook 拒了”。无论它多想加、给了多少理由,都不行。

需要物理拦截的理由,作者用对抗性测试给出了解释:

模型的注意力方向是**“回答用户的请求”**,不是”遵守规则”。当用户说”我 leader 让我加”或”demo 5 分钟后要用”,模型更倾向于帮用户,而不是帮规则。

5.2 借口表:只能从真实失败里抄

配套机制是维护一张 Rationalizations to Reject(该拒绝的借口表)——把 Agent 真实说过的绕过借口原话记下来。关键是这条硬约束:

这张表只能从真实失败里加行,禁止凭空扩写。

因为凭空想象的借口 Agent 不会真的说——它真实的借口往往更狡猾、更具体、更贴近当前场景。把真实借口和虚构借口混在一起,压力值就被稀释了,Agent 下次用稍微变形的借口就能绕过去。

“压力值被稀释”是个准确的说法:防御性规则的强度会被无效条目摊薄。

5.3 子 Agent 派发:把独立子任务隔离出主上下文

主 Agent 的上下文越用越脏——前面的调试日志、中间的探索、后面的实现全堆在一起。解法是把独立子任务派给带干净上下文的 worker,worker 做完就退出,主 Agent 只看最终产物并兼任 reviewer。

什么时候启用(满足任意一条):

  • 子任务 ≥ 3 个且互相独立
  • 单个任务会吃掉 > 30% 剩余上下文
  • 任务是”探索 + 实现 + review”混合形态
  • 即将多小时自动运行

都不满足就直接内联做——派发有开销(写合约、开 worker、review),小任务不划算。

子任务合约的五个字段(任何字段不能空):

## Goal                 <!-- 一句话,面向结果,不微管步骤 -->
## Inputs               <!-- worker 允许读的确切文件 -->
## Outputs              <!-- worker 必须产出/修改的确切文件 -->
## Forbidden Zones      <!-- 不许碰的文件/目录/副作用;不确定默认禁 -->
## Acceptance Criteria  <!-- 可机械验证的命令,如 `yarn tsc --noEmit` -->

两阶段 review——两个阶段都过才 merge:

阶段查什么
Stage Aspec 合规:Outputs 文件是否产出、有没有碰 Forbidden Zones、Acceptance 命令是否通过、有没有顺手改的东西
Stage B质量:代码本身、是否踩到已知坑、有没有做 AAR

Stage A 不过就重派,不要在主上下文里内联补——那正好把主上下文污染回去。

明确的禁止项:

  • 递归派发(worker 不能再开 worker)
  • 让 worker review 自己的产物
  • 中途往 worker 上下文塞”澄清”(合约错了就取消重写,worker 不得改合约)
  • 只跑 Stage A 或只跑 Stage B
  • “worker 基本对了,剩下 10% 我在主上下文补” —— 这是最常见的借口,也是最污染主上下文的动作

⚠️ 适用边界:这套协议针对的是单次工程任务里的临时子代理,不是”长期存在、定时运行、角色化的 Agent 团队”。后者是另一类系统,见 Multi-Agent工程实战与Persona设计。两者容易混淆但不是一回事。


6. 激活优于存储:判断知识有没有真生效

这是全套方法里适用范围最广的一条,远超 Skill 设计本身。

一个陷阱仅记录在 references/ 中是不够的。高代价陷阱必须同时:

  • 存储在正确的文件里
  • 激活在会触发它的任务路径上(workflow 的完成检查项 / SKILL.md 的 Known Gotchas / rules 摘要)

判断方法:“下次 Agent 走正常任务路径时,会自然读到这条经验吗?” —— 不会,那就只是”记下来了”,还没有”生效”。

这条判据可以直接拿来检验任何知识管理系统:团队 wiki、事故复盘、个人知识库。绝大多数文档失效不是因为写得差,而是因为它不在任何人的必经路径上。

配套的落地手法是**“一句话 + 锚点”**:坑点的一句话摘要放进每次都会读的入口文件,详细内容放引用文件。

## Known Gotchas
- Filter 必须在 app init 之前注册,否则首次渲染空白 → see `references/gotchas.md#filter-registration`

理由是坑点属于”价值密度最高、阅读成本也最高”的内容:全放入口文件会把路由中心变成坑点百科,全放引用文件又会让 Agent 在任务路径上看不到它。


7. Recording Threshold:什么值得记录

只增不减的规则文档会变成屎山。录入前先过 2/3 门槛——三问至少通过两条才记:

判据问什么
可重复?这个情况以后还会遇到吗?
代价高?不知道它会浪费大量时间吗?
代码不可见?光看代码能发现这件事吗?

实例对照:

发现 1:Atom 命名约定(xxxAtom)
  可重复?   是 → 通过
  代价高?   否(命名不一致不会导致错误)→ 不通过
  代码不可见?否(现有代码已经清晰展示了模式)→ 不通过
  结果:1/3 → 不录入

发现 2:Filter 必须在 app init 之前注册
  可重复?   是 → 通过
  代价高?   是(首次渲染空白,30+ 分钟调试)→ 通过
  代码不可见?是(时序依赖从代码里看不出来)→ 通过
  结果:3/3 → 录入

7.1 泛化规则

记录的内容必须脱离当前项目上下文也能看懂。 改写公式:

具体发现 → 抽象为通用 pattern → 说明不遵守的后果

7.2 反向操作:规则清退

只记不删同样有害——三个月前的坑现在已经不存在,规则还挂着会误导后来的 Agent:

情况处理
相关技术已移除直接删除整条
正在迁移中加作用域标注(“仅适用于 legacy 模块”)
不确定还有没有用加 <!-- DEPRECATED: reason, date -->,保留 1 个迭代周期再删

过时规则比缺失规则更有害——所以更新过时规则不需要过录入门槛,直接改。

7.3 边界:别让 skill 变成日记本

一个高频漂移是 Agent 把”记录教训”解读成”把会话也存档”,于是 references/ 下开始堆 2026-04-14-session-notes.md 这类文件。判断表:

内容类型目标位置
稳定约束 / 通用原则rules/
陷阱、架构笔记、生命周期坑references/
有序步骤 / 完成检查清单workflows/
会话历史 / 调试过程不要写进 skill——用 git commit / CHANGELOG

8. 结构演进:什么时候从单文件裂成文件夹

不要一上来就摆全套架构。 设计结构前先回答三个问题:

问题如果答案是…结论
有多少个不同主题的内容?少于 3 个单文件够了
绑定了多少种任务流程?没有不需要 workflows/
会随项目演进持续更新吗?不会不需要自演化机制

三个都是”否” → 单文件就是最优解。一上来就搭完整目录会制造”这个项目很完整”的错觉,但每个子目录只有一两行占位符,维护成本反而更高。

该拆分的信号(出现任意一条):

  • 入口文件开始出现”### X 相关”、”### Y 相关”的分节
  • 不同类型任务需要读不同规则(加接口和修 bug 读的不是同一套)
  • 同样的坑第二次踩,但没有地方记录它
  • 多人协作 / 多项目复用,规则开始有变体

拆开后的组织原则:

每个文件必须有自己独立的”被加载理由”;如果几个文件永远一起加载,它们就应该是一个文件。

按形式而非内容归类:

  • “你必须做 X”(指令性)→ rules/
  • “小心 X”(警告性)→ references/gotchas.md
  • “第 1 步、第 2 步”(流程性)→ workflows/

判断窍门:“我能做 X 吗?”→ rules;“这个坑怎么避?”→ references;“我现在该做什么?”→ workflows。

8.1 description 是命中率的唯一决定因素

入口文件 frontmatter 里的 description 是 Agent 在加载正文前唯一能看到的判断依据——写不好等于这个 skill 不存在。

# ❌ 错误 —— 无法匹配
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, or service layer logic.

反直觉的一点:模型天然倾向 undertrigger(保守激活)——该触发时不触发,比误触发更常见。所以 description 要主动覆盖用户可能的各种表达方式。


9. 常见误区

误区为什么错更好的做法
入口文件写几百行”最佳实践”上下文被污染、成本变差、规则互相冲突,Agent 反而变成工具人入口管方向,其他文档管深度;入口文件控制在 100 行内
只在入口写”请先读 SKILL.md”自然语言指令会被压缩摘要掉内联结构化路由表(薄壳)
把原则写成”应该保持简洁”执行后没有钩子触发验证改写成可自问的检验句
坑点写进 references 就算完不在任务路径上 = 没生效一句话摘要上浮到必经路径 + 锚点指向详情
借口表凭空扩写稀释真实借口的压力值只从真实失败里抄原话
从 GitHub 拉一堆同类 skill冲突必然出现,命中率越来越低少而精;靠自动触发而非手动指定
让 Agent 每次实时生成脚手架压力下会漏段,同一协议生成出五个版本用模板 cp -R + 占位符替换,Agent 只做填空
过度指令化(写死具体 class 名)设计系统一升级,几百条硬编码全失效给约束 + 上下文,不给具体值

10. 与本知识库其他章节的关联

相关笔记关系
Claude Code 扩展生态互补的两个视角:那篇是使用者视角(skill 是什么、怎么装、推荐哪些),本文是设计者视角(怎么写、怎么防失效)。先读那篇建立概念,再读本文
Harness工程与Agent解剖本文的四道防线全部属于 harness 层。Prompt / Context / Harness 三要素的划分、Guides(前馈)与 Sensors(反馈)的框架在那篇有完整展开
LLM典型失败模式同一问题的两次独立发现:本文的 Red Flags / 借口表,和那篇的表演性完成,讲的是同一类失败——Agent 声称完成但跳步、在压力下给自己找借口
Multi-Agent工程实战与Persona设计注意区分层次:本文 § 5.3 讲的是单次工程任务里的临时子代理(合约派发、做完退出);那篇讲的是长期存在、定时运行、角色化的 Agent 团队。两者都叫「多 Agent」但不是一回事
长程任务原语-Session-Workspace-Checkpoint-Resume本文 § 5.3 的子任务合约五字段约束的是派给 worker 的单个子任务;那篇 § 2.5 的 Task Anchor(Goal / Boundaries / Done When)约束的是整场任务的目标。同一思路的两种粒度
极简可控的Coding-Agent设计-pi对照阅读:pi 走的是”极简、可观测”路线,本文的方法论走的是”结构化、多层冗余”路线,两者对同一问题给出相反的取舍
上下文窗口与Token计费本文所有防御机制的前提是上下文会被压缩——压缩、滑动窗口、/compact 的底层机制在那篇
Agent记忆体系”激活优于存储”是记忆问题的一个侧面:写下来(存储)和想得起来(召回)是两件事

11. 来源与局限性

来源

  • 主要来源:linux.do 帖子《如何写一个好的 skill 让你的效率加倍!》(作者 woji_666,文档共建版块,2026 年,约 5 万字)
  • 原帖引用的上游材料:
    • https://github.com/forrestchang/andrej-karpathy-skills (§ 3 的”检验句”格式出自此项目)
    • obra/superpowers(§ 5 的 SessionStart hook、子 Agent 派发模式借鉴自此项目)
    • Anthropic 官方关于 Skill 编写的建议(本文 § 9 常见误区表中的”过度指令化”一行即源于此)
  • 本文的取舍:原帖含基础篇 6 节 + 进阶篇 12 节 + 附录,本文不复述其目录,只提取四个可迁移到 Skill 之外的核心方法(检验句、薄壳、激活优于存储、2/3 门槛),并补充了本知识库视角的关联分析。

局限性

维度客观情况
时效性涉及具体客户端行为(/compact 触发时机、hook schema 格式、exit 2 的语义)会随版本变化。原帖附录记录了一个真实版本坑:Claude Code CLI v2.1+ 的 PreToolUse 只认嵌套 hooks:[{type, command}] 格式,flat 写法启动不报错、debug 里也能看到 hook 名字,但 Edit 时静默不触发——而 SessionStart 恰好两种格式都吃,所以容易误以为配置正确
覆盖范围只覆盖原帖中”可迁移的方法论”部分。原帖的多 harness 入口适配、templates 目录设计、48 项 smoke-test 脚本、跨 skill 组合模式等工程实现细节未收录,需要时请回原帖
实战验证本文作者(本知识库维护者)未实际搭建过完整的 skill-based-architecture 项目。文中方法论来自原帖作者的实践总结,本知识库只做了提炼与交叉印证,未独立验证
论断强度原帖中”80% 的 skill 失败来自遗忘型错误”、“质量提升 2-3 倍”等量化说法未给出测量方法,宜当作经验判断而非结论引用

最后更新:2026-08-30