专栏 Agent 工程

观察反馈:执行结果如何回传

它执行了一条检查命令,看到输出是空的,于是判断 文件里没有遗留的调试代码 ,继续往下走。

观察反馈:执行结果如何回传

它执行了一条检查命令,看到输出是空的,于是判断”文件里没有遗留的调试代码”,继续往下走。

文件里其实有。命令失败了,只是它没看见。

一、观察是循环的输入

前面说过,循环的意义是每一轮都带回新信息。但”带回”这个词太轻了——带回来的东西如果失真,循环转得越顺,错得越远。

模型对一个动作的全部认知,来自宿主回灌的那条消息。这条消息里有什么、缺什么,直接决定它下一步怎么判断:

回灌内容 模型能判断什么
只有 stdout 有输出 = 大概成功了
stdout + 退出码 成功、失败、还是”没有匹配”
再加结构化错误码 失败的原因,以及该不该重试

差别不在信息量,在可判定的程度。一段文字要靠猜,一个字段不用。

二、一条命令的三种结局

拿 rg 举例,同一个命令有三种完全不同的结局:

结局 stdout 退出码
找到匹配 若干行 0
没有匹配 空 1
命令本身出错 报错信息 2

如果宿主只把 stdout 交回去,”没有匹配”和”命令出错”在模型眼里是同一件事:什么都没有。

这时候它会怎么做?大概率是认为”没有匹配”,然后继续。一个本该中断的错误,被当成了一次正常结果。

这不是假设。早期的 Agent 实现里有个真实的坑,就出在这一层。

三、退出码是怎么丢的

沙箱里的命令不是直接跑的。宿主要先拦截 execve,把调用转给 App 进程里的处理器,拿到结果之后再”假装”这次系统调用发生过。

早期做法很直接:把 execve 重写成

/bin/cat <临时文件>

处理器把输出写进临时文件,cat 把它吐到终端。模型看到的 stdout 完全正确。

问题在退出码。cat 的退出码永远是它自己读写文件的结果,不是被拦截那条命令的结果。命令失败,$? 还是 0。

在交互里几乎看不出来——人看输出就知道成没成功。但只要模型(或脚本)用退出码做判断,就会全线失灵:if 永远走成功分支,&& 后面的命令永远执行,重试逻辑永远不触发。

修法在源码注释里留着:

它过去无条件重写成 /bin/cat <临时文件>,所以 guest 的 $? 永远是 cat 的。
现在非零状态改成 /bin/sh -c 'cat "$1"; exit N' sh <临时文件>

一行脚本,把退出码补了回来。

这个案例值得反复看,因为它示范了一类典型缺陷:输出对了,语义没对。宿主以为自己完成了代理,其实只代理了一半。

四、观察要结构化

退出码是第一个该结构化的东西,但不是唯一一个。

工具执行完,宿主内部拿到的是一个结果对象,至少包含两项:输出文本,以及成功与否。成功标志会被翻译成消息里的 isError 字段,模型下一轮看到的不只是一段文字,还有一个明确的”这次失败了”。

失败时更讲究。系统级工具返回的不是一段散文,而是一个信封:

{"ok": false, "error": {"code": "PERMISSION_DENIED", "message": "…"}}

这个形状的价值在于可分支。模型看到 PERMISSION_DENIED,知道该去引导用户授权;看到 PACKAGE_NOT_FOUND,知道是名字错了;看到超时,知道该缩短任务。如果只有一句”操作失败”,它只能瞎猜。

反过来,人类可读的错误信息也不能省。结构给机器判断,文字给模型解释,两者都要。

五、观察也要限流

还有一个容易被忽略的约束:观察不能无限长。

一条 cat 大文件、一次递归 rg、一个 --verbose 的构建日志,输出可以是几十兆。全塞回上下文,下一轮直接爆窗。

所以回灌前要做截断,并且截断本身也要告诉模型。只说”输出如上”,模型会以为看到了全部,基于不完整的信息下结论。正确的做法是明确写清楚:输出被截断,原始内容多少行,只保留了前 N 行,完整内容在某个路径。

这和前面的外置是同一个思路:不是简单地少给,而是让模型知道自己少了什么,以及去哪里补。

六、静默成功最危险

把所有失败形态排个序,最危险的不是报错,是”看起来成功了”。

报错至少是一个信号,模型会停下来处理。静默的成功会让它带着错误的前提继续走:以为文件写好了,其实只写了一半;以为命令跑完了,其实超时被杀;以为检查通过了,其实退出码根本没传回来。

而且这类错误会累积。第一轮的错误前提变成第二轮的输入,第三轮在此基础上继续加工,等到用户发现不对,已经偏出去很远。

所以判断一个 Agent 的实现质量,有个很快的办法:看它的失败路径写了多少行。成功路径都差不多,失败路径才见功夫——超时怎么算、部分成功怎么报、截断怎么标注、权限拒绝怎么引导。

小结

模型没有办法亲眼看到世界。它看到的全部,是宿主愿意并且能够转述的那一部分。

转述得准,它就判断得准;转述得省,它就猜;转述得错,它会非常自信地做错事。

下一篇讲另一头:就算观察是真的,也不代表这个动作该被执行。谁来决定放行,以及凭什么。