Files
codex-app-server/TEST-REPORT.md
T
2026-10-26 00:11:40 +08:00

16 KiB
Raw Blame History

实测记录(2026-10-25,VM,codex-cli 0.164.0-alpha.9)

环境

  • 本机已有官方 codex:/opt/hatch-image/bin/codex,codex-cli 0.164.0-alpha.9
  • 无 cargo/rustc,2 核、可用内存约 0.5GB:从 Rust 源码现编不现实, 因此“照搬”走的是官方二进制自带的 app-server(即开源 core 编译产物), 而不是在本机重新编译一遍源码。协议 schema 同样由该二进制导出。
  • codex doctor:除“active model provider auth env var is missing”外 其余健康;MCP 有可选配置问题(缺 env var 的 server)。

协议导出

  • codex app-server generate-json-schema --out … 成功,导出 2.6MB, 本仓库只收了两个合并文件:protocol/codex_app_server_protocol.schemas.json 与 protocol/codex_app_server_protocol.v2.schemas.json。
  • 从 v2 schema 确认的方法:initialize、initialized、model/list、 thread/start、thread/resume、thread/fork、thread/list、thread/archive、 thread/delete、turn/start、turn/steer、turn/interrupt、turn/completed。
  • ThreadStartParams:cwd、model、modelProvider、sandbox、approvalPolicy、 approvalsReviewer、baseInstructions、developerInstructions 等。
  • TurnStartParams:threadId、input(text)、model、effort、cwd、 parentTurnId、rootTurnId(委派链路用)、sandboxPolicy、approvalPolicy 等。
  • features:multi_agent 为 stable/true,multi_agent_v2 为 stable/false。

客户端链路实测

隔离 CODEX_HOME 跑 client.py(见 README):

  • initialize:成功。
  • thread/start:成功,thread id 如 01a17394-…,cwd 正确落到 work/job-a。
  • turn/start:成功,收到 turn/started 与 userMessage 事件。
  • 模型调用:失败,401 Unauthorized: Missing bearer token, 重试 5 次后 turn/completed 状态 failed,耗时约 17 秒。
  • 结论:协议、线程、回合、事件流、错误回推全部走通; 唯一缺的是模型登录,这一步在隔离目录里本就没有, 在已登录的机器上不需要改任何代码。

宿主编排实测(2026-10-25,host.py,中转站 gpt-6.1-sol,low 档位)

测试仓:work/host-repo,git 初始化,sample.py 只有 hello()。 隔离 CODEX_HOME(/tmp/codex-host-test,配了中转站 config), 配对二进制 bin/codex。

  • 并行:parallel 一次派 host-a(加 add2)和 host-b(加 mul2), 单个 app-server 进程内两个 thread 并发,约 70 秒双双 completed, 各自 worktree 里的 sample.py 正确,互不污染。 首跑曾因隔离目录缺中转站 config 而 401 到 api.openai.com, 补 config 后重跑通过——这是测试配置问题,不是 host 代码问题。
  • 评审:review host-a 给出 M sample.py + 完整 diff。
  • 委派:delegate --parent-task host-a 子任务只读检查 add2, 结论列了 NaN/inf、字符串拼接、TypeError 等边界,未改文件; --report-to-parent 后主 thread 汇总了该结论。
  • fork / 续跑:fork host-a -> host-a-v2 拿到新 thread id; run host-a 续跑一回合“一句话说 add2 是干嘛的”成功。
  • 目标循环:goal goal1 目标是创建 result.txt(内容 goal-ok), 第 1 轮验收 test -f result.txt 通过,状态 goal-met。
  • 合并:merge host-b 先在 worktree 提交、再 --no-ff 合并, 主仓 sample.py 已含 mul2,worktree 已清理。 注意:merge 前必须先提交 worktree 改动,否则分支本身还是 旧 HEAD,合不到东西——这个 bug 在实测中发现并已修。
  • 自动化:automations --add daily-check + run-due, 到期任务自动派发并 completed,进评审队列。
  • 状态:host-state.json 当前任务:host-a completed、 host-b merged、host-a-review completed、host-a-v2 forked、 goal1 goal-met、daily-check-* completed。

宿主编排第二批对齐试验(2026-10-25 晚,中转站 gpt-6.1-sol,low 档位)

上一批测的是“能跑起来”,这一批测的是“边界和控制对不对齐”, 全部通过,只有一个协议坑被挖出来并已修成 host.py 的命令:

  • 沙箱边界:--sandbox read-only 的任务让 agent 建文件, agent 回答“当前工作目录是只读文件系统”,文件确实没建出来。 边界不是摆设,是真拦住了。
  • 审批 deny:--approval-policy untrusted --approval deny, agent 要跑 echo approval-test-123,宿主 deny 后 agent 明确说 “自动审批拒绝了”,没拿到输出。注意 host.py 里审批回答的 payload 是按实测反推的 {"decision": "deny"},这条路是通的。
  • 模型内子代理:3 个子代理并行检查 sample.py,安全 / 质量 / 测试覆盖三份视角汇总回来,未改任何文件,结论还点名了主仓 已合并的 mul2 和 0% 测试覆盖——子代理不是走形式。
  • 中断:长诗任务跑了 5 秒后发 turn/interrupt,turn/completed 状态直接是 interrupted,不用等它写完。
  • compact:直接对旧 thread 调 thread/compact/start 会报 thread not found,先 thread/resume 再 compact 就通了。 这是协议层面的先后顺序,host.py 已加 compact 命令, 内部就是 resume + compact,不让用户踩这个坑。
  • 合并冲突:conflict-a(hello-a)和 conflict-b(hello-b)改同一行, 先合 a 成功,主仓变 hello-a;再合 b 时 host 打印“合并冲突, 已 abort”,主仓仍是 hello-a 且干净,b 的 worktree 留着等人工解。
  • 自动化幂等:给 skip-test 设 interval 86400、last_run 为现在, run-due 没派发它。同一轮里到期的 daily-check 被派发了, 但那次调用我没传中转站 key,turn 401 失败,已清理掉那个 误建任务和 worktree——这是测试调用失误,不是 host 逻辑问题。

宿主编排第三批对齐试验(2026-10-25 晚,ChatGPT 宿主能力)

这一批测的是 ChatGPT 宿主最有区分度的几件:宿主给工具、 宿主换模型、运行中改向、多任务并行上限、评审的未追踪文件、 以及失败时宿主是否稳得住。

  • 宿主提供 MCP 工具(最重要的一条,带一个真修): 新写了 examples/mcp_echo.py(零依赖 stdio MCP,两个工具), 在隔离 CODEX_HOME 里注册为 [mcp_servers.echo]。 工具确实被 agent 发现了(工具名带 mcp__echo__ 前缀),但前两轮 都被审批拦住:第一轮 approval-policy=never 时直接报 “MCP tool call requires approval”;第二轮 untrusted + host 自动 放行时仍报 “user rejected MCP tool call”。 查 rollout 才找到根因:MCP 审批走的不是通用 approval, 而是 mcpServer/elicitation/request(mode=form),必须按 MCP elicitation 协议回 action=accept/decline + content, 回通用 {"decision": ...} 会被当成拒绝。 host.py 已按这个协议分流回答,第三轮 mcp-test3 成功: agent 拿到了固定事实和 20+22=42,且未改文件。 结论:宿主给工具这条路是通的,但审批 payload 不能一刀切。
  • 模型切换:同一 thread host-a(原 gpt-6.1-sol)用 --model gpt-6-sol 续跑一回合,turn 的 model 字段确实是 gpt-6-sol,回答正确——宿主可以在 thread 内换模型。
  • 运行中改向(steer):长诗任务跑 5 秒后发 turn/steer (expectedTurnId 校验),turn 完成时已按新指令只剩 4 行短诗, 不是等旧任务跑完再开新回合。
  • 3 并行:host-c/div2、host-d/pow2、host-e/mod2 三个 worktree 同时跑,全部 completed,文件各自正确、互不污染。
  • 未追踪文件评审:review goal1 时 diff 为空,但 status 和 untracked 栏都列出了 result.txt——评审不能只看 diff, 未追踪文件要单独看,这一点 host.py 的 review 是对的。
  • 失败不崩宿主:用不存在的模型派发,task 状态为 failed, 错误是中转站 404(模型不可用),host.py 没崩、没卡住, 该任务的 worktree 和分支已清理。
  • 未完成项:另有一项仓库级上下文试验,其工具输出未通过 安全检查,本批报告不采用它的细节、不做结论;上面六项 不依赖它,结论独立成立。

生产级试验(2026-10-25 晚,真实项目仓,不是玩具 sample.py)

试验仓 work/prod-repo:core.py(add/sub)+ features/ 独立模块 + tests/ 标准库 unittest,验收命令就是 python3 -m unittest discover -s tests。 起点 2 个测试,终点主仓 5 个功能、17 个测试全部通过。

  • 并行生产:prod-pow 和 prod-mod 各自建功能文件 + 测试文件, 不碰共享文件,并行完成,各自 worktree 4 个测试通过,互不污染。
  • 目标循环(真实测试验收):prod-safe-div 1 轮通过,验收是 整个测试套件,不是“文件存在”。
  • 评审闭环:prod-parse 先实现了一个 naive 版本,委派只读评审 找出 5 条边界问题,汇总回主任务后,主任务修复为严格校验 (只接受 str、只接受 ASCII 十进制、空串抛错等),测试从 5 个 涨到 8 个,全部通过。评审任务本身没改任何文件。
  • 合并链:pow、mod、parse、safe-div、clamp 依次 --no-ff 合并进主仓,每次合并后主仓测试都跑过,最终 17 个测试 OK。 合并前评审时发现一个缺口并已修:新文件默认 untracked, git diff 里看不到内容,review 现在会把未追踪文本文件内容 也打印出来,否则评审等于没看新文件。
  • 自动化:prod-nightly-tests(只读跑测试)run-due 派发完成, 紧接着再 run-due 一次全部跳过,幂等正确。
  • MCP(生产仓):prod-mcp 在生产仓 worktree 里调用宿主 MCP, 拿到固定事实和 7+8=15,未改文件。
  • 失败与恢复(生产级真正挖出来的东西):
    1. 委派评审第一轮因 shell 超时 300 秒被杀,turn_aborted, 且任务登记只在 turn 完成后才落盘,等于任务丢了。 已修:dispatch / delegate / goal / run-due 都先落盘再跑 turn, 中断后可用 run 续跑。
    2. fork 换 worktree 后沙箱被服务端重置为 read-only,fork 时传 sandbox 参数不认;同 cwd fork 则保留 workspace-write。 已修:turn 级 sandboxPolicy 会覆盖 thread 级设置,run 时按 任务沙箱传,fork 隔离 worktree + 写操作已验证通过。
    3. fork 拷工作区文件时把 .git 文件也拷过去了,新 worktree 的指针指回旧 worktree,git worktree 校验失败。 已修:拷文件时跳过 .git,已坏的 v2 清理、v3 指针已修复。
    4. 目标循环第一版只说“验收未通过”,不告诉 agent 错在哪, prod-retry 3 轮原地不动(测试数一直是 1)。 已修:目标循环把验收命令和失败输出喂给下一轮,且验收命令 本身要打印诊断信息(如 test_count=1, need >=3), prod-retry2 第 1 轮失败、第 2 轮补到 3 个测试、目标达成。
  • 生产级结论:本地、无 GUI 的宿主编排,ChatGPT 宿主在 本地能做的那部分,基本对齐了——并行隔离、测试验收、评审门、 委派修复闭环、合并链、自动化、MCP、模型切换、改向/中断、 失败恢复,都在真实项目上跑通了。 没对齐的仍然是云端和界面那部分:云端派发、推送通知、 GitHub/Slack 触发、CI/PR 集成、GUI inline 评论、Computer Use, 以及一个已知局限:host-state.json 是单文件,多 host.py 进程同时写会有 read-modify-write 竞态,同一进程内的并行 是安全的,跨进程并行没有锁,不要两个 host.py 进程同时跑 同一个 state 文件。

生产加固(2026-10-26,state 锁 / 项目配置 / 定时自动化)

  • state 锁:host-state.json.lock + fcntl 锁,锁只包住读/写 短临界区,不包整个模型 turn;保存时在锁里重读磁盘,按任务名 覆盖、按自动化名合并,tmp 文件带 pid。 实测:两个进程同时加载、睡 0.5 秒、各自加一个任务再保存, 最终两个任务都在,没有合并逻辑的话后保存的会把先保存的覆盖掉。
  • 项目配置:host.py init 生成 host-config.json(repo、 model、effort、沙箱、审批、codex_home、state,无密钥), host.py config 打印有效配置,优先级 CLI > 配置 > 内置默认; dispatch/goal 不传 --repo 时用配置里的 repo,没配则明确报错。 实测:/tmp 测试配置 init、config、repo 缺省路径都通; 项目里默认没有 host-config.json,旧调用方式不受影响。
  • 定时自动化:run-due 现在写 host-run.log(派发和跳过都记), automations --cron-line 只打印 crontab 行,不装机; automations --install-cron 才真正写 crontab 且幂等。 实测:未到期的自动化 run-due 输出为空,但日志里有一条 skip; cron 行已验证格式正确,本次没有实际装机——装机后模型任务 会自动跑、花中转站额度,所以装机这一步留给用户显式下令。 另注意:VM 会被平台回收,crontab 不一定活过回收,常驻机器 用 crontab,VM 上更稳的是 Muse cron 定时调 run-due。
  • 未修 / 已知局限:锁只能防同一台机器上的多进程竞态, 不能防两台机器同时写同一个共享 state 文件;定时任务本身 仍需外部调度器(crontab 或 Muse cron)触发,host.py 自己 不常驻。

补充:原始协议探针(同一晚,隔离目录)

  • model/list 成功:返回 gpt-6.1-sol,推理档位 low/medium/high/ xhigh/max/ultra,multiAgentVersion 为 v2。
  • thread/list 在隔离目录下为空,符合预期(不污染真实线程)。
  • 附带警告(不影响核心链路):本机缺 bubblewrap 时用内置兜底; Code Mode 因缺 codex-code-mode-host 可执行文件而 fail closed, 需该功能时要另装该组件。

工具执行修复(2026-10-25,用户选“先修好工具执行问题”后)

根因是两层缺件,不是中转站问题,rollout 日志里有明文证据:

  1. 缺 code-mode host:系统里的 codex 是裸二进制, /opt/hatch-image/bin 是只读挂载,旁边没有 codex-code-mode-host,app-server 报 “Code Mode is unavailable … host executable was not found”, 工具 fail closed,agent 称“写入工具不可用”。 修复:从官方 npm 平台包 @openai/codex@0.164.0-alpha.9-linux-x64 里取出同版本 codex-code-mode-host(73MB),与 codex 放到同一目录。 注意:host 只认真实可执行文件旁边的路径,放 PATH 里没用; 系统目录只读,所以配对目录只能放 workspace,不能直接补系统目录。
  2. 缺 bwrap:配对后工具能启动了,但写文件仍 3 次 instant failed, rollout 里 panic 明文是 “bubblewrap is unavailable: no system bwrap was found on PATH and no bundled codex-resources/bwrap binary was found next to the Codex executable”。 修复:把系统目录里的 codex-resources/bwrap 也复制到配对目录。

修复后配对目录:bin/codex(293MB)+ bin/codex-code-mode-host(74MB)+ bin/codex-resources/bwrap(502KB),合计约 366MB。 这是本机 VM 的补丁,Mac 上 npm 正常安装自带这三件套,不需要它; start.sh 已改成优先用 bin/codex,client.py 也支持 CODEX_BIN 环境变量指定配对二进制。

修复后实测(中转站,gpt-6.1-sol,effort low,workspace-write):

  • 回合 10.3 秒完成,状态 completed,不是 failed。
  • commandExecution 状态 completed,命令是 printf '%s\n' 'codex-ok' > test-result.txt && cat test-result.txt。
  • 落盘验证:work/xcm-test/test-result.txt 内容确为 codex-ok。
  • 此前失败的两个版本(max 60 秒、配对后无 bwrap 113 秒)均未建文件, 对照说明修的是执行环境,不是 prompt 或模型问题。

未实测

  • 真实模型出结果的一回合(缺登录,VM 里不放密钥)。——已过期: 上面中转站实测已覆盖,见“工具执行修复”一节。
  • ws / unix 传输的多客户端并发(start.sh 已把参数透传给官方 app-server,传输层本身是官方实现的,未在本机多客户端压测)。
  • 审批请求的完整回包字段:各版本间字段有差异,client.py 默认 用 never 绕开审批;on-request + auto/deny 的回包是尽力而为, 生产交互客户端应以本版 protocol schema 为准再对齐一次。