Article
跑 Terminal-Bench 时,Agent 把自己 kill -9 了
9 月 19 日晚上 11 点 31 分,Orca v0.4.32 开始跑 Terminal-Bench 2.0:88 道题,每题一次尝试,6 个并发,模型路由到 deepseek-v4-pro。第二天凌晨 3 点 13 分跑完,均分 0.7955,70 题通过。
没通过的 18 题里,有一题的失败原因是 NonZeroAgentExitCodeError,退出码 137,也就是 128 + 9,进程收到了 SIGKILL。这一题是 make-mips-interpreter,发出那个 SIGKILL 的,是 Orca 自己执行的一条命令。
第 61 次工具调用
这道题要求用 JavaScript 写一个叫 vm.js 的 MIPS 解释器,让编译成 MIPS 的 doomgeneric 能跑起来。Harbor(Terminal-Bench 2.0 的执行框架)通过仓库里的适配器启动 Orca,适配器把题目指令作为 orca exec 的最后一个参数传进去:
orca exec --mode full-auto --output-format jsonl -- \
'I have provided /app/doomgeneric_mips, a MIPS elf file, ... Please implement
a MIPS interpreter complete with handling system calls called vm.js so that ...'
这一题的轨迹有 92,636 个事件、26 MB。最后一个事件是一次 bash 调用:
for d in /proc/[0-9]*; do c=$(tr '\0' ' ' < $d/cmdline 2>/dev/null); case "$c" in *vm.js*) echo "$d: $c"; kill -9 ${d#/proc/} 2>/dev/null;; esac; done; echo cleaned
Agent 在清理前面测试时留下的 vm.js 进程,办法是扫一遍 /proc 里每个进程的命令行,含 vm.js 的一律 kill -9。orca exec 自己的命令行里正好有整段指令,指令里正好有 vm.js,于是这次循环把 Orca 本身也杀了。
这是这个会话的第 61 次工具调用,它没有返回。会话跑了 43 轮、发起了 61 次工具调用,到这里全部白做,也没有写下 session.completed。Terminal-Bench 记下的是一次 Agent 错误,这道题到底做没做出来,没有任何信息。更早的一次全量运行里,video-processing 也以 137 结束过,当时只在 #59 里记了一笔。
复现只要 20 秒:在指令里放一个记号,让 mock provider 发一条长时间运行的工具调用,再在旁边 pgrep -fl 这个记号,列出来的就有 orca 自己;把 pgrep 换成 pkill -9 -f,退出码是 137。
这类命令放在别的场景里也很普通。任务本来就可以查看和匹配进程的命令行,Agent 清理自己起过的进程也是正常操作;出问题的是 Agent 自己的命令行也在被匹配的范围里。修法是不让指令出现在 Orca 的参数里,适配器改成从 stdin 喂进去(#117,9 月 20 日合并):
printf '%s' '<instruction>' | orca exec --mode full-auto --output-format jsonl
说好留着的虚拟机,会话一结束就没了
同一轮还有一题丢得更冤:install-windows-3-11,要求装好 Windows 3.11 for Workgroups 并让虚拟机在后台一直跑着,验证器会去连它的 VNC。
轨迹里,Agent 装好了 QEMU、nginx 和 noVNC,用一次 bash 调用启动虚拟机,参数里带着 {"lifetime": "workspace"};然后用 QMP 截了一张 1024×768 的屏幕,确认画面停在 Windows 3.11 的桌面上。会话正常结束,退出码 0。
验证器接着跑,结果是:
AssertionError: QEMU process not found - Windows 3.11 VM is not running
AssertionError: VNC service not listening on port 5901
lifetime: workspace 在 Orca 的文档里写的是:用户明确要求保留的长期服务,比启动它的任务活得久,只能被显式停止。任务取消的路径照这个约定办了,过滤掉 workspace 级的服务;会话结束走的是另一条路径,terminate_all() 把所有 shell 会话一律取消,不看 lifetime。

同一个约定,两条退出路径,只有一条照办了。同一轮里 kv-store-grpc 也是这么丢的:会话里客户端已经和 server.py 来回通过一次,会话一结束,服务跟着没了。
为了不靠 Terminal-Bench 才发现这类问题,这次补了一个直接测约定的探针:通过 bash 调用起一个每秒写一行的服务,等会话结束,再看它还在不在写。修之前两种 lifetime 的结果一模一样:
[FAIL] lifetime=workspace exit=0 terminal=True ticks_at_exit=31 ticks_after=31
[PASS] lifetime=task exit=0 terminal=True ticks_at_exit=31 ticks_after=31
修复和 #114 一起在 #117 里合进去,连 Windows 的 detached job 和 PTY 归属也一并处理了。这个探针进了本地的回归套件,10 月 11 日那一轮里 2/2 通过,kv-store-grpc 也拿到了分。
三周后,同一道题换了一种死法
10 月 11 日用 v0.5.10 跑了下一轮,模型换成 deepseek-flash,思考强度 max。install-windows-3-11 又没拿到分,这次是 NonZeroAgentExitCodeError,退出码 143。
轨迹里,Agent 要关掉一台测试用的虚拟机:
kill $(cat /app/vm/test.pid) 2>/dev/null; sleep 2; pkill -f "win311.img" 2>/dev/null; ...
这条工具调用自己先以 143 结束,因为 pkill -f 匹配到了正在执行它的那个 shell。与此同时,整个 trial 也结束了。Harbor 是用 sh -c 执行适配器拼出来的这段命令的:
if [ -z "${SSL_CERT_FILE:-}" ] && ... ; then export SSL_CERT_FILE=...; fi; \
printf '%s' 'Run Windows 3.11 for Workgroups ... You image is in `/app/isos/win311.img` ...' \
| orca exec --mode full-auto --output-format jsonl 2>&1 | tee /tmp/orca-trajectory.jsonl
指令已经不在 orca 的参数里了,可它还在这个 sh 的参数里,而指令里有 /app/isos/win311.img。同一个 pkill -f "win311.img",把 Harbor 的这层包裹 shell 也杀掉了。Agent 当时还在生成,轨迹停在 326 个推理增量上,后面没有工具调用。验证器 2 项通过、2 项失败:QEMU 已经在跑,缺的是 VNC 和程序化的键盘输入。
#114 的修复把指令从 Orca 的 argv 里挪了出来,挪进了上一层进程的 argv。只要还在某个进程的命令行里,pgrep -f 和 pkill -f 就看得见。

这一条记成了 #126,写这篇时还开着。issue 里建议的修法是让指令不出现在任何进程的 argv 里:适配器先把指令写进容器里的文件,再 orca exec < /tmp/orca-instruction.txt;或者经由执行时的环境变量传进去,printf '%s' "$ORCA_INSTRUCTION" | orca exec。环境变量的值不在 /proc/<pid>/cmdline 里,pkill -f 匹配不到。
88 道题拆开看
这一轮的分数有三个数字,口径各不相同。
Harbor 自己的均分是 0.7273(64/88):所有排上的 trial 都算,没产出分数的记 0。只算产出了分数的 trial,是 0.8205。把 10 个环境类失败逐题重跑一遍,按验证器通过计入,是 71/88,即 0.8068。
没通过的 24 题按原因分开:
- 10 题 Agent 超时,验证器也没过,是真没做完。另有 4 题同样超时,但验证器全绿,Harbor 照样给了 1 分:
tune-mjcf4 项全过,crack-7z-hash、gpt2-codegolf、model-extraction-relu-logits也一项没挂。这 4 题的活已经干完了,Agent 却一直干到 900 秒被叫停。 - 3 题没有任何异常,验证器不过,是能力问题。
- 6 题环境启动超时,容器 1200 秒没起来。安静地单独重跑,5 题通过,第 6 题变成了验证器超时。
- 4 题验证器超时,其中 3 题卡在
pip download下载 torch 和 nvidia 的轮子上。重跑后 2 题通过,filter-js-from-html的验证器跑完是真实的 0 分,还有一题在本机 900 秒内仍然装不完那些轮子。 - 1 题就是上面的 exit 143。

环境启动超时还有一个跑完才看到的后果。这一轮下午 4 点 02 分结束,两三个小时之后,6 个启动超时的 trial 里还有 4 个的容器在跑:
hf-model-inference__4juevue__env-main-1 Up 2 hours
cobol-modernization__ftqtusb__env-main-1 Up 2 hours
mteb-leaderboard__uhher8q__env-main-1 Up 3 hours
pytorch-model-recovery__7gg8pvu__env-main-1 Up 3 hours
启动超时的路径把容器留在原地,它可能还在下载、还在构建。6 个并发跑上三四个小时,这些容器一直占着 CPU、内存和网络,后面启动的 trial 就更容易撞上 1200 秒的启动预算。这一条记成了 #129,处理办法是每轮前后按 trial 的命名模式回收容器,把数量写进报告。
这一轮和 9 月 19 日的 0.7955 不能直接比。上一轮路由到 deepseek-v4-pro,这一轮是 deepseek-flash,评测台账里把这一行标成了 deepseek-flash 的第一个基线。
评测先产出的是 bug
把评测的东西收进仓库,是 9 月 20 日的事(#116)。在那之前,几轮评测已经找出了一批真实的产品缺陷:重复的工具调用 id 让会话中断、SIGINT 和 SIGTERM 结束进程却不留终止记录、bwrap 在某些工作目录下从不被探测、并发的 orca trust 操作丢决定,再加上这次的 #115。可产出这些发现的东西都在仓库外面:jobs/ 被 gitignore,探针脚本从没提交过,报告是一台机器上没提交的文件。换一个干净的检出,复现不了任何一个数字,也看不到趋势,修好的缺陷悄悄回归也没有东西拦着。
于是可复用的那一半进了仓库:scripts/eval/ 下 25 个本地套件和 Terminal-Bench 的辅助脚本,一个能按脚本注入故障的 mock provider(截断一次、重复工具调用 id、522 一次、空闲卡住一次),以及一份评测台账。原始轨迹和容器日志动辄 GB 级,还带着工作区内容,留在仓库外。
台账开头写的是:协议比数字重要。两行只有在源码提交、执行框架、模型、尝试次数和并发、题目集合都一致时才能比较。每个发现最好留下一个常驻的检查,比如 #115 留下的就是上面那个 lifetime 探针。
10 月 11 日那一轮也说明,探针自己会过期。本地 26 个运行里唯一的红点是 linux_shell_probe.py:它断言一个裸的 debian:bookworm-slim 容器里能跑 orca exec,可 v0.5.8 把 HTTP 客户端迁到了 reqwest 0.13,只用系统信任库,没有 CA 证书的容器会直接报错。这是有意的改动,Terminal-Bench 的适配器也已经随二进制带上了 certifi 的证书包,过期的是探针。另一条是 exit_code_probe.py,把一次审批路径的失败标成了「blocked by #85」,而 #85 是一个早已关闭、和审批无关的工作流问题,这个注解会误导下一轮排查。两条都单独记了 issue。
这道题连着两轮丢分
报告里列的下一步,按顺序是:改适配器传指令的方式,单独重跑 install-windows-3-11 验证;更新两条过期的探针;每轮前后回收容器,给要下载 torch 的两题加验证器超时或预热 pip 缓存。再往后才轮到超时:10 题到 900 秒还没做完,另外 4 题做完了却没停,一直耗到被叫停。这 4 题这次拿到了分,是因为验证器检查的是最后的状态,它们没有在多出来的时间里把做好的东西改坏。
install-windows-3-11 已经连着两轮丢分。9 月 19 日,Agent 做完了,验证器开始检查之前,虚拟机被会话收走;10 月 11 日,Agent 还没做完,就被自己那条 pkill 连着外层 shell 一起带走。两次决定结果的,都是模型之外的东西。下一轮要看的,是把这些都排除之后,它能不能把这道题做完。
Keep Reading