Skip to content

Skills

Agent Skills 是什么、三层渐进式披露机制、description 触发原理、设计原则与面试追问。

Updated View as Markdown
For humans

Skills

一个 Skill 就是一个文件夹,里面一份 SKILL.md:开头一段 YAML(namedescription),下面是 Markdown 指令,旁边可以放脚本和参考资料。

expense-report/
├── SKILL.md                  # 必选:frontmatter + 指令正文
├── scripts/
│   └── validate.py           # 可执行代码,按需运行
├── references/
│   └── POLICY_FAQ.md         # 参考文档,按需读取
└── assets/
    └── report-template.md    # 模板等静态资源

内容本质是提示词,这一点和 prompt 没有区别。区别在加载方式:prompt 是每次对话全量塞进上下文,Skill 只在需要时才把对应层级的内容读进来。这套机制叫渐进式披露(Progressive Disclosure),是 Agent Skills 的核心设计。

为什么需要渐进式披露

上下文窗口是公共资源。装 50 个技能、每个正文 2000 tokens,如果全量加载,用户问一句“今天天气怎么样”也得先烧掉 10 万 tokens。更糟的是信号稀释:塞满规则之后,模型对眼前任务的实际注意力会下降,长上下文的推理质量本身就衰减。

更大的窗口不是答案。指令增长的速度比窗口扩容快,这是一场跑不赢的军备竞赛。解法是更聪明的加载,不是更大的盒子。

三层加载机制

渐进式披露的三层结构

  • 第一层,元数据常驻。 启动时所有技能的 namedescription 拼成清单放进上下文,正文一个字都不带。装几十个技能,常驻开销被压到窗口的 1% 左右
  • 第二层,正文触发时加载。 模型判断当前任务匹配某个技能,才把整份 SKILL.md 读进来
  • 第三层,资源按需。 references/scripts/assets/ 里的内容,只有正文明确指向时才读或执行。脚本更省:直接在上下文之外运行,只有输出进来,源码永远不占 token

一个经常被忽略的细节:正文注入不是塞进系统提示词,而是包装成一条带 isMeta 标记的用户消息插进对话流。因为系统提示词改动会导致 prompt cache 大面积失效、每轮重新计费,追加一条消息是纯增量。这个设计抠 token 抠到了骨子里。

触发机制:description 是唯一的广告位

模型怎么知道该用哪个技能?没有检索代码,没有关键词匹配器。启动时那份清单摆在模型眼前,它靠一次前向推理自己判断。

所以 description 的分量比想象中重得多:它是模型在决定加载之前唯一能看到的内容。写不好,技能等于不存在。

---
name: expense-report
description: >-
  按公司政策填写和审核报销单。用户提到报销、差旅费用、
  支出上限、贴发票时使用。
---

description 有三个硬要求:

  1. 说清两件事:做什么 + 何时用(触发条件)
  2. 塞进用户真会打出来的关键词。“报销”比“费用合规流程”更容易命中
  3. 第三人称。它会被原样拼进系统提示,第一人称会和人称体系打架

Anthropic 自己的说法是描述要写得“pushy”一点:技能普遍存在欠触发的问题,宁可写过头也别写含糊。官方仓库里甚至有一个 skill-creator 技能,用 20 条应触发/不应触发用例去评测 description 的触发率,再迭代优化。描述优化被当成了一件正经的工程活。

Claude Code 还给清单设了预算:所有描述总和限制在上下文窗口的 1%~2%,单条 250 字符,超了直接截断。触发词放开头,就是这个原因。

一次完整触发的时序:

一次技能触发的完整时序

进阶机制

Claude Code 在标准三层之上还做了几件事:

触发控制。 disable-model-invocation: true 之后模型不许自主调用,只能用户敲 /skill-name,适合部署、提交这类有副作用的操作;user-invocable: false 反过来,从斜杠菜单里消失,只有模型能触发。

条件激活。 frontmatter 里声明 paths(如 "src/**/*.ts")的技能,启动时不暴露。当模型读、写、改的文件匹配路径时,技能才被激活注入。技能集合会随你打开的文件变化。

列表增量下发。 技能清单不是每轮全量重发。sentSkillNames 记录已发送的,之后只发新增的,resume 之后还会抑制重复,避免污染上下文。

fork 模式。 context: fork 让技能在一个隔离的子代理上下文里执行,中间过程不进入主对话,完成后只把结果摘要交回来。适合会产生大量中间输出的复杂任务。

动态注入。 正文里可以嵌 !command 语法,技能被调用时先执行这条命令,把输出填进正文再交给模型。比如 !gh pr diff 让技能加载时就带上真实的 PR 内容。

和相邻概念的边界

概念 关系
CLAUDE.md 项目级全局配置,每次会话全量加载,管“总是要用的规则”。Skill 是按需模块,管“特定任务才用的知识”。每次都需要的放 CLAUDE.md,按需的放 Skills
Prompt 提示词是知识本身;Skill 是知识的加载策略。同样一份内容,塞 prompt 是常驻,做成 Skill 是三层懒加载
MCP MCP 管“接得上”(工具怎么暴露和调用),Skill 管“干得对”(行为规范),Subagent 管“不干扰”(上下文隔离)。三个层次,不是替代关系
RAG RAG 用向量相似度检索文档片段;Skill 用 description 让模型显式判断要不要加载。一个是检索系统,一个是决策机制

设计原则

SKILL.md 只放“几乎每次都会用到”的内容。 经验值是 100 到 400 行。低于 100 行可能根本不用拆,高于 400 行就该考虑渐进式披露了。一个 1800 行的技能拆完主文件往往只剩 180 行,大多数对话只付这 180 行的成本。

references 要靠正文明确指向。 模型不会主动翻技能目录。正文里写“遇到 OpenAPI 3.1 时读 references/openapi-3-1.md”,比写“有疑问时查阅参考资料”可靠得多。一次引用一层,别做链式引用。

scripts 要注册、要幂等。 模型不会扫 scripts/ 文件夹,必须在正文里介绍每个脚本的用途和用法。脚本自己要保持幂等,同样的输入跑两次结果一样,别在脚本里改全局状态、推 commit、发邮件。

不触发先改 description。 一个技能从来不触发,90% 是描述的问题。先改措辞补触发词,别动正文。正文改得再漂亮,模型到不了第二层也是隐形的。

过度拆分也是问题。 SKILL.md 里全是“看 references/X”“读 references/Y”,模型做任何事都得跳来跳去,管理成本就超过了省下的上下文。拆到每个文件只有 20 行,说明拆错了。

安全边界。 技能会指示模型执行代码、连外部网络。只装可信来源的技能,装之前读一遍打包的文件,特别注意脚本和“连接某某服务器”之类的指令。

面试追问

  1. Skill 和 prompt 的区别? 内容上都是提示词,区别在加载方式。prompt 全量常驻,Skill 分层按需:平时上下文里只有一行描述,模型判断用得上,正文才注入,参考文档还能再懒一层。提示词是知识本身,Skill 是知识的加载策略
  2. 为什么正文注入用 user message 而不是 system prompt? 系统提示词改动会让 prompt cache 大面积失效、每轮重新计费;追加一条消息是纯增量,前面的缓存不受影响
  3. 描述总预算多少? Claude Code 限制在上下文窗口的 1%~2%(约 2000~8000 字符),单条描述 250 字符。50 个技能全量加载要 15 万 tokens,渐进式披露把常驻开销压到 2000
  4. 渐进式披露的代价? 分层写作更难,description 设计不好会漏触发,排错要看工具链暴露的加载痕迹。另外有对照实验表明:单本书场景下强 agent 自己就能导航原文,披露机制收益趋近于零;语料规模变大时才变得决定性。它买的是上下文,不是智能
Navigation

Type to search…

↑↓ navigate↵ selectEsc close