本节看 Codex 这一侧。关键问题是:为什么 companion 要接 app-server、thread、turn,而不是自己实现 agent loop。
Codex app-server 是什么位置?
在这套桥里,Codex app-server 是目标 runtime 的入口。Claude 和 Node companion 都不直接执行 agent 推理,真正的任务执行交给 Codex app-server。
flowchart TD A[Node companion] --> B[connect Codex app-server] B --> C{是否 resume?} C -->|否| D[thread/start] C -->|是| E[thread/resume] D --> F[turn/start] E --> F F --> G[Codex agent loop] G --> H[工具调用 / 文件读写 / 命令执行] H --> I[finalMessage]
Thread 和 Turn 要怎么理解?
可以用聊天软件类比:
| 概念 | 类比 | 在这里的作用 |
|---|---|---|
| thread | 一个持续会话 | 保存 Codex 对某个任务的上下文 |
| turn | 会话里的一次输入输出 | 执行一次新的任务请求 |
| finalMessage | 本轮最终回复 | 返回给 companion,再回到 Claude |
| threadId | 会话 ID | 用于后续 resume |
| turnId | 本轮 ID | 用于日志和追踪 |
为什么 resume 必须依赖 threadId?
因为真正保存 Codex 上下文的是 Codex thread,不是 Claude 的聊天记录。--resume 的核心不是“把上一轮文本复制一遍”,而是找到之前任务留下的 threadId,然后让 Codex app-server 继续那个 thread。
flowchart LR A["/codex:rescue --resume"] --> B[companion 查找最近 job] B --> C[取出 threadId] C --> D[thread/resume] D --> E[turn/start 新请求]
sandbox 为什么放在 Codex 侧?
进入 Codex runtime 后,读写文件、跑命令、是否能改 workspace,主要由 Codex 自己的 sandbox 和权限策略决定。Claude Code 层只是发起者,不应该假定自己的权限规则自动覆盖 Codex。
flowchart TD A[Claude Code settings] --> B[约束 Claude 自己的工具] C[Codex config / sandbox] --> D[约束 Codex runtime] B -.不是同一层.-> D
这也是 --write 重要的原因:它会把任务从偏只读调查切换成允许 workspace 写入的 Codex 执行模式。
app-server 方案的收益
接 app-server 带来的收益:
- 可以复用 Codex 已有登录态。
- 可以复用 Codex config.toml。
- 可以复用 Codex 的 thread/turn 语义。
- 可以让任务天然支持 resume。
- 可以让插件不必重写 agent loop。
这就是低侵入设计:桥不复制 Codex,只把请求送到 Codex 已有入口。