原文:Designing, Refining, and Maintaining Agent Skills at Perplexity · Perplexity Research · 2026-05-01
引言:为什么 Skill 不是软件
如果你是个写过十年代码的工程师,第一次去给 Agent 写 Skill,大概率会写出一份"看上去很专业、实际效果却比不加载还差"的东西。
Perplexity 给出的判断很直接——Agent Skill 不是软件,是上下文。它的读者是 LLM,是一个带着训练数据、按 token 收费、上下文窗口里挤着 3~5 个 Skill 同时争夺注意力的协作者。你过去写好软件的那些直觉——显式、覆盖、抽象、防御性——拿到这里几乎条条都要翻面。
Perplexity Agents 团队把 Skill 质量看得和代码质量一样重。原文里有一句话不长,但值得贴出来:
Self-generated Skills provide no benefit on average, showing that models cannot reliably author the procedural knowledge they benefit from consuming.
模型能消费程序性知识,却不能可靠地造出它——这正是为什么 Skill 设计是个人类工程师的活,而且是个需要专门方法论的活。下面这篇文章,就是从原文里提炼的五条心智模型 + 一套可上手的流程。
一、Zen of Skills:五条与 Python 反向的原则
最直观的对照表,原文摆得很清楚:
逐条解释一下为什么是反的:
- 简单 vs. Skill 是文件夹:单文件 Skill 看起来"干净",但它会强迫你把所有细节铺到一层,结果要么塞不下,要么塞下了让模型抓不到重点。结构本身就是一种信号,目录层级帮模型知道"什么时候去读哪一段"。
- 显式 vs. 模式匹配 + 渐进披露:传统软件会在头部 import 全部依赖,运行起来心里有数。Skill 反过来——所有重资料都默认不加载,只在描述命中、模型真去读它的时候才付钱。
- 稀疏 vs. 单 token 信号最大化:写代码我们追求行数稀疏;写 Skill 我们追求每个 token 都打在点上。这两者完全不冲突,但在直觉上很容易混淆。
- 特殊情况 vs. Gotchas 才是宝藏:在软件里,特殊情况是技术债;在 Skill 里,特殊情况就是 Skill 的全部价值。正常路径模型自己会,它不会的是那些"看起来合理但会翻车"的边缘情况。
- 易于解释 vs. 易于解释就该删掉:这条最反直觉,但也最关键——如果一段话你能轻松向同事解释清楚,模型大概率也已经从训练数据里知道它了。把这段话写进 Skill,等于花上下文成本买"模型已经会的东西"。
记住这五条,就有了"我现在写的这一段,到底该不该出现在 Skill 里"的判断标尺。
二、Skill 的四个定义性属性
原文给 Skill 下了一个不算定义的定义——“Skill 是模型与它的环境共享的上下文”。但它有四个非常具体的属性:
① 它是一个目录,不是一个文件
典型结构:
my-skill/
├── SKILL.md # frontmatter + 主指令
├── scripts/ # 模型会调用的脚本,让它"组合"而不是"重造"
├── references/ # 重资料,按需读取
├── assets/ # 输出模板、schema
└── config.json # 首次运行的用户配置
这是 hub-and-spoke——SKILL.md 是 hub,其它文件是按条件向外辐射的 spoke。
为什么必须分层? 原文给了一个特别戳的反例:
Perplexity 早期的 U.S. Income Tax Skill 用三层主题嵌套。但更早的实验显示:把 IRS 全部 1945 节扁平塞进一个文件夹,效果比根本不加载这个 Skill 还差。
把所有信息一股脑摆在模型面前,不是"信息更全",而是让模型在每次决策时都要重新筛一遍噪音。Skill 的目录结构,本质是替模型预先做了一遍粗排。
② 它有规定的格式
SKILL.md 的 frontmatter 是强约束:
- 必须有
name,且与目录名一致,全小写连字符 - 必须有
description——但它不是给人读的文档,是路由触发器 - 可选
depends:实现层级依赖,会被递归加载 - 可选
metadata:放评测、责任人等辅助信息
注意一个细节:Perplexity Computer 的实现里,frontmatter 在交给模型前会被剥离——也就是说,写在 frontmatter 里的元信息不进模型上下文,是给加载器看的。这给出一条实操建议:评测、维护者、版本号这类元数据,应该写在 frontmatter,不要写进 body。
③ 它在运行时才被装载
Skill 不是默认塞进系统提示词的。Perplexity Computer 的加载流程是:
- Agent 主动调用
load_skill(name="...") - 整个 Skill 目录被复制到一个隔离的沙箱
depends:里声明的依赖 Skill 递归加载- frontmatter 被剥离
- 模型看到 body + 这些附加文件
Computer 默认不把目录树暴露给模型,每个 Skill 可以单独覆盖这个行为。这是个挺细的设计——它逼着 Skill 作者在 body 里主动指引模型去读哪些 spoke 文件,而不是让模型在文件列表里自行猜测。
④ 它是渐进式的
最关键的一条属性,单独开一节讲。
三、上下文成本:Index / Load / Runtime 三层税
理解 Skill 经济学,必须理解每个 Skill 都在三个不同的层级收税:
| 层级 | 加载内容 | 单位成本 | 谁来付 |
|---|---|---|---|
| Index | 每个未隐藏 Skill 的 name + description | ~100 tok / Skill | 每次会话、每个用户、永远 |
| Load | 整段 SKILL.md body |
~5,000 tok(建议上限) | 命中路由的会话 |
| Runtime | scripts/ references/ assets/、子 Skill 内容 |
不限 | 模型真去读时 |
这张图的核心信息是:Index 这一层是永远在场的"基础税"。
如果你有 50 个 Skill,每个 description 平均 100 tokens,那就是 5,000 tokens——在用户还没说一句话之前,模型上下文就已经被吃掉一段了。而且这部分支出和会话内容无关,每次都付。
Load 层的预算约束更现实:原文建议 SKILL.md body 控制在 5,000 tokens 以内。原因是一个 Agent 线程里经常同时加载 3~5 个 Skill——如果每个都写到 1 万 tokens,那是几万 tokens 的占用,任何一个臃肿的 Skill 都会拖累其它 Skill 的发挥。
这也解释了为什么 Perplexity 这么强调"短"。短不是审美洁癖,是经济学。
一句话判断你的 Skill 该不该瘦身
“Would the agent get this wrong without this instruction?”
把每一句话都拿这个问题去过一遍。回答是"不会"的,立刻删。这是原文里唯一一条强制 SOP,比"先写 eval"还硬。
四、Description 是最难写的一行
如果你只能从这篇文章里记一件事,那就是:Skill 的描述不是文档,是路由器的输入。
原文用了 PR 监控 Skill 当例子。两种写法:
反例(写成文档)
description: This skill monitors pull requests by polling
GitHub API, checking CI status, and reporting back to the user.
听起来很专业。但用户在 Cursor 里实际敲的话是什么?
“帮我盯着这个 PR” / “watch this CI” / “babysit it until it lands”
monitor / poll / API 这些词在用户嘴里根本不出现。结果是:这条 Skill 永远不会被加载,而它的 100 tokens 索引费照付。
正例(用用户的话)
description: Load when user asks to babysit, watch, or
shepherd a PR through CI until it lands.
Perplexity 给出的硬性 checklist:
- 起手必须是
Load when...,不是This Skill does... - ≤ 50 词
- 描述用户意图,最好直接抄真实查询里的用词
- 绝对不要总结 Skill 的实现流程
一个被反复强调的副作用
Changing a description after merge without new evals means you’re off-track.
改 description 不是改文档,是改路由。哪怕只换一个动词,都可能让相邻的、你根本没动过的 Skill 突然失效——原文把这种现象叫 action at a distance,远距离副作用。这也是 Perplexity 要专门跑一套"加载/读取" Eval Suite 的根本原因。
五、Gotchas Flywheel:维护比构建更重要
写一个新 Skill 不难,难的是它上线半年后还在变好。
Perplexity 给出的方法论是 append-mostly——大部分维护工作都是往 gotchas 段尾追加,而不是改描述、不是重写正文。
为什么?因为:
- 改描述 → 影响路由 → 牵动相邻 Skill → 需要补 eval
- 改正文 → 影响 Load 层成本 → 影响其它同时加载的 Skill
- 追加 gotchas → 只增不删,影响范围最小
四种典型观察 → 动作映射如下:
| 你看到的现象 | 该做的事 |
|---|---|
| Agent 在某情境下做错 | 写一条 gotcha 进去 |
| Skill 被错误激活(off-target) | 收紧 description + 加负例 eval |
| 该加载时没加载 | 补关键词 + 加正例 eval |
| 系统提示词改了 | 检查 Skill 是否与之冲突 / 重复 |
原文里有句话特别能打消"我是不是应该把这个 Skill 重写一遍"的冲动:
一个 Skill 从 80/20 爬到 99.9% 的成功率,靠的不是写得更长,而是 gotchas 越攒越准。
一个隐藏的工程纪律
5 分钟拍脑袋开个 PR 加新 Skill,几乎一定会出岔子。 因为:
- 你没跑 hero query
- 你没补负例 eval
- 你的 description 没人挑过用词
- 它会偷走相邻 Skill 的路由命中
Perplexity 的做法是:单次 changeset 必须带评测集。Skill PR 不允许"先合入再补 eval"——增量审查在 Skill 上几乎做不了,因为一个小词的变化就可能把 50 条评测的命中率拉下 10 个点。
六、实战五步法 + 四套 Eval Suite
把上面所有东西串起来,落到一个具体的 Skill 上:
Step 0 · 先写 Eval(不是先写代码)
来源有三:
- 真实用户查询:从生产里采样
- 已知失败:以前因为没这 Skill 而翻车的 case
- 相邻领域:会误触发的、但应该路由到别的 Skill 的查询
负例 eval 比正例还重要——它们决定了你的描述是不是太宽泛。
Step 1 · 写 Description
按上一节的硬性 checklist 来。这一步是整个流程的瓶颈,预算上花在这一行的时间应该多于花在 body 上的时间。
Step 2 · 写 Body
跳过显而易见。原文给了一个反例:
# 反例:当模型已经会做的事情,逐行写出来
git log # find the commit
git checkout main
git checkout -b <clean-branch>
git cherry-pick <commit>
应该写成:
“把那个 commit cherry-pick 到一条干净分支上。保留意图地解决冲突。如果落不下去,解释为什么。”
要点:
- 别 railroad(“先做 A 再做 B 再做 C"那种写法)
- 把重点放在 gotchas 和负例上——这两块是单 token 信号最大的内容
- 重资料、长流程、有条件的内容 → 移到
references/,让模型按需读
Step 3 · 用好层级
| 文件夹 | 干什么 | 例子 |
|---|---|---|
scripts/ |
装确定性逻辑,让模型组合而非重造 | 已经写好的工具脚本 |
references/ |
重资料,按条件加载 | “If API returns non-200, read api-errors.md” |
assets/ |
输出模板 | report-template.md、output schema |
config.json |
首次运行设置 | 让用户填 Slack channel 后存下来复用 |
复杂场景下,可以把一个大 Skill 拆成"主 Skill + 多个子 Skill”,用 depends: 串起来。Perplexity 内部就这么做。
Step 4 · 迭代
从无 Skill 状态开一个分支,跑 hero query 集,再跑 eval。单 changeset 提交——别想着"先合再修",Skill 的增量审查极难做。
Step 5 · Ship
发布前再过一遍上面提到的"每句话都该被删"的判断。如果体感超过 5,000 tokens,强行瘦身一轮。
Perplexity 的四套 Eval Suite
不是只有最后才跑 eval,而是贯穿全流程:
- 加载 & 读取 eval:precision、recall、forbidden 检查——确保新 Skill 不破坏既有 Skill 的边界,这是防"远距离副作用"的核心。
- 渐进式加载 eval:检验 Skill 是否真的会去读它该读的辅助文件(比如 finance Skill 有没有读
FORMATTING.md)——这是检验 hub-and-spoke 设计是否生效的关键。 - 端到端任务完成度:跑完整 Agent loop,LLM judge 按 rubric 打分。最贵也最关键,看真实产出质量。
- 跨模型 eval:Perplexity Computer 支持 GPT、Claude Opus、Claude Sonnet 作为编排器,原文明确说"Sonnet 和 GPT 在与 Skill 交互时行为差异很大"——不能假设一套 Skill 在所有模型上等效。
这四层 eval 加在一起,本质是一套"Skill 不会偷偷劣化"的保险机制。Perplexity 把这个保险机制看得和单元测试一样不可妥协。
七、给落地者的 7 条带走清单
最后,把上面所有内容压成一张可贴在 monitor 上的清单:
- 先写 Eval,再写 Skill——含负例与"禁止加载"案例,没有 eval 的 Skill = 没有验收的代码。
- Description 是最难写的一行——
Load when...,用用户的话,每词都在抢路由器注意力;改一个动词都要补 eval。 - Gotchas 才值钱——起手薄,随失败逐条追加;append-mostly。
- 警惕远距离副作用——新增一个 Skill 可能让其它 Skill 失效,这正是"加载/读取 eval"存在的理由。
- Less is more——不必凡事都做 Skill:模型已会的写成文档;系统提示能装下的直接放;变化太快的别做 Skill,做工具。
- Skill 是复利——把 postmortem、PR 评审、周报这种高频自动化为 Skill,买回的时间再投入下一个 Skill。
- 不要让模型自己写 Skill——“Self-generated Skills provide no benefit on average.” 模型能消费程序性知识,却不能可靠地造出它。
写在最后
Perplexity 这篇文章最有价值的不是某条具体技巧,而是它给"Skill"这个概念真正确立了一个工程纪律:
- 它有边界——Skill 是文件夹,有目录约定
- 它有协议——
Load when...的描述格式、depends:、frontmatter - 它有成本——Index / Load / Runtime 三层税
- 它有验收——四套 Eval Suite
- 它有维护范式——Gotchas Flywheel
这是把"prompt engineering"从手艺活推到"上下文工程"的关键一步。如果你正在搭自己的 Skill 体系——不管是给 Claude Code 写 Skill、给 Cursor 写 rules、还是给内部 Agent 写工具集——这套方法论几乎都可以原样平移过来用。
最后留一句 Pascal 1657 年的话,原文也引了,放在这里再合适不过:
“Je n’ai fait celle-ci plus longue que parce que je n’ai pas eu le loisir de la faire plus courte.”
“我没把这封信写短,只是因为我没时间把它写短。”
写好一个 Skill,真正花时间的,是把它写短。
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付