Agent 不报错也不超时的失败,要怎么才能发现
我自己带着一批 Agent 干两类活:一类是写内容,一类是写这套站群的代码。两条线上都出过事故,事后回看有个共同点让我很不舒服——出事的那一刻,屏幕上什么都没有发生。没有报错,没有超时,没有一行红字。命令老老实实退出了,脚本老老实实打印了成功,Agent 老老实实在那儿待着。
这篇复盘的就是这一类失败:它不是「做错了」,是「没做,但看起来做完了」。我关心的也不是它们各自怎么修,而是一个更靠前的问题——当一次失败不产生任何信号,我到底是靠什么把它逮住的。
那几次失败长什么样
先把现象摊开,都是我这条产线上实际发生过的:
- 构建过滤器写法不对,过滤器匹配不到任何目标包,于是命令正常结束、退出码是 0。我看到的是「构建通过」,实际上什么都没构建。
- 大体量站点的部署传输中断,脚本照常打印「已上线」。而它的动作顺序是先清掉旧目录再顶上新的,于是线上顶着一个空目录,整站返回 403。
- 提交时的路径参数没匹配上新文件,静默跳过。子代理写的那一整批实现,在提交里一个字都没有。
- 分类与维度这两个字段的配置没生效,没有任何提示,页面就按默认值渲染出来了,光看页面看不出错。
- 写公众号文章那条线上,确认节点的消息没有出现在人那一侧。Agent 认为自己在等人,人并不知道轮到自己动了。
作为对照,同一批事故里有一个反例:内容字段的值写到了枚举范围之外,构建当场就崩了。这个反而是这堆事情里最省心的一个——它吵。吵的失败不需要发现机制,它自己会找上门。
它们分别是怎么被我发现的
这一段是我这次复盘里真正想留下来的部分。把发现路径一条条对出来之后,结论有点刺眼:没有一次是系统主动报出来的,全都是人为了别的目的顺手看了一眼。
第一次,消息没送达那件事。它不是被任何检查发现的,是运营者自己开口问的——他在等一份提纲,等不到,于是发了一句「提纲我在飞书上没有看到,请显示出来」。触发发现的动机不是巡检,是他本人正卡在这一步等结果。换句话说,这次能发现,靠的是恰好有个人在等;如果这一步的下游不是活人,而是另一个 Agent,那就没人会问了。
第二次,产物目录命名的问题。当天写了好几篇文章,目录名只带日期,还夹着几个主题未定时随手起的占位名。发现它的场景不是我在审命名规范,是我要从当天那堆东西里翻出其中一篇,翻起来伤神。后来的改法很小,就是在日期后面插了个当日序号。发现动机是「找不着」,不是「查得出」——日期粒度的命名在一天一次的时候完全够用,一天多次就立刻退化了。
第三次更难受一点。模板里的注释被计入了正文长度,导致那道长度质量门长期给出假结果。它具体是哪一次、被什么动作发现的,我没有记录,只知道它假了很久。这一条的性质和前两条不同:前两条是某件事没发生,这一条是用来发现问题的那道门自己失效了,而它每一次都在报「通过」。
三次放一起看,我承认的现实是:我这套流程当时压根没有针对零信号失败的探测能力。能被发现,靠的是有人在下游等、有人要翻东西、有人碰巧去核了一下。这三件事都不可靠,因为它们都不是流程的一部分。
为什么这类失败天生没有信号
想清楚成因之后,我不再觉得这是「不够仔细」。
成功信号取自动作的发起端,而不是目标端。 调用返回了、脚本打印了、进程退出了——这些说的都是「我这边做完了」,没有一个字是在说「那边收到了」。中间任何一段断掉,发起端的观感完全不变。
有客观真值的地方反而更危险。 写代码这条线是有真值的:构建能不能过、测试红不红,看起来一清二楚。可正因为有真值,人和 Agent 都会停止怀疑——看到退出码 0 就认为成功了。反倒是写内容那条线,好坏没法自动判定,逼得我每一步都去人工确认,静默失败在那边活不长。
「在等」和「卡住」从外部看是同一副样子。 一个正在等待人工确认的流程,和一个已经死掉的流程,共同特征都是没有新输出。而「没有输出」这件事本身,不产生任何记录,也就没有任何东西可以被检查。
查的方法自己会坏掉。 除了注释混进字数统计,我还遇到过残留的开发进程让我看到的是旧代码的表现、依赖模块自身路径推导资源目录的写法在打包后指向了错的位置。这几类的共同点不是代码错了没查出来,而是验证环节本身失效了,却依然返回「通过」。
我把哪几处改成了回查
改法说穿了只有一句:把成功判据从「调用没报错」换成「回去看目标端的状态」。落到具体动作上是这么几条,都已经固化成我这条产线的纪律:
- 构建过滤器一律用路径式写法,不用名称式——直接绕开「匹配不到就正常退出」这个行为。
- 大体量部署改成四步:先打包成文件、传输、在目标端校验文件数、再切换。不用一条管道从头贯到尾。
- 每次部署完回查线上的文件数和状态码,不看脚本的成功输出。
- 提交时只列显式文件名,提交完回查提交里到底有什么。
写公众号文章那条线上,还有一个我觉得更值得说的时序细节:草稿写入这个功能至今没有接通,但它的成功标准早就写死在说明书里了——以拿到接口返回的素材 id 之后再次查询草稿列表为准,并且明文写着禁止把「接口已提交」误报为「草稿已创建」。这不是一次已经发生的事故,是在还没做这个功能的时候,先把验收标准立在那里。前面那些坑吃过的亏,是以这种形式回写进规格的。
回查也有边界,不是越多越好。回查本身有成本,它自己也可能失败、也可能读到过期的缓存。链路很短、一失败立刻就能看见的动作——比如往本地写个临时文件——给它配回查属于过度设计。我用的判据是一句话:只要这个动作的结果会被下一步当作前提使用,就必须回查。理由很直白:前提一旦是假的,后面所有步骤都会在这个假设之上继续跑,而且跑得特别顺,最后交出一份看起来很完整的错误产物。
还有一条是留给「等待」的。人工确认这类节点依赖消息真的送到,所以闸门处得有送达检查,否则流程会静默停住:Agent 认为在等人,人不知道该自己动。这一步的意义就是把「没消息」从一种默认状态,变成一个需要被解释的事件。
最后我给验证手段本身留了一道人工抽查。自动化能覆盖「代码是否正确」,覆盖不了「验证是否有效」——那道长度门假了很久这件事,自动化里没有任何一环有资格发现它。
带走的那一句
没有消息,不等于一切正常;「没有消息」这件事本身,得有人或有东西专门负责去解释它。
对我来说这句话有个很实际的用法:每加一个交给 Agent 的外部动作,我会先问一句——如果这一步悄悄失败了,谁会第一个察觉?如果答案是「下游那个正在等结果的人」,那这一步就还没做完,因为它把发现失败的责任摊给了一个恰好在场的人。如果答案是「没有人」,那它迟早会以一份看起来完整的错误产物的形式,在很靠后的地方爆出来。
这篇写的是我自己那套流程里的一次实际情况,不是通行做法。你的场景、工具和团队规模不同,结论未必适用,判断方式可能比结论更值得拿走。
延伸阅读
- 上一篇(跑闭环):Agent 的验收样本要按失败分支覆盖,不是按成功次数累计
- 下一篇(跑闭环):Agent 产物的自检要看内容特征,不是看文件名
- 这个专题的主论点:Agent 调用没报错,不等于这件事办成了
- 专题导读与七个阶段的地图:把 Agent 当成一个要上岗的员工来带:这个专题讲什么