对比篇 · Claude Code 与 Codex 的 Harness 哲学

Claude Code; Codex; Agent Harness; 架构对比; Agent 2458 words 13 min read
This post is not yet available in English. Showing the original.

前面六篇一直在拆 Claude Code 一个系统的内部。这一篇换个视角

——OpenAI 的 Codex CLI——放在一起对照。

它们在做几乎相同的产品

Agent。但把两份源码并排读,你会发现它们走的根本不是一条路。有一个很传神的观察
Claude Code 源码里 grep "transition.reason" 有 7 个匹配,同样的命令打到 Codex 的 Rust workspace 上,结果是 0。两个 Agent 在做同一件事,但其中一个把”为什么要继续这一轮”刻进了类型系统,另一个连这个概念都不存在。

一句话概括这篇的结论:Claude Code 用 TypeScript 类型做加法,Codex 用操作系统 sandbox 做减法。 下面沿 8 个维度看它们怎么把同一道题解成两个样子。

说明

(Claude Code v2.1.88 源码于 2026-03-31 因 npm sourcemap 泄露而完整可读;Codex CLI 为 Apache 2.0 开源)。主要参考见文末。所有结论都可回到源码自行验证。

两种 harness,两种世界观

先看技术栈,第一眼就能分出两条路:

Claude CodeCodex CLI
语言/运行时TypeScript + BunRust + Tokio
代码组织~1900 个 .ts/.tsx、512K+ 行,单 npm 包30+ 个 Rust crate 的 workspace
默认模型Claude Sonnet 4.6(YOLO 用 Haiku,复杂任务拉 Opus)GPT-5.x 系列
分发形态需要 Node 运行时,npx 一行跑起单二进制,跑遍 macOS/Linux/Windows
扩展方式Markdown(hook/skill/plugin),热加载编 cdylib 或重启进程

两边的 README 都写着 “agent harness”,但它们对这个词的理解几乎相反:

  • Claude Code 的 harness 像一个”代模型决策的中央控制层”
    ?要不要压缩?要不要重试?这些由 harness 替模型答完,模型只负责决策内容。
  • Codex 的 harness 更像一层”薄壳”
    、递权限,把操作系统的栏杆挪到面前,但不替模型决策太多——每个工具调用都被 syscall 级 sandbox 框死,错了也跳不出虚拟边界。

Codex 的 crate-per-job 切法(core / protocol / tui / exec / sandbox / mcp-server……)暴露了一个产品意图:把 harness 的每一块都做成可复用单元,外部 IDE 只挑 app-servermcp-server 接进来就行。Claude Code 没这一层,所有整合靠 bridge/ 子目录里的协议从一个进程内部分流。

8 个维度的对照

1. 主循环
vs 朴素循环

Claude Code 的主循环是一个 AsyncGenerator,配一个不可变 State 对象,把 10 种终止原因(Terminal)7 种继续原因(Continue) 全部显式枚举——每条岔路都写进类型。

Codex 是一个朴素的 loop {} + break,只有 4 种 TurnAbortReason 收口。

差别在哪: 一个把”为什么停/为什么继续”当成一等公民钉死在类型里,让运维能按 reason 分桶、让测试能精确断言恢复路径;另一个信奉”先跑起来,坑出来了再加枚举”。

2. 工具系统
vs 锁驱动

Claude Code 每个工具自带一组元数据(readOnly / destructive / concurrencySafe 等),调度器读这些元数据决定能否并发。Codex 则简单得多

与否就靠一个锁。

3. 上下文压缩
层漏斗 vs 单层摘要

Claude Code 是一套 5 层渐进压缩漏斗(Snip → MicroCompact → Collapse → AutoCompact……),免费手段优先,并带 MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 的熔断。Codex 是单层 LLM 摘要,但把”压缩在哪儿跑”做成了枚举

/ remote / remote-v2 三套实现,共享同一份摘要模板,按部署形态选。

差别在哪: Claude Code 在压缩”用什么手段”上分层,Codex 在压缩”在哪台机器上跑”上分层。前者服务于 fleet 成本优化,后者服务于”本地二进制 + 远端 daemon”的多部署形态。

4. 权限与 Sandbox
解析 vs OS 内核

这是分歧最大的一维。

  • Claude Code 在应用层拦截
    Bash AST 解析命令,配三态权限规则(allow/ask/deny),在 TypeScript 里把”危险动作”挡掉。
  • Codex 把 fail-closed 压到操作系统
    用 Landlock、macOS 用 Seatbelt、Windows 用 WFP。默认 SandboxPolicy::ReadOnly,错了也跳不出内核边界。

一个信任应用层的解析能力,一个只信内核。服务端无人值守场景 Codex 的路线更稳;开发者自愿安装的桌面工具,Claude Code 的 AST + 一键 always allow 的 UX 更顺。

5. 子 Agent
种 Task 类型 vs 内核级 spawn

Claude Code 有 7 种 Task 类型 + 隔离膜(前几篇讲的 Explore/Plan/General/Verification 等)。Codex 把 spawn_agent 做成一个工具,把策略留给上层——内核提供能力,编排逻辑交给调用方。

6. 扩展机制
个 Hook + Skills 懒加载 vs MCP 双向

Claude Code 靠 27 种 hook event + 懒加载 skill + plugin 打包,这套”用 Markdown 表达扩展”的能力几乎只有动态语言做得这么轻。Codex 走 mcp-server + rmcp-client 双 crate 的 MCP 双向集成。这直接由技术栈决定

要做 Markdown 热加载扩展得编 cdylib,自然不选这条路。

7. 跨端/多入口
vs 单二进制多 mode

Claude Code 用”进程内多端共享主循环”——一个 Node 进程跑 REPL、SDK、Bridge 远端,区分靠回调有无。Codex 用”单二进制多 mode + 跨语言 SDK”——通过 SessionSource enum + JSON-RPC 协议,让 Java/Go/Python 后端都能直接调。

共同点很关键:两边的主循环代码都完全不 care 自己跑在哪个环境,都避免了 if mode == 'repl' else if mode == 'sdk' 这种到处分叉的反模式。一个用可选回调实现,一个用枚举实现。

8. 成本与可观测性
字段对齐 vs 压缩位置枚举

Claude Code 把 prompt cache 命中当头号优化

Agent 的 5 个 CacheSafeParams 字段必须字节级和父 Agent 一致才能命中缓存(cache read 约为 cache write 的 10% 价格)。当 fleet 每周有数千万次 Explore agent spawn 时,命中与否是几个零的成本差距。Codex 则把压缩位置做成枚举来适配不同部署。

可观测性上,Claude Code 的 reason 枚举密集(10 种 Terminal + 7 种 Continue + 27 种 hook event),直接对接 OpenTelemetry 都不用适配层;Codex 用 lifecycle 枚举(7 种 AgentStatus + 4 种 TurnAbortReason),力度粗一些但也够用。

你在做什么,就抄哪边

这张表把 8 个维度收成可执行清单——按你的产品定位选:

你在做的事推荐抄的部分
个人/小团队 coding 助手Codex 的朴素 loop {} + TurnAbortReason,坑出来再升级
自托管企业 Agent SaaSClaude Code 的 reason 枚举,从一开始让运维能按 reason 分桶
服务端无人值守 AgentCodex 的 OS sandbox 三件套;只信内核,别信应用层
桌面 dev toolClaude Code 的 Bash AST + 三态权限,UX 能一键 always allow
多 LLM 编排框架Codex 的 spawn_agent 内核能力,策略留给上层
富 Markdown 扩展生态Claude Code 的 hook + 懒加载 skill + plugin 打包
上下文压缩第一版Codex 的单层摘要 + Pre/Post hook,先留好生命周期插槽
上下文压缩 fleet 优化Claude Code 的 5 层漏斗;务必加压缩失败熔断
多语言 SDK/IDE 集成Codex 的 JSON-RPC + Rust 单二进制

5 条可迁移的设计准则

两份源码战术上完全相反,战略上却惊人一致。把共识层提炼成 5 条,按项目阶段排序:

  1. 【Day 1】fail-closed 默认值贯穿一切。 新增能力时如果默认偏激进,迟早有一个早晨醒来发现昨夜烧了几十万次 API 调用。safeParse 失败默认不并发,sandbox 默认只读无网络。
  2. 【Day 1】熔断必须有,不要靠”下次能成”。 任何重试逻辑必须有上限,任何队列必须有 max size,任何解析必须有 timeout。那些看起来很随意的常量(3、100、50ms)都是踩坑得来的。
  3. 【Scale 后】退出/继续原因做成 enum,不要做成日志字符串。 日志字符串一升级文案 dashboard 就崩;枚举能让运维按 reason 分桶,让测试精确断言恢复路径。
  4. 【Scale 后】隔离默认开,逃生通道写注释。 所有 mutable 默认 no-op,只显式开必要的窗户;例外的地方注释写清楚为什么。
  5. 【Fleet 级】prompt cache(或等价成本维度)从设计阶段就考虑。 跑大 fleet 时,cache 命中率是真金白银。

收束
才是产品力

如果只能带走一句话:

Harness 承担不变量,模型承担决策。

剩下的所有差异——TS 还是 Rust、5 层漏斗还是单层摘要、27 hook 还是 MCP 双向、应用层 AST 还是 OS syscall——都只是这条原则在不同部署形态下的折中。

两边都明白一件事:prompt 不是产品力,harness 才是产品力。 差异化不来自”调哪个 LLM”(这部分谁都能调到),而来自 harness 怎么把那个 LLM 卡进自己的工程不变量层。

这也正好呼应了整个专题的主线

Claude Code 的每一项功能之后,真正值得带走的不是某个命令、某个配置,而是它背后那套”如何用工程约束驯服一个不确定的模型”的思路。下一篇是本专题的收尾——我们把这套思路收拢成一份”构建你自己的 Agent Harness”的路线图。


主要参考:

  • 《Claude Code 与 Codex Harness 设计对比
    ,一种减法》(基于两者公开源码的架构分析)
  • 《御舆
    Agent Harness》(claude-code-book,CC BY-NC-SA 4.0)第 2/3/4/7 章
  • Anthropic《Effective context engineering for AI agents》(2025)、Prompt Caching 文档
  • OpenAI Codex CLI 官方文档(codex-rs/docs/)