做 agent 开发,我最大的感受是:skill 这个东西,改起来比写起来难。写 skill 是从零到一,照着模板拼一拼,能跑就算成功;改 skill 却是在一个已经能工作的系统上动刀,动不好,不仅没变强,反而把原来能用的能力也搞坏了。最近读了一篇叫 SkillFocus 的论文,它提出的观点非常朴素,但特别戳痛点:先把能力拆清楚,再决定 skill 怎么改。核心意思是,任何 skill 的改动都应该建立在一张“能力地图”之上,而不是靠感觉、靠临时拍脑袋。这篇文章我会结合自己的实操,把 SkillFocus 的方法拆开讲透,顺便聊聊怎么用这套方法解决 agent skill 迭代中最常见的一堆问题。如果你正在维护一个 agent 项目,或者正在写属于自己的 skill 插件,这篇文章应该能帮你少踩几个坑。
1. 为什么“改 skill”之前必须先拆能力
1.1 一个典型的改 skill 翻车现场
我维护过一个信息整理类的 agent,里面有个 skill 叫“会议纪要生成”。因为用户反馈纪要里的行动项经常提取不全,我直接往 skill 的 prompt 里塞了一大段关于“行动项”的说明,还加了几个示例。改完第一轮测试,发现行动项提取确实比之前全了,但原来能正常识别的“讨论结论”却开始出错,经常把闲聊内容也当成结论输出。当时我第一反应是“模型抽风了”,折腾了半天才发现问题出在我自己身上:我往 skill 里加内容的时候,没有明确加在哪一层,也没有划定边界,结果所有相关行为都被新加的内容影响了。
这个场景做 agent 开发的朋友应该都不陌生。一个 skill 在运行时会同时处理多种输入、产出多种输出,它们共享同一份 prompt 和同一批上下文。你为了加强 A 能力加进去的话术,很可能会把 B 能力带偏。更麻烦的是,这种带偏还不容易发现,因为你的测试用例往往只覆盖了你关心的 A 场景,B 场景得等用户吐槽才知道。
1.2 SkillFocus 论文的核心主张
SkillFocus 这篇论文解决的就是这个痛点。它的核心主张是:agent 的每一个 skill 都应该有一个明确的能力定义,这个定义不是一句话描述,而是三层结构化信息——行为层、知识层、边界层。每次改动之前,先把你想要改的 skill 的三层画出来,然后明确这次改动会影响到哪一层,其他层要不要同步调整。
这个思路听起来简单,但绝大多数 agent 开发者没有做到。我在各个技术社区看别人分享 skill 修改经验,几乎都在说“prompt 加什么词”“few-shot 怎么给”,很少有人先回答“这个 skill 当前能力边界是什么”。SkillFocus 把这个问题拎出来,等于告诉我们:你之所以越改越乱,是因为你根本不知道自己在改什么。行为层、知识层、边界层这三层,就是帮你搞清楚“自己在改什么”的工具。
2. 三层能力地图:把 skill 拆成可管理的结构
2.1 行为层:把 skill 当成一个有输入输出的函数
任何 skill 在运行时,本质上都是一个函数:接收输入,产生输出,可能调用工具。行为层要写清楚的就是:这个 skill 的输入是什么?输出是什么?在什么条件下被激活?激活后调用哪些工具?怎样算成功,怎样算失败?
用代码来类比,行为层就是这个函数的签名和异常处理。我经常用一段伪代码来画行为层,比如:
skill: 会议纪要生成 trigger: 用户提供会议录音或文字稿,并要求生成纪要 input: 原始会议内容,可选指定格式 output: 结构化纪要(结论、行动项、负责人、截止时间) tools: 文本解析工具、日期识别工具 failure: 输入文本太短无法识别会议主题时,返回提示并请求补充你可能会觉得这有点繁琐,但画出来之后,你才能回答“这个 skill 到底能不能改”。比如前面说的翻车案例,如果我先画了行为层,就会看到原版的行为输出除了行动项,还有“结论”和“讨论过程摘要”,而我改的时候只盯着“行动项提取不全”这一个输入输出对,忽略了其他输出项,导致其他输出被新增内容干扰。行为层的意义,是让你意识到每一次 skill 改动都是在改一个多输入多输出的映射关系,而不是在改一段文字。
2.2 知识层:skill 的“肉”,也是改动最频繁的地方
知识层包含 skill 需要参考的领域知识、示例、术语表、few-shot 样本等。比如一个“代码审查 skill”,知识层里应该有你们团队的编码规范、常见 bug 模式、安全注意事项。改知识层是最容易的,因为往里塞内容立竿见影,这也是为什么大多数人改 skill 都喜欢从这一层下手。
但难点在于,知识层的知识是相互关联的。你新加一条规则,可能会和已有规则冲突。我在实践中会为知识层建一张表:每条知识用一行记录,标明它影响的行为、依赖的其他知识、可能的冲突面。比如加一条“禁止使用 eval”,它影响的行为是“代码执行建议”,依赖的是“安全规范”,冲突面是“某些用户可能希望快速验证表达式”。这样改知识层之前,你就能判断这条新知识会不会把其他行为带歪。
另外,知识层不是越满越好。塞进去的知识如果和当前场景无关,反而会稀释模型对重点规则的注意力。SkillFocus 强调的是“必要知识”而不是“全部知识”。我见过有人为了让翻译 skill 更专业,把一百多条行业术语全塞进去,结果常见句子的翻译质量反而下降了,因为模型被大量低频术语干扰了。
2.3 边界层:比“能做什么”更重要的是“不该做什么”
边界层定义 skill 的能力范围,包括:不该触发的场景、不该回答的内容、不该调用的工具、低于某个置信度时不该硬答。大部分 agent 开发者在写 skill 时几乎不关注边界层,但 SkillFocus 论文里特别强调,边界层是防止 agent 失控的第一道防线。
举个安全相关的例子:一个“翻译 skill”如果输入里出现敏感词,它应该停下来询问用户怎么处理,而不是尝试解释这个词。再比如一个“数据分析 skill”,它不应该在没有授权的情况下尝试调用外网访问工具。边界层的本质是给 agent 的能力画一个围栏。我们做 agent 安全时,很多问题本质上不是模型不行,而是 skill 的边界层没写清楚,导致模型在模糊场景下自由发挥。
边界层也要避免写得太“死”。边界不是禁止一切灵活性,而是明确哪些情况属于“能力范围之外”。比如翻译 skill 可以规定:“如果输入中超过 30% 的内容是代码,则不执行翻译,直接返回原文并提示用户。”这就是一条有弹性、可执行的边界规则,而不是笼统地写“不要翻译代码”。
3. 动手实操:用三层地图给 skill 做一次能力体检
3.1 能力体检的四步流程
先别急着改任何 skill,先按下面四步做一次能力体检:
- 列出当前 agent 用到的所有 skill 清单。不要凭记忆,去框架配置里一个个找出来。
- 为每个 skill 画三层地图。可以用表格,也可以用刚才的伪代码格式,重点是让地图完整覆盖行为、知识、边界。
- 标注问题类型。我习惯分三类:空白区(该有能力但没有)、模糊区(能力边界不清楚,容易和其他 skill 重叠)、冲突区(多个 skill 对同一场景的反应互相矛盾)。
- 输出优先级排序。先解决冲突区,再补空白区,最后收紧模糊区。因为冲突区会让 agent 行为随机,风险最大。
注意,这一步不用追求一次把所有 skill 都画完。你可以只挑用户反馈最多的两三个 skill 开始,画完地图之后再做后续判断。慢慢来,能力地图是沉淀资产,画完的以后都能复用。
3.2 体检表模板与示例
我每次准备改 skill 的时候,都会先填下面这个表格,填完就能发现自己之前想改的方向是不是对的:
| Skill 名称 | 行为层定义 | 知识层内容 | 边界层定义 | 问题类型 | 改动建议 |
|---|---|---|---|---|---|
| 会议纪要生成 | 输入会议稿,输出结构化纪要 | 会议角色、行动项关键词、结论识别规则 | 不输出逐字稿,不解释会议背景 | 冲突区 | 调整知识层中“行动项”与“结论”的区分规则 |
| 翻译助手 | 输入源语言文本,输出译文 | 术语表、风格要求、常见语序规则 | 不翻译代码,不解释敏感词 | 模糊区 | 补充“遇到代码片段保持原样”的规则 |
| 代码审查 | 输入代码段,输出审查意见 | 编码规范、常见 bug 模式、安全注意事项 | 不直接修改代码,不执行代码 | 空白区 | 新增一个独立的“安全漏洞扫描”skill |
填完这个表,你会发现自己原以为的改动方向,可能并不是最佳方案。比如我想提升“翻译助手”的术语准确性,但填表后发现它的模糊区是“代码和文本混排时处理方式不一致”,那么这次改动的重点就不只是术语,而是边界层规则。
3.3 如何判断“拆清楚”了
判断标准很简单:如果有人拿着你的三层地图,不需要看 skill 原始 prompt,就能准确说出这个 skill 的职责和能力边界。另外,当你要修改 skill 时,能够明确指出这次改的是哪一层,以及会影响到哪些行为。如果说不出来,说明还没拆清楚,先不要动手。
我自己还有一个附加判断:拿一个典型的测试用例,问自己“这个用例会不会被地图里描述的边界规则拦截”。如果答案是“可能会”,说明边界还不够明确。如果答案是“不会,因为……”,并且这个理由能在图上找到对应条目,那说明拆得足够清楚了。
4. 能力拆清之后,skill 改动的四个落地动作
4.1 拆分和合并:什么时候该动结构
能力体检之后,如果发现一个 skill 的行为层有多个差异很大的输入输出对,就应该考虑拆分。比如“文档处理 skill”同时承担“格式转换”和“内容摘要”,这两个行为输入输出都不一致,合并在一起只会让 prompt 越来越长,触发冲突越来越多。拆开之后,用户需要摘要时只加载摘要 skill,需要转换格式时只加载转换 skill,上下文也被精简了。
反过来,如果两个 skill 的知识层高度重叠,且行为层也类似,就应该合并。比如“用户反馈分析”和“评论情绪判断”,底层都依赖情感词库,合并成一个“文本情绪分析”skill 可以减少重复上下文,也方便统一维护情感知识。拆分和合并的原则不是“职责单一”或者“粒度越小越好”,而是“行为差异足够大才拆,知识重叠足够高才合”。
4.2 改写 prompt 时锁定“能力锚点”
能力锚点是指那些已经被验证稳定、用户反馈良好的能力。在改写 skill 时,先找出锚点,并在新版本中保持锚点的行为绝对不变。具体做法是:用行为层定义来标记锚点,比如“输出结构化纪要”是锚点,那么不管知识层怎么改,都不能改变输出格式。
锚点需要固化下来,变成可执行的测试。比如“输入包含三句话的会议稿,输出必须包含‘结论’字段”“输入翻译请求,输出必须保留原文的换行格式”。把这些断言写成简单的测试脚本,放到 skill 的测试目录里,每次改动后跑一遍。我自己的项目里,每个 skill 都对应一组锚点测试,运行 agent 框架自带的回归命令就能自动验证。锚点测试没过,说明这次改动动到了根子上,必须回滚。
4.3 处理 skill 之间的冲突:触发优先与互斥规则
Agent 框架中经常会有多个 skill 同时被触发的情况。SkillFocus 的思路是,冲突不在运行时解决,而是在设计期解决。具体操作:在能力地图中标注依赖关系和冲突关系。比如“会议纪要生成”依赖“时间识别”工具,“日程安排”也依赖“时间识别”,但两者通常不会同时触发,因为输入场景不同。
对于可能同时触发的情况,要明确优先级。例如“生成代码”和“解释代码”如果同时满足触发条件,应该先执行“生成代码”,因为用户更倾向于先看到结果再听解释。在 skill 定义中加入规则字段,比如:
name: generate_code priority: 10 conflict_with: explain_code如果在 agent 框架的编排层看到conflict_with,harness 就会在处理完高优先级 skill 后,重新评估低优先级 skill 是否还需要触发。这里顺便说下 harness 和 agent 的区别:harness 是调度容器,负责加载 skill、管理上下文、执行工具调用;agent 是决策实体。能力拆解的结果最终要交给 harness 来编排,才能减少冲突。很多 agent 项目出问题,就是因为 skill 定义里没有这些规则,harness 只能随机选一个执行。
4.4 回归验证:用对比实验确认改动是进化还是退化
改完 skill 后,不能只靠一两个用例判断好坏。我至少准备三类用例:锚点用例(检验之前稳定的能力有没有被破坏)、目标用例(这次想改进的场景)、对抗用例(以前容易翻车的场景)。分别跑新旧版本,记录通过率和输出质量评分。
跑测试时要注意一件事:顺序。模型对输入顺序有一定敏感性,如果你的目标用例总是排在前面,可能只是位置带来的偏差。把用例的顺序打乱,多跑几轮,然后取平均分。如果目标用例提高了但对抗用例下降了,说明利弊参半,需要继续调整;如果锚点用例挂了,必须回滚。我在实践里遇到最多的情况是:目标用例提升了 30%,对抗用例下降了 50%,这种改动看似“有效”,实际净收益是负的。用表记录一下:
| 用例类型 | 旧版本通过率 | 新版本通过率 | 影响判断 |
|---|---|---|---|
| 锚点用例 | 100% | 60% | 严重退化,回滚 |
| 目标用例 | 40% | 90% | 目标达成 |
| 对抗用例 | 80% | 30% | 出现新的误伤,需重新设计 |
5. 几个容易踩的坑和我的排查思路
5.1 拆解后发现能力全是缺口,怎么办
体检结果可能很惨:所有 skill 都有一堆问题。这时候千万不要全改。按优先级排序,先改影响用户最频繁的一两个,其余记录到 backlog。因为一次性改动太多,你根本定位不了问题。我踩过这个坑:连续改了五个 skill,结果 agent 整体表现下降,最后只能靠 git 回滚,白忙了两天。
正确做法是:把体检表当成产品需求池,每次迭代只取出一个“冲突区”或“空白区”问题来解决。改完、测完、发布,观察几天,确认没有新问题,再动下一个。这个过程很慢,但很稳。
5.2 改完 skill 后 agent 反而变笨了,先查这三个地方
第一,检查是否动了能力锚点。这是最常见的翻车原因,改着改着顺手改了一个输出格式,导致下游依赖全部失效。第二,检查知识层新增内容是否与已有规则冲突。比如你加了一条“遇到不确定的回答要坦诚说明”,但原有规则里有一条“所有回答必须给出确定性结论”,这两条直接打架。第三,检查边界层是否被意外放宽。为了修复“漏答”问题,让 skill 在低置信度时也给出答案,结果开始瞎编,这是典型的边界放宽导致的退化。
我的经验是,90% 的“变笨”发生在边界层。你想要一个更宽容的行为,结果把禁区也开放了。修复方式不是放宽边界,而是增加一个分支处理低置信度场景。比如边界规则写成“如果置信度低于 60%,返回‘我需要更多信息’并列出缺失项”,而不是“无论什么都给出答案”。
5.3 避免“过度拆解”导致 skill 碎片化
有时候拆得太细,一个功能变成了五个 skill,结果上下文里塞满了 skill 描述,反而干扰模型判断。SkillFocus 的拆解原则很明确:行为层差异足够大才拆,知识层重叠足够高才合。不要为了追求“职责单一”而把强耦合的能力拆开。
我遇到过一个小型 agent,早期只有一个“数据处理 skill”,后来为了“结构清晰”拆成了“数据清洗”“数据转换”“数据校验”三个 skill。结果很多任务同时需要这三个能力,模型每次都要加载三段描述,上下文开销变大,而且这三个 skill 的触发条件经常互相踩。最后又合并回去了。我的经验是,如果两个 skill 经常需要同时被调用,且知识层共享超过 50%,就不要再拆了。
6. 结合 SkillFocus 再看 agent skill 开发的几个现实问题
6.1 skill 编码和框架的标准化
当前各框架对 skill 的定义、加载方式、触发机制都有差异。有的用 markdown 文件,有的用 yaml,有的用 json。SkillFocus 提供的是方法论,不绑定具体格式。你可以把三层地图抽象成约定的字段,填到自己的 skill 模板里。例如在 skill 的 yaml 头部加入:
behavior: | input: ... output: ... trigger: ... tools: [...] knowledge: | - rule1 - rule2 boundary: | - forbidden1 - forbidden2这样做的好处是,哪怕你换了一个 agent 框架,只要把这三个字段翻译成新框架支持的格式,能力定义就能保留下来。现在市面上有各种 agent 框架,各有各的 skill 加载方式,但底层对“能力边界”的需求是一致的。提前用统一格式记录能力地图,能减少以后迁移框架时的返工成本。
6.2 能力拆解和 agent 安全
边界层和 agent 安全是直接挂钩的。在我做的 agent 项目里,很多安全问题其实是“能力越界”导致的:一个负责数据分析的 skill 被诱导去执行了系统命令。为了防这个问题,我在边界层里明确写了“禁止执行任何系统命令”,同时在 harness 层做白名单拦截。SkillFocus 的思路让我意识到,安全不只是模型层、框架层的事,skill 层也必须有自己的边界声明。
把边界层写到 skill 里,harness 做强制校验,是双保险。Skill 文件里的边界声明相当于“意图”,harness 的白名单相当于“能力”。如果 skill 的意图里写着禁止执行系统命令,但 harness 白名单里却允许了,那实际上还是不安全。所以我的建议是:skill 编写者负责声明边界,harness 配置者负责执行校验,两边都要定期对齐。
6.3 能力地图要跟着 skill 一起迭代
每次改动 skill,都要更新它的三层地图,并保留旧版本。就像代码合入前要先更新注释一样,能力地图就是 skill 的注释。这样后续维护时,你能看到能力演进的轨迹:为什么这个 skill 加了这条边界规则?上次是因为什么拆分了行为?有了这些记录,后续维护者不用靠猜。
我甚至会把能力地图直接写到 skill 文件的注释里。这样一来,新同事接手项目时,打开一个 skill 文件就能看到它的完整能力定义,不用去翻文档、查历史记录。这一步做起来很简单,但坚持下来后,agent 项目的可维护性提升非常明显。
最后分享一个我在实际项目里的小技巧:每次改完 skill,除了更新能力地图,顺手补一条 changelog。格式不用复杂,就写“这次改了哪一层、为什么改、影响了哪个行为、验证用例有哪些”。等过了三个月回头看,你会发现这几行字比任何口头说明都管用。SkillFocus 给我的启发也就在这里:它不是告诉你“怎么让 agent 变强”,而是告诉你“怎么让自己知道你改了什么”。想清楚这一点,skill 的持续进化才真正开始。