第 3 章 颗粒度与验证
为什么大多数 Agent 项目会失控?核心不在技术栈,不在模型选择,而在"交付单元的颗粒度"和"验证强度"的组合。
开篇:一份 390 行的调试计划,和一个始终立不起来的系统
我做第一个 Agent 项目的时候,犯了一个现在看来很低级的错误。
那会儿我雄心勃勃,想做一个挺复杂的东西——一个虚拟办公室,有自主行为的 AI 角色,能自然地互动。我让 Agent(准确地说是让 Claude Code)一次性帮我搞,目标定得很高:六层架构、三层决策系统、四层记忆系统,还要对接好几家 LLM。
动手之前,我写了一份调试计划。
三百九十行。
计划里排了七个阶段,从"数据库持久化测试"一直排到"用户验收测试和 24 小时长期运行"。甚至还有五周的时间表——第一周基础验证,第二周角色系统调试,第三周世界模拟测试,第四周前端和性能,第五周集成和优化。排得整整齐齐,像一份正规的项目甘特图。
然后我让 Claude Code 开工了。
大概两天时间,它几乎一把梭完了整个项目。说实话,那个过程太爽了——我就坐在那儿看着它自己写代码,文件一个接一个地冒出来,架构一层一层地搭起来,数据库、接口、调度器、前端组件,唰唰唰地成型。那种看着一个复杂系统在眼前凭空生长出来的感觉,特别上头。我几乎没怎么干预,它写我就看,它问我我就说"继续"。两天下来,一个看起来有模有样的系统就摆在那儿了。
然后我尝试启动它。
噩梦开始了。
报错。改了再启动,还是报错。换一个报错改了,又冒出来三个新的。我让 Claude Code 去 debug,它改一通,启动,又崩在别的地方。我自己上手翻代码,发现这个项目已经太大太大了——文件几百个,依赖关系盘根错节,我连"这个错误到底是哪一层引起的"都看不明白。
那种感觉很难形容。就像掉进了一个无限循环的地狱——你修好一个洞,墙的另一面又裂开;你顺着调用链往下查,查到第十层早就忘了从哪开始查的。无论是让 Claude Code 通过 debug 代码的方式找问题,还是我自己上手,都束手无策。代码量太大,问题太碎,彼此牵连,根本无从下手。
最后我放弃了。这个项目始终没能真正立起来。
后来回去翻它的遗骸,才看清了死因的全貌。
首先是系统根本立不住。启动日志里清清楚楚写着两次"Application startup failed",前后崩在不同的地方。其中一次是因为一个性能监控组件的初始化方法返回了空值,代码还在那儿等它,等一个空值就抛异常了。
好不容易有一次半只脚迈过了启动这关,又是一地鸡毛。八个数据仓库的创建方法全都漏了提交事务的调用——记忆、任务、项目、事件、消息,几乎所有写入操作的数据都没真正存进数据库。写进去的东西转眼就没了,系统像是得了失忆症。
更让人崩溃的是那个错误日志。从那天下午四点开始,一直到第二天凌晨将近五点,十几个小时里,日志在不停重复同一行字:"LLM 调用失败"。每隔几分钟一次。系统的调度器像个没头苍蝇一样不停触发任务,LLM 一直调用失败,没有任何熔断、降级或者告警。就那么半死不活地空转了十几个小时。
那份三百九十行的调试计划呢?一百多个待办项,一个都没勾。
这件事让我琢磨了很久。计划写得那么详细,为什么执行会崩成这样?
问题的本质:颗粒度和验证脱节
后来我想明白了:计划颗粒度和执行颗粒度是两回事。
那份三百九十行的计划,颗粒度非常细——细到"验证角色状态序列化的字段一致性"这种程度。但它有一个致命的问题:每一项都没有验证手段。
"验证字段一致性"——怎么验证?跑什么脚本?通过了长什么样,没通过长什么样?全都没写。那这一项就是一句空话。当所有的待办项都是一句句空话的时候,这份计划就不是计划,是一份自我安慰的幻觉。
我把这种失败模式叫做**"憋大招"**:一次性甩出一个宏大的目标,不拆分、不验证、不留中间状态,指望一口气做完。憋大招在传统软件里就很危险,但在 Agent 时代,它的危险性被放大了——因为 Agent 的产出是概率性的,你让它一次性产出整个系统,任何一处出错都会连锁崩溃,而且你根本定位不到错在哪。
我的那个虚拟办公室就是典型。Agent 一次性产出了整个系统的代码,但持久化层漏了提交——这种"大面积基础缺陷"只会在没有增量验证的场景里出现。如果是每个模块写完就跑通,这个错误在第一个数据访问模块就会被发现。
第一道转折:学会做减法
吃了第一次的亏,做第二个项目的时候,我做了一个改变——先写不做什么。
这次我老老实实写了一份产品需求文档,但和第一次不一样的是,我在里面专门列了一节叫"非目标":
- 不是传统意义的"玩游戏"(没有胜负、没有关卡)
- 不是角色扮演游戏(玩家不直接扮演角色)
- 不是经营模拟(不以盈利扩张为目标)
- 不是剧情游戏(没有固定剧本)
四条"不做"。现在看这四条不起眼,但它们是我从第一次失败里学到的最重要的东西——学会拒绝。第一次那个虚拟办公室什么都想要,自主行为、记忆系统、社交关系、赛博朋克可视化,结果什么都做不好。这次我逼自己先画一条边界:什么是绝对不碰的。
技术选型也跟着收敛了。第一次用的是比较重的技术栈,这次我主动降级了两次:前端从一个游戏引擎换成了普通的 Web 技术,通信从一个双向长连接换成了更简单的服务端推送。每次降级的理由都一样——够用就行,复杂度能砍就砍。
这次项目最终跑起来了。有了一个能运行的基础骨架。但说实话,功能离我最初的设想还很远。最有意思的是,项目里有两个目录——AI 决策层和业务逻辑层——我留空了。没实现。
留空,不是放弃,是诚实。我那时候确实还做不好 AI 决策,与其硬写一坨跑不通的代码,不如先承认这部分我还搞不定,把确定能做的做完。 留白比硬上更有工程价值。这是第一次失败教给我的第二件事。
第二道转折:每阶段都要有"用户能感知的验证"
做减法解决了"要不要做"的问题,但没解决"怎么知道做成了"的问题。这件事我到第四个项目才算真正想通。
前三个项目,我的验证方式都是"技术指标":数据库表建好了吗?接口通了吗?性能达标了吗?这些指标有一个共同的问题——它们衡量的是过程,不是结果。
第四个项目,我在规划文档里第一次写下了这么一条原则:
每个阶段都要有可独立验证的体验效果,而非纯技术里程碑。
"体验效果"是什么意思?举个例子。那个项目的第一阶段,验证标准不是"完成记忆模块的数据表设计"——这是技术指标,用户根本感知不到。验证标准是:"用户能在对话里看到当天的报告结论,预警能推送到钉钉"。
你看,这句话主语是用户,动词是"能看到""能收到"。它是从用户体验的角度写的,一眼就知道成了没有。不需要跑脚本,不需要看日志,你和用户聊一句就知道。
这个转变听起来简单,但它彻底改变了我做项目的方式。我把整个迭代拆成了四个阶段,每个阶段都有这样一句"可验证的体验效果"。阶段之间有明确的依赖关系——第二阶段依赖第一阶段的地基,第三阶段依赖前两者的稳定。甚至第四阶段我都标注了"评估性,非必进"——意思是做完前三个阶段之后,再看第四阶段值不值得做,而不是一上来就承诺四个阶段全做。
后来翻代码提交历史,阶段一、二、三都有对应的提交记录,阶段四到现在都没有动。和计划完全吻合。
这就是"小颗粒 × 强验证"的样子:交付单元足够小(一个阶段),每个单元都有用户能感知的验证标准。对比第一次那个"三百九十行计划一个都没勾"的项目,简直是两个物种。
第三道转折:可回滚,才有真稳定
在"颗粒度"和"验证"之外,还有一件事我没提,但它其实是稳定的底座——可回滚。
在探索 Agent 项目之外,我也用传统方式做过一个临时支撑业务的项目。回头看它为什么稳,关键不在代码写得多好,而在我给它搭了一套部署机制。
每次发布,构建产物的文件名里带着当天的日期和代码版本号。发布的时候不是直接覆盖,而是放到一个版本化的目录里,然后把一个软链接指过去。系统只认软链接,所以"切换版本"就是改一下软链接的指向,一秒钟的事。目录里始终保留最近五个版本,超了就把最旧的删掉。
这套东西有什么用?用处在于——我不怕改了。
传统项目里,改一个 bug 是有心理压力的,因为你怕改出新问题,而新问题没法快速回退。但有了这套可回滚的部署,改坏了大不了把软链接指回上个版本,一秒钟恢复。这种安全感让你敢做高频的小颗粒迭代——每次改动都很小,每个小改动都立刻验证、立刻发布。出问题就回滚,没问题就继续。
那个项目有近五十次提交记录,我一条条翻过,全是小步迭代:统一一下排序逻辑、修个浮点精度隐患、加个小按钮。没有一次是"憋大招"。
所以我后来总结出一条经验:稳定不是"没有 bug",而是"出了 bug 能立刻回滚"。没有可回滚的部署,就没有真正的稳定;没有版本管理,就没有可回滚的部署。这条链条上任何一环缺失,项目就会失控。
颗粒度 × 验证:一个判断模型
把这几段经历抽象一下,可以得到一个判断模型。一个 Agent 项目会不会失控,可以用两个轴来定位:
横轴是交付颗粒度——你每次交付的东西有多大。一次性交付整个系统是颗粒度大,分阶段、每个阶段一个可验证的小单元是颗粒度小。
竖轴是验证强度——你交付之后怎么确认它对不对。完全不验证是弱,跑 eval、看用户反馈、能回滚是强。
这四个象限里:
- 颗粒度大 × 无验证(憋大招):必然失控。我的第一个项目就死在这。
- 颗粒度小 × 弱验证(能跑但易偏):能跑起来,但功能容易跑偏。我的第二个项目接近这个状态。
- 颗粒度小 × 强验证(理想区):交付单元小、验证严、出问题能回滚。这是 Agent 项目应该追求的状态。
- 颗粒度大 × 强验证:成本极高。你要为一个庞大的交付单元配上足够强的验证,工作量会指数膨胀。
这里需要说清楚一个点:这个模型不是说传统软件不讲颗粒度。 传统软件的敏捷迭代、价值交付,同样强调小颗粒度快速交付。差异不在颗粒度大小,而在验证方式。传统软件的测试是确定性的——一个函数输入固定、输出固定,跑通一次就算过。但 Agent 的产出是概率性的,同样的输入可能不同输出,单次跑通不能说明任何问题,你得跑多次看通过率,得用 eval、用用户反馈来验证。
换句话说,颗粒度这件事,传统软件和 Agent 项目的要求是一致的——都要小。区别在于,Agent 时代的"验证"从确定性的变成了概率性的,门槛更高了。所以对 Agent 项目来说,颗粒度小 + 概率性验证,两条缺一不可。
怎么判断自己手上的项目在哪个象限?问自己三个问题就够了:
- 我这次的交付单元是什么?是一个完整功能,还是一个被测试覆盖的行为?
- 出了问题,我能定位到哪一步吗?能回滚吗?
- 我的测试,是跑通一次就算过,还是跑多次看通过率?
如果三个都答"前者",你多半在危险区或者危险区边缘。
这一章的工具:项目自检清单
🔧 项目失控自检清单
开一个新 Agent 项目前,或者在项目进行中觉得"越来越失控"时,过一遍这个清单:
颗粒度
- [ ] 我这次的交付单元是什么?能用一句话描述吗?
- [ ] 这个单元需要多久能做完?超过半天的话,能不能再拆?
- [ ] 我有没有写过"非目标"清单?明确说清楚什么不碰?
验证
- [ ] 每个交付单元,我怎么验证它做对了?(靠人眼看 / 跑脚本 / 看用户反馈)
- [ ] 我的验证标准,是从"技术指标"写的,还是从"用户体验"写的?
- [ ] 我能不能一句话说出"这一步成了"长什么样?
可回滚
- [ ] 我有没有版本管理?(听起来像废话,但我第一个项目就没有)
- [ ] 我有没有可回滚的部署机制?出问题了能多快恢复?
- [ ] 我的提交粒度是大是小?有没有"一次性提交半个系统"的情况?
危险信号(出现任何一条就要立刻停下来重新规划)
- [ ] 计划文档越来越长,但完成进度几乎没动
- [ ] 在反复打补丁修同一个区域的问题,而不是退一步重新设计
- [ ] 代码改动牵一发动全身,改一个地方冒出来好几个新问题
- [ ] 错误日志在重复刷同一行,但你不知道,因为没有告警
小结
这一章讲的东西,说穿了就是一句话:让每个交付单元都自带验证能力。
"把大需求拆成小需求"这个道理谁都懂,但在 Agent 时代它有个额外的要求——每个小单元不能只是"小",还得"自带验证"。自带 eval、自带回滚、自带一句"成了长什么样"的验收标准。没有这三样,颗粒度再小也是失控,只是失控得更隐蔽而已。
我的第一个项目死于"颗粒度大 × 无验证"。花了三个项目的时间,我才真正学会怎么把颗粒度降下来、把验证强度提上去。希望你看完这章,能少走两个项目的弯路。
下一章,我们往细节走一步——当颗粒度和验证都到位之后,单个工具该怎么设计,才能让 Agent 听话。
下一章
第 4 章 · 工具设计原则 —— 工具描述不是文档,是 prompt 的一部分。工具的每一个设计细节,都在影响 Agent 的行为质量。