| name | tb4-five-arm |
| description | 在 Terminal-Bench 4.0 上做 codex harness 五臂对照(裸 codex / 原生 /goal / LoopX 三模式)。复用 SWE-Marathon 那套驱动,靠 WEN_BENCH 切换。重点是 TB4 相对 SWE-Marathon 的四处结构性差异——每一处踩错都**退出码 0、有轨迹、有分数**,只是分数不对。 |
Terminal-Bench 4.0 五臂对照
五臂定义、LoopX profile 装配、agent 类、监控工具全部与
[[swe-marathon-five-arm]] 相同,先读那一份。这里只写 TB4 特有的部分。
对照维度仍是 harness 不是模型:五臂的模型、effort、工具面、沙箱、容器一致。
切换方式
export WEN_BENCH=tb4
source env.sh
./scripts/prebuild_images.sh music-harmony
./scripts/verify_envs.sh music-harmony
./scripts/canary_timeout.sh
./scripts/marathon_all.sh --dry
benchmark 相关的量全在 scripts/bench/tb4.sh,驱动脚本里没有任何 TB4 常量。
WEN_BENCH 不设时默认 swe-marathon,行为与引入 bench 层之前逐字一致
(已用 bash -x 逐参数 diff 验证过)。
数据来源
terminal-bench/ 是 harbor-framework/terminal-bench 的 tag v4.0.0
(commit 452bf305c6),66 个任务,见 terminal-bench/VERSION。
- 不能用
harbor run -d terminal-bench@4.0:harbor 的注册表(Supabase
dataset 表)里只有 terminal-bench@2.0,实测查 4.0 返回 null。
- 不要跟 main:上游 README 明说 "will be continuously updated",
跟 main 跑出来的结果不可复现。
tasks/dataset.toml 里每个任务有 sha256 digest,task.toml 与 tests/
一个字节都不能改。要注入环境变量走 harbor 的 --ve / --ae。
TB4 vs SWE-Marathon:四处结构性差异
| 项 | SWE-Marathon | TB4.0 |
|---|
agent.timeout_sec | 3600–36000 各异 | 全部 28800(统一 8h) |
verifier.environment_mode | 无(shared) | 全部 separate |
network_mode | 三态,逐任务 | 一个都没声明 → harbor 默认 public |
| 每任务镜像数 | 1(environment/) | 2(environment/ + tests/) |
| 连续分 | metrics.json 有 | 没有,只有二值 reward |
排除 3 个要 H100 的任务(fp8-rmsnorm-gemm / jax-speedrun-gpu /
math-eval-grader),本机是 4090D。剩 63 个,其中 11 个是多容器
(environment/docker-compose.yaml),1 个(medical-claims-processing)
还要 MCP server(playwright,sse http://playwright-mcp:3080/sse)。
坑(都会静默通过)
一、最毒的一个:WEN_BENCH 用前缀赋值
WEN_BENCH=tb4 source env.sh
export WEN_BENCH=tb4; source env.sh
bash 在 source 返回后会把前缀赋值的变量还原成未设置,但 WEN_TASKS_DIR
是 export 的、留了下来。于是后续任何脚本自己 source env.sh 时:WEN_BENCH 空 →
回退 swe-marathon profile,却继承着 TB4 的任务目录 —— 拿 TB4 的任务、套
marathon 的网络策略(from-task 找不到 network_mode 就断网)、
开着判官注入、写进 marathon-full/。退出码 0,全程无警告。
env.sh 里有个一致性闸门专门拦这个(WEN_TASKS_DIR_BENCH 与当前 bench 不符
就丢弃继承值),但闸门只保证状态自洽,不保证是你想要的那个 bench。
开跑前看一眼各脚本打印的 bench: 那一行。
二、网络策略反了 → 66 个任务全跑成断网
TB4 的 66 个 task.toml 一个都没有声明 network_mode / allow_internet,
而 harbor 0.20.0 的 NetworkPolicy.network_mode 默认是 PUBLIC ——
上游的标定条件是联网。
而 marathon_run.sh 原本的做法是 grep task.toml 的 network_mode、
找不到就回退 no-network。直接套用会把 66 个任务全跑成断网:
退出码 0、有轨迹、有分数,只是分数偏低,看着像"模型不行"。
bench/tb4.sh 里 BENCH_NET_POLICY=public 显式钉死,不走那条 grep。
另注:--allow-agent-host 在 public 下是空操作。 实测 harbor 会打印
UserWarning: Run-specific allowlist host(s) ['<model-gateway>', '<container-gateway>'] are
ignored because the effective network policy is public.
模型端点的可达性靠代理环境变量和宿主机路由,不靠这个参数,别以为加了就生效。
再注:本机实测容器不穿代理也能出网(docker run 里直连 pypi.org 得 200)。
代理注入是沿用 marathon 对 public 任务的既有做法、属冗余保险;
no_proxy 已包含模型网关,不会劫持 codex 的调用。
三、environment_mode = "separate":两个镜像、两个环境
全部 66 个任务都是 separate。harbor 0.20.0 的实际时序
(harbor/trial/single_step.py:38-55、trial/trial.py:610-680):
跑 agent → 上传 agent 日志 → 收 artifacts → **停掉 agent 环境**
→ 起一个从 tests/ 构建的独立 verifier 环境 → 验证 → 停
三个后果:
-
每个任务两个镜像(66 × 2 = 132)。prebuild_images.sh 原来只扫
environment/,漏掉 tests/。漏了的话 verifier 镜像会在运行期首次构建,
而运行期没有代理(那是刻意的,代理进运行时容器会破坏隔离),dockerd 自己钉的
<dead-dockerd-proxy> 又是死的 → apt-get 超时 → verifier 起不来记 errored。
症状极像"任务没做出来":agent 阶段完全正常、有轨迹、有 token 消耗。
现在由 BENCH_IMAGE_DIRS=(environment tests) 覆盖,且跳过会计数。
-
只有声明在 artifacts 里的文件能跨到 verifier(66 个全声明了)。
agent 把活干在别处、没写到声明路径 → verifier 看到空目录 → reward 0,
而 agent 轨迹完全正常。这是 shared 模式下不存在的失败模式。
-
串行,所以每 trial 的容器/网段峰值不翻倍,但多一次构建、
多一个 compose project。
四、二值 reward 在紧预算下没有区分度,而 TB4 没有连续分
SWE-Marathon 靠任务自写的 metrics.json(partial_score / pass_rate /
pytest / gates_*)补救;TB4 不写这个文件,只有 harbor 的二值 reward。
bench/tb4.sh 设 BENCH_HAS_PARTIAL=0,于是:
_partial.py 整体跳过并打印原因,不静默兜底
_compare.py 整列不渲染 partial,而不是渲染成一列 0.0
(一列 0.0 会被读成"全都没得分",比不显示更坏)
_compare.py 的跨跑次对照自动改用 reward 做差,表头跟着变
marathon 那轮实测:撞死线的 23 条 trial 无一得分,自己收尾的 14 条中 11 条得分
——决定分数的是"能不能在预算内做完"。TB4 统一 8h 预算而我们的死线远短于此,
这个问题只会更严重。唯一还能分辨的信号是终止原因(自己收工 vs 撞死线),
_receipts.py 有这个数据,报表必须并排给出。
五、超时倍率不要照抄 marathon
marathon 用的是 build 6× / setup 3× / verifier 4×,那是为未做资源标定的
Dockerfile 定的。TB 4.0 的卖点恰恰是"重新标定了 time/CPU/memory"
(build_timeout_sec 中位数 900、verifier.timeout_sec 中位数 600),
照抄 6× 等于把上游的标定压掉。bench/tb4.sh 用 4 / 3 / 2。
六、canary 的预算比例要重算
canary_timeout.sh 的 CANARY_TIMEOUT_MULT=0.05 是按 8h 声明预算定的
(8h × 0.05 = 24 分钟 > GOAL_TIMEOUT_SEC 5 分钟,保证我们先到)。
TB4 恰好也统一 8h,所以同一个值仍成立。换到声明预算短的 benchmark 上,
harbor 侧可能反而先到,冒烟就白做了 —— 而那是静默的:
五臂照样出 result.json,只是走的是另一条超时路径。
七、全量的量级要先量再定
63 任务 × 5 臂 = 315 trial,外加 126 个镜像构建。
MARATHON_AGENT_TIMEOUT_MULT 沿用 marathon 的 0.3 得 8640s(2.4h)/trial。
先拿冒烟的真实单 trial 墙钟再算总时长,别直接开全量。
网段池是硬约束:默认约 32 个 bridge 网络,机器共用。起跑前确认池子有余量
(marathon_all.sh 有 MARATHON_NET_CAP 闸门),清理容器时连 compose 网络
一起清(docker rm -f 不删网络)。
八、11 个多容器 + 1 个 MCP 任务未验证
冒烟只覆盖单容器路径。ctr-optimization cumulative-layout-shift
freight-dispatch-shift heat-pump-warranty intrastat-meldung
kv-live-surgery legacy-utility-triage live-database-cutover
medical-claims-processing nextjs-performance payments-pipeline-fix
容器数与内存压力显著更高;medical-claims-processing 还要
MCP server 起得来。全量前单独验这 11 个。
产物
tb4-full/<task>/<arm>/<stamp>/<arm>/result.json job 级,含 stats
/<trial>/verifier/ reward.txt
/agent/ trajectory.json / goal_receipt.json
tb4-full/.claims/<task>__<arm>.claim
tb4-jobs/ marathon_run.sh 单独跑的落脚点
verify-envs-tb4/ canary-timeout-tb4/
成功判据与 marathon 相同(scripts/_is_done.py,驱动与监控共用):
n_completed_trials >= 1 且 n_errored_trials == 0