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

267 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 实测记录(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 为准再对齐一次。