← 所有文章

我的智能体看不见的那五个技能

来自指南: Claude Code Comprehensive Guide

我装了 84 个技能,并想当然地以为这 84 个都在工作。其中五个并没有。swiftuitesting-philosophytypesetweb-performanceupdate-shortcuts-guide 抵达模型时只有一个光秃秃的名字,没有附带 description,而它们在磁盘上的文件里写着完全合格的 description。description 就是路由信号,因此没有 description 的技能根本无法被路由到。这五个技能永远不可能自动激活,却没有任何地方提醒过我。没有报错,没有警告,没有一行日志。我是偶然发现它们的,而我之所以能证明原因,唯一的理由是修复动作在我眼前把它逆转了过来。 {.answer-block}

TL;DR

  • 技能的 description 会加载进每一轮的上下文,并共享一份固定的字符预算。一旦超出,description 就会被悄悄丢弃。12
  • 我装了 84 个技能,其中 82 个带有 description,合计 21,848 个字符。有五个抵达时名字在、description 不在。在磁盘上,这五个各自有 206 到 336 个字符。
  • 没有任何文件属性能解释为什么是这五个被丢掉。description 长度、文件大小、YAML 格式、name 与目录名不一致、修改时间,在被丢弃与被保留的两组之间全都重叠。
  • 我把 74 条 description 重写到合计 9,885 个字符。五个原本隐形的技能全部在会话中途、在同一段对话里带着完整的 description 回来了。假设、干预、确认。
  • 确切的预算没有文档说明,目前仍有争议。Anthropic 有多个未关闭的 issue 报告说,这个比例是按固定的 200K 基线计算的,忽略了 1M 的上下文扩展。34
  • 让它能装下的重写规则是:description 的唯一职责是回答什么时候应该调用它。流程、理念和文件路径属于正文,而正文只在被调用时才加载。

一次没有报错信息的失败

大多数 harness 的 bug 会自报家门。钩子以非零状态退出,MCP 服务器拒绝启动,工具调用返回一段堆栈。description 预算什么都不做。它只是悄悄把一份比磁盘上更短的清单端给模型,而所有下游症状看起来都像模型的问题,而不是管道的问题。

我是靠读自己的上下文窗口发现的,而不是靠读文件系统。扫过技能清单时,有五条只有名字,后面什么都没有。其他每一条都是名字加一句话。于是我把文件调了出来:

swiftui                    HAS  322 chars on disk -> DROPPED in context
testing-philosophy         HAS  291 chars on disk -> DROPPED in context
typeset                    HAS  248 chars on disk -> DROPPED in context
web-performance            HAS  206 chars on disk -> DROPPED in context
update-shortcuts-guide     HAS  336 chars on disk -> DROPPED in context

后果比技能变慢或出错更糟。智能体是靠读 description 来决定要不要调用一个技能的。抽掉 description,不是让路由退化,而是把路由整个拿走。技能装好了、有效,却无法抵达。swiftui 是我的 iOS 26 模式技能,也就是说我跑过的每一次 Swift 会话都是在没有它的情况下盲飞。

Anthropic 有未关闭的 issue 描述了同样的失败,其中一条的标题是「Skill description budget silently truncates routing information, causing skill routing failures」。2 可见这是一个已知的 bug,而不是本地配置有误。知道这一点很有用,但它并不能帮我找出自己有哪些技能受了影响。

排除掉那些容易的答案

最诱人的做法是猜出机制然后动手修。我先去试着证伪这些猜测,因为「有五个技能坏了」和「有五个技能因为这个原因坏了」是两个非常不同的主张。

如果某个文件层面的属性标记了哪些技能会被丢弃,那么被丢弃的一组应该在某个可测量的地方与被保留的一组不同。于是我做了对比:

属性 被丢弃(5 个) 被保留(77 个)
description 平均长度 280 个字符 266 个字符
平均文件大小 9,106 字节 7,608 字节
块标量写法的 YAML description 5 个中有 2 个 77 个中有 30 个
name 与目录名不同 5 个中有 1 个 77 个中有 4 个
修改日期 1 月至 7 月 1 月至 7 月

没有任何东西把两组分开。被丢弃的 description 不是最长的,文件也没有明显更大,YAML 风格在两组里都是混着的,修改日期覆盖同一个区间。按字母顺序排位的解释也不成立:排在 swiftui 之后的技能都保住了自己的 description。

到这一步,诚实的立场是:我有一个可复现的症状,却没有机制。于是我就这样把它写下来,然后去找一个测试,而不是去找一套说法。

那个测试

如果起作用的是总预算,那么无论我削减哪些文件,只要把总量降下来,被丢弃的 description 就应该恢复。这个预测可证伪,而且成本很低。

我重写了 74 条 description,把总量从 21,848 个字符降到 9,885 个。五个此前隐形的技能带着 description 回来了,就在同一段会话里,也没有重启。

整个实验就这些。一个预测,一次干预,一次确认。丢弃是总体积的函数,而不是单个文件任何属性的函数——这恰恰解释了为什么没有任何逐文件的属性能区分这两组。

一个不留痕迹的 bug 仍然留下了反事实。如果检查失败现场找不到原因,那就改动一个变量,看看失败会不会跟着变。

我想把自己没有确立的东西说清楚。我不知道确切的预算,也不打算发布一个自己拿不出出处的数字。官方文档没有写出这个上限。5 社区的测量把实际上限放在技能元数据总量 15,500 到 16,000 个字符附近,并指出每条大约有 109 个字符的开销,来自 XML 标签、技能名称和 location 字段,而这些都不会被单纯的 description 字符数统计到。6 按 84 个技能算,光这部分开销就约有 9,156 个字符。与此同时,Anthropic 的贡献者报告说,预算比例是按固定的 200K 基线计算的,忽略了 1M 的上下文扩展,因此同一台机器上的两个会话可能拿到不同的预算。34

我第一次写这个发现时,声称自己的配置「超预算 119%」。我拿一个未经验证的假设(1M 窗口的 1%)去乘一个真实的测量值,造出了一个自信满满、底下却什么都没有的数字。观测到的事实站得住:21,848 个字符丢掉了五条 description,9,885 个字符一条也没丢。那个百分比没站住,而它本来就不该被写出来。

description 的唯一职责是路由

砍掉 12,000 个字符听起来很破坏性。其实不是,因为那些 description 里的绝大部分内容,本来就没在做路由的活。

下面是我的 jiro 技能当时挂出来的招牌,686 个字符:

面向代码质量与职业自豪感的匠人(Shokunin)手艺哲学。在实现功能、重构代码、编写测试、评审工作,或处理 FastAPI/Python、Swift/SwiftUI、HTMX 前端与基础设施代码中任何不琐碎的改动时激活。内嵌三种核心理念:匠人(在看不见的细节上追求卓越)、款待(以手艺提供服务)、Rick Rubin(创造性的引导与提炼)。核心决策关卡:Evidence Gate(拿出质量的证据,而不是对质量的感觉)。使用场景:构建功能、重构、测试、评审代码、修复 bug,或任何在报告完成之前需要质量证据的工作。

其中大约 500 个字符是在解释这个技能里有什么。没有一句能帮忙决定要不要打开它。替换后的版本是 126 个字符:

面向代码质量的手艺与证据标准。在实现、重构、测试、评审或修 bug 时使用。

同样的触发词,同样的路由行为,成本只有五分之一。理念并没有消失,它住在正文里,而正文只在技能真正运行时才加载。为它在每一轮付费,什么也换不来。

这个模式在整套技能里反复出现。九个 update-*-guide 技能带着 3,024 个字符几乎一模一样的套话,讲的都是扫描来源、同步副本、跑翻译。压缩到每个约 115 个字符后,它们依然路由正确,因为区分它们的是更新哪一份指南,而不是它们共用的那条流水线。

真正起作用的是三条规则:

  1. 留下触发词,砍掉解释。 名称、斜杠命令,以及用户真的会敲进去的词,留着。对内部流程的描述,去掉。
  2. 文件路径属于正文。 路径帮不了模型判断什么时候该调用某样东西。
  3. 共用的套话是纯粹的开销。 如果九个技能说同一句话,那句话谁也区分不了。

第二笔税:不被调用也在起作用的 description

削减暴露出一笔更隐蔽的成本。我的 description 里有 11 条、合计 3,808 个字符带着祈使语气:ALWAYS、NEVER、MUST、PROACTIVELY、BEFORE。distribute 说 NEVER,no-shortcuts 说 ALWAYS,git-custody 说 BEFORE。

无论那个技能会不会运行,这些词都坐在每一轮的上下文里。它们读起来像指令,因为它们本来就是按指令写的;而模型没有可靠的办法,一边把 description 当成惰性的目录文案,一边把措辞完全相同的系统指令当成有约束力的命令。

近期的研究为这个效应起了名字。《The Regression Tax》在两个办公自动化基准和三套 harness 上测量了约 6,000 次运行,识别出 skill description osmosis(技能 description 渗透):一个技能仅仅因为存在于上下文中就改变了智能体的行为,哪怕它从未被调用。1 它最主要的发现是,最好的技能之所以胜出,靠的是退化更少,而不是收益更多;技能在流程指导上过度投入,在事实依据与验证上却投入不足。

生产环境的证据比理论来得更早。Anthropic 在 v2.1.215 撤回了内置 /verify/code-review 技能的自动激活,改为只能显式调用。7 又过了两个版本,/deep-research 也不再自我调用。8 这些都是重量级技能,未经请求的运行付出的代价超过了它们挣回的东西——这正是厂商在实际环境中观察到的渗透,而且是靠移除激活、而不是靠重写 description 来纠正的。

所以一条过大的 description 要付两遍成本。它吃掉别的技能做路由所需的预算,又施加了没人要求的行为压力。两笔成本都落在这个技能毫无贡献的那些轮次上。

让人不安的部分:正文可能同样管不住

「把它挪到正文里」是我刚给出的建议,而它带着一个值得说出口的假设:智能体在调用时加载的流程,真的能管住它的行为。新的基准研究表明,这个假设没有听上去那么牢靠。

HANDBOOK.md 测的正是这一点。65 项任务,篇幅 20 到 124 页的政策文档,智能体在模拟公司里跨越邮件、聊天、日历与电商工作,以及 824 条程序化评分标准。30 种模型配置中最好的一种只通过了 36.2% 的试验,大多数前沿配置低于 25%。9

被点名的失败模式恰恰是这里要紧的。智能体会让一个看起来合理的环境内请求推翻既定政策。它们执行了要求的检查,然后做出与检查结果相反的动作。它们在长跨度里丢掉规则的细节。这些都不是检索失败,文档自始至终都在那儿。

所以我这条规则的诚实版本,比「description 负责路由,正文负责解释」要窄。把流程搬出 description 依然是对的,因为这样能收回别的技能做路由所需的预算,也能阻止未被调用的文本去引导行为。这两点都是实打实的收益,而且都不依赖正文管得好不好。它换不来的,是对搬过去的流程会被遵守的信心。一份 124 页的手册和一段 3,000 词的 SKILL.md 正文,落在同一条曲线上。

实务上的读法是:把正文长度当成成本,而不是免费的停车位。如果某条规则真的必须成立,那 description 是放它的错误位置,长正文也只是稍好一点点。强制应当属于某个确定性的地方(一个钩子、一条权限规则、一个测试),而不是一段散文,指望模型一边干别的事一边把它记住。

审计你自己的配置

从会话内部开始。运行 /context,它会报告是否有技能被排除。5 如果它标出了排除项,问题就已经确定,诊断到此为止。

我没有从这里开始,原因本身很有启发:我那五个技能并没有被排除,它们是名字完好、description 被剥掉之后抵达的,这比整条不见更安静,也可能不会以同样的方式暴露出来。所以无论如何都要拿文件系统来核对一遍。这项检查除了一个 shell 什么工具都不需要:

python3 - <<'PY'
import os, re, glob
rows = []
for f in glob.glob(os.path.expanduser('~/.claude/skills/*/SKILL.md')):
    name = os.path.basename(os.path.dirname(f))
    fm = re.match(r'^---\s*\n(.*?)\n---\s*\n', open(f, encoding='utf-8', errors='replace').read(), re.S)
    if not fm:
        continue
    d = re.search(r'^description:\s*(.*?)(?=\n[a-zA-Z_-]+:|\Z)', fm.group(1), re.S | re.M)
    if not d:
        continue
    desc = ' '.join(d.group(1).split()).strip('"\'').lstrip('|').strip()
    rows.append((len(desc), name))
rows.sort(reverse=True)
print(f'{len(rows)} skills, {sum(r[0] for r in rows)} description chars')
for length, name in rows[:15]:
    print(f'  {length:4d}  {name}')
PY

然后把输出和模型实际收到的内容做对比。两者之间的落差,就是整个发现。如果某个技能在你的上下文里只有名字、后面没有一句话,那它就是装好了却无法抵达。

从这次审计里可以引出三个习惯:

给每一个新技能算预算,不只是长的那些。 每条固定的开销都会跟着技能走,与 description 长度无关,所以第十个 90 个字符的技能,代价不止 90 个字符。

加完技能后重新统计。 我没法给你一个安全余量,因为上限没有文档说明,而且据报告会随比例的计算方式而变化。34 一次实测胜过一个建立在假设之上的算出来的余量——我犯的正是这个错。

动手削减之前先做快照。 我的大多数技能目录都没有被 git 跟踪,还有七个是指向一个根本不是仓库的目录的符号链接,所以 git add 以「beyond a symbolic link」为由拒绝了它们。我先把每一条原始 description 写进了一个 JSON 文件。没有验证过的版本控制不是备份。

关键要点

  • 没有 description 的技能不是退化了,而是无法抵达。 description 承载了全部的路由决策。
  • 这个失败在构造上就是无声的。 没有报错,没有警告,没有日志。先跑 /context 看排除警告,再把你的上下文清单与文件系统作对比,因为被剥掉的 description 比缺失的条目更安静。
  • 决定丢弃的是总体积,不是逐文件的属性。 单个文件的任何属性都没能预测出哪些技能会丢掉 description。
  • 检查失效时,就去干预。 我没能靠检查失败现场找到机制。改变总量、看着失败被逆转,一步就把它证明了。
  • description 负责路由,正文负责解释。 description 里任何无助于判断何时调用的内容,都要在每一轮付费,却什么也挣不到。
  • description 里的祈使句在未被调用时就作用于你。 ALWAYS 和 NEVER 会从目录里引导行为,这正是被测量到的渗透效应。1
  • 不要发布你拿不出出处的百分比。 我自己第一版的写法,就是拿真实测量值乘上一个猜出来的预算,产出了一个自信而错误的数字。

常见问题

我的 Claude Code 技能为什么不激活?

先看模型是不是真的能看见它的 description。技能的 description 会加载进每一轮的上下文并共享一份固定的字符预算,一旦超出,description 就会被悄悄丢弃,没有报错、没有警告、也没有一行日志。我的 84 个技能里有五个抵达模型时只剩名字,而它们在磁盘上的文件里写着完全合格的 description。没有 description 的技能无法被路由到。12

Claude Code 的技能 description 预算是多少?

确切的预算没有文档说明,目前仍有争议,而我不会发布一个自己拿不出出处的数字。我测到的是:21,848 个字符的 description 丢掉了五条,9,885 个字符一条也没丢。社区的测量把实际上限放在 15,500 到 16,000 个字符附近,并计入每条约 109 个字符的开销;Anthropic 的贡献者则报告说,预算比例是按固定的 200K 基线计算的。346

我怎么审计自己有哪些技能丢了 description?

先在会话里跑 /context,它会报告是否有技能被排除。然后无论如何还是要拿文件系统核对一遍,因为我那五个并没有被排除:它们是名字完好、description 被剥掉之后抵达的,这比缺失一整条更安静。把你各个 SKILL.md frontmatter 里的 description 字符数加起来,再把这份清单和上下文里实际显示的内容作对比。5

技能的 description 与正文各自该放什么?

description 的唯一职责,是回答这个技能什么时候该被调用。留下触发词、名称,以及用户真的会敲的斜杠命令,把流程、理念和文件路径搬进正文——正文只在被调用时才加载。我的 jiro description 从 686 个字符降到 126 个,路由行为完全一样,因为其中大约 500 个字符只是在解释这个技能里有什么。

即使技能从不运行,它的 description 会影响行为吗?

会,这就是第二笔税。我的 description 里有 11 条带着 ALWAYS、NEVER、MUST、PROACTIVELY 和 BEFORE,这些词坐在每一轮的上下文里,而且因为是按指令写的,读起来就像指令。《The Regression Tax》把这个效应命名为 skill description osmosis:一个技能仅仅因为存在于上下文中就改变了智能体的行为,哪怕它从未被调用。1

参考资料


  1. 《The Regression Tax》,arXiv:2607.22520,2026 年 7 月 24 日。在两个办公自动化基准和三套 harness 上进行了约 6,000 次运行。命名了三种退化模式:skill description osmosis(未被调用、仅因存在于上下文而产生的行为变化)、事实依据置换、验证置换。主要发现:表现最好的技能主要靠退化更少、而不是收益更多来胜过其他技能。 

  2. Skill description budget silently truncates routing information, causing skill routing failures,anthropics/claude-code issue #64606。另见 Skill descriptions truncated due to context budget constraints,issue #56710。 

  3. skillListingBudgetFraction is calculated against a fixed ~200K baseline, not the model’s actual context window,anthropics/claude-code issue #57941。 

  4. Skill description budget uses base context, ignores [1m] extension,anthropics/claude-code issue #57168。 

  5. Extend Claude with skills,Claude Code 文档。已发布的文档没有说明技能 description 的总字符预算。另见 Skills docs omit the 250-character cap for /skills descriptions,issue #40121。 

  6. Claude Code skill budget research。社区测量,把实际上限放在技能元数据总量 15,500 到 16,000 个字符附近,每条约有 109 个字符的开销(XML 标签约 85、技能名称约 20、location 字段约 4),并观察到在所测的这些情形里,被隐藏的是整条条目,而不是逐条被截断。 

  7. Claude Code CHANGELOG,v2.1.215,2026 年 7 月:内置的 /verify/code-review 技能不再自我调用,需要显式调用。 

  8. Claude Code CHANGELOG,v2.1.218,2026 年 7 月 22 日:/code-review 作为后台子智能体运行,/deep-research 不再自我调用。 

  9. Liudas Panavas、Sebastian Minus、Bradley Monton、Derek Ray、Suhaas Garre、Sushant Mehta 与 Edwin Chen,《HANDBOOK.md: A Benchmark for Long-Context Agentic Instruction Following》,arXiv:2607.25398,2026 年 7 月 28 日提交。覆盖十家虚构公司五个领域(金融、医疗账单、保险、物流、人力资源)的 65 项任务,配有专家撰写的 20 到 124 页标准作业流程和 824 条程序化评分标准。在评测的 30 种模型配置中,最好的一种通过了 36.2% 的试验;大多数前沿配置仍低于 25%。被点名的失败模式包括:让看起来合理的环境内请求推翻既定政策、执行了要求的检查却做出与结果相反的动作,以及在长跨度里丢失规则细节。 

相关文章

上下文即新型记忆

上下文工程是智能体开发中影响力最大的技能。三层压缩策略将200K token窗口从负担转化为优势。

15 分钟阅读

AI代理技能需要行为审计,而不是通过率

AI代理技能可能会改变行为,而通过率保持不变。建立信任之前,行为审计会对比执行轨迹、声明能力和副作用。

12 分钟阅读

上下文工程即架构:650个文件之后的实践

横跨650个文件、七层层级结构的AI代理上下文工程。三次生产故障、真实的token预算,以及在这些考验中存活下来的系统。

11 分钟阅读