跳转至

5. 预定义的能力包:Skill 交互原理

你的团队有一份 Go 编码规范。不长,大概两千字,涵盖了错误处理模式、日志格式、包命名约定、接口设计原则。每个新人入职的时候都会读一遍,然后在代码审查中被反复提醒,直到这些规范变成肌肉记忆。

现在你想让 AI 编程助手也遵循这份规范。

第一个念头:把规范塞进 System Prompt。你试了,效果还行,Agent 写出来的代码确实更符合团队风格了。但问题是,System Prompt 的空间是有限的。你的规范占了两千字,加上工具描述、任务说明、项目上下文,System Prompt 已经膨胀到了好几千 Token。而且你不只有 Go 项目,还有 Python 项目、TypeScript 项目,每个项目的规范不同。你总不能把所有语言的编码规范都塞进去吧?

第二个念头:做成一个 MCP 工具。写一个 get_coding_standard 工具,Agent 需要的时候调用它,获取编码规范的内容。你试了,发现一个尴尬的问题:Agent 经常忘了调用这个工具。它直接开始写代码,按照自己训练数据中学到的通用风格来写,完全没有参考你的团队规范。你在 System Prompt 里加了一句写代码前请先调用 get_coding_standard 获取编码规范,好了一些,但还是不稳定,有时候它调了,有时候它没调。

问题出在哪里?

编码规范不是一个工具。它不是一个你调用一次就完的操作,而是一组需要持续影响 Agent 行为的约束。它更像是 Agent 的工作习惯,而不是 Agent 的工具箱里的一把锤子。你不会每次拧螺丝之前都去查一遍螺丝刀使用手册,你已经知道怎么用螺丝刀了,这个知识内化在你的行为模式里。编码规范对 Agent 来说也应该是这样:不是需要的时候去查,而是从一开始就知道。

但 System Prompt 装不下所有的从一开始就知道。不同的项目需要不同的规范,不同的任务需要不同的知识,不同的角色需要不同的行为模式。你需要一种机制,能够按需加载这些行为约束,写 Go 代码的时候加载 Go 规范,做代码审查的时候加载审查清单,处理数据库迁移的时候加载迁移最佳实践。

这种机制,就是 Skill。

5.1 不是所有能力都是"工具"

在 Agent 的世界里,能力可以粗略地分为两类:

第一类是做一件具体的事。 读取一个文件、搜索一段代码、执行一条命令、查询一个数据库。这类能力有明确的输入和输出,有明确的执行时机(Agent 决定现在需要执行该操作时),有明确的完成标志(操作执行完毕,返回结果)。MCP 工具完美地覆盖了这类能力。

第二类是以一种特定的方式做事。 按照团队的编码规范写代码、按照安全审查清单检查代码、按照特定的架构模式设计系统、按照固定的流程做代码审查。这类能力更像是工作模式行为约束。它们没有像工具调用那样离散的触发瞬间和明确的输入输出,而是在被激活的生命周期内(无论是贯穿整个任务,还是在特定步骤被按需加载),作为一种背景上下文持续改变 Agent 思考和生成内容的方式。

第二类能力用 MCP 工具来表达,会遇到我们开头描述的那个问题:Agent 可能忘了调用,或者调用了但没有持续遵循。因为工具的设计语义是做一件事,不是持续影响行为。

Skill 就是为第二类能力设计的抽象。它不是一个函数,不是一个工具,也不是一个提示词模板。它是一个能力包,一组指令、资源和工作流的集合,打包在一起,可以被 Agent 按需加载。

5.2 Skill 的组成:指令、资源、工作流、工具编排

理解了 Skill 是为持续影响行为而设计的之后,自然的问题是:一个 Skill 内部应该装什么?要让一个能力包既能改变 Agent 的行为,又能在不同场景下被复用,仅有一段自然语言指令是不够的,它需要承载多种类型的内容。一个完整的 Skill 通常由四种东西组成。

最核心的一层是行为指令:一组告诉 Agent 你应该怎么做的规则。这是 Skill 改变模型行为的主要载体,也是它和工具描述最大的区别。工具描述告诉模型这是一把锤子,它能钉钉子;行为指令告诉模型钉钉子的时候要垂直下压、不要斜着敲。比如一个“Go 编码规范 Skill”的行为指令可能包括:“错误处理必须使用 error wrapping 模式,格式为 fmt.Errorf("context: %w", err)”、“日志使用结构化日志库,不使用 fmt.Println”、“公开函数必须有注释,注释以函数名开头”。这些规则不是在某个时刻被调用,而是在 Skill 被加载的整个生命周期内,持续约束模型生成的每一个 Token。

但纯指令往往不够,很多约束用文字描述远不如直接给一份范例来得有效。这就是参考资源的位置:Skill 可以引用外部的文档、代码示例、配置模板,让 Agent 在需要的时候参考。比如一个“微服务架构 Skill”可能引用一份架构设计文档、一组服务模板代码、一份 API 设计规范。在第二章我们讨论过 Few-shot 的注意力机制,示例对模型的引导效果远超文字描述。参考资源在 Skill 中扮演的就是这个角色:当行为指令说按照团队的服务模板创建新服务,参考资源就是那个具体的模板,Agent 直接看到标准答案长什么样,比看到一段抽象的描述要有效得多。

行为指令解决了怎么做,参考资源解决了参照什么做。但有些 Skill 不只是约束做事的方式,它本身就是一套做事的流程。代码审查不是一个动作,而是一个先看 PR 描述、再看 diff、逐文件审查、出报告的流程;测试驱动开发不是一条规则,而是先写测试、再写实现、再重构的循环。这类场景需要的是工作流定义:明确的步骤、明确的顺序、每一步的目标和产出。一个代码审查 Skill 可能定义了一个完整的审查流程:第一步检查代码风格、第二步检查错误处理、第三步检查安全漏洞、第四步检查性能问题、第五步生成审查报告。工作流让 Skill 从约束升级为剧本,Agent 不再是按规矩做事,而是按剧本演出。

最后还有一层经常被忽略的内容:工作流中的每一步往往需要调用具体的工具。是先跑测试再 lint,还是先 lint 再跑测试?是先读所有文件再统一分析,还是边读边分析?这些工具调用的顺序和组合方式本身就是 Skill 知识的一部分。这就是工具编排:Skill 可以指定在执行过程中应该使用哪些工具、以什么顺序使用。比如一个测试驱动开发 Skill可能指定:先运行现有测试确认基线 → 编写新测试 → 运行测试确认失败 → 编写实现代码 → 运行测试确认通过 → 运行 lint 检查。工具编排把使用哪些工具和按什么模式使用绑在一起,同一组工具,编排不同,做出来的事情完全不同。

Skill 的四个组成要素:指令、资源、工作流与工具编排

把这四层放在一起看,Skill 和它周围那些容易混淆的概念之间的边界就清楚了。

和 MCP 工具的区别在于做事的层次。工具是做一件具体的事,Skill 是以一种特定的方式做事。如果 MCP 工具是锤子、螺丝刀、扳手,Skill 就是木工手册,手册告诉你什么时候用锤子、怎么用螺丝刀、按什么顺序组装。和 System Prompt 的区别则在于作用的时机。System Prompt 是全局的、固定的,在整个对话过程中始终生效;Skill 是局部的、动态的,可以按需加载和卸载。你不会把整个图书馆的书都搬到办公桌上,Skill 让你能够需要哪本书就拿哪本书。

四个组成要素听起来抽象,但落到具体的产品形态上其实非常具体。以 Claude Code 的 Skills 为例:一个 Skill 就是一个目录,目录里有一份 SKILL.md,文件顶部是一段 YAML frontmatter,至少包含 namedescription 两个字段,下面才是给 Agent 看的完整指令正文;同一个目录里还可以放参考资料、模板文件、甚至可执行脚本。

回到本章开头那份团队 Go 编码规范,把它做成一个最小化的 Skill,目录大概长这样:

go-coding-standard/
├── SKILL.md                  # 行为指令 + 工作流
├── references/
│   └── error-handling.md     # 参考资源:错误处理范例
└── scripts/
    └── check-style.sh        # 可执行物:调用 golangci-lint

SKILL.md 本身也很短:

---
name: go-coding-standard
description: Go 编码规范,含错误处理模式、日志格式、命名约定
---

# Go 编码规范

写 Go 代码时请遵循以下约束:

1. 错误处理必须使用 error wrapping 模式:`fmt.Errorf("context: %w", err)`。
   完整范例参见 `references/error-handling.md`2. 日志使用结构化日志库 `slog`,不使用 `fmt.Println`3. 公开函数必须有以函数名开头的注释。

写完代码后,运行 `scripts/check-style.sh` 做最终检查。

这个目录里同时出现了前面讲的四类东西:SKILL.md 正文里那几条规则是行为指令,先写代码、再跑脚本检查的隐含顺序是工作流,工具编排则体现在让 Agent 在最后一步调用 check-style.sh 这个具体动作上;references/error-handling.md参考资源,Agent 在不确定错误处理该怎么写时会被指引去读它;scripts/check-style.sh 则是 Agent 可以直接以 Bash 调起来运行的可执行物。最后这一项让 Skill 跨过了知识和执行的边界,它不只是一份能改变行为的文档,而是一个带可执行物的能力包,这一点在后面讲交互流程时会再次出现。

5.3 渐进式披露:Skill 的核心机制

知道了 Skill 是什么,更重要的问题是:Skill 是怎么工作的?

Skill 如何改变模型的行为

回忆第一章的核心结论:大模型的每一步生成都是 P(下一个 Token | 前面所有 Token),给定前面所有的 Token,预测下一个 Token 的概率分布。上下文中的内容直接决定了这个概率分布的形状。

Skill 的工作原理正是建立在这个基础上。当一个 Skill 被加载时,它的指令和资源被注入上下文窗口。从这一刻起,模型在生成每一个 Token 时,都能看到这些指令。Skill 不是在某个特定时刻被调用的,它通过持续存在于上下文中,改变了模型在整个任务过程中的概率分布

这就是 Skill 和工具的本质区别。工具的描述虽然也在上下文中,但它告诉模型的是你能做什么操作:读文件、搜代码、跑命令。模型看到工具描述后,在需要的时候决定是否调用,调用完就结束了。Skill 告诉模型的是你应该怎么做事:用什么模式处理错误、按什么风格写代码、以什么顺序执行步骤。模型看到 Skill 指令后,不是在某个时刻调用它,而是在整个生成过程中持续受其约束。一个是能力声明,一个是行为约束,它们对概率分布的影响方式完全不同。

举个具体的例子。假设 Agent 正在写一段 Go 的错误处理代码,没有加载任何 Skill 时,模型可能生成:

if err != nil {
    return err
}

这是训练数据中最常见的模式,概率最高。但如果加载了 Go 编码规范 Skill,上下文中多了一条指令:错误处理必须使用 error wrapping 模式,格式为 fmt.Errorf("context: %w", err)。现在模型在生成错误处理代码时,fmt.Errorf 这个 Token 序列的概率被显著提升了,因为上下文中有明确的指令指向它。模型更可能生成:

if err != nil {
    return fmt.Errorf("create user: %w", err)
}

Skill 没有教会模型新的知识,模型本来就知道 error wrapping 模式。Skill 做的是让这个模式的生成概率升高,让其他模式的概率降低。 这和第二章讲的 System Prompt 的作用机制是一样的,只不过 Skill 是动态的、可插拔的。

理解了这个机制,一个自然的问题浮现出来:既然 Skill 通过注入上下文来影响模型行为,那应该把多少 Skill 塞进上下文?全部塞进去行不行?

答案是不行。这引出了 Skill 系统最重要的设计原则:渐进式披露(Progressive Disclosure)

为什么需要渐进式披露

渐进式披露的核心思想是:不要一次性把所有信息都塞给模型,而是在合适的时机披露合适的信息。

为什么不能一次性全塞进去?两个原因:

第一,上下文窗口是有限的。把所有可能用到的 Skill 都加载进去,窗口就满了,留给实际任务的空间就不够了。

第二,更关键的是:信息过多会降低每条信息的影响力。 第二章讲过注意力机制的特性:上下文中的内容越多,模型分配给每条内容的注意力就越分散。如果你同时加载了 Go 编码规范、Python 编码规范、TypeScript 编码规范、安全审查清单、性能优化指南、数据库最佳实践……模型面对这一大堆指令,对每条指令的"遵循概率"都会下降。这就像一个人同时听十个人说话,每个人说的都有道理,但他谁的话都没听清。

想象一个全栈开发者的日常。上午写 Go 后端服务,下午写 React 前端页面,晚上做代码审查。写 Go 代码的时候不需要 React 组件设计模式,做代码审查的时候不需要数据库迁移指南。无用的知识不只是浪费空间,它还会干扰模型的注意力,消耗你的money。

渐进式披露解决这个问题的方式是:在任务的不同阶段,只披露当前阶段需要的信息。 写 Go 代码的时候才展开 Go 编码规范 Skill 的正文;写 React 的时候,新一段任务里展开的是 TypeScript 规范 Skill;做代码审查的时候,再展开审查 Skill。Agent 的能力集随着任务的变化而变化,上下文空间被尽量留给最相关的知识。

但这里有一个值得提前澄清的细节:"按需展开"很容易,"展开之后再收回去"在当前的工程实现里其实并不容易做到。这一点我们紧接着展开。

Skill 动态加载:不同场景加载不同能力集

渐进式披露的两层结构:简介常驻,正文按需

渐进式披露不是一个粗暴的"加载/卸载"开关。落到当前 Skill 系统的真实实现上,它其实是一个非常朴素的两层结构。

简介层:始终在场。 会话一开始,Agent 就把所有可用 Skill 的简介,名称加上一句话描述——一次性放进上下文。在 Claude Code 的 Skills 里,这就是每个 SKILL.md 顶部 YAML frontmatter 里的 description 字段。简介本身很短,几十个 Token,把几十个 Skill 的简介全列出来也不会撑爆窗口。它的作用是让模型知道有哪些能力可用,相当于桌上摆着一份目录。

正文层:按需展开。 简介层告诉模型"有哪些章节",但章节内容默认是合上的。只有当 LLM 根据任务判断自己当前需要 Go 编码规范时,对应那个 Skill 的正文:完整的行为指令、参考资源链接、工作流定义,才会被加载到上下文中。一个 Skill 的正文动辄上千 Token,但因为是按需展开,整个会话期间真正展开过的往往只有两三个。

这两层就是 Skill 渐进式披露在当前工程上的真身:轻量的简介常驻,沉重的正文按需展开。第二章讲过注意力会被无关信息稀释,简介层的设计正是对这个问题的直接回应,它让模型既掌握全局的能力地图,又不为不会用到的能力付出完整 Token 的代价。

谁来决定展开哪一份正文

两层结构回答了披露什么,但还剩下一个问题没回答:到底由谁来决定哪份正文该展开?常见的做法有三种,现实中地位很不一样。

第一种是配置驱动:在项目级配置文件里显式声明这个项目需要哪些 Skill,Agent 按声明无条件加载,不需要 LLM 判断。它对应你大概率已经用过的东西,Claude Code 的 CLAUDE.md、Cursor 的 .cursorrules、Copilot 的 .github/copilot-instructions.md。这些文件的内容和 Skill 行为指令是同一类东西,只是因"项目级信息每次都要,所以省掉了简介匹配那一步,直接装进上下文。它适合作为基线层使用,团队规范、技术栈偏好这类每次都需要的内容,没必要走选择流程。

第二种是LLM 自主选择全量内容:把所有 Skill 的完整正文一次性塞给模型,让它自己决定哪段相关。理论上最灵活,但工程上几乎不可行,上下文会被瞬间撑爆,注意力被稀释,模型在大量内容里也很难稳定挑出该用哪段。这种方式在严肃的生产产品里基本被淘汰,主要作为反面教材存在,用来解释为什么需要简介层。

第三种是当前主流产品采用的简介驱动:也就是上面两层结构本身,简介常驻让模型看见有什么,LLM 根据任务语义决定展开哪一份正文,Agent 收到决定后再去加载完整内容。它把轻量声明和模型选择绑在一起,这是 Skill 系统在工程上真正跑得通的方式

三种方式并不是互斥的,实际产品里通常是分工的:配置驱动负责每次都要的基线信息(项目规范、团队偏好),简介驱动负责看任务而定的能力(代码审查、迁移脚本、安全审计)。两者一起构成了一个会话的完整上下文骨架。

一个延伸:Skill 内部的步骤级披露

到这里讲的都是 Skill 之间的披露:简介常驻、正文按需。还有一个更细的尺度值得提一下:单个 Skill 内部的披露。

设想一个代码审查 Skill,它定义了风格 → 错误处理 → 安全 → 性能 → 报告的五步流程。一种理想形态是,Skill 不在被选中那一刻就把五步的全部细节注入上下文,而是走到检查安全这一步时才把安全相关的详细清单展开,同时把前几步的中间结果压缩或移除。这样每一步都只为当前关心的内容付出 Token,主线流程则始终保持清爽。

这个思路在原理上完全成立,但需要诚实地说一句:它在当前主流 Skill 系统里还没有标配实现。Claude Code 的 Skill 一旦被选中,正文是整段注入的,并没有内置的走到第几步才展开第几步细节的机制。真要做到这种动态披露,通常需要 Skill 自身把流程拆成多个子 Skill,或者借助 Agent 框架层面的上下文压缩、子 Agent 隔离来实现,这一点我们会在 5.4 节讲多 Skill 协作时再回来。

展开之后呢:Skill 正文的生命周期

讲到这里,渐进式披露的展开侧已经讲完了,但还剩一个反方向的问题没回答:一个 Skill 的正文被展开之后,它什么时候、怎么从上下文里消失?

答案可能和直觉不太一样。在当前主流 Skill 系统(包括 Claude Code 的 Skills)里,并没有一个显式的"卸载 Skill"动作。一旦某个 Skill 的正文被注入上下文,它默认就一直待在那里,直到下面三件事之一发生:

第一种是自然挤出:Skill 正文和工具调用结果、对话历史一起竞争窗口空间。当窗口接近上限,框架会按时间或重要性把早期内容压缩、摘要或丢弃。Skill 正文在这个过程中没有特殊待遇,就是一段比较长的历史消息,会和别的历史一起被处理掉。这是当前最常见的默认行为。

第二种是上下文压缩:很多 Agent 框架会在窗口接近上限时主动触发一次压缩,把历史段落摘要成更短的版本。Skill 正文在这一刻可能被压成一句已加载过 Go 编码规范,关键约束:error wrapping、结构化日志……,它没有真的被卸载,只是被有损压缩了。这一过程对用户基本不可见,且其行为表现也并非绝对稳定。

第三种是会话结束:整个上下文被丢弃,所有 Skill 内容一并消失。这是最干净的释放,但前提是任务也结束了。

注意这三种机制都是被动的:没有任何一种是任务从写 Go 切到写 React 时,Agent 主动把 Go Skill 从上下文里删掉。也就是说,前面讲的渐进式披露在工程现实里只完成了一半——展开是按需的,但收回基本不是按需的。这表明,如果你在一个长会话里先后用到了五个不同的 Skill,到后期你的上下文里通常同时躺着这五个 Skill 的全部正文,哪怕其中四个早就用不上了。注意力被稀释的问题在这种长会话末端会比开头更严重。

那有没有真的用完就释放的方案?有,但通常不在单一上下文里实现,而是 5.4 节会讲到的子 Agent 隔离:把一个 Skill 完整地交给子 Agent 在独立上下文里跑,子 Agent 进程结束时整个上下文连同 Skill 正文一并销毁,主 Agent 只收到一份结论。这相当于通过换一个上下文来实现卸载,比在原上下文里删一段内容要干净得多。换言之:单一上下文内的 Skill 释放在工程上还是个未解的问题,子 Agent 是当前最实用的绕过方案。

Agent ↔ Skill ↔ LLM 的交互流程

把渐进式披露放到完整的交互流程中,三者的协作关系就清楚了。下面这套流程不是纯粹的理论推演,它正是 Claude Code 的 Skills 在工程上跑起来的样子,SKILL.md 顶部的 description 字段对应的就是下面要说的简介,正文则对应完整内容。

Agent ↔ Skill ↔ LLM 交互流程

一个完整的交互循环是这样的:

  1. 用户发起任务。 帮我写一个 Go HTTP 服务器,包含错误处理和结构化日志。
  2. Agent 注入 Skill 简介列表。 Agent 把所有可用 Skill 的简介(名称 + 一句话描述)放入上下文,发送给 LLM。注意:此时只有简介,不包含完整的指令和资源。比如上下文中会出现这样一段:go-coding-standard: "Go 编码规范,含错误处理模式、日志格式、包命名约定"security-review: "安全审查清单,含注入防护、认证检查"。这些简介占用的 Token 很少,但足够让 LLM 知道"有哪些能力可用"。
  3. LLM 决定需要哪个 Skill。 模型看到任务描述和 Skill 简介列表,推理出"这个任务涉及 Go 编码和错误处理,我需要 go-coding-standard 的完整内容"。这个决定和决定调用哪个工具的机制完全一样,都是 LLM 基于上下文做出的概率性选择。
  4. Agent 加载完整 Skill 内容。 Agent 收到 LLM 的请求后,从 Skill 库中获取完整的 Skill 内容(行为指令、参考资源、工作流定义),注入上下文窗口。从这一刻起,完整的 Skill 指令持续影响后续所有的 Token 生成。
  5. LLM 在 Skill 约束下生成代码。 模型看到上下文中的完整 Skill 指令:"错误处理必须使用 error wrapping 模式"、"日志使用结构化日志库",生成的代码自动遵循这些约束。
  6. LLM 决定调用工具,Agent 执行。 LLM 决定需要写入文件或运行测试,生成工具调用请求;Agent 负责实际执行工具调用,把结果塞回上下文。
  7. 循环继续,按需加载新 Skill。 如果任务进入新的阶段(比如从写代码进入写测试),LLM 可能再次从简介列表中选择新的 Skill,Agent 把新的完整内容追加到上下文。注意是追加,之前展开过的 Skill 正文通常仍留在上下文里。

这就是渐进式披露的完整机制:先给目录,按需翻开具体章节。 Agent 不会一开始就把所有 Skill 的完整内容塞进上下文,那样会撑爆窗口,也会稀释每条指令的影响力。它只提供一份轻量的菜单,让 LLM 自己决定需要什么,再按需展开。

注意 Agent 和 LLM 在这个流程中的分工:LLM 负责决策(选哪个 Skill、调哪个工具、生成什么代码),Agent 负责执行(加载 Skill、调用工具、管理上下文)。LLM 是大脑,Agent 是手脚。LLM 说我需要 Go 编码规范,Agent 去拿;LLM 说写入这个文件,Agent 去执行。

同时也要注意:Skill 不是命令模型做什么,而是通过改变上下文来影响模型的概率分布。加载了编码规范 Skill,Agent 写出来的代码大部分时候符合规范,但不是保证符合。Skill 让正确的做法更可能被选中,但不能消除错误的做法被选中的可能性。

5.4 Skill 编排:当多个能力包需要协作

简单场景下,一个任务只需要一个 Skill。但真实的工程任务很少这么简单。

"帮我实现这个功能,写好代码,写好测试,然后做一次自审。"这个任务至少涉及三个 Skill:编码规范 Skill、测试规范 Skill、代码审查 Skill。三个 Skill 需要在同一个任务中协作。

多 Skill 协作引出了几个问题。

执行顺序。 三个 Skill 应该以什么顺序生效?是先写代码再写测试再审查(瀑布式),还是写一点代码就写一点测试(TDD 式)?如果由 Agent 自己判断,它可能做出合理的决策,也可能做出不合理的决策。如果由预定义的编排逻辑来决定,灵活性就受限了。

指令冲突。 这是多 Skill 协作中最棘手的问题。你的编码规范 Skill 说函数体不超过 50 行,你的性能优化 Skill 说减少函数调用开销,尽量内联关键路径。当 Agent 在写一个性能关键的函数时,它应该听谁的?

模型面对矛盾的指令时,行为是不可预测的。它可能听先出现的(因为位置靠前),也可能听后出现的(因为注意力权重更高),也可能试图折中,也可能完全忽略冲突。

目前常见的应对策略大致有三种,但每一种都不够优雅。优先级声明最直接:给 Skill 标上安全 > 性能 > 风格之类的优先级,让模型在冲突时优先听级别高的。这条路在原理上成立,但优先级要靠模型自己读出来再主动遵守,仍然是概率性的,不是硬约束。冲突检测走另一条路,在加载多个 Skill 时静态扫描指令文本,发现潜在冲突就提醒用户手动解决;问题是 Skill 指令大多是自然语言,函数体不超过 50 行和内联关键路径这种语义层面的冲突,靠文本扫描很难真正识别出来,多半只能抓到关键词级别的浅层撞车。

听起来最优雅的其实是第三种:作用域隔离:编码规范 Skill 只在写代码阶段生效,测试规范 Skill 只在写测试阶段生效,互相不见面,自然就不会冲突。但这条路在单一上下文里几乎做不到,原因正是 5.3 节末尾那个未解的尾巴:Skill 展开容易、收回难。要让一个 Skill 在某个阶段失效,前提是阶段切换时能把它的正文从上下文里拿掉,而当前主流系统里既没有显式的卸载动作,阶段切换本身也没有清晰的边界信号,多数时候是 LLM 自己读上下文判断走到哪一步了,调度器并不知道。结果就是:编码规范 Skill 的正文进入上下文后,写测试阶段它依然在场,所谓的"作用域"在工程上只是一种愿望。

作用域隔离真正能落地的形态,不在单一上下文里,而是后面要讲的子 Agent:把不同 Skill 交给不同子 Agent 在各自独立的上下文里跑,物理上根本不见面。换句话说,三种策略里只有这一种在工程上能彻底兑现,前提是你愿意把隔离的边界从一段上下文推到一个多段上下文中。指令冲突本质上是一个多约束满足问题,在 Agent 当前的判断能力下,与其指望模型在同一段上下文里把矛盾的指令调和好,不如从一开始就别让它们同时出现。

上下文空间的竞争。 多个 Skill 同时加载,每个都占用上下文空间。这里有一个微妙的权衡:Skill 的指令越详细,Agent 遵循得越好,但占用的空间也越大。在多 Skill 场景下,你可能需要在每个 Skill 都写得很详细和能同时加载足够多的 Skill之间做出选择。实践中的经验是:核心 Skill 写详细,辅助 Skill 写精简。

5.5 设计哲学与边界

在设计 Skill 的时候,有一个根本性的选择:Skill 应该是声明式的还是过程式的?

声明式 Skill 描述目标状态:错误处理必须使用 error wrapping 模式、测试覆盖率不低于 80%。Agent 自己决定怎么达到这些目标。灵活,但不确定。

过程式 Skill 描述执行步骤:第一步读取源文件 → 第二步找到错误处理代码 → 第三步检查是否使用了 error wrapping → 第四步生成修改建议。可控,但僵化。

实践中最有效的 Skill 通常是混合式的:在高层用声明式定义目标和约束,在关键步骤用过程式定义具体操作。比如一个代码审查 Skill:声明式部分定义审查应该覆盖代码正确性、错误处理、安全性、性能、可维护性,过程式部分定义先看 PR 描述 → 再看 diff → 逐文件审查 → 出报告。关键流程是固定的,确保不会遗漏重要步骤;具体判断是灵活的,确保能适应不同的代码和场景。

最后,Skill 有自己的边界,需要诚实面对:

Skill 不能替代模型能力。 如果模型本身不擅长某类任务,再好的 Skill 也帮不上忙。Skill 能做的是引导模型以正确的方式使用它已有的能力,而不是赋予模型它没有的能力。

Skill 的效果是概率性的。 这一点贯穿全章:Skill 通过改变上下文来影响概率分布,但概率不是确定性。在某些边界情况下,模型仍然可能生成不符合 Skill 指令的输出。

Skill 需要持续维护。 编码规范会更新,架构模式会演进。如果 Skill 的内容和实际需求脱节——编码规范更新了但 Skill 还是旧版的,Agent 就会按照过时的规范写代码。这种Skill 腐化是渐进的、隐蔽的,需要定期审查和更新。这点我们会在后续的章节中重点探讨。


回顾一下我们在卷二中走过的路。第三章让 Agent 能"做事"了,第四章给了它标准化的工具,本章给了它做事的方式:Skill 通过渐进式披露,在正确的时机向模型注入正确的知识,改变它的行为模式。

但 Agent 的能力栈越完整,一个结构性的矛盾就越突出:一个 Agent 承担的角色越多,它的上下文就越拥挤,工具描述、Skill 指令、任务历史、中间结果,全部挤在同一个窗口里。它的决策空间越大,做出正确决策的概率就越低。这一矛盾并不会随着模型能力的提升而自动消失,因为它是上下文窗口有限性的直接后果。