AI Agent 团队的 Spec-First 工作流
大多数团队并没有"决定"成为 AI agent 团队。一个开发者先用上了编码 agent,另一个把 agent 接进了 CI,一个季度之后,一半的 pull request 已经是机器写的。代码产出的速度,超过了任何人决定"该做什么"和验证"做出来了什么"的速度。Spec-first 工作流解决的正是这个失衡:把人力集中到 agent 无法替代的两个环节——决定意图,和评审证据。
spec.md、tasks.md、验收标准和证据要求时,先下载这套入门模板。
瓶颈换了位置,工作流却没换
在编码 agent 出现之前,交付流程里最慢的一步是写代码,所以流程都围绕开发者吞吐量优化。有了 agent,实现几乎免费,另外两步变成了约束:精确决定要做什么,以及确认结果符合这个决定。沿用旧工作流的团队会感受到一种很具体的痛:PR 堆积的速度超过评审速度;两个 agent 用互不兼容的方式解决了重叠的问题;某个只存在于一名开发者聊天窗口里的提示词,产生了一个没人批准过的产品决策。
问题不在 agent,而在于团队的意图没有存在于任何 agent 或评审者可以核对的地方。聊天记录不可评审、不可复用,也没法拿来和最终 diff 对照。Spec-first 的回答是让意图变成持久的东西:在任何 agent 写代码之前,团队先写好一小组 artifact,之后的每个阶段都拿自己的产出和这些 artifact 核对。
五阶段工作流一览
下面这个工作流不依赖任何具体 agent。Claude Code、Cursor、Copilot 编码 agent 或者混用都可以,因为契约存在于文件里,不在任何工具的记忆里。
| 阶段 | 负责人 | 产物 | 通过门禁 |
|---|---|---|---|
| 1. 需求进入 | 功能负责人 | spec.md 规格包 | 目标、非目标和验收标准经人批准 |
| 2. 任务拆分 | 技术负责人或规划 agent | tasks.md | 每个任务都有写入范围和可验证的结果 |
| 3. 并行实现 | 一个任务一个 agent | 分支或 worktree diff | diff 不越出任务的写入范围 |
| 4. 证据收集 | 同一个 agent | evidence.md | 规格中点名的测试、日志和检查存在且通过 |
| 5. 人工评审 | 人类评审者 | 关联 artifact 的 PR | 按清单对照标准评审,而不是凭感觉 |
阶段一:把工单变成规格包
下游一切都继承这一步的质量,而这恰恰是团队最想跳过的一步。输入通常是一张"让支持管理员可以重试失败的导出"这类工单。输出是一页规格包,固定四件事:用可观察的语言描述的目标、划定边界的非目标、测试可以检查的验收标准,以及评审者将要索取的证据。
在选择哪个 agent 来实现之前就把它写完。规格包是产品歧义被"人"消解的地方——否则歧义会泄漏成 agent 的自行假设。如果你从没写过规格包,规格包生成器可以从一段自然语言描述生成骨架,规格包指南逐节讲解写法。最重要的纪律是非目标:"不改 schema、不动邮件模板、不加新依赖"这一行,直接堵住了 agent 最常见的三种"好心扩权"。
阶段二:把规格拆成有边界的任务
规格包直接塞给一个 agent 当提示词还是太大。agent 工作的可靠单位,是一个 agent 一次能做完、一个人一次能审完的任务。拆分是 agent 团队里技术负责人真正的价值所在:怎么拆,决定了什么能并行、什么必须串行。
tasks.md 里的每个任务除了描述还需要三个字段:列出 agent 可以修改哪些文件的写入范围;从规格包复制或派生的验收标准;点名哪个测试或检查能证明完成的证据要求。SDD 任务计划模板提供了现成的结构。
任务 2/3:新增重试端点 写入范围: - src/exports/retry.ts - src/exports/retry.test.ts 依赖: 任务 1(重试状态列)已合并。 验收: - 对失败导出 POST /api/exports/:id/retry 返回 202,重复调用返回相同 retry_id - 对非失败状态的导出重试返回 409 证据: - test: export_retry_idempotent - test: export_retry_wrong_state_conflict
共享文件的任务绝不能并行。如果两个任务都要碰 billing/index.ts,要么合成一个任务,要么用依赖行显式串行。agent 之间的合并冲突不是工具问题,是任务拆分问题。
阶段三:让 agent 在写入范围内并行
任务有了边界,并行就变得安全而无聊——这正是目标。给每个 agent 一个任务、对应的规格包,以及一个隔离的分支或 git worktree。任务里的写入范围就是执法线:agent 改了范围外的文件,diff 在进入评审前直接拒绝,无需讨论。现代 agent 大都能遵守这样的指令:"只修改写入范围列出的文件;如果任务需要动其他文件,停下来报告,而不是直接改。"
这条"停下来报告"比看上去重要得多。agent 最昂贵的失败不是实现错了,而是一个安静的决策——多加一列、改个函数名、引入一个新依赖——埋在一个看起来很合理的 diff 里。范围强制把这些安静的决策变成响亮的问题,人几秒钟就能回答。
阶段四:先交证据,再请求评审
agent 声称"工作完成"没有任何价值;让完成变得可检查的是 artifact 链。在任务的 PR 打开之前,同一个 agent 要填写 evidence.md:新增了哪些点名的测试及其通过输出、每个测试覆盖哪条验收标准,以及规格包要求的日志、截图或契约检查。SDD 证据日志模板让所有 agent 的证据格式一致,评审者不用每个 PR 重新学一遍。
证据也是"幻觉式完成"消亡的地方。一个说"所有测试都通过"却拿不出 export_retry_idempotent 运行输出的 agent,就是没做完。采用测试证据门禁的团队反馈:这道门禁拦下的坏合并比代码评审本身还多,因为它是机械的——点名的测试要么存在且通过,要么不是。
阶段五:按清单评审 PR
人工评审是整个系统里最稀缺的资源,所以只把它花在前面门禁查不了的事情上:diff 是否符合意图?有没有混进没人要求的东西?AI 编码 PR 评审清单为这一遍提供结构:评审者确认 diff 没有越界、每处改动都能映射到某个任务、抽查证据,并专门找那些没有任何任务要求过的改动。
artifact 越好,评审越快。当评审者能把标准、测试和 diff 放在一起看时,批准一个有边界的任务只要几分钟。三者缺任何一个,评审就退化成从代码反推规格——那正是这个工作流要消灭的工作。
实战示例:一个功能,三个 agent
一次真实运行的形态如下。功能:"支持管理员可以重试失败的数据导出。"规格包固定了策略(仅管理员、同一时间只能有一个重试、不重新设计 schema)和证据标准(幂等性测试、审计日志条目、导出文件格式不变)。
spec: exports/retry-failed-export/spec.md
tasks:
1. 重试状态列 + 迁移 agent A,范围: db/migrations, models/export.ts
2. 重试端点 + 幂等性 agent B,范围: src/exports/retry.ts + 测试
3. 管理后台重试按钮 agent C,范围: console/exports/*.tsx + 测试
串行约束: 任务 1 先合并;任务 2、3 随后并行
评审: 一个人,三个 PR,每个走一遍清单,
整个功能评审总耗时约 40 分钟
这次运行里最有教学价值的失败:agent B 好心地在模型里加了一个 retry_count 列——超出了它的写入范围。范围检查在评审前就标记了它。团队判断这个计数确实有用,把它写进规格包作为后续任务,同时拒绝了这次越界修改。这就是工作流在发挥作用:好想法留下来了,未经评审的决策没有。
头 30 天该量什么
先在一条功能线上采用这个工作流,不要全团队铺开,并且量三个数。每个 agent PR 的评审耗时:随着 artifact 标准化,它应该逐周下降。评审前拦截的越界修改数:早期应该不为零(说明门禁在工作),后期应该下降(说明提示词在进化)。返工率,即评审后需要二次实现的 PR 占比:这是说服怀疑者的那个数字。30 天采用计划讲了推广节奏;想追溯这些阶段的来源,可以看 OpenSpec vs Superpowers vs Spec Kit 的模式对比。
可以预期的失败模式
| 症状 | 根因 | 修复 |
|---|---|---|
| agent 产出互相冲突的 diff | 任务共享文件 | 重拆使写入范围不相交,或用依赖行串行 |
| 写规格包比实现还慢 | 规格包范围太大 | 一个可评审的功能切片一个规格包,不要按 epic 写 |
| 证据日志沦为套话 | 标准不可测试 | 先把验收标准改写成可观察的结果,再拆任务 |
| 评审者对绿色清单盖章放行 | 清单替代了判断而不是聚焦判断 | 评审者对每个 PR 负责回答"哪些改动没人要求过?" |
稳定状态并不光鲜:规格很短,任务很无聊,agent 可以随意替换,评审很快。当人把时间花在意图和证据上、机器把时间花在两者之间的一切上时,看起来就是这样。