生成式软件工程
搜索
开始阅读/第 2 部分 · 把工具用起来/第 6 章

技能与配置

你可能已经对 AI 说过第五遍「先跑测试,再说改好了」。这一章做的事,是把这句话写进一个文件。

这一页讲什么
  • 上一章解决了「够得着」,为什么还需要另一样东西解决「会做事」;
  • 一份技能文件长什么样,头部元信息与正文各写什么;
  • 什么时候该写技能,为什么门槛是「同一件事说到第三遍」;
  • 可判定的规则与检查点,以及配置文件的层级关系与常见写错法;
  • 钩子与子智能体各自解决什么,代价在哪里。

一、够得着,与会做事,是两件事

上一章讲的协议让智能体能连上外部系统。连上之后还有一层麻烦:工具在手上,它仍不知道你们这一套做法。语言模型极其擅长照着长篇流程做事,把这种能力暴露出来,是任何一个运行框架都要提供的东西。

翻成具体场景:你给了它待办系统的工具,它知道怎么新建一条任务;它不知道你们班的做法是「先复现问题、再写一个会失败的检查、再改、最后把改动关联到原来那条任务上」。这套做法不在工具里,在人的脑子里。它每次都要你重新讲一遍,讲漏一步,它就在那一步上自由发挥。

这层关系可以说得很干脆:有了工具不等于会做事。技能(Agent Skill)就是用来补这一层的,它把「某类任务该按什么顺序做、做到什么程度算完」写成一份文件,智能体在需要时自己读进来。

生活类比 新同学进社团,学长先给他开通各种账号:借教室的系统、打印机的账号、群相册的权限。账号开完,他仍不知道怎么筹办一场活动:先找谁审批、海报什么时候贴、场地几点能进。 账号是上一章那套协议,那本《活动筹办手册》才是技能。只给账号不给手册,他会自由发挥;只给手册不开账号,他一步也走不动。两样都要有。

二、技能文件长什么样

一个技能就是一个文件夹,核心是一份叫 SKILL.md 的文本文件。这个后缀说明它是 Markdown 格式,也就是用井号表示标题、用短横线表示列表的那种纯文本,记事本就能打开编辑。文件夹里还可以放脚本、参考资料和模板,那些属于可选部分。

flowchart TB
  F["技能文件夹"] --> S["SKILL.md 必需"]
  F --> P["scripts 可选 放可执行脚本"]
  F --> R["references 可选 放详细资料"]
  F --> A["assets 可选 放模板与图片"]
  S --> Y["头部元信息 写名字与用途"]
  S --> B["正文 写步骤与检查点"]

文件开头是一段被三条短横线夹起来的元信息,这种写法叫 YAML 头部(frontmatter)。里面至少要有两个字段。name 是技能的名字,最长 64 个字符,只能用小写字母、数字和连字符,不能以连字符开头或结尾,也不能出现两个连续的连字符,并且必须和文件夹名一模一样。description 是用途说明,最长 1024 个字符。

正文部分没有固定格式,一个实用的模板把它分成两段:一段写「什么时候该用这个技能」,一段写具体指令,例如分步骤的做法、这个领域的约定、遇到不清楚的需求时该向你确认。你完全可以按自己的习惯改,只要做到一件事:读的人照着做,能做出你要的结果。

---
name: weekly-report
description: 按固定格式生成每周学习周报。当我说「写周报」「这周总结」或者到了周日晚上时使用。
---

# 每周学习周报

## 什么时候用
- 我说「写周报」或「这周总结」时
- 周日晚上整理本周记录时

## 步骤
1. 读 notes/ 下面本周一到周日的记录文件,缺哪天的先告诉我,不要猜。
2. 按「本周完成、遇到的问题、下周计划」三段写,每段不超过 5 条。
3. 数字全部来自记录文件,记录里没有的数字一律不写。
4. 写完把提到但没做的任务单独列一行,标上「待确认」。

## 完成标准
- 三段齐全,每段都有内容
- 每个数字都能在记录文件里找到出处
- 待确认清单已列给我看

注意 description 那一行。它是整套机制里最关键的一行,因为智能体平时只加载这一行,靠它判断「现在这个任务要不要读那份文件」。写得含糊,例如「帮助处理报告」,它永远不会触发;写得具体,把你说过的原话也塞进去,命中率立刻不一样。

关键结论 技能省地方的办法是三层按需加载。启动时只加载每个技能的名字和用途说明,每个大约几十个词元;你的话命中某条用途说明之后,才读那份文件的正文; 正文里若写着「遇到某类情况再读某个文件」,那时才去读附件或者执行脚本。装 100 个技能,平时也只多占几千个词元。

三、什么时候该写一个技能

没有一个精确的计数门槛,但有一个很好用的观察方法:去翻自己的对话记录,找三种时刻。你打断它的时刻,例如「等等,先别改,先跑一下测试」;你纠正它的时刻,例如「不对,我们项目里日期统一用协调世界时」;你重复说同一句话的时刻,例如第三次说「记得先写一个会失败的检查」。

这三种时刻就是技能素材。一句话被你说到第三遍,说明它属于流程,应该从对话里搬进文件。这个「三次」算不上硬指标,是从上面那个观察方法里推出来的可用门槛:说一遍可能是特例,说两遍可能是巧合,说三遍就是你每次都想要的东西。

反过来说,有些事不值得写成技能。一年做一次的活,写文件的时间比它带来的收益长;判断标准含糊、你自己也说不清的活,写成文件只会把含糊固定下来。判断的方法很简单:这件事你上个月做过吗,下个月还会做吗,两次做法的步骤一样吗。三个问题都是「是」,就值得写。

生活类比 寝室里贴一张「值日表」和贴一张「大扫除流程」是两回事。值日表管谁来做,大扫除流程管先擦窗还是先拖地、插座要不要断电、垃圾袋放哪。 前者写一次管一学期,后者才是每次都能少吵一架的东西。技能文件是后者:它记的是顺序和检查点,谁来做留给别的地方。

四、写判断标准和检查点

技能文件里最该反复打磨的地方,是「怎么判断做对了」。一个从没执行过的技能只是草稿,一份没有被跑通过的文件,和一份写得漂亮的愿望清单没有区别。

具体到写法,可以对比两组句子。下面这一组是要求,看起来很多,实际上没法判断做没做到:

换一组,每条都能看一眼就判断是或否:

后面这种叫可判定的规则:看一眼代码就能判断是否违反。要求写不成可判定的形状,智能体就只能猜,猜错的概率不比不写低多少。

检查点比标准再往前一步,它说明在哪一步停下来核对。例如「改完后端先跑一遍测试,全部通过再动前端」「截图要包含整个页面,只截局部不算」「测完把自己启动的进程关掉,不要按进程名去杀」。最后这一条尤其值得记:按进程名去杀别的程序,会把别人开着的服务一起杀掉,也会把你自己刚启动的证据一起清掉。

常见坑:把技能写成一堆要求 十条「要注意质量」抵不上一条「参数超过 3 个改成对象参数」。写技能的时候逐条问自己:这条能不能判成是或否? 判不了,就换成能判的形式,或者干脆删掉。写不成的规则留在文件里,只会让读的人多花时间,还可能把真正重要的三条淹掉。

五、配置文件:放在哪、写什么、为什么比口头交代可靠

技能解决「某类任务怎么做」,配置文件解决「这个项目是什么」。刚装好的智能体对你的项目一无所知,它用的是训练时见得最多的做法:装它最熟悉的那个包管理器,把文件放在它觉得顺手的目录,检查敲的是它猜出来的命令。你们项目原本的约定,一条都没被遵守。

配置文件就是一个普通的纯文本文件,最常见的名字是 AGENTS.md 和 CLAUDE.md,放在项目根目录,每次会话自动加载,和 README.md 并排。README 写给人看,介绍项目、贴截图、讲怎么上手;配置文件写给智能体看,讲命令、约定、边界和坑。

里面写四块就够。

块写什么解决什么麻烦
常用命令安装、启动、测试、检查,四条能直接复制的完整命令它不会用这个项目
目录地图三到五个重要文件夹,每个后面一个说明它不知道该去哪找文件
约定命名、目录习惯、必须配的东西产出不符合规范
安全与完成标准不许读的文件、不许改的配置、怎样算做完它不知道边界在哪
# AGENTS.md

课程作业管理项目,只用 pnpm。

## 常用命令(在仓库根目录运行)
- 安装依赖:pnpm install
- 启动开发:pnpm dev
- 单元测试:pnpm test
- 类型检查:pnpm typecheck

## 代码库地图
- src/pages 页面
- src/components 组件
- tests 测试

## 约定
- 组件放在 src/components 下,一个文件一个组件
- 新增页面必须配一个测试

## 安全
- 不要读取或打印 .env 文件
- 不要修改 .github/workflows

## 完成标准
- pnpm typecheck 与 pnpm test 全部通过

为什么要写「只用 pnpm」这种看起来很小的事?智能体的默认选择来自训练数据里最常见的做法:看见一个前端项目,它可能顺手就敲 npm install。你不写清楚,它就去猜;猜错了就要多一轮「报错、纠正、重来」,严重的时候还会装出两套依赖,把项目弄乱。

写进文件,比每轮口头交代可靠在哪

口头交代只活在当前这一轮对话里。文件不一样:它默认进入每一次新会话,是所有定制手段里唯一一个「不用你开口就在场」的东西。它还多了三个好处:能被同学看见,能留版本记录,能被评审和修改。

但文件有一个硬限制:它占的是模型的指令预算。有人根据一组实验给出的经验法则是,前沿的思考型模型大约能稳定遵循 150 到 200 条指令,而外壳的系统提示本身已经占掉约 50 条。另一项研究用 500 条指令测了 20 个模型,即使最好的模型准确率也只有 68%。你写进去的每一条,都在和别人抢位置。

来源对根配置文件的长度建议
课程材料300 行以内
一家模型厂商根文件 200 行以内,指定一个负责人
一家工程团队业界共识 300 行以内,他们自己的根文件不到 60 行
一款命令行工具合并后的项目文档默认上限 32 KiB

还有一个容易踩的坑:智能体读到配置文件时,同时会收到一句类似「这些内容可能与你的任务无关,除非高度相关否则不要理会」的提醒。文件里跟当前任务无关的段落越多,模型越可能把整份文件当成「大概无关」而淡着处理,连其中真正重要的规则一起漏掉。写进去的每一行都在向模型证明自己值得被读。

关键结论 一条好规则的判断标准是可验证:人或脚本能判断它有没有被遵守。 如果一条规则完全可以被工具检查(格式、缩进、类型),它就该交给格式化工具和检查工具去执行,写进配置文件只会占掉本该留给别的内容的位置。

六、层级关系与常见写错法

配置文件不止一层,越往下越具体。以一款常见的编辑器智能体为例,它从四个地方读取。

层级位置谁能看到
托管策略系统统一安装目录下的配置文件组织内所有人,个人设置无法排除
用户级你的用户目录下的配置文件夹只有你,对你所有项目生效
项目级仓库根目录的配置文件全队共享,随项目一起保存
本地级仓库里的本地配置文件只有你,只在这个项目,不提交

加载顺序是从文件系统根一路往下到你的当前工作目录,逐级拼接,越靠近工作目录的文件越靠后读,也就越具体。工作目录以下的子目录文件不在会话开始时就加载,等到智能体真的去读写那个目录时才读进来,这叫按需加载。当前这一条请求的优先级最高,常驻指令排在它后面。

什么时候才需要子目录配置?只在目录之间真有差异的时候:不同的包管理器、自动生成的代码、单独的部署审批。子目录文件只写与根文件不同的部分,重复根规则等于白白占用窗口。

常见的写错法有四类。

  1. 写成一堆互相冲突的要求。文件前后两段,一段说只用 pnpm,另一段从别处抄来的示例里写着 npm install;一处说提交前必须跑测试,另一处说测试太慢可以先跳过。面对冲突它只会随机挑一个执行,你看到的结果时好时坏。
  2. 写得太长。把只在某个流程里用到的步骤搬到技能文件里,用的时候才读;把能被工具检查的规则交给检查工具;把领域知识放进文档目录,根文件里只留一句指路的话。
  3. 写进会变的东西。密钥、过时的目录清单、这周的任务笔记。前两样会让人误判权限边界,第三样下周就失效。
  4. 用一键生成命令把整份文件自动产出来就直接采用。配置文件是整个体系里影响面最大的一处,代码里一行坏东西影响一处实现,配置文件里一行坏东西影响每一次会话的调研、计划和实现。

七、钩子与子智能体:让检查自动发生,让脏活在别处做完

钩子是在固定事件上自动运行的一段命令或脚本,由外壳执行,跟模型记不记得、愿不愿意没有关系:事件发生,脚本就跑。事件的种类按节奏分三档:每个会话一次的有会话开始与会话结束;每轮对话一次的有你按下回车、它准备结束回答;每次工具调用一次的有调用之前与调用成功之后。

脚本用退出码表态:返回 0 表示放行,返回 2 表示「停下,不要做这件事」,其他非零值只报个错,流程照常往下走。最常见的两个用法:写完文件之后自动跑一次格式化,免得之后在自动检查里报错;准备结束回答之前跑一遍检查,不通过就返回 2 让它接着改。后一个用法要防死循环——脚本开头先看输入里表示「这一轮已经是因为钩子才继续的」那个字段,如果是,就直接放行。

生活类比 宿舍楼下贴一张「请随手关门」,那是一条提醒,总有人忘。刷卡机是钩子:不管你记不记得,门都会锁上。 提醒的效果取决于对方当时的心情,钩子的效果取决于事件有没有发生。

子智能体是主智能体干活过程中派生出来的一个分身。它有自己的上下文窗口,可以自己去搜文件、读代码、跑命令,干完之后只把最后一段结论交回主会话。它解决的问题是上下文被中间过程塞满:让主智能体去「找出所有调用旧登录函数的地方」,它可能翻三十多个文件、跑十来条命令,这些过程对最后那句「一共 12 处,分布在 3 个模块」没有任何贡献,却会一直占着窗口。

写法是往约定好的目录里放一个 Markdown 文件:开头用三条短横线夹着的元数据(名字、用途说明、可用工具、用哪个模型),后面的正文就是它的系统提示。其中用途说明最关键,主智能体靠它决定什么时候自动派活,所以要写清「做什么」加「什么时候用」。子智能体可以并行运行,最多嵌套 5 层。代价也要说清:它从零开始收集上下文,会拖慢速度、多花 token;主智能体只看得到它的结论,结论错了你很难察觉。重要的结论,最好要求它附上证据——文件路径、行号、命令输出。

常见坑:安全提醒 钩子脚本以你自己的用户权限运行任意命令。只启用你亲自读过的钩子;项目里别人提交的钩子脚本,合并之前要像代码一样看过一遍。 钩子与权限规则是两层独立的东西:钩子在命令执行前拦,权限规则在更前面就不允许这个动作。
现在就做 十分钟,写你人生第一份技能文件。
① 打开你的 AI 对话记录,往上翻,找一句你对它说过两遍以上的话。找不到就现在问它一个问题,看自己会不会补一句「记得先……」;
② 在桌面新建一个文件夹,名字只用小写字母和连字符,例如 check-homework;
③ 用记事本在这个文件夹里新建 SKILL.md,注意保存时把「文件类型」选成「所有文件」,否则会存成 SKILL.md.txt;
④ 文件里写三部分:三条短横线夹着的名字与用途、三步做法、一条完成标准。用途那一行把你说过的原话抄进去;
⑤ 保存,然后照着它写的内容自己动手做一遍。卡住的地方就是需要改的地方。

自测

1. 有了协议接上工具之后,为什么还需要技能文件?
协议解决「能调用哪些外部能力」,技能解决「该按什么顺序完成一类任务」。工具连上了,那套做法仍只在你脑子里,它每做一次都要你重讲一遍。语言模型很擅长照着长篇流程执行,把这些流程打包成按需加载的文件,正是运行框架该提供的机制。两者是互补的:协议是账号权限,技能是操作手册。
2. description 为什么是整套机制里最关键的一行?
启动时只加载名字和用途说明,正文被触发时才读。智能体判断「要不要用这个技能」,唯一依据就是那一行。写得含糊,例如「帮助处理文档」,它永远命中不了;写得具体,把用户会说的原话、会出现的场景都放进去,才能在合适的时刻被读到。
3. 「代码要优雅」这条规则为什么对智能体没有用?
因为它不可判定:看到一段代码,没人能给出是或否的结论,智能体只能猜。换成「函数参数超过 3 个时改成对象参数」,看一眼就知道违不违反。写规则的诀窍是每一条都能被判成是或否,含糊的标准等于没写。
4. 配置文件里为什么不该写「每次编辑后都跑格式化」?
因为这句话只是给模型的一个请求,模型可能记得,也可能忘;长会话、模糊场景、或者文件里夹带的提示注入都可能让它违反。这类「必须每次发生」的事应该交给钩子:事件触发,脚本执行,不依赖模型是否听话。
5. 子智能体和技能都能按需加载,区别在哪?
区别在内容进了谁的窗口。技能的正文加载进主会话,流程当着你的面一步步展开,你随时能插话调整;子智能体在独立的上下文窗口里跑,主会话只收到最后一段结论,中间过程你看不到。需要你盯着每一步的流程用技能,会产生大量无用中间结果的旁支任务用子智能体。