以规格驱动的编码智能体开发。
在智能体工作流里,一份规格只有在「有东西能凭它把构建判失败」时才配占一个位置;否则你只是加了一份评审者会略读、而智能体会换个说法背给你、以此证明自己读懂了的文档。这件事在智能体身上比在人身上更要紧,原因在于:智能体写的计划、生成的测试、产出的代码,全都源自它对任务的同一次理解——所以一旦那次理解是错的,每一件产物都与其他每一件产物彼此印证,而评审就这么过了。一份规格值得写的程度,恰好等于它有多大程度来自那个循环之外、并且也能从循环之外被核查。
循环没法检查自己,而这就是全部论点。
编码智能体的内层循环在自我纠错上是真的强:写补丁、跑测试、读失败、再来一次。它做不到的,是察觉自己解的是另一个问题,而不是你手上的那个。它的测试编码的是它的理解。它的计划复述的是它的理解。它的总结准确地描述了自己造出来的东西——对照的却是一份没人核查过的理解。循环收敛了,收敛到错的靶子上,而且每一个内部信号都是绿的。
这正是「智能体写了测试而且都过了」比听上去要弱的原因,而它与补丁生成与测试里列举的「抖动与过拟合」是不同的弱点。那些是判据不够严格的失败,这里是判据不够独立的失败:同一个源头,同一个盲区。
一份规格从外部提供了判据。它在智能体动手之前,由一个对结果负责的人写下,讲的是系统必须做到什么,而不是怎么做。这样一来,智能体的理解才有一个可供它「弄错」的对照物。这份文档的活儿就只有这一件,而下面每一条规矩都是从它推出来的。
要测你的规格有没有在干这份活儿:把实现删掉,在一个干净的会话里把规格交给第二个智能体。如果拿回来的东西在行为上等价,这份规格就是承重的。如果拿回来的东西在要紧之处有微妙不同,那你手上的只是一份对你已有代码的描述,它什么也拦不住。
规定可观察的行为;其余的一切都该待在规格以外的地方。
规格最常见的失败方式,是被实现细节塞满。它读起来很顺,智能体乖乖照办,而它已经悄悄变成了一份计划——一件无法与代码相抵触的产物,因为它就是代码,只不过写成了散文。
该放进去的是:
- 可观察的行为。输入、输出,以及两者之间的映射,写法要让一个看不到源码的人也能判断它是否成立。
- 边界上的契约。端点形态、schema、错误码、线上格式、事件载荷——那些别人的代码依赖着、而一次重构绝不能悄悄改掉的东西。
- 不变式。每次操作之后必须为真的东西。这些是文档里价值最高的几行,因为它们约束住了作者从未设想过的那些实现。
- 错误与边界行为。重复提交时会怎样、空列表时会怎样、事务中途超时会怎样、调用方没有某项权限时会怎样。智能体在这里系统性地弱,因为寻常的训练数据里,顺利路径占比过高。
- 明确的非目标。收益最高、而人人都省掉的那一节。一个手边就有一项看似合理的相邻改进的智能体,一定会顺手把它做了;而「不要碰认证中间件」这一行,第一次拦住事故时就把整份文档的成本赚回来了。
- 验收标准。那份可供核查的清单——见下一步。
不该放进去的是:文件名、类层次、算法选择与库的挑选。那些是智能体的活儿;更要紧的是,当它发现代码库与你脑中的模型并不一致时,你希望它能自由地改主意——这正是编码智能体架构里描述的那份长处。同样要排除的是:任何你核查不了的东西。像「代码应当易于维护」这样的标准不是一条弱标准,它压根不是标准;它消耗评审注意力,却什么也没约束住。
每一条验收标准都要配一个检查项 id,或者一个具名的人。
这一步把「规格驱动的开发」与「长得像规格的仪式」分开。逐条过一遍验收标准,给每一条指派一个可执行的检查——一个测试名、一个脚本、一条 lint 规则、一次迁移核验查询。做不成可执行的那些标准,就在文档里写上将由谁人工核验。
AC-1 Duplicate submit within 60s returns the original receipt
-> test_idempotent_submit_returns_original
AC-2 Expired token yields 401 with code=token_expired, never 500
-> test_expired_token_401
AC-3 No endpoint returns a raw provider error body
-> lint: forbid-provider-passthrough
AC-4 Migration is reversible on a populated table
-> scripts/verify_down_migration.sh
AC-5 Onboarding copy reads clearly to a first-time user
-> MANUAL: review by product owner
这张表会掉出两个数字,两个都值得盯:有多少条标准配上了检查,以及还有多少条人工项没销。一份十二条标准里有九条什么也对不上的规格,并没有在驱动开发;而现在你是看见了这件事,而不是隐约感觉到。
次序和映射一样要紧。在智能体动手之前写好标准,再让它去写满足这些标准的测试——这个顺序把「意图」留在人手里,把「劳力」交给自动化,而正是这道分工让测试生成智能体用起来是安全的。把顺序颠倒过来,你得到的就是一堆断言「实现碰巧是怎样」的测试。
计划是另一件产物,寿命也不同——评审它,然后扔掉。
智能体会产出计划,而计划是有用的。它们只是不是规格,而把两者混为一谈会把两者都赔进去。
规格由人撰写,陈述意图,与代码一同版本化,并在这次改动之后继续存在。计划由智能体撰写,陈述路子,在工作合并的那一刻就该丢掉。它们发挥价值的时刻也不同:规格抓的是「你做错了东西」,计划抓的是「你正要用错的方式去做它」。
评审计划是整条工作流里杠杆最高的五分钟,因为那是最后一个便宜的时刻。一份说「先重构会话存储,再加那个字段」的计划,是在免费告诉你有一场你并不想要的两天评审正在路上——而修正它只需一句话,而不是等活儿干完之后再打回一份 diff。这与先规划后执行之所以值得多花一次往返,是同一套经济学,只不过这次用在回路里的人身上,而不是模型身上。
两条实用规矩。别把计划留在仓库里——一份被合并进去的计划会变成一份过期文档,而下一个智能体会把它当作当前设计来读。以及,当计划与规格相抵触时,那是一个「停下来对齐」的信号,而不是一处交给智能体自行消解的不一致;这通常意味着规格有歧义,而此刻消除这份歧义还很便宜。
规格漂移才是那个失败模式,而一份过期的规格比没有规格更糟。
文档会腐坏。读者是人的时候,这还扛得住——人会动用判断,也会注意到这个文件上次被碰是两年前。读者是智能体的时候就扛不住了,因为智能体读一份过期规格时的笃定程度,与读一份新鲜规格时一模一样;它会把文档所描述的行为重新生成出来——把一次没人写下来的、刻意为之的改动给撤销掉。过期的规格不是中性的,它是一条主动要求你回退的指令。
四条规矩能让它保持诚实:
- 规格住在仓库里,紧挨着它所管辖的代码。不在 wiki 里、不在工单里、不在智能体读不到的文档工具里。不在磁盘上就不在上下文里,不在上下文里就什么也改变不了。
- 行为改动与规格改动在同一个 pull request 里发。就用你强制其他任何「协同修改」规则的办法来强制它:一条 CI 检查,凡是动了某个模块却没动它规格的 diff 就标出来,要求一次显式豁免。豁免正是重点所在——它让例外可见,而不是沉默。
- 给每份规格标上日期与适用范围,并删掉你不打算维护的那些。一份管着一年没人碰过的模块的规格,是一项没有对冲收益的负债。删掉它是对仓库信噪比的一次实打实的改善,而且只需一分钟。
- 让智能体去标记矛盾,而不是去消解矛盾。「规格说的是 X,代码干的是 Y」是智能体在这里能产出的最有价值的输出,而它只花你一行指令。否则智能体会默默挑一个——挑的那个会是能让测试通过的那个。
对于跨会话、跨智能体或者跨周的工作,规格同时也是交接件。周四接手这个任务的后台智能体,对周二那场对话毫无记忆;规格是唯一持续存在的上下文——这也是这套纪律在后台编码智能体与大规模迁移上回本最快的原因。
搞清楚它在哪儿划算,并在不划算的地方停手。
规格驱动开发是一项有回报的额外开销,而回报并不均匀。到处都用就变成了仪式,团队会反感,然后它会连同那些本来奏效的场景一起被抛弃。
它划算,当:
- 是全新开发。没有既有行为可供推断,于是规格是唯一可用的判据。这里这套技法几乎是必需的。
- 契约比实现活得更久。公开 API、事件 schema、文件格式,以及任何别的团队要消费的东西。
- 改动很大或者拖得很长。迁移、重写,以及任何跨会话或跨智能体的工作。
- 做错了代价很高。资金流转、权限、数据删除、受监管的行为——这些地方,一个看上去合理却错了的实现,不会靠用户投诉被抓出来。
它不划算,当:
- 一个失败的测试本身就是规格。对一次缺陷修复来说,复现用例是一份更好、更便宜、可执行的意图陈述。围着它再包一份文档,什么也没加上。
- 工作是探索性的。如果你还不知道「对」长什么样,事先写下的规格就是一次猜测,而它会把智能体往你猜错的方向上箍。先做原型,再为你决定留下的那部分写规格。
- 改动比文档还小。改名、依赖升版、文案微调。
有一个反模式值得直说:用规格的仪式感替代一套测试。一个有着漂亮规格却测试单薄的仓库,处境比一个没有规格但测试很好的仓库更糟,因为那些规格制造出一种没有任何东西在强制的「覆盖感」。规格的存在是为了说清该检查什么;套件才是真的去检查的那一方——而「从前者推导出后者」的这套纪律,与上一层的评测驱动的智能体开发是同一套。
如果只做一件事,就在智能体动手之前,给每一条验收标准旁边写上一个检查项 id。标准由你自己用可观察的措辞写下,并配一份明确的非目标清单;把每一条对到一个测试名、一个脚本或一位具名的人;让智能体去写满足这些标准的测试,而绝不让它写标准本身。就这一个次序,决定了这份规格是一个独立的判据,还是一份「对最后造出来的东西的总结」;而它也是挡在你与这样一个 pull request 之间的唯一一样东西:计划、测试、代码与总结彼此完全一致——并且一致地描述着一个没人要过的功能。然后让它活着:规格与行为在同一个 PR 里一起改,并删掉任何你不愿维护的规格,因为智能体遵从一份过期文档的忠实程度,与遵从一份当前文档一模一样。