观察反馈:执行结果如何回传
它执行了一条检查命令,看到输出是空的,于是判断 文件里没有遗留的调试代码 ,继续往下走。
观察反馈:执行结果如何回传
它执行了一条检查命令,看到输出是空的,于是判断”文件里没有遗留的调试代码”,继续往下走。
文件里其实有。命令失败了,只是它没看见。
一、观察是循环的输入
前面说过,循环的意义是每一轮都带回新信息。但”带回”这个词太轻了——带回来的东西如果失真,循环转得越顺,错得越远。
模型对一个动作的全部认知,来自宿主回灌的那条消息。这条消息里有什么、缺什么,直接决定它下一步怎么判断:
| 回灌内容 | 模型能判断什么 |
|---|---|
| 只有 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 的实现质量,有个很快的办法:看它的失败路径写了多少行。成功路径都差不多,失败路径才见功夫——超时怎么算、部分成功怎么报、截断怎么标注、权限拒绝怎么引导。
小结
模型没有办法亲眼看到世界。它看到的全部,是宿主愿意并且能够转述的那一部分。
转述得准,它就判断得准;转述得省,它就猜;转述得错,它会非常自信地做错事。
下一篇讲另一头:就算观察是真的,也不代表这个动作该被执行。谁来决定放行,以及凭什么。