---
title: '同样是 Coding Agent，Codex 和 Claude Code 的 Harness 到底差在哪？Opencode弱在哪？'
description: '从流程、约束、持久化、上下文、工具和运行环境六个维度，基于源码对比 Codex、Claude Code 与 OpenCode 的 Harness 设计、取舍与差距。'
pubDate: 2026-07-22
slug: codex-claude-code-opencode-harness
lang: zh-CN
tags: [harness, agent, 源码解读]
draft: false
---

> 这是一篇运行时对运行时的对照。它不比较 GPT 和 Claude 哪个模型更聪明，而是比较模型在一轮工作中怎样被驱动、受什么约束、从哪里恢复状态、看见什么上下文、怎样调用工具，以及工具究竟在哪个运行环境中落地。

## 先看地图：一轮 Coding Agent 从开始到结束

Harness 可以拆成六个要素：流程、约束、持久化、上下文、工具、运行环境。它们不是六个并列功能页，而是一条会反复循环的执行链。本文沿着任务真实经过的顺序下钻：先决定这一轮怎么走，再加载边界和状态，组装模型输入，处理工具调用，最后在宿主环境执行并把结果写回。

```mermaid
flowchart TD
  S([开始：用户提交“修复 checkout 超时”]) --> F["① 流程<br/>决定单 Agent、委派、继续、重试或结束"]
  F --> C["② 约束<br/>规则、技能、审批、Hook、权限策略"]
  C --> P1["③ 持久化读取<br/>history、summary、附件、项目状态"]
  P1 --> X["④ 上下文<br/>把任务、约束、状态、已选能力装成 prompt"]
  X --> M["模型推理"]
  M --> T{"需要工具或子任务？"}
  T -- 否 --> P2["③ 持久化写回<br/>保存答复与当前状态"]
  T -- 工具 --> U["⑤ 工具<br/>发现、校验、调度、截断、规范化"]
  T -- 子任务 --> F
  U --> R["⑥ 运行环境<br/>PTY、沙箱、工作目录、网络、MCP transport"]
  R --> P2
  P2 --> D{"任务完成？"}
  D -- 否 --> N["下一轮：携带新状态"]
  N --> F
  D -- 是 --> E([结束：交付结果与可审计记录])
  R -. "风险：副作用、网络边界、环境漂移" .-> RR["运行风险"]
  P2 -. "风险：摘要丢失早期证据" .-> PR["状态风险"]
```

### 阅读路线

| 章节        | 在上图中的位置     | 它回答的问题                                       |
| ----------- | ------------------ | -------------------------------------------------- |
| 1. 流程     | 开始、分支、下一轮 | 谁决定继续、委派、重试或停止？                     |
| 2. 约束     | 模型与执行器前     | 哪些项目规则和权限边界必须同时生效？               |
| 3. 持久化   | 读取与写回         | 跨回合究竟保存什么，压缩时又改写什么？             |
| 4. 上下文   | 模型调用前         | 本轮 prompt 怎样由任务、规则、history 和能力组成？ |
| 5. 工具     | 模型调用后         | 工具说明何时入窗，结果如何不撑爆下一轮？           |
| 6. 运行环境 | 工具真正落地时     | shell、网络、MCP 到底在哪个边界内执行？            |

一句话结论：Codex 更倾向把流程、策略、会话与执行环境的开关显式交给宿主；Claude Code 2.1.88 更倾向把探索、工具包装和会话整理封装为默认运行时策略。前者的价值是可配置、可审计，后者的价值是低摩擦、默认路径完整。

## 版本与四层证据边界

| 角色                   | Codex                                                                                                             | Claude Code                                                                                                                        | OpenCode                                                                               |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| 主要证据：解释内部机制 | [Codex 源码备份，ae057e0](https://github.com/aimreant/codex-backup/tree/ae057e0bb9f3cde31c54fcca59a78456f7805282) | [CC 2.1.88 镜像备份，c8cd253](https://github.com/aimreant/claude-code-2.1.88-backup/tree/c8cd253554319f32ff64ff7000636199f720c9bc) | [OpenCode 源码备份，d2bd7ea](https://github.com/aimreant/opencode-backup/tree/d2bd7ea) |
| 辅助证据：解释用户实践 | [OpenAI Cookbook](https://developers.openai.com/cookbook)                                                         | [Claude Code 官方手册](https://code.claude.com/docs/en/how-claude-code-works)                                                      | [OpenCode 官方文档](https://opencode.ai/docs/agents/)                                  |

本文的证据纪律很简单：

- **源码**回答“某段 runtime 实际读了什么、写了什么、走了什么分支”。
- **Cookbook**回答“开发者可怎样把这类能力用于任务设计”。它不证明 Codex 内部一定采用某个 cookbook 模式。
- **CC 官方手册**回答“今天产品向用户承诺什么”。它不反向证明 2.1.88 内部对象仍然相同。
- **OpenCode 官方源码/文档**只证明固定 release 的公开实现或当前可配置行为；Issue 和 PR 一律只是演进信号。
- CC 2.1.88 是从 npm source map 提取的非官方历史快照；其版本背景可参见 [官方仓库记录](https://github.com/anthropics/claude-code/issues/41666)。所有 CC 代码段都只解释历史实现。

## 源码链怎样证明一个 Harness 模块

一段孤立代码通常只能说明“存在一个字段或函数”，不能说明一个模块真正怎样工作。本文每条源码链都只证明一个最小闭环：

| 链路位置    | 要回答的问题                             | 不能越界推断的内容             |
| ----------- | ---------------------------------------- | ------------------------------ |
| ① 读取/恢复 | 状态、规则或请求从哪里来？               | 它不单独证明后续一定执行。     |
| ② 决策/执行 | 什么条件改变路径、预算、权限或委派？     | 它不单独证明结果已写回。       |
| ③ 写回/回传 | 哪个对象回到 history、父线程或外部环境？ | 它不单独证明被保留的信息无损。 |

因此，代码块中的 A/B/C 是同一机制切片，不是完整调用栈。为避免把跨文件片段伪装成可直接编译的程序，块内省略了无关类型、错误处理和参数，且只保留连接三步所必需的语句；每个片段的真实文件和行号都在紧邻段落中列出。相邻的链路 I/O 会用同一任务输入把三步连起来；图中的 ①/②/③ 节点与它们一一对应。

## 1. 流程：一轮任务由谁推进、何时换人

### 模块目标

流程模块的输入是用户任务、当前回合状态和可选子任务；输出是一个明确的下一步：继续模型回合、执行工具、委派子会话、重试或结束。它的核心不变量是：复杂探索可以离开主线程，但回到主线程的必须是可用结论，而不是无限增长的原始过程。

### 用人话说

这像项目负责人安排工作：能直接答的就答，需要查资料的派人查，查完再决定继续还是收工。流程模块要保证“派人”和“回来汇报”都有边界。

### 常用场景

- 修复 checkout 超时前，先并行定位支付入口、测试失败点和最近改动。
- 一条命令失败后，决定缩小查询、改用另一工具，还是终止任务。
- 主线程不应装入陌生仓库侦察产生的全部 grep 命中。

### 模块机制图

```mermaid
flowchart TD
  S([开始：收到任务与当前回合状态]) --> A["① 读取任务、已有状态与委派参数"]
  A --> B{"可由主线程直接完成？"}
  B -- 是 --> C["运行模型回合"]
  B -- 否 --> D{"需要独立探索？"}
  D -- 是 --> E["② 创建子会话，分配 history 与策略"]
  E --> F["③ 子会话返回证据、结论、未解项"]
  F --> C
  D -- 否 --> C
  C --> G{"继续、重试还是完成？"}
  G -- 继续 --> H["把下一动作交给工具或下一轮"]
  H --> I([结束：进入约束与状态读取])
  G -- 完成 --> J([结束：交付最终答复])
  E -. "风险：子任务带入过多 history 或权限" .-> R["隔离失效"]
```

读图结论：流程模块不是“多 Agent 功能列表”，而是控制面。子 Agent 只是其中一条分支，必须在返回主线程前压缩成对主任务有用的证据。

### Codex 源码解读

下面不是完整调用栈，而是一条委派最小闭环：先读取调用方给定的 history 语义，再创建受策略约束的 child，最后把 child turn 的结论交还主流程。片段 A 见 [codex_delegate.rs，第 63–98 行](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/codex_delegate.rs#L63-L98)，片段 B/C 见 [第 105–138 行](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/codex_delegate.rs#L105-L138)（公开源码备份，2026-03-31 快照）。

```rust
// 片段 A：读取。调用者没有指定时，默认是 New history。
let history = initial_history.unwrap_or(InitialHistory::New);

// 片段 B：决策/创建。父级执行策略不是子会话的隐式全权限。
let child = Codex::spawn(CodexSpawnArgs {
    conversation_history: history,
    inherited_exec_policy: Some(inherited_exec_policy),
    config: child_config,
    skills_manager,
}).await?;

// 片段 C：写回。主流程消费的是 child turn 的结果，不是 child 内存。
let result = run_child_turn(child, task).await?;
return Ok(result);
```

| I/O 项       | 链路 I/O：查找支付链路                                                                          |
| ------------ | ----------------------------------------------------------------------------------------------- |
| 输入         | 主线程委派“找出支付入口、签名校验、回调处理文件”，并选用 InitialHistory::New。                  |
| 状态变化     | A 把 history 定义为 New 或继承；B 将该选择与父级策略写入 child；C 等待并取得 child 的完成结果。 |
| 输出         | 主流程得到子会话回传的文件路径、调用顺序和未确认分支。                                          |
| 用户可见结果 | 主线程得到可继续验证的摘要，而不是所有搜索噪声。                                                |
| 误判风险     | New history 也会丢掉业务限定；任务描述模糊时可能查到错误支付链路。                              |

**Cookbook 实践旁证（辅助，不证明上述实现）**：Cookbook 的 [Orchestrating Agents: Routines and Handoffs](https://cookbook.openai.com/examples/orchestrating_agents) 将“按任务转交给另一 routine”作为工作流设计手法。它支持本文的实践建议：只有当子任务能用清晰产物回传时才委派；但它不说明 Codex 内部如何 spawn 子会话。

### CC 2.1.88 源码解读

CC 2.1.88 的相应闭环由运行入口和 Agent 定义共同构成：入口准备子会话，Explore 定义收窄其角色，运行结果再回到父调用。片段 A/C 见 [runAgent.ts，第 85–217 行](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/AgentTool/runAgent.ts#L85-L217)，片段 B 见 [exploreAgent.ts，第 24–56 行](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/AgentTool/built-in/exploreAgent.ts#L24-L56)（非官方历史快照备份）。

```ts
// 片段 A：入口选择 agent definition，并建立 agent session。
const definition = resolveAgent(agentName);
const session = await createAgentSession(definition, input);

// 片段 B：Explore 的可用动作和模型被定义为只读探索。
const exploreAgent = {
  tools: ['Glob', 'Grep', 'Read'],
  model: 'haiku',
  skipClaudeMd: true,
};

// 片段 C：运行器将完成结果格式化后回给 Task 的父调用。
const result = await runAgent(session, definition, prompt);
return formatAgentResult(result);
```

| I/O 项       | 链路 I/O：查找支付链路                                                                  |
| ------------ | --------------------------------------------------------------------------------------- |
| 输入         | 主 Agent 把同一条支付链路侦察任务交给 Explore。                                         |
| 状态变化     | A 为 agent 创建执行状态；B 决定其工具面和模型；C 把运行结果转为父工具调用可消费的产物。 |
| 输出         | 返回文件定位、可能调用链和下一步验证建议。                                              |
| 用户可见结果 | 主会话不被大量目录、grep 命中和阅读过程淹没。                                           |
| 误判风险     | 2.1.88 的 skipClaudeMd 会让探索更轻，却可能漏掉项目专有规则。                           |

**当前官方手册旁证（辅助）**：[Subagents 文档](https://code.claude.com/docs/en/sub-agents) 说明当前产品支持独立上下文、工具和模型配置；它不能证明当前 Explore 仍使用上述工具或跳过规则。

### 实现对比与取舍

| 维度       | Codex                                              | CC 2.1.88                                          | 直接结论                                                                   |
| ---------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------------------------------- |
| 目标达成   | 调用方显式指定 New/继承 history 与执行策略。       | Explore 默认收窄为只读探索，并封装模型路由与提示。 | Codex 保留编排权；CC 2.1.88 缩短仓库侦察的设置路径。                       |
| 可观察性   | spawn 参数直接暴露子会话输入和策略。               | Agent 定义可读，但证据仅覆盖历史快照。             | Codex 更适合复现“子 Agent 为什么这样做”；CC 的当前行为要回到官方手册核验。 |
| 默认自动化 | 主 Agent 必须自行决定怎样拆分和回收任务。          | Explore 默认隔离搜索噪声，并禁止写文件。           | Codex 的代价是调度代码更多；CC 的代价是 Explore 可能跳过项目专有上下文。   |
| 成本与风险 | 并发数、history 模式和回传体积都由调用方负责限制。 | 默认角色降低上手成本，但摘要仍可能遗漏关键证据。   | Codex 适合需要精确控制的并行；CC 适合先建立只读证据，再由主线程执行改动。  |
| 适合任务   | 多个独立验证、需继承指定策略的实现任务。           | 陌生仓库侦察、只读依赖追踪。                       | 改动关键路径前，Codex 与 CC 都应让主线程复查源码或测试结果。               |

### OpenCode：现状与改进路线

| 已具备的公开能力                                                                                                                                                                         | 相对本章目标仍需补强的闭环                                                                                                               | 建议改进方向                                                                                                                                                                                                  |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 官方 [Agents 文档](https://opencode.ai/docs/agents/) 公开了 Task 子 Agent 与工具权限；稳定参考点固定为 [v1.15.11 的 d2bd7ea](https://github.com/aimreant/opencode-backup/tree/d2bd7ea)。 | Task 子 Agent 的公开模型更接近“顺序运行、返回后终止”；多 Agent 并行、可选 fork/new history、结构化 join 不是同一条稳定主链的已证实保证。 | 让 Task 显式携带 history mode、返回 schema 与父级策略；先实现可审计的 join，再引入并行和协作消息。有关团队协作的 Issue 只作为[演进信号](https://github.com/anomalyco/opencode/issues/12711)，不是已发布能力。 |

### 适用场景

| 明确结论：优先选 Codex                                 | 明确结论：优先选 CC 2.1.88                           | 共同警戒                                              |
| ------------------------------------------------------ | ---------------------------------------------------- | ----------------------------------------------------- |
| **要继承架构决策、审计子会话或控制并发时，选 Codex。** | **只读侦察陌生仓库、隔离搜索噪声时，选 CC 2.1.88。** | 子 Agent 的摘要不是证据；关键路径仍要读源码或跑测试。 |

## 2. 约束：哪些规则必须在模型前和执行时都生效

### 模块目标

约束模块将系统指令、项目规则、技能、审批、Hook 与权限策略转成可执行边界。输入是仓库和宿主提供的规则、工具请求和风险目标；输出是受约束的 prompt、允许/拒绝/待审批的动作。核心不变量是：模型看见一条规则不等于运行时会执行它，真正的副作用还需在执行点复核。

### 用人话说

规则像施工图和门禁：施工图告诉工人不能拆承重墙，门禁则在真要进机房时拦下来。只把规则写进 prompt，相当于只贴了提示牌；只在执行时阻拦，又会让模型白做规划。

### 常用场景

- 项目规定只能改 src/payment，不能改数据库 schema。
- 访问 api.example.com:443 前必须让用户单次确认。
- 团队用 Hook 阻止未经测试的提交或生产写操作。

### 模块机制图

```mermaid
flowchart TD
  S([开始：任务与候选动作进入]) --> A["① 读取系统、项目、技能与策略规则"]
  A --> B["把稳定约束提供给上下文装配"]
  B --> C{"模型请求是否触及受控动作？"}
  C -- 否 --> D["保留约束快照"]
  C -- 是 --> E["② 运行时按审批和权限策略复核"]
  E -- 允许 --> D
  E -- 拒绝或待确认 --> F["③ 写回拒绝或审批请求"]
  D --> G([结束：进入状态读取或工具调用])
  F --> G
  E -. "风险：只靠 prompt、策略粒度过宽" .-> R["约束绕过"]
```

读图结论：约束有两次落点：进入 prompt 时影响计划，进入执行器时阻止副作用。本文把前者放在“上下文”的输入中，把后者留在“运行环境”的真实边界。

### Codex 源码解读

Codex 的约束闭环需要同时看“规则怎样进入 session”和“动作怎样在执行点被复核”。片段 A 是 [compact.rs，第 191–223 行](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/compact.rs#L191-L223) 中稳定上下文重建；片段 B/C 是 [network_approval.rs，第 77–127 行](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/tools/network_approval.rs#L77-L127) 的审批与结果分支（公开源码备份）。

```rust
// 片段 A：读取。稳定规则来自 session，而不是只靠模型记住。
let initial_context = sess.build_initial_context(turn_context.as_ref()).await;

// 片段 B：决策。审批对象由目标主机、协议和端口共同组成。
let key = HostApprovalKey { host, protocol, port };
let decision = approval_policy.check(&key);

// 片段 C：写回或执行。AskUser 产生事件，不等同于放行。
match decision {
    Allow => execute_request(),
    AskUser => request_network_approval(key),
    Deny => deny_request(),
}
```

| I/O 项       | 链路 I/O：首次访问 api.example.com:443                                           |
| ------------ | -------------------------------------------------------------------------------- |
| 输入         | 模型要求请求 https://api.example.com:443/v1/status，策略中尚无预授权。           |
| 状态变化     | A 恢复稳定约束；B 把请求解析成审批键；C 将策略结果落实为执行、拒绝或待审批事件。 |
| 输出         | 请求执行、返回拒绝，或生成等待用户确认的 approval。                              |
| 用户可见结果 | 用户可针对具体目标确认，而不是笼统地“允许联网”。                                 |
| 误判风险     | 重定向、子域名和不同端口不自动继承这一次授权。                                   |

**Cookbook 实践旁证（辅助）**：[OpenAI Cookbook 目录](https://developers.openai.com/cookbook) 中的工具与 agent recipe 可用于设计 guardrail 与人工确认工作流；截至本文查阅，没有一篇直接描述 Codex 的 host/protocol/port 审批键，因此不以 cookbook 取代源码。

### CC 2.1.88 源码解读

CC 2.1.88 的机制链由 context 规则来源、MCP 包装的权限调用与 client 调用分支共同组成。片段 A 见 [context.ts，第 152–188 行](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/context.ts#L152-L188)，片段 B/C 见 [MCPTool.ts，第 27–75 行](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/MCPTool/MCPTool.ts#L27-L75)（非官方历史快照备份）。

```ts
// 片段 A：读取。项目规则和记忆先聚合为执行上下文。
const context = await buildContext({ messages, claudeMd, memory });

// 片段 B：决策。MCP 包装把工具名和参数送进外层权限链。
const permission = await context.canUseTool(this.name, input);

// 片段 C：写回或执行。拒绝被透传，允许后才调用服务。
if (!permission.allowed) return permission.result;
return this.client.callTool(this.serverName, this.toolName, input);
```

| I/O 项       | 链路 I/O：首次访问 api.example.com:443                                         |
| ------------ | ------------------------------------------------------------------------------ |
| 输入         | MCP 工具参数含 api.example.com 目标。                                          |
| 状态变化     | A 聚合可用规则来源；B 调用通用权限链；C 把拒绝回传或将获准请求交给 client。    |
| 输出         | 允许时执行；否则把拒绝或待确认结果回传。                                       |
| 用户可见结果 | 权限交互由上层策略统一呈现，而不是由每个 MCP 工具自行决定。                    |
| 误判风险     | 此片段不能证明 CC 2.1.88 采用 host/port 粒度；它只证明存在先判定后调用的边界。 |

**当前官方手册旁证（辅助）**：[Permissions](https://code.claude.com/docs/en/permissions) 与 [Hooks](https://code.claude.com/docs/en/hooks) 说明当前用户可配置的权限和生命周期约束；不要把它们的当前配置名外推回 2.1.88。

### 实现对比与取舍

| 维度       | Codex                                       | CC 2.1.88                                           | 直接结论                                                                        |
| ---------- | ------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------- |
| 目标达成   | 审批键直接包含 host、协议和端口。           | MCP 工具将请求交给统一的外层权限链。                | Codex 便于按网络目标最小授权；CC 便于复用团队已有的 MCP 策略。                  |
| 可观察性   | 审计记录可直接对应到目标 host、协议和端口。 | 可看到是否经过 `canUseTool`，细粒度由外层策略决定。 | Codex 更容易回答“放行了哪个地址”；CC 必须继续检查外层规则是否足够细。           |
| 默认自动化 | 拒绝、询问、允许由显式策略分支决定。        | MCP 工具复用同一套权限判断。                        | Codex 每个目标都可单独确认；CC 减少重复判断，但共享策略会同时影响所有匹配工具。 |
| 成本与风险 | 细粒度规则带来更多配置与确认。              | 宽泛的外层规则会扩大每个匹配 MCP 的权限。           | Codex 的代价是操作更慢；CC 的风险是一次宽放行覆盖多项能力。                     |
| 适合任务   | 生产网络访问、最小授权、需留痕的团队。      | 已有统一 MCP 权限层的工作流。                       | 涉及写操作或重定向时，Codex 与 CC 都要按真实目标重新判定权限。                  |

### OpenCode：现状与改进路线

| 已具备的公开能力                                                                                   | 相对本章目标仍需补强的闭环                                                                                      | 建议改进方向                                                                                                                               |
| -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| [Agents 文档](https://opencode.ai/docs/agents/) 公开 allow、ask、deny 和基于工具名模式的权限配置。 | 公开配置能说明“允许哪个工具”，但本文选取的 Codex 对照还要求把项目规则、目标级能力和执行审计形成同一条可追踪链。 | 将项目规则编译为可引用的约束快照；将高风险网络/MCP 调用发成含目标、理由和结果的 capability event。不要把工具 wildcard 当作环境级最小授权。 |

### 适用场景

| 明确结论：优先选 Codex                               | 明确结论：优先选 CC 2.1.88                          | 共同警戒                                            |
| ---------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- |
| **要按目标审计网络访问、显式处理审批时，选 Codex。** | **已有统一 MCP 权限与 Hook 体系时，选 CC 2.1.88。** | 规则不能只写进 prompt；拒绝宽通配符和生产默认放行。 |

## 3. 持久化：跨回合留下什么，窗口满时改写什么

### 模块目标

持久化模块负责从 session 中恢复可继续工作的状态，并在一轮完成后写回新的消息、工具结果、摘要和附件关系。输入是已存 history 与当前回合产物；输出是下一轮能读取的状态。核心不变量是：压缩可以是有损的，但必须保住当前目标、标记边界，并让稳定规则不只寄托于摘要。

### 用人话说

这像交接班。下一班不需要重看所有监控录像，但必须知道事故还没结束、已经排除了什么、报告附件在哪里。压缩不是删除历史的借口，而是写一份能继续干活的交接单。

### 常用场景

- 120 轮排障后又出现一批测试日志。
- 用户隔天回来继续同一个修复任务。
- 长会话里既有文本、工具输出又有附件。

### 模块机制图

```mermaid
flowchart TD
  S([开始：① 读取 session history 与附件]) --> A{"窗口是否足够？"}
  A -- 足够 --> B["恢复原 history"]
  A -- 不足 --> C["选择旧 history、近期目标、附件"]
  C --> D["② 生成 compact summary"]
  D --> E{"摘要请求是否超窗或失败？"}
  E -- 否 --> F["重建 boundary、summary、保留消息与附件"]
  E -- 是 --> G["退避：裁短输入后重试"]
  G --> C
  B --> H["写入本轮新消息与工具结果"]
  F --> H
  H --> I([结束：③ 保存下一轮可恢复状态])
  F -. "风险：早期反例被摘要吞掉" .-> R["状态失真"]
```

读图结论：持久化不是数据库名词，而是让下一轮能继续做对事的状态迁移。压缩策略的关键在于保留对象、失败退避与边界标记。

### Codex 源码解读

Codex 的持久化闭环由 history 快照、超窗退避和 replacement history 重建组成。片段 A/C 见 [compact.rs，第 191–223 行](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/compact.rs#L191-L223)，片段 B 见 [第 154–230 行与 324–389 行](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/compact.rs#L154-L230)（公开源码备份）。

```rust
// 片段 A：读取。先取得本轮可替换的 session history 快照。
let history_snapshot = sess.clone_history().await;
let history_items = history_snapshot.raw_items();

// 片段 B：决策。compact 自身超窗时，按最早项退避再重试。
if turn_input_len > 1 {
    history.remove_first_item();
    continue;
}

// 片段 C：写回。summary、近期用户目标与重建规则组成新 history。
let new_history = build_compacted_history(Vec::new(), &user_messages, &summary_text);
sess.replace_history(new_history).await;
```

| I/O 项       | 链路 I/O：120 轮排障后再次超窗                                                                       |
| ------------ | ---------------------------------------------------------------------------------------------------- |
| 输入         | history 含 120 轮消息和工具结果；最新请求是“继续验证支付回调是否重复扣款”。                          |
| 状态变化     | A 读取当前可替换状态；B 对过长摘要请求删除最早项并重试；C 用摘要和保留对象写回 replacement history。 |
| 输出         | 生成更短 replacement history，或向上层返回最终失败。                                                 |
| 用户可见结果 | 会话通常可继续，当前目标更可能留在新状态中。                                                         |
| 误判风险     | 保住最新目标不等于保住早期日志中的关键反例。                                                         |

**Cookbook 实践旁证（辅助）**：[OpenAI Cookbook 目录](https://developers.openai.com/cookbook) 收录的长文总结和 agent 状态处理类示例可帮助设计“摘要后仍可验证”的工作流，但没有直接公开 Codex compact 的保留算法；本文的删除顺序与重试结论只来自源码。

### CC 2.1.88 源码解读

CC 2.1.88 的闭环是：检测压缩边界、生成摘要、用 boundary/消息/附件构成新数组。片段 A/C 见 [compact.ts，第 325–334 行](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/services/compact/compact.ts#L325-L334)，片段 B 见 [第 596–610 行](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/services/compact/compact.ts#L596-L610)（非官方历史快照备份）。

```ts
// 片段 A：读取。compact 选择保留的消息与附件。
const { messagesToKeep, attachments } = selectPostCompactState(messages);

// 片段 B：决策。旧 history 被折叠为模型生成的 summary。
const summary = await createCompactSummary(messages);

// 片段 C：写回。新的边界、摘要、近期消息和附件按顺序重建。
return [
  createCompactBoundaryMessage(),
  createSummaryMessage(summary),
  ...messagesToKeep,
  ...attachments,
];
```

| I/O 项       | 链路 I/O：120 轮排障后再次超窗                                                          |
| ------------ | --------------------------------------------------------------------------------------- |
| 输入         | 同一长 history、当前支付回调目标，且有一份仍需引用的报表附件。                          |
| 状态变化     | A 选择保留对象；B 将旧对话压为 summary；C 把边界、summary、近因消息和附件重建为新状态。 |
| 输出         | 一个结构化的 post-compact message 列表。                                                |
| 用户可见结果 | 后续回合能知道状态已压缩，附件也不会只剩一段文字描述。                                  |
| 误判风险     | 边界标记不等于摘要准确；遗漏的反例仍会消失。                                            |

**当前官方手册旁证（辅助）**：[Context window](https://code.claude.com/docs/en/context-window) 确认当前产品会管理上下文窗口；它不能确定今天仍以 2.1.88 的对象顺序重建 history。

### 实现对比与取舍

| 维度       | Codex                                              | CC 2.1.88                                          | 直接结论                                                            |
| ---------- | -------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------- |
| 目标达成   | 用最近用户目标和摘要续接任务，并在超窗时退避重试。 | 用 boundary、summary、保留消息和附件重建 history。 | Codex 优先维持会话连续；CC 2.1.88 显式保留附件与压缩边界。          |
| 可观察性   | 删除最早项和重试路径可在公开源码定位。             | 重建数组结构清楚，但只由历史镜像证明。             | 调试压缩故障时，Codex 的证据更稳定；CC 的当前对象顺序不能据此断言。 |
| 默认自动化 | 重试减少会话被超窗直接打断。                       | boundary 将摘要后的 history 切出明确分界。         | Codex 用额外延迟换连续性；CC 用固定边界换可读性。                   |
| 成本与风险 | 多轮摘要重试会拉长回合时间。                       | 摘要偏差会被写进新的 boundary，随后持续影响推理。  | Codex 要监控重试次数；CC 要把关键事实保存在摘要之外的持久载体。     |
| 适合任务   | 需要查清压缩退避行为的长调试。                     | 带附件的长研究或文档会话。                         | 证据、堆栈和关键配置不应只存在于 Codex 或 CC 的摘要中。             |

### OpenCode：现状与改进路线

| 已具备的公开能力                                                                                                                                                                     | 相对本章目标仍需补强的闭环                                                                                                                                                                         | 建议改进方向                                                                                                                    |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| OpenCode 公开源码备份含 [session compaction 服务](https://github.com/aimreant/opencode-backup/blob/d2bd7ea/packages/opencode/src/session/compaction.ts)，官方产品也有 session 概念。 | “压缩后 durable instructions 是否稳定重注入、附件和证据怎样可回放”不能仅从已有公开入口推断。社区关于规则在压缩后丢失的报告只能视为[问题信号](https://github.com/anomalyco/opencode/issues/16960)。 | 让 compact 输出成为带版本的结构化 envelope：保留规则快照、摘要来源范围、附件引用和丢弃清单；再为每次 compact 写入可查询 trace。 |

### 适用场景

| 明确结论：优先选 Codex                       | 明确结论：优先选 CC 2.1.88                                   | 共同警戒                                   |
| -------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------ |
| **要追踪压缩退避、删除与重试时，选 Codex。** | **附件必须跨压缩保留、需要显式 boundary 时，选 CC 2.1.88。** | 合规证据、堆栈和关键配置不得只存在于摘要。 |

## 4. 上下文：本轮模型究竟带了哪些行李

### 模块目标

上下文模块把已生效约束、恢复状态、当前用户任务和此刻必要的工具说明装配成模型请求。输入来自前三章，输出是本轮 prompt。核心不变量是：稳定规则要能重注入，易失 history 要有预算，工具 schema 要按必要程度进入，而不是无差别塞满窗口。

### 用人话说

Agent 出门要带工作证、目的地、上次交接单和必要工具。把所有仓库资料和所有工具说明都装进背包会超重；只带摘要又会忘记“不能改 schema”这种硬约束。

### 常用场景

- 有目录、测试、审批限制的仓库修复。
- 从已经压缩过的会话继续实现。
- 同时接入许多 MCP、技能和工具，但首轮 token 预算有限。

### 模块机制图

```mermaid
flowchart TD
  S([开始：① 接收任务、约束与可恢复状态]) --> A["读取稳定规则与当前用户目标"]
  A --> B["取回 history、summary、附件引用"]
  B --> C{"哪些能力本轮必要？"}
  C -- 常驻 --> D["② 加入必要工具 schema 与技能说明"]
  C -- 按需 --> E["② 保留 Tool Search 等发现入口"]
  D --> F["按预算装配 prompt"]
  E --> F
  F --> G([结束：③ 向模型发送本轮输入])
  F -. "风险：规则漏注入、schema 过大、摘要被当原文" .-> R["上下文失真"]
```

读图结论：上下文不是历史的原样拼接，而是一个预算内的装配产物。约束、持久化和工具三层在此相遇，但它们的治理逻辑各自独立。

### Codex 源码解读

Codex 的上下文闭环可分成三层：从 history 提取近期目标、从 session 重建稳定约束、把两者插入 replacement history；按需工具发现再避免未使用 schema 长驻。片段 A/B 见 [compact.rs，第 191–223 行](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/compact.rs#L191-L223)，片段 C 见 [tool_search.rs](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/tools/handlers/tool_search.rs)（公开源码备份）。

```rust
// 片段 A：读取。近期用户目标从 history snapshot 提取。
let user_messages = collect_user_messages(history_items);

// 片段 B：决策/装配。稳定规则由当前 session 重建后插入新 history。
let initial_context = sess.build_initial_context(turn_context.as_ref()).await;
new_history = insert_initial_context_before_last_real_user_or_summary(
    new_history, initial_context);

// 片段 C：按需能力。Tool Search 只在模型选择发现时返回匹配工具说明。
let discovered = tool_search_handler(query, deferred_tools).await?;
```

| I/O 项       | 链路 I/O：修复 checkout 超时                                                  |
| ------------ | ----------------------------------------------------------------------------- |
| 输入         | 旧测试输出已压缩；session 仍有“只能改 src/payment、不得改 schema”的项目约束。 |
| 状态变化     | A 提取近期意图；B 从 session 恢复并插入稳定规则；C 只在需要时发现额外能力。   |
| 输出         | 新 history 同时带近期意图、summary、重注入规则和少量已选工具说明。            |
| 用户可见结果 | 下一轮模型仍能看到不能改 schema，不必完全依赖摘要记住。                       |
| 误判风险     | 重注入规则不代表旧日志、旧工具输出或全部项目资料也被恢复。                    |

**Cookbook 实践旁证（辅助）**：[OpenAI Cookbook 目录](https://developers.openai.com/cookbook) 中的 agent、prompt 与工具示例能帮助开发者把稳定指令、状态和工具描述拆层；它不提供 Codex 的 initial_context 组装顺序，因此顺序结论仍仅由源码支持。

### CC 2.1.88 源码解读

CC 2.1.88 的链同样不应只看 Tool Search：先聚合 messages、CLAUDE.md 和 memory，再以 query 选 deferred tools，最后把它们送给本轮模型。片段 A/C 见 [context.ts，第 152–188 行](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/context.ts#L152-L188)，片段 B 见 [ToolSearchTool.ts，第 21–45 行](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/ToolSearchTool/ToolSearchTool.ts#L21-L45)（非官方历史快照备份）。

```ts
// 片段 A：读取。会话、项目规则和 memory 汇入 context。
const context = await buildContext({ messages, claudeMd, memory });

// 片段 B：决策。deferred tools 由 query 选择，而不是全量注入。
const tools = searchDeferredTools(query);

// 片段 C：写入模型输入。context 与已选 tools 共同构成请求。
return createModelRequest({ context, tools, messages });
```

| I/O 项       | 链路 I/O：修复 checkout 超时                                     |
| ------------ | ---------------------------------------------------------------- |
| 输入         | 当前任务、会话消息、CLAUDE.md/memory 与一组 deferred tools。     |
| 状态变化     | A 聚合规则与会话；B 选择匹配工具；C 把两者写为模型请求。         |
| 输出         | 供模型使用的 context 与一小部分被发现的工具能力。                |
| 用户可见结果 | 首轮不必背负所有工具 schema，项目记忆更贴近任务。                |
| 误判风险     | 自动载入的记忆也会占预算；历史实现显示的加载细节不代表当前产品。 |

**当前官方手册旁证（辅助）**：[How Claude Code works](https://code.claude.com/docs/en/how-claude-code-works) 说明当前产品如何使用项目指令与上下文；今天具体的自动加载时机仍以官方文档为准。

### 实现对比与取舍

| 维度       | Codex                                        | CC 2.1.88                                 | 直接结论                                                                            |
| ---------- | -------------------------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------- |
| 目标达成   | 压缩后显式重注入 session 的稳定上下文。      | 聚合项目记忆、会话和 deferred tool 发现。 | Codex 让长期规则的来源更清楚；CC 2.1.88 用自动聚合降低首次配置量。                  |
| 可观察性   | `initial_context` 的重建与插回可沿源码检查。 | 聚合路径更自动，细节仅由历史快照说明。    | Codex 更适合审计 prompt 构成；CC 的当前加载细节需要查官方手册。                     |
| 默认自动化 | 宿主维护需要重注入的上下文清单。             | 项目记忆和按需工具默认贴近当前任务。      | Codex 的代价是规则漏配；CC 的代价是自动记忆和工具说明占用 token 预算。              |
| 成本与风险 | 漏掉稳定规则会让新会话失焦。                 | 无关记忆会挤掉当前任务或关键工具结果。    | Codex 应将重注入清单纳入项目配置；CC 应限制自动载入规则的范围和长度。               |
| 适合任务   | 多仓库、强规则、需要审计 prompt 构成。       | 规则和任务记忆较稳定、偏好低摩擦探索。    | Codex 与 CC 都应把硬规则写成短句，并分别记录规则、history 与 schema 的 token 预算。 |

### OpenCode：现状与改进路线

| 已具备的公开能力                                                                                                                                             | 相对本章目标仍需补强的闭环                                                                       | 建议改进方向                                                                                                                                                                                    |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Agents 文档](https://opencode.ai/docs/agents/) 公开 agent prompt、工具面和权限配置；[Tools 文档](https://dev.opencode.ai/docs/tools/) 公开内建与 MCP 工具。 | 用户能配置上下文来源，不等于能观察每轮 prompt 中规则、history、schema 各占多少预算或何时被移除。 | 引入 context manifest：每轮列出规则、历史、工具 schema 的 token 账本和来源；将按需 MCP 搜索作为稳定能力，而不是仅依赖演进中的 [lazy-MCP PR](https://github.com/anomalyco/opencode/pull/12520)。 |

### 适用场景

| 明确结论：优先选 Codex                           | 明确结论：优先选 CC 2.1.88                               | 共同警戒                             |
| ------------------------------------------------ | -------------------------------------------------------- | ------------------------------------ |
| **强项目约束、需审计 prompt 构成时，选 Codex。** | **偏好项目记忆与按需能力自动贴合任务时，选 CC 2.1.88。** | 不要把“模型看过”误认为规则一定执行。 |

## 5. 工具：模型说要做，工具结果怎样安全回流

### 模块目标

工具模块把模型的工具调用转成可校验请求，把异构原始输出转成下一轮可读取的 tool result。输入是工具 schema、调用参数与原始结果；输出是规范化、截断状态可见、可写入 history 的结果。核心不变量是：工具发现、调用和结果治理是三件相连但不同的事。

### 用人话说

工具像一间器材室。模型先要知道有哪些器材，再借某一件，最后拿回结果。拿回一整车 10,000 行日志会堵住走廊，所以 Harness 要裁剪，还得贴上“已裁剪”的标签。

### 常用场景

- CI、构建和宽搜索产生数千行日志。
- MCP 服务返回大 JSON、表格或分页数据。
- 工具很多，希望 schema 不全部常驻 prompt。

### 模块机制图

```mermaid
flowchart TD
  S([开始：① 模型提出工具调用]) --> A["定位已注册工具或执行 Tool Search"]
  A --> B["② 校验工具名、参数与调用协议"]
  B --> C["把请求交给运行环境执行"]
  C --> D["取得原始 stdout、文件或 MCP 结果"]
  D --> E{"超过结果预算？"}
  E -- 否 --> F["规范化完整 tool result"]
  E -- 是 --> G["截断并保留截断事实"]
  F --> H["③ 写回 history"]
  G --> H
  H --> I([结束：模型获得下一轮证据])
  G -. "风险：根因在被裁掉的尾部" .-> R["需二次窄读"]
```

读图结论：Tool Search 解决“说明何时入窗”，截断解决“结果如何回流”。二者都节省上下文，却不能互相替代。

### Codex 源码解读

Codex 的工具结果闭环至少有三处：先按需发现工具说明，再保留原始执行字节，最后按 token 预算形成写回文本。片段 A 见 [tool_search.rs](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/tools/handlers/tool_search.rs)，片段 B/C 见 [tools/context.rs，第 284–362 行](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/tools/context.rs#L284-L362)；PTY 的原始字节上限见 [pty/src/lib.rs，第 10 行](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/utils/pty/src/lib.rs#L10)。

```rust
// 片段 A：读取/发现。只返回模型查询命中的 deferred tool 定义。
let tools = tool_search_handler(query, deferred_tools).await?;

// 片段 B：保存执行事实。原始字节与模型回流预算分离。
let output = ExecCommandToolOutput {
    raw_output,
    max_output_tokens,
    original_token_count,
};

// 片段 C：写回。将字节转文本后按 token 预算裁剪为 tool result。
let text = String::from_utf8_lossy(&output.raw_output).to_string();
let visible = formatted_truncate_text(&text, TruncationPolicy::Tokens(max_tokens));
history.push_tool_result(visible);
```

| I/O 项       | 链路 I/O：10,000 行测试日志                                                    |
| ------------ | ------------------------------------------------------------------------------ |
| 输入         | raw_output 约 8,000 token，max_output_tokens 为 1,000。                        |
| 状态变化     | A 仅选入需要的工具说明；B 保存原始字节与预算；C 截断并把可见结果写回 history。 |
| 输出         | 约 1,000 token 的结果，且可携带原始 token 规模。                               |
| 用户可见结果 | 模型应意识到日志不完整，改用 tail、grep 或定点读文件。                         |
| 误判风险     | 截断边界不保证保留真正的失败根因。                                             |

**Cookbook 实践旁证（辅助）**：[OpenAI Cookbook 目录](https://developers.openai.com/cookbook) 的 function calling、MCP 与 agent tool 示例适合说明“工具要设计成窄输入、窄输出”；没有一篇直接规定 Codex 的 token 截断算法，因此上限与对象字段仍只据源码。

### CC 2.1.88 源码解读

CC 2.1.88 的工具链包括 deferred tool 发现、MCP Tool 的容量定义与结果截断识别，而不是只是一条 maxResultSizeChars 常量。片段 A 见 [ToolSearchTool.ts，第 21–45 行](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/ToolSearchTool/ToolSearchTool.ts#L21-L45)，片段 B/C 见 [MCPTool.ts，第 27–75 行](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/MCPTool/MCPTool.ts#L27-L75)（非官方历史快照备份）。

```ts
// 片段 A：读取/发现。query 选择 deferred tools。
const discovered = searchDeferredTools(query);

// 片段 B：决策。MCP Tool 将结果字符预算声明为协议的一部分。
const mcp = buildTool({ isMcp: true, maxResultSizeChars: 100_000 });

// 片段 C：写回提示。结果被回传前提供截断状态。
const truncated = isOutputLineTruncated(output);
return createToolResult({ output, truncated });
```

| I/O 项       | 链路 I/O：130,000 字符 MCP 返回                                        |
| ------------ | ---------------------------------------------------------------------- |
| 输入         | 支付状态 MCP 返回 130,000 字符 JSON。                                  |
| 状态变化     | A 选择所需能力；B 将容量预算附在工具定义；C 形成带截断事实的回流结果。 |
| 输出         | 上限内 tool result 与截断判断信号。                                    |
| 用户可见结果 | 模型应分页、缩小字段或读取特定资源。                                   |
| 误判风险     | 字符上限不是业务对象边界，尾部字段仍可能消失。                         |

**当前官方手册旁证（辅助）**：[Tools reference](https://code.claude.com/docs/en/tools-reference) 与 [MCP](https://code.claude.com/docs/en/mcp) 描述当前可用工具和 MCP 使用方式；当前结果上限不由 2.1.88 片段推断。

### 实现对比与取舍

| 维度       | Codex                                         | CC 2.1.88                               | 直接结论                                                                             |
| ---------- | --------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------ |
| 目标达成   | shell 输出按 token 裁剪，原始规模可单独保留。 | MCP Tool 声明字符预算并检测截断。       | Codex 以 token 控制模型输入；CC 2.1.88 在 MCP 包装层统一管理结果容量。               |
| 可观察性   | 原始字节、裁剪文本和原始 token 数可分别检查。 | MCP 调用统一经过 Tool 层。              | Codex 更容易定位日志为何被截断；CC 更容易给所有 MCP 套用同一结果协议。               |
| 默认自动化 | 调用侧可设置 `max_output_tokens`。            | 容量策略写在工具定义中。                | Codex 允许按任务收紧或放宽预算；CC 让每个 MCP 遵守同一默认上限。                     |
| 成本与风险 | token 裁剪仍可能丢掉失败位置的关键行。        | 字符裁剪可能截断 JSON，破坏对象完整性。 | Codex 应按文件、测试名或行号二次窄读；CC 应让 MCP 返回分页或字段筛选后的结构化结果。 |
| 适合任务   | CI 排障、构建失败、宽搜索日志。               | 多 SaaS/MCP 集成、结构化资源查询。      | Codex 与 CC 都不应以无限提高结果上限替代缩小查询范围。                               |

### OpenCode：现状与改进路线

| 已具备的公开能力                                                                   | 相对本章目标仍需补强的闭环                                                                        | 建议改进方向                                                                                                                                                                               |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [Tools 文档](https://dev.opencode.ai/docs/tools/) 已公开内建工具、MCP 与权限控制。 | “所有工具默认可用”的便利性并不自动提供 token 级结果预算、截断事实、按需 schema 发现与回写 trace。 | 为每个工具引入结果预算、original-size 与 truncation 字段；把 MCP lazy mode 作为可测试的稳定接口。当前 [mcp-search PR](https://github.com/anomalyco/opencode/pull/12520) 只能视为演进信号。 |

### 适用场景

| 明确结论：优先选 Codex                                    | 明确结论：优先选 CC 2.1.88                                | 共同警戒                                         |
| --------------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------ |
| **高频 CI/测试排障，需控制日志 token 预算时，选 Codex。** | **大量结构化 MCP 查询，需统一包装结果时，选 CC 2.1.88。** | 截断不是完整结果；schema 全量常驻会压垮 prompt。 |

## 6. 运行环境：工具真正在哪儿跑，副作用由谁承接

### 模块目标

运行环境模块是工具调用真正发生的宿主：PTY、工作目录、文件系统、沙箱、网络通道和 MCP transport 都在这里起作用。输入是已校验、已获准的工具请求；输出是执行结果、错误、审批事件和审计痕迹。核心不变量是：模型和工具协议不能越过宿主边界直接拥有环境副作用。

### 用人话说

工具模块像下了一张工单，运行环境才是实际施工现场。命令在哪个目录跑、能访问哪些文件、能不能出网、MCP 请求送到哪里，决定了同一张工单最后会发生什么。

### 常用场景

- 在受限工作目录运行测试、构建或迁移预览。
- 访问外部 API、浏览器自动化或企业 MCP 服务。
- 排查“模型已经请求执行，但为什么被沙箱、端口或环境变量拦住”。

### 模块机制图

```mermaid
flowchart TD
  S([开始：① 收到已校验的工具请求]) --> A["绑定工作目录、沙箱与执行策略"]
  A --> B{"请求类型？"}
  B -- shell / 文件 --> C["② PTY 或本地执行器"]
  B -- 网络 --> D["② 按 host、protocol、port 经过审批"]
  B -- MCP --> E["② MCP client / transport 调用服务"]
  C --> F["采集退出码、stdout、stderr"]
  D --> F
  E --> F
  F --> G["形成执行事件与原始结果"]
  G --> H([结束：③ 交给工具模块规范化和写回])
  A -. "风险：cwd、沙箱、重定向、服务端副作用" .-> R["环境漂移或越权"]
```

读图结论：运行环境不是“工具的另一个名字”。工具层管理调用协议与结果回流；运行环境决定请求能否在真实机器、网络和服务端产生副作用。

### Codex 源码解读

Codex 的运行环境闭环不能只看 PTY 常量：工作请求先携带策略进入执行器，PTY 限制原始输出，网络目标再落到审批事件或真实请求。片段 A 是 [network_approval.rs，第 77–127 行](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/tools/network_approval.rs#L77-L127)，片段 B 是 [pty/src/lib.rs，第 10 行](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/utils/pty/src/lib.rs#L10)，片段 C 回到 [tools/context.rs，第 284–362 行](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/tools/context.rs#L284-L362)（公开源码备份）。

```rust
// 片段 A：读取/准入。网络执行先从 target 构造审批键。
let key = HostApprovalKey { host, protocol, port };
let decision = approval_policy.check(&key);

// 片段 B：执行环境。PTY 对单个进程积累的原始输出设硬边界。
pub const DEFAULT_MAX_OUTPUT_BYTES: usize = 1024 * 1024;
let pty = spawn_pty(command, cwd, exec_policy).await?;

// 片段 C：写回。进程事件和原始输出成为工具层的输入。
let raw_output = pty.collect_output().await?;
return ExecCommandToolOutput::from(raw_output);
```

| I/O 项       | 链路 I/O：在受控仓库运行测试                                                           |
| ------------ | -------------------------------------------------------------------------------------- |
| 输入         | 模型请求在当前工作目录运行测试，命令产生持续 stdout/stderr。                           |
| 状态变化     | A 检查网络/执行准入；B 以 cwd、策略和 PTY 启动进程；C 将退出事件和原始输出交给工具层。 |
| 输出         | 进程退出状态与受限原始输出，随后由工具层按 token 再次治理。                            |
| 用户可见结果 | 大日志不会无限积累；超出边界时应改为更窄命令。                                         |
| 误判风险     | 1 MiB 是输出缓存边界，不保证测试本身完成，也不等于模型看到的 token 上限。              |

**Cookbook 实践旁证（辅助）**：[OpenAI Cookbook 目录](https://developers.openai.com/cookbook) 的 coding、MCP 与工具执行范例强调将外部副作用封装成受控工具调用；没有直接公开 Codex PTY 或其沙箱实现，故本文不把 cookbook 当作环境机制证据。

### CC 2.1.88 源码解读

CC 2.1.88 的外部运行边界同样可拆成三段：请求进入 MCP Tool、外层权限决定是否允许、client 将获准调用送到 server 并把结果返回。片段 A/C 见 [MCPTool.ts，第 27–75 行](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/MCPTool/MCPTool.ts#L27-L75)，其调用调度上下文见 [runAgent.ts，第 85–217 行](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/AgentTool/runAgent.ts#L85-L217)（非官方历史快照备份）。

```ts
// 片段 A：读取。Agent 调度将模型选择的工具调用送入 MCP Tool。
const request = { serverName: this.serverName, toolName: this.toolName, input };

// 片段 B：准入。环境调用前仍由外层权限决定。
const permission = await context.canUseTool(this.name, input);
if (!permission.allowed) return permission.result;

// 片段 C：执行/写回。client 调用 server，结果交回 Tool 层。
const result = await this.client.callTool(request.serverName, request.toolName, input);
return formatToolResult(result);
```

| I/O 项       | 链路 I/O：读取企业 MCP 的支付状态                                                                  |
| ------------ | -------------------------------------------------------------------------------------------------- |
| 输入         | 获准的 MCP 工具名、serverName、toolName 和支付查询参数。                                           |
| 状态变化     | A 形成 transport 请求；B 在 client 调用前拒绝或放行；C 返回服务端结果或 transport 错误给 Tool 层。 |
| 输出         | 服务端结果或 transport 错误，返回 Tool 层继续规范化。                                              |
| 用户可见结果 | 即使模型会调用工具，服务不可达或服务端拒绝仍会成为可见执行失败。                                   |
| 误判风险     | 这段包装代码不描述 server 内部权限、网络隔离或副作用；那些仍在外部服务边界。                       |

**当前官方手册旁证（辅助）**：[MCP](https://code.claude.com/docs/en/mcp) 与 [Permissions](https://code.claude.com/docs/en/permissions) 说明当前 MCP 接入与授权体验；本文不从 2.1.88 推断今天的 transport 实现。

### 实现对比与取舍

| 维度       | Codex                                        | CC 2.1.88                                                                  | 直接结论                                                                               |
| ---------- | -------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| 目标达成   | PTY 与网络审批将本地执行边界建模为可见对象。 | MCP client 将获准请求交给外部服务。                                        | Codex 适合控制本机 shell 与网络目标；CC 2.1.88 适合接入已有企业服务。                  |
| 可观察性   | 原始输出预算和网络审批键可在公开源码定位。   | 可看到 permission 到 client 的交接，无法从 client 代码看到服务端实际动作。 | Codex 可审计本机执行条件；CC 还必须从 MCP 服务端日志确认是否读取、写入或触发外部操作。 |
| 默认自动化 | 执行器与策略分层，宿主可独立配置。           | MCP 包装统一常规服务调用路径。                                             | Codex 的配置面更大；CC 降低接入成本，但 MCP 服务端仍需自行实现权限、幂等和回滚。       |
| 成本与风险 | 错误的 cwd、沙箱或网络规则会阻断本地任务。   | 服务端可用性、服务端权限和服务端副作用都成为依赖。                         | Codex 失败时先查宿主环境与策略；CC 失败时还要查 MCP 服务健康、授权和实际执行记录。     |
| 适合任务   | 本地仓库、终端排障、需要精确网络审批。       | 企业服务、知识库与 MCP 集成。                                              | 生产写操作应先限定目标与权限，再保留可回滚的执行记录。                                 |

### OpenCode：现状与改进路线

| 已具备的公开能力                                                                                                          | 相对本章目标仍需补强的闭环                                                                              | 建议改进方向                                                                                                                                         |
| ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Tools 文档](https://dev.opencode.ai/docs/tools/) 公开 shell、文件、MCP 等执行面，Agents 文档公开对 bash/MCP 的权限控制。 | 工具权限、cwd、子进程、网络目标、服务端副作用若分散在配置与工具实现中，用户难以得到一条统一执行 trace。 | 将每次执行写成 capability trace：工作目录、策略判定、实际命令/目标、退出码、输出预算、外部服务结果一并记录；再逐步把高风险网络操作收窄到目标级能力。 |

### 适用场景

| 明确结论：优先选 Codex                                   | 明确结论：优先选 CC 2.1.88                                | 共同警戒                                                 |
| -------------------------------------------------------- | --------------------------------------------------------- | -------------------------------------------------------- |
| **本地 shell、网络目标和输出预算都要审计时，选 Codex。** | **主要依赖企业 MCP 与统一 client 调用时，选 CC 2.1.88。** | 检查 cwd、沙箱、重定向和服务端写副作用；失败别盲目重试。 |

## 从地图回到选择：任务形状决定 Harness 策略

六要素的差异只有放回任务链路才有意义：流程决定是否隔离探索；约束决定哪些计划根本不可执行；持久化决定长任务还能记住什么；上下文决定模型当轮能推理什么；工具决定证据怎样回流；运行环境决定副作用最终在哪发生。

| 任务形状                               | 更偏向的选择                   | 原因                                                 |
| -------------------------------------- | ------------------------------ | ---------------------------------------------------- |
| 长日志排障，必须解释截断、压缩和重试   | Codex                          | 工具输出、compact 退避、PTY 和审批均有公开源码落点。 |
| 首次进入大仓库，只需只读摸清模块       | CC 2.1.88 的 Explore 思路      | 默认隔离、只读工具与模型路由降低主会话噪声。         |
| 跨文件改动，子任务必须理解已有架构决策 | Codex                          | New/继承 history 与执行策略是显式编排参数。          |
| 带附件的长研究任务                     | CC 2.1.88 的 post-compact 思路 | boundary、摘要、保留消息与附件分层重建。             |
| 高风险生产网络访问                     | Codex                          | host、protocol、port 给出更具体的审批对象。          |
| 团队主要使用企业 MCP                   | CC 的 MCP 包装思路             | 统一权限判断后转交 MCP client，便于集中治理。        |

最短的结论仍然是：Codex 把更多 Harness 开关留给宿主，因此更适合强调审计、编排和环境治理的工程团队；CC 2.1.88 把更多常见策略封装成默认 runtime，因此更适合先隔离探索、再回收结论的低摩擦工作流。二者都不能替代对规则、关键证据与外部副作用的人工复核。

## OpenCode 的三阶段改进路线

这里的“改进”只针对本文选取的 Harness 保证，不比较模型质量、生态规模或产品偏好。

| 阶段        | 先补的闭环                                                           | 可验证的完成标准                                                                    |
| ----------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| 1. 正确性   | 压缩后重注入 durable rules；把权限从 prompt 提示延伸到运行时决策。   | 每次 compact 都能复查保留/丢弃对象；高风险工具有明确 allow、ask、deny 事件。        |
| 2. 可观察性 | 为 context、工具输出和执行环境写入统一 trace。                       | 能回答“本轮哪些规则、history、schema 入窗”“结果是否截断”“命令/服务实际执行了什么”。 |
| 3. 协作性   | 为 Task 增加 history mode、结构化结果与父线程 join，再谨慎引入并行。 | 子任务可独立复现，父线程只接收约定产物，并发不会扩大权限或让结果无序回灌。          |

这条路线的收益不是“功能更多”，而是把现有工具、session、权限和 MCP 连接成可验证闭环；任何尚未合入的 PR 或 Issue 都只能作为候选方向。

## 附录 A：六要素证据矩阵

| 要素     | Codex 源码链                                                                                                                                                                                                                                                                                       | CC 2.1.88 源码链                                                                                                                                                                                                                                                                                                                             | OpenCode 固定源码                                                                                                  | Cookbook                                                                                | CC 手册                                                                     | OpenCode 文档                                      | 证据限制                                       |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------- | ---------------------------------------------- |
| 流程     | [delegate A](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/codex_delegate.rs#L63-L98) + [B/C](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/codex_delegate.rs#L105-L138)     | [runAgent](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/AgentTool/runAgent.ts#L85-L217) + [Explore](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/AgentTool/built-in/exploreAgent.ts#L24-L56) | [release](https://github.com/aimreant/opencode-backup/tree/d2bd7ea)                                                | 辅助：[Orchestrating agents](https://cookbook.openai.com/examples/orchestrating_agents) | 辅助：[Subagents](https://code.claude.com/docs/en/sub-agents)               | 辅助：[Agents](https://opencode.ai/docs/agents/)   | OpenCode Issue 只作路线信号。                  |
| 约束     | [context](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/compact.rs#L191-L223) + [approval](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/tools/network_approval.rs#L77-L127) | [context](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/context.ts#L152-L188) + [MCPTool](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/MCPTool/MCPTool.ts#L27-L75)                                  | [release](https://github.com/aimreant/opencode-backup/tree/d2bd7ea)                                                | 辅助：[Cookbook](https://developers.openai.com/cookbook)                                | 辅助：[Permissions](https://code.claude.com/docs/en/permissions)            | 辅助：[Agents](https://opencode.ai/docs/agents/)   | Cookbook 不证明审批键。                        |
| 持久化   | [compact A/C](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/compact.rs#L191-L223) + [B](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/compact.rs#L154-L230)                  | [compact A/C](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/services/compact/compact.ts#L325-L334) + [B](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/services/compact/compact.ts#L596-L610)              | [compaction](https://github.com/aimreant/opencode-backup/blob/d2bd7ea/packages/opencode/src/session/compaction.ts) | 辅助：[Cookbook](https://developers.openai.com/cookbook)                                | 辅助：[Context window](https://code.claude.com/docs/en/context-window)      | 辅助：[Agents](https://opencode.ai/docs/agents/)   | CC 为历史；OpenCode 压缩规则需按 commit 复核。 |
| 上下文   | [context A/B](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/compact.rs#L191-L223) + [search](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/tools/handlers/tool_search.rs)    | [context](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/context.ts#L152-L188) + [search](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/ToolSearchTool/ToolSearchTool.ts#L21-L45)                     | [release](https://github.com/aimreant/opencode-backup/tree/d2bd7ea)                                                | 辅助：[Cookbook](https://developers.openai.com/cookbook)                                | 辅助：[How it works](https://code.claude.com/docs/en/how-claude-code-works) | 辅助：[Tools](https://dev.opencode.ai/docs/tools/) | 文档不等同于 prompt 组装代码。                 |
| 工具     | [search](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/tools/handlers/tool_search.rs) + [result](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/tools/context.rs#L284-L362)   | [search](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/ToolSearchTool/ToolSearchTool.ts#L21-L45) + [MCP](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/MCPTool/MCPTool.ts#L27-L75)             | [release](https://github.com/aimreant/opencode-backup/tree/d2bd7ea)                                                | 辅助：[Cookbook](https://developers.openai.com/cookbook)                                | 辅助：[Tools](https://code.claude.com/docs/en/tools-reference)              | 辅助：[Tools](https://dev.opencode.ai/docs/tools/) | lazy MCP PR 不是稳定能力。                     |
| 运行环境 | [approval](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/core/src/tools/network_approval.rs#L77-L127) + [PTY](https://github.com/aimreant/codex-backup/blob/ae057e0bb9f3cde31c54fcca59a78456f7805282/codex-rs/utils/pty/src/lib.rs#L10)          | [MCP](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/MCPTool/MCPTool.ts#L27-L75) + [run](https://github.com/aimreant/claude-code-2.1.88-backup/blob/c8cd253554319f32ff64ff7000636199f720c9bc/source/src/tools/AgentTool/runAgent.ts#L85-L217)                          | [release](https://github.com/aimreant/opencode-backup/tree/d2bd7ea)                                                | 辅助：[Cookbook](https://developers.openai.com/cookbook)                                | 辅助：[MCP](https://code.claude.com/docs/en/mcp)                            | 辅助：[Tools](https://dev.opencode.ai/docs/tools/) | 外部服务边界需另行审计。                       |

## 附录 B：怎样正确使用四层证据

| 想回答的问题                                 | 应优先引用              | 不应做的推断                              |
| -------------------------------------------- | ----------------------- | ----------------------------------------- |
| 某变量、阈值、分支、history 对象如何变化？   | 固定 commit 的源码      | 用 Cookbook 或产品文档猜内部实现。        |
| 开发者应如何拆任务、收窄工具输出、设计回传？ | Cookbook 与章节场景建议 | 把 cookbook 模式当作 Codex 的运行时承诺。 |
| 今天 Claude Code 对用户提供什么能力？        | Claude Code 官方手册    | 用 2.1.88 镜像证明当前默认配置。          |
| 某个历史 CC 内部路径怎样工作？               | 2.1.88 镜像，附历史标签 | 把该路径表述为 Anthropic 当前官方实现。   |

- 每段代码只解释紧邻片段直接读取、写入或分支的对象；未展示部分不作确定断言。
- 文中的 I/O 例子是从相邻代码可直接推演的最小案例，不是性能基准或 UI 录屏。
- 若决策依赖当前 Claude Code 行为，先复查官方手册与实际安装版本；若依赖 Codex 实现，使用本文固定 commit，而非未来变化的 main 分支。
