一、一个具体问题
假设你想让 Claude 同时会写 React 组件、做代码审查、调用你内部的 PostgreSQL。直觉做法是把三件事的规则全塞进 system prompt:
你是一个全栈助手。
对于 React 任务:使用 hooks,避免 class 组件,遵循我们的命名约定...(30 行)
对于代码审查:检查 SOLID、检查测试覆盖率...(40 行)
对于数据库:连接串是 ...,schema 是 ...,注意 RLS...(50 行)
此外还有一些通用规则...(80 行)
写到 200 行的时候你会观察到一个反常识现象:Claude 不是变得更专业,而是变得更含糊。它会在写 React 时混入数据库术语,在审查代码时强行套 schema 检查,给你一个跟问题无关但符合"全栈助手"人设的回答。
为什么塞更多规则反而让它更笨?
二、朴素方案的代价
每写一行 system prompt,你其实在做两件互相打架的事。
第一,你在告诉模型"这套知识是相关的"。模型每生成下一个 token 时都在用全部上下文做注意力计算。你写进去的每一条规则都在参与决策,无论当前任务是不是真的需要它。
第二,你在挤占输出可用的窗口空间。上下文窗口有限。在 system prompt 里塞 200 行专家规则,就少了 200 行用来读用户代码、读报错日志、放工具调用结果的空间。
更糟糕的是规则之间的干扰。当 React 规则说"用函数组件"和数据库规则说"考虑 stored procedure"同时存在时,模型并不知道当前对话该屏蔽哪一套。它的注意力被均匀分给两套不相关的领域知识,于是给出一个"两套都沾一点"的折中答案。
这跟人类的认知模式正好相反。人在切换任务时会主动屏蔽无关知识,写 React 时不会想着 SQL 语法。当前的 LLM 不会主动屏蔽,所有 token 都参与计算。system prompt 越长,这种"被动卷入"的代价越大。这个问题在你只想做 React 任务的会话里同样发生:那 50 行数据库规则没用上,但它们在 token 层面影响了每一个判断。
三、关键 insight
Claude Code 的 superpower skills 系统给了一个反直觉的解:不要把专家知识塞进 system prompt,把它存成一组按需加载的文件,让模型自己决定什么时候去取。
我最初看到这套机制时,以为它的核心是"更专业的 prompt 模板"。翻了几个 skill 的实现才意识到不是。核心其实是把"知识"和"调用知识的判断"拆成两件事。
人写的 system prompt 是主动喂,skills 是被动取。后者把判断"现在需不需要这条知识"的责任交回给模型本身。听起来像废话,但它真正改变的是:模型的注意力总量恒定,而你能给它的专业领域数量被解耦了出来。
四、设计是怎么落地的
我翻了一遍 Claude Code 当前可用的 skill 列表,这套系统主要由几个机制叠加起来工作。
1. 每个 skill 是一个目录
skill 不是 prompt 字符串,而是一个目录里的 SKILL.md 文件。最关键的是这个文件顶部的 YAML frontmatter:
---
name: writing-tech-blog
description: 全自主起草高质量技术博客(中文 3000-4000 字符 / 英文 2200-2800 words)
...当用户说 "写一篇博客 / 写技术文章 / 写分享文" 触发...
---
加载 Claude Code 时,只有这段 description 进入上下文。SKILL.md 的正文几千字根本不在初始 prompt 里。
这个细节很重要。description 不是给人看的注释,它是模型决定"要不要进这个 skill"的唯一依据。写好它,等于给模型一段能在不读正文的情况下做出正确路由判断的目录。
我观察 superpower 集合里的 description 字段,普遍特征是:列出多个触发短语(用户的口头表达),明确不适用场景(避免误触),偶尔加上"必须 BEFORE 任何回应"这种紧迫性提示。它在文件里看着像普通元数据,本质是一段 retrieval prompt。
2. 双层 progressive disclosure
一个 skill 的内容可能几百 KB。比如我这次用的 writing-tech-blog 就有十几个 references 文件,加起来超过 50 KB 文本。但这些不会一次性进上下文。
层级是这样的:
flowchart LR
A[用户消息] --> B{description 匹配?}
B -->|否| C[不加载]
B -->|是| D[加载 SKILL.md]
D --> E{正文指向 references?}
E -->|否| F[直接执行]
E -->|是| G[再读对应 reference]
G --> F
第一层:description 决定要不要进。第二层:SKILL.md 主体里写"读这份文件的时机:起草前必读",模型读到再去拉对应 reference。
实际效果:一个能力很重的 skill,平时占用上下文为零。激活时占几 KB 的 SKILL.md。只有真正需要细则时再读对应章节。这跟代码里的 lazy import 在原理上是同一件事,只是 import 的不是模块,是判断和清单。
3. 1% 概率相关就必须调用
skills 系统里有一条看着像玩笑的规则:
如果你认为有哪怕 1% 的概率某个 skill 适用,你必须调用它。
这条不是夸张。它在赌一种代价不对称:
| 情况 | 代价 |
|---|---|
| 该用 skill 没用 | 模型用低质量直觉作答,错了用户事后才发现 |
| 不该用 skill 用了 | 多读几 KB 文件、多花一两秒、判断不适用就丢 |
漏调用是潜在的质量塌方,误调用是几 KB 上下文。在这种代价对比下,把决策阈值压到 1% 是合理的。尽量调,错了不亏。这个设计哲学和"宁可多写一个测试也不漏 bug"是同源的。
4. 多个 skill 可以同时激活
skills 不是互斥菜单。同一段对话里可以同时激活多个。比如做代码改动加写博客记录的会话里,可能 brainstorming、verification-before-completion、writing-tech-blog 都被激活。
每个 skill 只负责自己那块。模型按各自的 description 路由进来,按各自的 SKILL.md 工作,最终把多种专业行为叠加在同一段对话里。底层都是同一个 Claude,只是临时戴了几顶帽子。
五、和传统 prompt 工程的对照
把这套机制摊开看:
| 维度 | 塞 system prompt | superpower skills |
|---|---|---|
| 知识总量 | 受限于上下文窗口 | 几乎无限(按需读) |
| 注意力消耗 | 全部规则常驻 | 仅激活的那部分 |
| 领域切换 | 模型自己分裂判断 | 由 description 显式路由 |
| 维护成本 | 一处改动全局回归 | 各 skill 独立演化 |
| 失败模式 | 规则互相干扰 | 漏激活 skill |
它的失败模式从"规则打架"变成"漏召回"。这是一种更可观察、更可调试的失败。description 没匹配上,看日志就知道,改 description 就能修。规则打架是隐性的,调起来要重写几十行 prompt 看效果,玄学。
六、什么时候 skills 不值得
skills 不是免费的。建一个 skill 至少要写一份 SKILL.md 加几个 references,通常几千字以上。考虑这套架构的前提是:
这个能力会被反复用到。一次性脚本任务没必要建 skill,比如临时跑一次数据迁移、回答一个具体代码问题,写一段 prompt 就够。
能力本身有内部决策树(什么时候做 X、什么时候做 Y)才需要 progressive disclosure。线性的"输入到输出"用普通工具或函数就够,分层加载反而是负担。
你能写出一个让模型在没读正文时也能做对路由判断的 description。这一条最难。我看 example-skills 里有些 description 写到 200 字以上,把所有触发场景列出来。这不是啰嗦,是因为 description 是唯一的决策入口。description 写不准,整个 skill 就是死的,加载量再大也没人调它。
如果你只想让 Claude 在自己一个项目里用一套约定,CLAUDE.md 几十行就够了,不必上 skill 这一层。skills 的价值在于"跨会话、跨项目、有内部细节、需要被发现"的能力。
七、留白
我还没完全想清楚的部分:
多个 skill 的 description 触发条件重叠时(比如 brainstorming 和 writing-tech-blog 都能被"我想写一篇分享"激活),模型怎么仲裁?观察下来更像是"两个都激活,靠 SKILL.md 内部的 priority 段落自我协调",但具体优先级算法不公开。
1% 召回阈值在长会话里会不会过激?skill 数量增长到 50 个、100 个时,光是 description 列表本身就开始挤占上下文。这套架构的天花板在哪,目前没看到公开实测。
如果你做过相关实验,告诉我。