这是"用 TDD 驯服 AI 编码代理"系列的第二篇。上一篇讲了测试层的需求锚定方法[1]。测试的前提是需求清晰。本篇回头补上游——需求层的消歧实践。

第一版的教训:一句话需求,371 行污染

Aristotle 第一版没有 GEAR 协议[2],没有角色分工。整个反思功能写在一个 371 行的 SKILL.md 里。需求描述只有一句大意:系统能识别用户对 AI 错误的纠正,生成可复用的规则。

AI 拿到这句话,直接开工。3 个 commit,从 SKILL.md 到测试脚本到 README,一气呵成。37 个静态断言全部通过,E2E 测试一路绿灯[3]。

上线用了才发现,反思任务根本没走后台。它直接在主 session 里跑了起来,371 行 SKILL.md 全部注入上下文。子进程完成后,background_output(full_session=true) 又把整份 RCA 报告拉回主 session。审核流程更离谱——task() 创建的子 agent session 是非交互式的(这是 OpenCode 的架构限制),用户根本无法在子 session 里做审核确认。

设计原则里写了"主会话上下文零污染"“过程对用户透明”。一句都没兑现。

为什么?因为需求里没说清楚三件事:

  1. 反思必须在独立的后台 session 执行,主 session 不能被污染——没写。AI 自然选了最简单的实现方式:把所有东西塞进同一个 session。
  2. 审核必须在用户可交互的环境中完成——没写。AI 不知道 task() 创建的 session 是非交互式的,它直接假设子 session 能做审核。
  3. 反思完成后只能通知用户,不能把完整输出推回主 session——没写。AI 选了最直接的信息传递方式:全量推送。

这不是 AI 的 bug。是我给的需求有缺失。AI 按照最常见的模式填充了所有我没说清楚的细节。在传统开发中,开发者会追问这些约束条件。AI 不会追问——它直接填空。

为什么 AI 不会追问需求

LLM 的结构特性决定了它不会主动追问。它被训练成"给出答案",不是"提出问题"。这不是缺陷,是设计目标。

用户输入一个问题,LLM 会在训练数据中寻找最可能的回答。它的训练目标是最大化下一个 token 的预测准确率。提问意味着预测中断,不符合训练目标。

这种特性在生成内容时是优势。在需求工程中是灾难。 你给的需求越模糊、越不完整,AI 填充的细节越多。这些细节不是你想要的,是训练数据里最常见的。等你发现偏差时,已经有几百行代码写好了。

AI 没有"追问需求"的天然动力。你必须在上游堵死漏洞——不是堵歧义,是堵缺失。

GEAR 协议:从漏洞中诞生的结构化方法

GEAR 全称 Git-backed Error Analysis & Reflection。它不是一开始就规划好的。它是被第一版的四个问题逼出来的。

第一版只有一个 agent 角色,所有逻辑塞在一个 371 行的 SKILL.md 里。翻车后拆成了四个文件:路由逻辑留在 SKILL.md(84 行),反思启动在 REFLECT.md,审核在 REVIEW.md,子 agent 分析协议在 REFLECTOR.md。每个文件只在一个场景被加载。

但拆文件只解决了上下文污染。更深的问题是:谁负责写规则,谁负责审核,谁负责消费——这些角色职责没有定义清楚。拆文件之后,我意识到需要一个协议来规定角色之间的关系。这就是 GEAR 的起源。

GEAR 的核心是 PAC 模型(Production-Audit-Consumption)和 Δ 决策因子。PAC 把写规则(R)、审规则(C)、用规则(L)三种角色完全分离,R 只管产不管判,C 只管审不管改,L 只管用不管审。Δ 因子根据置信度、风险权重和历史数据决定审核级别——新规则强制人工审核,积累足够数据后逐步自动化。这套机制解决了规则质量管理的问题[4]。

但 GEAR 解决的是错误反思的流程规范。需求文档怎么写,GEAR 本身没有规定。tdd-pipeline 项目[5]在需求阶段用的是 User Stories + Given-When-Then 验收标准,加上 core/secondary 两级优先级,和 GEAR 的 PAC/Δ 没有关系。

这篇文章的主题是需求缺失。GEAR 的价值不在于直接指导需求写法,而在于它提供了一个教训:没有结构化的协议,AI 会按最省事的方式填空。 这个教训在需求层同样成立。

需求文档该怎么写

第一版的需求只有一句话。结构化的需求文档应该包含什么?从第一版的教训反推,至少需要四个部分。

以反思触发功能为例。

模糊版需求

“当用户纠正 AI 的错误时,系统应提示用户启动反思流程。”

结构化版需求

需求编号:AC-001 所属模块:反思触发 优先级:core(核心功能)

正常路径

  1. 用户发送包含明确错误纠正的消息 → 系统显示反思提示卡片
  2. 用户点击"启动反思" → 系统创建独立反思 session,不污染主会话
  3. 反思完成后 → 系统生成规则提交审核,提示用户查看结果

边界条件

  1. 用户说"不对"但未说明具体错误 → 仍然触发提示
  2. 用户纠正的是产品设计意见而非事实错误 → 不触发提示
  3. 用户在同一轮对话中多次纠正 → 只显示一次提示
  4. 对话历史为空 → 不触发提示
  5. 纠正关键词出现在代码块注释中 → 不触发提示

错误路径

  1. 反思 session 创建失败 → 显示错误提示,引导用户重试
  2. 规则生成失败 → 显示失败原因,不静默
  3. 审核流程超时 → 自动保存草稿,通知用户后续处理

平台约束

  1. task() 创建的子 session 是非交互式的——审核不能在子 session 中进行,必须在主 session 实现
  2. 反思完成后只能通知用户,不能把完整输出推回主 session——信息流必须是拉取模式

对比一下:模糊版只说了"做什么"。结构化版把"什么时候做"“什么时候不做"“做失败了怎么办"“平台有什么限制"全写清楚了。AI 拿到结构化版,不需要猜。

注意"平台约束"这一段。这是第一版需求完全缺失的部分,也是翻车的直接原因。如果当时写了”task() 创建的子 session 是非交互式的"这条约束,AI 就不会假设用户能在子 session 里做审核。

这四个部分不是 GEAR 定义的。是行业里验收标准、边界条件、错误路径的通用写法。核心原则只有一条:凡是你没写的,AI 会自己填。你填的越详细,AI 填的空间越小。

怎么逼自己写出这四个部分

知道该写四个部分,不代表能写出来。第一版的时候,我根本没意识到"平台约束"是一个需要写的东西——我不知道自己不知道。

tdd-pipeline 项目[5]用了一个方法来解决这个问题:苏格拉底提问。在需求阶段(Phase 1),流程强制要求对用户做 deep-interview,至少提 3 个澄清问题,目标是 3-5 个。问题包括:

  • 这个解决什么问题?
  • 谁是参与者?
  • 系统的边界在哪里?
  • 什么明确不在范围内?

接下来还有一个关键步骤:挑战模糊术语。“快速”、“安全”、“用户友好"这类词必须被逼到可量化。未声明的约束(法规、性能、兼容性)必须被识别。所有歧义不解决完,不准进入下一阶段。

这不是指望 AI 自己追问。是在流程里强制规定:必须追问,至少 3 个问题,不解决完不许往下走。

在传统开发中,这个追问发生在开发者脑子里——有经验的开发者会自然地问这些问题。AI 不会。所以你必须把"脑子里的追问"外化成流程步骤。tdd-pipeline 做的就是这件事。

系列预告

这篇文章讲的是需求层的消歧实践。管线的其他环节,每一步都有各自的坑和值得我学习的教训:

  • 设计层:PRD 到技术方案的映射——第二次重构时,需求写清楚了,但 task(run_in_background=true) 的异步行为没有提前验证,agent O 仍然在主 session 里被拉起。为什么跳过技术调研,AI 会自作主张填补空白
  • 审核机制:Ralph Loop 的设计哲学——AI 的错误是系统性的,需要结构化审核而非随机 review
  • 全流程总结:从需求到代码的一张图——严格流程的边际成本随项目复杂度下降,随 AI 参与度上升而必要

整个系列的核心主张:AI coding agent 是放大器。它放大你的工程能力,也放大你的工程债务。驯服它需要比传统开发更严格的流程纪律。


参考

  1. 上一篇文章:先写测试文档,再写测试代码:AI 开发的需求锚定
  2. GEAR 协议 RFC:一份 Markdown 的三次生命:从静态规则到 Git 版本管理的 MCP Server
  3. Aristotle 改造实录:从四道伤疤到一套铠甲:Aristotle 改造中的驾驭工程实践
  4. GEAR 协议规范:DOI 10.5281/zenodo.19660780
  5. tdd-pipeline 项目:https://github.com/alexwwang/tdd-pipeline