最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Claude Code 與 Codex Harness 設(shè)計對比分析:一種加法,一種減法

  發(fā)布時間:2026-05-20 10:12:05   作者:Joseph Cooper   我要評論
文章對比了ClaudeCode和CodexCLI的設(shè)計哲學(xué),從技術(shù)棧、主循環(huán)、工具系統(tǒng)、壓縮、權(quán)限、子agent、擴展機制、跨端、成本與可觀測性等8個維度進行了深入分析,感興趣的朋友跟隨小編一起看看吧

Claude Code 與 Codex Harness 設(shè)計對比:一種加法,一種減法

圖 1:兩個 CLI agent 的 harness 哲學(xué)對照——左邊 TS/Bun 把不變量編進類型,右邊 Rust/Tokio 把不變量壓到內(nèi)核 syscall。

摘要:把 Claude Code v2.1.88 和 OpenAI Codex CLI 兩份源碼并排放著看,會發(fā)現(xiàn)這是同一道題的兩種相反答卷——一種用 TS 類型 + AST 解析做加法,一種用 OS sandbox + 幾個 enum 做減法。這篇拆給你看。

一、寫在前面:為什么把這兩個放一起讀

grep -r "transition.reason" Claude Code 源碼 → 7 個匹配。同樣命令打到 OpenAI 的 codex-rs/ 整個 workspace → 0 個匹配。兩個 CLI agent 在做幾乎相同的產(chǎn)品,但其中一個把"為什么繼續(xù)"刻進了類型,另一個連這個概念都不存在。

第一遍讀這兩份代碼很容易感慨它們多像,仔細對照之后會發(fā)現(xiàn)它們走的根本不是一條路:

  • Claude Code v2.1.88(2026 年 3 月底因 npm sourcemap 泄露而完整可讀,約 1900 個 .ts/.tsx 文件、512K+ 行;默認(rèn)對接 Claude Sonnet 4.6,YOLO 旁路用 Haiku 4.5,復(fù)雜任務(wù)可拉到 Opus 4.7):用 TypeScript 把 harness 寫成一個龐大的狀態(tài)機系統(tǒng),類型上把每條 recovery 路徑都釘死,然后讓 Bun 在終端里跑。
  • OpenAI Codex CLI(開源、Apache 2.0、codex-rs/ 多 crate Rust workspace;默認(rèn)對接 GPT-5.x 系列):用 Rust 把 agent loop 寫成一個樸素的 loop {},絕大部分安全保證甩給操作系統(tǒng)的 Landlock/Seatbelt/Seccomp,然后編譯成一個二進制跑遍 macOS/Linux/Windows。

兩邊的 README 都在說 “agent harness”,但它們對 harness 這個詞的理解幾乎是相反的。

Claude Code 的 harness 像一個代模型決策的中央控制層:要不要并發(fā)?要不要壓縮?要不要重試?10 種 Terminal 怎么映射成生產(chǎn)事故分類?這一切由 harness 替模型答完,模型只負責(zé)決策內(nèi)容。

Codex 的 harness 更像一個給模型遞工具、遞權(quán)限、把 OS 這層欄桿挪到面前的薄殼。它不替模型決策太多,但每個工具調(diào)用都被 syscall 級別的 sandbox 框死,錯了也跳不出虛擬邊界。

這兩段判斷要看完整本文章 8 個維度的對比才站得住,下面就沿著這 8 個維度對著兩邊的源碼看:技術(shù)棧、主循環(huán)、工具系統(tǒng)、壓縮、權(quán)限、子 agent、擴展機制、跨端、最后再把"成本與可觀測性"作為第 8 維度收束。每條結(jié)論都帶文件路徑和行號,下次你想驗真?zhèn)?,自?grep 一下就行。

二、技術(shù)棧與項目布局:第一眼能看出的區(qū)別

圖 2:左 Claude Code 是 Bun 跑的 1900 文件 TS 單倉;右 Codex 是 30+ Rust crate 的 workspace,按職責(zé)切成 core/protocol/tui/exec/sandbox/mcp 等獨立二進制單元。

Claude Code 的 src/ 頂層是一個相對扁平的前端項目結(jié)構(gòu):Tool.ts / query.ts / QueryEngine.ts / Task.ts 全是文件級模塊,下面再展開 tools/、services/、utils/、bridge/、coordinator/、ink/memdir/、skills/plugins/。所有東西都在同一個 npm 包里。

Codex 的 codex-rs/ 是個標(biāo)準(zhǔn) Rust workspace,每個 crate 都是獨立單位:

crate干什么
core主循環(huán)、turn、agent、tool 調(diào)度、compact
protocol公共 enum/struct(AskForApproval、SandboxPolicy、SessionSource…)
tui終端 UI
exec非交互執(zhí)行入口
mcp-server / rmcp-client / codex-mcpMCP 服務(wù)端與客戶端
linux-sandbox / sandboxing / windows-sandbox-rs三平臺 sandbox 實現(xiàn)
apply-patch / execpolicy / tools工具與策略
app-server / app-server-daemon長駐守護進程
sdk/typescript / sdk/python語言綁定

這種 crate-per-job 的切法暴露了 Codex 的一個產(chǎn)品意圖:把 harness 的每一塊都做成可復(fù)用單元,外部 IDE 或服務(wù)可以只挑 app-servermcp-server 接進來。Claude Code 沒這一層,所有 IDE/Bridge 整合都靠 bridge/ 子目錄里的 JWT + workSecret 協(xié)議從一個 npm 進程內(nèi)部分流。

差異背后的折中很明顯:

  • TS/Bun 的代價是每次都要拉一個 Node 運行時,但好處是改代碼不用編譯;hook、skill、plugin 都是 markdown 或 TS 文件,熱加載非常順。
  • Rust 的代價是修一行代碼要編譯數(shù)十秒,但好處是單二進制可以直接跑在沒有 Node 的服務(wù)器上,內(nèi)存安全順便白送。

這也直接決定了兩邊的擴展生態(tài)長什么樣。Claude Code 的 hook + skill + plugin 這套用 Markdown 表達擴展的能力,幾乎只有動態(tài)語言才能做得這么輕——開發(fā)者寫一份 frontmatter 加正文就能塞進 skills/ 目錄。Rust 這邊要做對應(yīng)能力得編 cdylib 或重啟進程,自然不會選這條路。

三、維度 1:主循環(huán)——狀態(tài)機 vs 樸素循環(huán)

圖 3:左 Claude Code 用 AsyncGenerator + State 對象 + 10 種 Terminal + 7 種 Continue 顯式枚舉每條路徑;右 Codex 用 loop {} + break + 4 種 TurnAbortReason 收口。

Claude Code:把每條岔路都寫進類型

src/query.ts L219 的簽名長這樣:

export async function* query(
  params: QueryParams,
): AsyncGenerator<
  | StreamEvent
  | RequestStartEvent
  | Message
  | TombstoneMessage
  | ToolUseSummaryMessage,
  Terminal
> {

返回值類型是 Terminal——整個主循環(huán)結(jié)束時必須給出一個具體的退出原因。源碼里出現(xiàn) 10 種:

blocking_limit / image_error / model_error / aborted_streaming /
prompt_too_long / completed / stop_hook_prevented / aborted_tools /
hook_stopped / max_turns

每一種都對應(yīng)一個生產(chǎn)事故分類,可以直接進 dashboard 做餅圖。

State 對象是 9 個字段,跨迭代顯式傳遞(src/query.ts L204-217):

type State = {
  messages: Message[]
  toolUseContext: ToolUseContext
  autoCompactTracking: AutoCompactTrackingState | undefined
  maxOutputTokensRecoveryCount: number
  hasAttemptedReactiveCompact: boolean
  maxOutputTokensOverride: number | undefined
  pendingToolUseSummary: Promise<ToolUseSummaryMessage | null> | undefined
  stopHookActive: boolean | undefined
  turnCount: number
  transition: Continue | undefined
}

注意最后那個 transition 字段。每次循環(huán)要 continue 都寫一次它,告訴下一輪"剛剛是因為什么原因再來一遍"。源碼里所有 transition.reason 加起來一共 7 種:

collapse_drain_retry / reactive_compact_retry / max_output_tokens_escalate /
max_output_tokens_recovery / stop_hook_blocking / token_budget_continuation /
next_turn

這玩意兒乍一看像是工程潔癖,實際上解決的是任何寫過 agent loop 的人都遇到過的痛點:這一輪到底是因為什么繼續(xù)的?沒有 transition.reason 你就只能去翻消息內(nèi)容自己反推。有了它,測試可以直接 assert(state.transition.reason === 'reactive_compact_retry'),監(jiān)控可以按 reason 畫分布。

最值得拎出來看的是 L1293-1297 那段保護:

// Preserve the reactive compact guard — if compact already ran and
// couldn't recover from prompt-too-long, retrying after a stop-hook
// blocking error will produce the same result. Resetting to false
// here caused an infinite loop: compact → still too long → error →
// stop hook blocking → compact → … burning thousands of API calls.
hasAttemptedReactiveCompact,

注釋說人話:"曾經(jīng)把這個標(biāo)志位 reset 成 false,結(jié)果觸發(fā)了幾千次 API 調(diào)用的死循環(huán)。"這種先把人類語言寫進注釋、再用一個布爾位扛起來的代碼,是生產(chǎn)環(huán)境踩坑的化石樣本。

Codex:樸素loop {}+ 4 種退出原因

Codex 這邊在 codex-rs/core/src/session/turn.rs L141 的 run_turn 函數(shù)里直接 loop {},每輪:

  1. 排空 pending_input(用戶插話)
  2. 構(gòu)建 sampling_request_input
  3. 調(diào)用 run_sampling_request
  4. 檢查 needs_follow_up 和 token limit

退出條件是 L519 一行:

if !needs_follow_up {
    last_agent_message = sampling_request_last_agent_message;
    // ... run stop hooks ...
    break;
}

中斷/取消統(tǒng)一收到 TurnAbortReasonprotocol/src/protocol.rs L3707):

pub enum TurnAbortReason {
    Interrupted, Replaced, ReviewEnded, BudgetLimited,
}

注意這里 TurnAbortReason 只有 4 種,比 Claude Code 的 10 種 Terminal 粗很多。再加上 AgentStatus(L1680)的 7 種狀態(tài)(PendingInit / Running / Interrupted / Completed / Errored / Shutdown / NotFound),Codex 一個 turn 總的 lifecycle 狀態(tài)空間也就十幾個枚舉值。

這種粗粒度不是偷懶——Codex 的恢復(fù)策略是"指數(shù)退避重試 + transport fallback",邏輯上簡單一些就夠。run_sampling_request L1106-1119 處的 fallback:WebSocket 跑掛了,自動降級到 HTTPS 再來一次。粗暴有效。

差別在哪

兩條主循環(huán)映射的是兩種不同的"生產(chǎn)可觀測性"哲學(xué):

  • Claude Code 的態(tài)度是先把每條恢復(fù)路徑命名,讓運維和測試能直接對枚舉值斷言。代價是 query.ts 一個文件 1500+ 行,新增一種恢復(fù)原因要改 5 處枚舉/類型/狀態(tài)機。
  • Codex 的態(tài)度是先簡單跑通,只把外層中斷原因(4 種)和異步狀態(tài)機的 7 種 lifecycle 區(qū)分清楚。代價是中間出錯時你只能從日志里往回翻。

如果是自己寫 agent loop,建議先抄 Codex 的樸素結(jié)構(gòu),等踩到具體的死循環(huán)或詭異恢復(fù),再把 transition.reason 這種字段加上去。Claude Code 的 7 種 continue reason 不是憑空設(shè)計的,每一種背后都至少有一次生產(chǎn)事故。

Takeaway:能用 enum 表達的退出/繼續(xù)原因就別用字符串日志。當(dāng)你想加第 8 種 reason 時,類型系統(tǒng)會強迫你把 5 處分支都更新一遍——這是好事不是壞事。

四、維度 2:工具系統(tǒng)——metadata 驅(qū)動 vs 鎖驅(qū)動

圖 4:Claude Code 把每個工具的 isConcurrencySafe / isReadOnly / isDestructive 寫成 per-input 函數(shù),連續(xù) safe 的打成并發(fā)批;Codex 用一個 RwLock,supports_parallel 拿讀鎖,不 parallel 拿寫鎖。

Claude Code:每個工具自帶一組元數(shù)據(jù)

src/Tool.ts L402-406 定義 Tool 接口的幾個判定方法:

isConcurrencySafe(input: z.infer<Input>): boolean
isEnabled(): boolean
isReadOnly(input: z.infer<Input>): boolean
/** Defaults to false. Only set when the tool performs irreversible
    operations (delete, overwrite, send). */
isDestructive?(input: z.infer<Input>): boolean

注意這里所有判定方法都接收 input 參數(shù)。同一個 BashTool 跑 ls 和跑 rm -rf 都會被分別判斷——是 per-input 而不是 per-tool 的判定。這就把"工具元數(shù)據(jù)"從靜態(tài)變成了根據(jù) input 動態(tài)推斷的東西。

更耐看的是 L748-769 的默認(rèn)值:

const TOOL_DEFAULTS = {
  isEnabled: () => true,
  isConcurrencySafe: (_input?: unknown) => false,
  isReadOnly: (_input?: unknown) => false,
  isDestructive: (_input?: unknown) => false,
  checkPermissions: (input, _ctx?) =>
    Promise.resolve({ behavior: 'allow', updatedInput: input }),
}

除了 isEnabledcheckPermissions,所有元數(shù)據(jù)默認(rèn)全部選保守的那一側(cè)。新工具忘了聲明 metadata 不會讓系統(tǒng)更激進,只會讓它更保守。這是非常工業(yè)化的 fail-closed 默認(rèn)值——很多團隊會把它做反,導(dǎo)致 prod 上線后才發(fā)現(xiàn)自己默認(rèn)放行了一類危險操作。

調(diào)度邏輯在 src/services/tools/toolOrchestration.ts L91-116:

function partitionToolCalls(toolUseMessages, toolUseContext): Batch[] {
  return toolUseMessages.reduce((acc, toolUse) => {
    const tool = findToolByName(toolUseContext.options.tools, toolUse.name)
    const parsedInput = tool?.inputSchema.safeParse(toolUse.input)
    const isConcurrencySafe = parsedInput?.success
      ? (() => { try { return Boolean(tool?.isConcurrencySafe(parsedInput.data)) }
                 catch { return false } })()
      : false
    ...
  }, [])
}

13 行講清楚了 Claude Code 的并發(fā)哲學(xué):連續(xù) isConcurrencySafe = true 打成一個并行批次,只要插一個不安全的工具就切回串行。safeParse 失敗時默認(rèn)不并發(fā),回調(diào)拋異常時也默認(rèn)不并發(fā)——兩層 fail-closed。

并發(fā)上限在 L8-11,默認(rèn) 10:

function getMaxToolUseConcurrency(): number {
  return parseInt(process.env.CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY || '', 10) || 10
}

還有一個值得單獨拎出來看的優(yōu)化:StreamingToolExecutorsrc/services/tools/StreamingToolExecutor.ts L48-60)。模型還在流式輸出 tool_use block 時,executor 已經(jīng)開始執(zhí)行:

// Child of toolUseContext.abortController. Fires when a Bash tool errors
// so sibling subprocesses die immediately instead of running to completion.
// Aborting this does NOT abort the parent — query.ts won't end the turn.
private siblingAbortController: AbortController

注釋解釋得很清楚:siblingAbortController 是父 abortController 的子,一個 Bash 工具失敗時它一刀砍掉所有兄弟工具,但不會讓外層 turn 直接終止。這種只爆一層、不會把整個 session 拖死的精細 abort 鏈,是任何寫過 supervisor 的人都該抄的。

Codex:parallel 與否就靠一個鎖

Codex 的工具定義在 codex-rs/tools/src/tool_definition.rs 簡單到只有 5 個字段:

pub struct ToolDefinition {
    pub name: String,
    pub description: String,
    pub input_schema: JsonSchema,
    pub output_schema: Option<JsonValue>,
    pub defer_loading: bool,
}

這里沒有 is_concurrency_safe / is_read_only / is_destructive 這些元數(shù)據(jù)。Codex 走的是另一條路:讓工具自己聲明 supports_parallel,剩下交給一個共享 RwLock 來串。

codex-rs/core/src/tools/parallel.rs L84-129:

let _guard = if supports_parallel {
    Either::Left(lock.read().await)   // 并行:讀鎖
} else {
    Either::Right(lock.write().await) // 串行:寫鎖
};

一個 RwLock,supports_parallel 拿讀鎖,不 parallel 拿寫鎖。讀鎖不互斥,所以并行的工具可以一起跑;寫鎖互斥,所以串行的工具自然進隊列。極簡。

Codex 的工具有十幾種,全在 codex-rs/core/src/tools/handlers/shell/、apply_patch.rsmulti_agents.rs、multi_agents_v2/mcp.rs、request_permissions.rs、request_user_input.rsview_image.rs、tool_search.rs(動態(tài)發(fā)現(xiàn))、plan.rs、goal/。注意有個 tool_search.rs——Codex 默認(rèn)不會把所有工具都塞給模型,模型可以反過來通過 tool_search 工具按 schema 查找當(dāng)前能用什么。這是一種相反方向的"懶加載"。

ToolName 還多了個 namespace 字段(protocol/src/tool_name.rs L9):

pub struct ToolName {
    pub name: String,
    pub namespace: Option<String>,
}

這是給 MCP 工具用的——同名工具來自不同 MCP server 時靠 namespace 區(qū)分,避免和內(nèi)置工具撞車。

而工具流式執(zhí)行 Codex 也有,在 turn.rs L1840 與 L2018:

let mut stream = client_session.stream(prompt, ...).await??;
let mut in_flight: FuturesOrdered<BoxFuture<'static, CodexResult<ResponseInputItem>>> =
    FuturesOrdered::new();

// 收到 OutputItemDone(FunctionCall) 立即 spawn 工具 future
if let Some(tool_future) = output_result.tool_future {
    in_flight.push_back(tool_future);
}

FuturesOrdered 保證按收到順序的 future 完成順序執(zhí)行后續(xù) step——這和 Claude Code 的 siblingAbortController 處理"半成品狀態(tài)"的思路不同:Codex 選擇讓 future 自然完成或被 cancel token 中斷,不主動一刀切兄弟。

如果你做的是分鐘級長跑后臺 agent,Claude Code 的精細 abort 鏈更穩(wěn)——一個 Bash 卡死你不希望另外幾個并行的 grep 全部干等到 timeout;如果是秒級短任務(wù),Codex 的"future 自然完成"策略反而省心,少一層錯誤傳播。

差別在哪

兩套工具系統(tǒng)對應(yīng)兩種安全模型:

  • Claude Code 把工具元數(shù)據(jù)寫在 TS 類型里,由 harness 在調(diào)度前根據(jù) input 動態(tài)推斷。模型不需要懂"哪些工具能并發(fā)",harness 替它判斷。
  • Codex 把工具元數(shù)據(jù)簡化成一個 supports_parallel 布爾,靠 RwLock 把并發(fā)約束物化成鎖競爭。模型需要懂"自己能調(diào)幾個工具",但 OS sandbox 兜底。

哪種更好?要看場景信不信 OS。如果目標(biāo)用戶是開發(fā)者本地用,OS sandbox 完全可信,那 Codex 的極簡法更省心。如果是 SaaS 或 web 服務(wù),OS 不一定可控的情況下,更傾向 Claude Code 這種應(yīng)用層細粒度元數(shù)據(jù)——多一道紙糊的墻也好過沒墻。

Takeaway:fail-closed 默認(rèn)值不只是寫漂亮代碼,是踩過坑之后的剩余物。新增一類工具時如果默認(rèn)值傾向"激進允許",遲早會有一個早晨醒來發(fā)現(xiàn)昨夜燒了 25 萬次 API 調(diào)用。

五、維度 3:上下文壓縮——5 層漏斗 vs 單層 LLM 摘要

圖 5:Claude Code 的 5 層漸進漏斗——免費的 Snip / Microcompact 在前,最貴的 AutoCompact 在后;Codex 是單層 LLM 摘要,但區(qū)分 PreTurn / MidTurn / Standalone 三種觸發(fā)階段,并配 Pre/Post hook 生命周期。

這是兩邊差異最大的一塊,也是 take-away 最多的一部分。

Claude Code:5 層漏斗 + 250K 次 / 天的熔斷

Claude Code 把上下文壓縮拆成 5 層,按"從最便宜到最貴"的順序逐級回退(按 query.ts L401 / L414 / L440 / L454 順序觸發(fā)):

  1. Snip:直接丟老的 round(feature gate HISTORY_SNIP,services/compact/snipCompact.ts),不調(diào) LLM。
  2. Microcompact:按 tool_use_id 去重工具結(jié)果(services/compact/microCompact.ts,緩存編輯模式叫 CACHED_MICROCOMPACT),仍然不調(diào) LLM。
  3. Context Collapse:把老段折疊成 summary(feature gate CONTEXT_COLLAPSE),可 projection 回放。
  4. AutoCompact:調(diào) LLM 生成整體摘要(services/compact/autoCompact.ts)。
  5. Reactive Compact:在 prompt_too_long 錯誤發(fā)生后才按需觸發(fā),作為最后一道墻。

閾值常量在 autoCompact.ts L62-70:

export const AUTOCOMPACT_BUFFER_TOKENS = 13_000
export const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000
export const ERROR_THRESHOLD_BUFFER_TOKENS = 20_000
export const MANUAL_COMPACT_BUFFER_TOKENS = 3_000
// BQ 2026-03-10: 1,279 sessions had 50+ consecutive failures (up to 3,272)
// in a single session, wasting ~250K API calls/day globally.
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3

最后那個常量值得單獨拎出來。注釋直接寫:2026 年 3 月 10 號統(tǒng)計到 1279 個 session 出現(xiàn)過 50+ 連續(xù) autocompact 失敗,最嚴(yán)重一個 session 跑了 3272 次,全 fleet 一天浪費約 25 萬次 API 調(diào)用。然后他們加了 MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3:連續(xù)失敗 3 次熔斷,停止重試。

這就是生產(chǎn)經(jīng)驗的化石——你看不到事故復(fù)盤報告,但事故的疤痕被一行常量永遠固定在源碼里。

post-compact 還有一組保留預(yù)算(compact.ts L122-131):

export const POST_COMPACT_MAX_FILES_TO_RESTORE = 5
export const POST_COMPACT_TOKEN_BUDGET = 50_000
export const POST_COMPACT_MAX_TOKENS_PER_FILE = 5_000
export const POST_COMPACT_MAX_TOKENS_PER_SKILL = 5_000
export const POST_COMPACT_SKILLS_TOKEN_BUDGET = 25_000

意思是:壓縮完不是"光禿禿留個摘要",而是再補 5 個最近文件、每文件 5K 內(nèi)、最近調(diào)用過的 skills 共計 25K——避免壓縮后立刻又去讀同一批文件造成 thrashing。

Codex:單層 LLM 摘要 + 三階段 + Pre/Post Hook

Codex 走的是另一種思路:只做一種壓縮動作(LLM 摘要),但區(qū)分觸發(fā)階段執(zhí)行位置

codex-rs/core/src/compact.rs 的核心常量:

pub const SUMMARIZATION_PROMPT: &str = include_str!("../templates/compact/prompt.md");
pub const SUMMARY_PREFIX: &str = include_str!("../templates/compact/summary_prefix.md");
const COMPACT_USER_MESSAGE_MAX_TOKENS: usize = 20_000;

注意 prompt 是從 markdown 文件 include_str! 進來的——可以單獨 review,可以讓產(chǎn)品經(jīng)理修改而不需要改 Rust。

觸發(fā)閾值通過 model_auto_compact_token_limitconfig/mod.rs L527)配置,turn loop L480 判斷:

let token_limit_reached = total_usage_tokens >= auto_compact_limit;
if token_limit_reached && needs_follow_up {
    run_auto_compact(..., CompactionReason::ContextLimit, CompactionPhase::MidTurn).await;
}

CompactionPhase 枚舉三種:PreTurn / MidTurn / StandaloneTurn。意思是同一段壓縮邏輯會被三個不同時機調(diào)用——在新 turn 開始前預(yù)先壓(preventive)、在當(dāng)前 turn 中間被打斷時壓(reactive)、或者作為單獨的 standalone turn 被用戶觸發(fā)。

執(zhí)行位置上 Codex 還有三種實現(xiàn):

run_inline_auto_compact_task          —— 本地 inline
run_inline_remote_auto_compact_task   —— 遠程
run_inline_remote_auto_compact_task_v2 —— 遠程 v2(迭代版)

更有意思的是 Codex 給 compact 配了 hook 生命周期:PreCompactHookOutcomePostCompactHookOutcome。開發(fā)者可以在壓縮前后插入自己的邏輯,比如審計、備份、metric 上報。Claude Code 的 PreCompact / PostCompact 在 27 個 hook 事件里也有,但觸發(fā)點的可定制粒度上 Codex 顯式做成了 outcome enum 而非 string event。

InitialContextInjectioncompact.rs L59)這個枚舉也很有意思:

pub(crate) enum InitialContextInjection {
    BeforeLastUserMessage,
    DoNotInject,
}

壓縮完之后要不要把"前情提要"重新塞回最后一條 user message 之前?兩個選項,二選一。這種把決策維度寫成最小封閉枚舉的味道貫穿 Codex 全棧。

差別在哪

Claude Code 的 5 層漏斗背后是一種"省錢第一"的產(chǎn)品觀:免費手段(截斷 / 去重)在前,貴的 LLM 調(diào)用在后;每層都有自己的熔斷;甚至 compact 調(diào)用本身也走父 prompt cache 共享,fleet 累計節(jié)省的 compute 折到周維度也是 Gtok 數(shù)量級(同一段 runAgent.ts L385-410 注釋里 “~5-15 Gtok/week” 那一組數(shù)字)。

Codex 的單層摘要 + 三階段背后是"分清楚層次"的工程觀:壓縮只有一種實質(zhì)動作,但觸發(fā)時機、執(zhí)行位置、生命周期 hook 各自正交。開發(fā)者要擴展時,不用動核心算法,挑一個 phase 或 hook 插進去。

哪種更好?比較站得住的判斷是:5 層漏斗只有 fleet-scale 才劃算。Claude Code 那 5 層每加一層都需要踩一個具體坑——HISTORY_SNIP 是一個 feature gate,CONTEXT_COLLAPSE 是另一個 feature gate,它們都不是從 day 1 設(shè)計的,都是"線上某場景太貴了,加一層免費的"演化出來的。如果你是個起步項目,先抄 Codex 單層摘要,等用戶量上來發(fā)現(xiàn)錢燒得太兇了,再把免費層往前加。

Takeaway:壓縮策略最容易踩的坑不是"算法不對",而是"沒有熔斷"。MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 這種常量看上去不起眼,沒有它你的服務(wù)有概率把自己燒穿。

六、維度 4:權(quán)限與 Sandbox——AST 解析 vs OS 內(nèi)核

圖 6:Claude Code 把 fail-closed 壓在 TS 類型 + Bash AST 解析 + 三態(tài)權(quán)限規(guī)則上;Codex 把 fail-closed 壓在 Linux Landlock+Seccomp+bubblewrap / macOS Seatbelt / Windows ACL+WFP 這些 OS 內(nèi)核能力上。

這是兩邊哲學(xué)差異最顯眼的一塊。

Claude Code:用應(yīng)用層把"危險動作"擋掉

Claude Code 沒有 OS-level sandbox。它的安全完全壓在應(yīng)用層的幾道關(guān)卡上:

第一關(guān):deny rules 過濾工具集。filterToolsByDenyRules 在 system prompt 構(gòu)建前就把 deny 列表里的工具濾掉,模型根本看不到。

第二關(guān):Bash AST parsersrc/utils/bash/bashParser.ts L1-30 有這么一段非常野的注釋:

/**
 * Pure-TypeScript bash parser producing tree-sitter-bash-compatible ASTs.
 * Validated against a 3449-input golden corpus generated from the WASM parser.
 */
const PARSE_TIMEOUT_MS = 50
const MAX_NODES = 50_000

3449 條 golden corpus 是從原來的 WASM tree-sitter parser 生成的——也就是說他們先用 WASM 跑過幾千條真實命令拿到正確 AST,再把這些 case 當(dāng)測試集,然后從頭寫了一份純 TS 的 parser 來匹配。50ms 解析超時 + 50K 節(jié)點上限是給對抗輸入留的逃生口。

為什么不繼續(xù)用 WASM?純 TS 啟動更快、bundle 更小、不依賴外部 .wasm 文件。代價是寫一個 bash parser 從零開始,golden corpus 驗證 3449 條——這個工程量光聽描述就肝疼。

第三關(guān):三態(tài)權(quán)限規(guī)則src/Tool.ts L123-138):

export type ToolPermissionContext = DeepImmutable<{
  mode: PermissionMode
  alwaysAllowRules: ToolPermissionRulesBySource
  alwaysDenyRules: ToolPermissionRulesBySource
  alwaysAskRules: ToolPermissionRulesBySource
  ...
}>

allow / deny / ask 三態(tài),規(guī)則按來源分(user-config / project-config / managed-policy / built-in),優(yōu)先級有講究。

第四關(guān):YOLO classifier。auto mode 下跑一個 Claude Haiku 旁路評估每條命令的風(fēng)險等級(src/utils/permissions/yoloClassifier.ts)。System prompt 從 yolo-classifier-prompts/auto_mode_system_prompt.txt 加載。Haiku 成本幾乎可以忽略,但能在主模型決定執(zhí)行前再做一次 LLM-級別的安全過濾。

四道關(guān)卡全是應(yīng)用層。優(yōu)勢是細粒度高、可以做語義判斷(“這條命令在意圖上是不是危險”);劣勢是只要 Claude Code 的 TS 進程被繞過,sandbox 就沒了。

Codex:把 fail-closed 壓到 OS

Codex 的安全模型是另一種風(fēng)格——把內(nèi)核能力請到臺前。

Linuxcodex-rs/linux-sandbox/src/landlock.rs 用 Landlock ABI + Seccomp BPF:

pub(crate) fn apply_permission_profile_to_current_thread(
    permission_profile: &PermissionProfile,
    cwd: &Path,
    apply_landlock_fs: bool,
    allow_network_for_proxy: bool,
    proxy_routed_network: bool,
) -> Result<()> { ... }

文件系統(tǒng)訪問由 bubblewrap (bwrap) 命名空間隔離,網(wǎng)絡(luò)由 seccomp 系統(tǒng)調(diào)用過濾——子進程在 syscall 層直接被禁止訪問限制路徑或建 outbound socket,不靠應(yīng)用層"判斷這條命令危不危險"。

macOScodex-rs/sandboxing/src/seatbelt.rs

const MACOS_SEATBELT_BASE_POLICY: &str = include_str!("seatbelt_base_policy.sbpl");
pub const MACOS_PATH_TO_SEATBELT_EXECUTABLE: &str = "/usr/bin/sandbox-exec";

直接調(diào)系統(tǒng)的 /usr/bin/sandbox-exec,policy 寫在 .sbpl 文件里。

Windowscodex-rs/windows-sandbox-rs/,含 ACL、WFP(Windows Filtering Platform)、Desktop 隔離、Token 權(quán)限降低、進程屬性。這是 Codex 倉庫里最大的一個 sandbox crate,因為 Windows 的隔離能力本來就比 Unix 系散亂。

Sandbox 的策略走 protocol/src/protocol.rs L994 的 SandboxPolicy

pub enum SandboxPolicy {
    DangerFullAccess,
    ReadOnly { network_access: bool },
    ExternalSandbox { network_access: NetworkAccess },
    WorkspaceWrite { writable_roots, network_access, exclude_tmpdir_env_var, ... },
}

四個 variant 把"完全開放 / 只讀 / 外部 sandbox / 工作區(qū)可寫"這四種典型部署形態(tài)顯式枚舉出來。WorkspaceWrite 還能配 writable_roots、exclude_tmpdir_env_var 等細節(jié)。

審批策略 AskForApproval(L900):

pub enum AskForApproval {
    UnlessTrusted,    // 僅只讀命令自動通過
    OnFailure,        // 沙箱失敗才問
    OnRequest,        // 模型決定何時問(默認(rèn))
    Granular(GranularApprovalConfig),
    Never,
}

OnFailure 這種命名本身就在表態(tài)——日常都讓 sandbox 兜,只有當(dāng) sandbox 真的攔下來了,才升級到問用戶。這個策略名等于在告訴用戶:信內(nèi)核能力,不信應(yīng)用層判斷。

codex-rs/execpolicy/ 還提供命令級 prefix rule 白名單作為補充。

差別在哪

兩條思路的根本分歧:

  • Claude Code 信應(yīng)用層語義判斷:Bash AST 解析后能識別 rm -rf / 這種語義危險——哪怕 syscall 層是合法的;YOLO classifier 還能在意圖層再判一道。代價是凡是 TS 進程內(nèi)的代碼都能繞過。
  • Codex 信內(nèi)核:syscall 在 namespace / Landlock / Seatbelt 層就被攔掉,應(yīng)用層邏輯被繞過也沒用。代價是只能攔"系統(tǒng)能攔的"——對于"語義危險但 syscall 合法"的命令,OS sandbox 幫不了你。

這兩條不是對立的。比較穩(wěn)的做法是:真正生產(chǎn)級的 agent harness 應(yīng)該兩層都有——syscall 兜底 + 應(yīng)用層 AST 做語義判斷。Codex 自己也有 execpolicy 做 prefix 白名單——只是它的應(yīng)用層比較淺。

Takeaway:如果你只能選一種,先選 OS sandbox。AST 解析靠人寫黑名單,永遠跟不上手段更新。Landlock/Seatbelt 這種 OS 能力是被很多年攻防驗證過的。

七、維度 5:子 Agent——7 種 Task 類型 vs 內(nèi)核級 spawn_agent

圖 7:左 Claude Code 的 7 種 TaskType 各自對應(yīng)一種執(zhí)行模式(local_bash / local_agent / remote_agent / in_process_teammate / local_workflow / monitor_mcp / dream),靠 forkedAgent.ts 的 CacheSafeParams 共享父 prompt cache;右 Codex 直接做 AgentControl + AgentRegistry,模型用 spawn_agent / wait_agent 工具顯式編排。

一個常見的誤解是只有 Claude Code 才有"子 agent"概念。讀完 Codex 之后會發(fā)現(xiàn)兩邊都做了,但路徑完全不同。

Claude Code:7 種 Task 類型 + 隔離膜

src/Task.ts L6-13:

export type TaskType =
  | 'local_bash'
  | 'local_agent'
  | 'remote_agent'
  | 'in_process_teammate'
  | 'local_workflow'
  | 'monitor_mcp'
  | 'dream'

7 種類型,各自對應(yīng)一種生命周期:本地 shell 任務(wù)、本地 agent 進程、遠端 agent、同進程的 teammate 協(xié)作、workflow 編排、MCP 監(jiān)控、還有一個叫 “dream” 的(猜測是離線/低優(yōu)先級的任務(wù))。

子 agent 上下文克隆在 src/utils/forkedAgent.ts,關(guān)鍵是 5 個字段必須字節(jié)級一致才能命中父的 prompt cache(L57-68):

export type CacheSafeParams = {
  systemPrompt: SystemPrompt
  userContext: { [k: string]: string }
  systemContext: { [k: string]: string }
  toolUseContext: ToolUseContext
  forkContextMessages: Message[]
}

注釋里提到 budget_tokens 改一個數(shù)字 cache 就全沒——thinking config 也是 cache key 的一部分。

更精彩的是 L413-417 那個逃生通道

// Task registration/kill must always reach the root store, even when
// setAppState is a no-op — otherwise async agents' background bash tasks
// are never registered and never killed (PPID=1 zombie).
setAppStateForTasks:
  parentContext.setAppStateForTasks ?? parentContext.setAppState,

子 agent 默認(rèn)所有 mutable state 都被替換成 no-op(包括 setAppState),但 setAppStateForTasks 單獨走逃生通道直連根 store。原因?qū)懺谧⑨尷铮汉笈_ bash 任務(wù)必須能注冊到根 AppState 才能被正確 kill,不然變 PPID=1 僵尸進程。

這種絕大多數(shù)都隔離、只開一個最窄的逃生通道的設(shè)計值得停下來體會。這是工程審美。

src/tools/AgentTool/runAgent.ts L385-410 還有一段經(jīng)驗金句:

// Read-only agents (Explore, Plan) don't act on commit/PR/lint rules from
// CLAUDE.md — the main agent has full context and interprets their output.
// Dropping claudeMd here saves ~5-15 Gtok/week across 34M+ Explore spawns.
const shouldOmitClaudeMd = agentDefinition.omitClaudeMd && ...

每周在 3400 萬次 Explore 調(diào)用上省 5-15 Gtok。這種優(yōu)化只有 fleet-scale 才會發(fā)現(xiàn)——但思路是任何規(guī)模都能抄的:只讀 agent 不需要 commit message 規(guī)則。

Codex:spawn_agent 是一個工具

Codex 這邊,子 agent 不是 7 種類型,而是一個內(nèi)核能力:AgentControlcodex-rs/core/src/agent/control.rs L137):

pub(crate) struct AgentControl {
    session_id: SessionId,
    manager: Weak<ThreadManagerState>,
    state: Arc<AgentRegistry>,
}

spawn_agent_with_metadata(L185):

pub(crate) async fn spawn_agent_with_metadata(
    &self,
    config: crate::config::Config,
    initial_operation: Op,
    session_source: Option<SessionSource>,
    options: SpawnAgentOptions,
) -> CodexResult<LiveAgent> { ... }

模型通過 spawn_agent、wait_agentclose_agent 這些工具調(diào)用顯式 spawn 子線程。子 agent 繼承父的 model/sandbox/approval 配置,可選 role 覆蓋。

SpawnAgentForkMode(L48)二選一:

pub(crate) enum SpawnAgentForkMode {
    FullHistory,
    LastNTurns(usize),
}

要么完整傳父歷史,要么只傳最后 N 輪——和 Claude Code 的 forkContextMessages 類似但更顯式。

還有 exceeds_thread_spawn_depth_limit 檢查避免無限嵌套。

multi_agents.rs 里實現(xiàn)了 inter-agent 郵箱機制——子 agent 可以異步給父 agent 發(fā)消息,父 agent 也可以等待子 agent 完成。這就比 Claude Code 的"父只看子的最終輸出"強一些。

差別在哪

兩條 sub-agent 哲學(xué):

  • Claude Code 的 sub-agent 是產(chǎn)品形態(tài):7 種類型按使用場景命名,每種都有 UI 反饋、有 sidechain transcript。模型不用關(guān)心 spawn / wait / close 這些原語,只調(diào)一次 Task tool 就行。
  • Codex 的 sub-agent 是內(nèi)核能力:模型直接看到 spawn_agent / wait_agent / close_agent 這些原語,自己負責(zé)編排。harness 提供機制,不提供策略。

產(chǎn)品形態(tài)決定選哪種:

  • 如果你做的是面向終端用戶的 CLI,Claude Code 那種"7 種命名好的 task 類型"產(chǎn)品感更強,用戶能從 UI 看出"這個 task 在跑 Explore 還是 Plan"。
  • 如果你做的是給 LLM 操作的內(nèi)核(比如下游再有個 agent 編排框架),Codex 那種 spawn/wait/close 原語反而更通用——你可以在上面疊任何調(diào)度策略。

Takeaway:sub-agent 設(shè)計的核心問題不是"要不要支持",而是"暴露給模型的是產(chǎn)品語義還是內(nèi)核原語"。這兩條路都走得通,但中間那種"既不像產(chǎn)品也不像內(nèi)核"的設(shè)計是踩坑陷阱。

八、維度 6:擴展機制——27 個 Hook + Skills 懶加載 vs MCP 雙向

圖 8:Claude Code 的擴展是 27 種 hook 事件 + Skills 懶加載 + Plugin 打包三層獨立結(jié)構(gòu);Codex 把所有擴展面收到 MCP 這一根 wire 上,靠 mcp-server / rmcp-client 雙向 stdio JSON-RPC 完成。

Claude Code 的擴展面積非常大,hook 事件 27 種全部列在 src/entrypoints/sdk/coreTypes.ts L25-53:

export const HOOK_EVENTS = [
  'PreToolUse', 'PostToolUse', 'PostToolUseFailure', 'Notification',
  'UserPromptSubmit', 'SessionStart', 'SessionEnd', 'Stop', 'StopFailure',
  'SubagentStart', 'SubagentStop', 'PreCompact', 'PostCompact',
  'PermissionRequest', 'PermissionDenied', 'Setup', 'TeammateIdle',
  'TaskCreated', 'TaskCompleted', 'Elicitation', 'ElicitationResult',
  'ConfigChange', 'WorktreeCreate', 'WorktreeRemove',
  'InstructionsLoaded', 'CwdChanged', 'FileChanged',
] as const

PreToolUse 這種 hook 還能改命——AggregatedHookResultsrc/types/hooks.ts L277-289):

export type AggregatedHookResult = {
  preventContinuation?: boolean
  permissionBehavior?: PermissionResult['behavior']
  additionalContexts?: string[]
  updatedInput?: Record<string, unknown>
  updatedMCPToolOutput?: unknown
  permissionRequestResult?: PermissionRequestResult
  retry?: boolean
}

返回 preventContinuation: true 阻止工具執(zhí)行;返回 updatedInput 改寫模型的參數(shù);返回 additionalContexts 給后續(xù) turn 注入提示。這是把"harness 代模型決策"做到極致的設(shè)計。

還有一個非常細的工程細節(jié),utils/hooks/hookEvents.ts L20 / L57-78 的事件隊列:

const MAX_PENDING_EVENTS = 100
const pendingEvents: HookExecutionEvent[] = []
let eventHandler: HookEventHandler | null = null
function emit(event: HookExecutionEvent): void {
  if (eventHandler) { eventHandler(event) }
  else {
    pendingEvents.push(event)
    if (pendingEvents.length > MAX_PENDING_EVENTS) { pendingEvents.shift() }
  }
}

handler 還沒注冊時事件先進隊列,handler 上線時批量 flush。這是"遲到訂閱者也能拿到啟動事件"的標(biāo)準(zhǔn)做法——對插件和 skill 的動態(tài)加載非常關(guān)鍵。

Skill 走的是另一條懶加載路徑。src/skills/loadSkillsDir.ts L97-103:

/**
 * Estimates token count for a skill based on frontmatter only
 * (name, description, whenToUse) since full content is only loaded on invocation.
 */
export function estimateSkillFrontmatterTokens(skill: Command): number {
  const frontmatterText = [skill.name, skill.description, skill.whenToUse]
    .filter(Boolean).join(' ')

啟動時只 load frontmatter(約 100 tokens),正文(可能 20K+)只在 SkillTool 調(diào)用時才 fetch。50 個 skill 啟動成本是 5K 而不是 1M。

LoadedFrom 類型(L67-73)區(qū)分了來源:

export type LoadedFrom =
  | 'commands_DEPRECATED' | 'skills' | 'plugin' | 'managed' | 'bundled' | 'mcp'

isRestrictedToPluginOnlysrc/utils/settings/pluginOnlyPolicy.ts)被 7 個文件引用,企業(yè)管理員可以強制"只允許官方 plugin 提供 skill / hook / MCP"。

而 Codex 這邊——基本上沒有 hook這個獨立機制。擴展面積主要落在 MCP 上:

  • codex-rs/mcp-server/ 讓 Codex 暴露自己作為 MCP server,外部應(yīng)用可以通過 stdio JSON-RPC 調(diào)用 codex_tool_runner、exec_approval、patch_approval 等。
  • codex-rs/rmcp-client/ 讓 Codex 消費外部 MCP server 提供的工具(stdio_server_launcher.rs 啟動外部進程,elicitation_client_service.rs 反向 elicit user input)。
  • codex-rs/codex-mcp/ 管 namespace 路由。

這就是 Codex 的擴展觀:所有擴展都走 MCP 這一條 wire。要擴 tool?寫 MCP server。要讓 Codex 給別的工具用?把 Codex 跑成 MCP server。

Compact 那邊 Codex 倒是有 PreCompactHookOutcome / PostCompactHookOutcome,但這是個例不是體系。

差別在哪

  • Claude Code 的擴展是面向開發(fā)者:27 種 hook、Skills 懶加載、Plugin 打包,每種擴展形態(tài)對應(yīng)一種使用場景。代價是擴展面積爆炸,企業(yè)管控需要 isRestrictedToPluginOnly 這種閘門。
  • Codex 的擴展是面向系統(tǒng)集成:MCP 雙向就是一切,外部系統(tǒng)通過 stdio JSON-RPC 接 Codex,Codex 通過 stdio JSON-RPC 接外部系統(tǒng)。代價是細粒度操控(如 PreToolUse 改命)做不到,只能在 tool 層面拼。

如果你做的是開發(fā)者社區(qū)生態(tài),Claude Code 的多種擴展形態(tài)更順;如果你做的是企業(yè) IDE 集成,Codex 這種"全走 MCP"反而更省心——一種協(xié)議覆蓋所有邊界。

Takeaway:擴展機制最容易踩的坑是"什么都做"。Claude Code 27 種 hook 的演化是踩坑驅(qū)動的,不是 day 1 設(shè)計的。Codex 把擴展面收到 MCP 一條 wire 上,是另一種克制。

九、維度 7:跨端 / 多入口——可選回調(diào) vs 單二進制多 mode

圖 9:左 Claude Code 在一個 Bun 進程里靠 ToolUseContext 上的可選回調(diào)區(qū)分 REPL/SDK/Bridge;右 Codex 是單 Rust 二進制 + 5 種 mode(CLI/TUI/Exec/MCP server/app-server-daemon),加 TS 和 Python SDK。

Claude Code 的多端實現(xiàn)非常巧妙。所有 mode 共享同一個 queryLoop,區(qū)分僅靠 ToolUseContext 上的可選回調(diào)(來自 Tool.ts 節(jié)選):

export type ToolUseContext = {
  options: { /* tools, mcpClients, commands, ... */ }
  abortController: AbortController
  readFileState: FileStateCache
  getAppState(): AppState
  setAppState(f): void
  setToolJSX?: SetToolJSXFn
  addNotification?: (...)
  setStreamMode?: (mode)
  handleElicitation?: (serverName, params, signal) => Promise<ElicitResult>
  requestPrompt?: (sourceName, summary) => (req) => Promise<PromptResponse>
  agentId?: AgentId
}

注意那些 ?:——非本環(huán)境的回調(diào)直接 undefined。主循環(huán)代碼完全不 care 自己跑在哪個環(huán)境,只 care “這個回調(diào)在不在”。這是極簡的多端接口。

Bridge 那邊是另一套機制。src/bridge/workSecret.ts L6-31:

export function decodeWorkSecret(secret: string): WorkSecret {
  const json = Buffer.from(secret, 'base64url').toString('utf-8')
  ...
  if (typeof obj.session_ingress_token !== 'string' || ...)
    throw new Error('Invalid work secret: missing or empty session_ingress_token')
}

workSecret 是 base64url 編碼的 JSON,包含 session_ingress_tokenapi_base_url。Bridge 進程靠這個 token 接管 session,把本地狀態(tài) pipe 給手機或 IDE。

src/bridge/capacityWake.ts L1-9 的注釋把多端協(xié)作的核心問題寫得很清楚:

/**
 * Shared capacity-wake primitive for bridge poll loops.
 * Both replBridge.ts and bridgeMain.ts need to sleep while "at capacity"
 * but wake early when either (a) the outer loop signal aborts (shutdown),
 * or (b) capacity frees up (session done / transport lost).
 */

“at capacity 時睡著,shutdown 或 session 完成時立刻醒”——這是任何長連接代理都要解決的問題。

Codex 那邊走的是另一條路。codex-rs/cli/src/main.rs 是一個 dispatcher,根據(jù)子命令進入不同 mode:

  • codex_tui::Cli — TUI 交互
  • codex_exec::Cli — 非交互執(zhí)行(headless)
  • MCP server 模式
  • app-server / app-server-daemon — 長駐守護進程(類 LSP protocol)

SessionSourceprotocol/src/protocol.rs L2519)枚舉了所有來源:

pub enum SessionSource {
    Cli,
    VSCode,
    Exec,
    Mcp,
    Custom(String),
    Internal(InternalSessionSource),
    SubAgent(SubAgentSource),
    Unknown,
}

然后 app-server-protocol 是個 JSON-RPC 協(xié)議——VS Code、自家 SDK、其他 IDE 都通過這個協(xié)議跟 daemon 通信。sdk/typescript/sdk/python/ 就是協(xié)議的語言綁定。

差別在哪

  • Claude Code 用進程內(nèi)多端共享主循環(huán):一個 Node 進程跑 REPL、跑 SDK 調(diào)用、跑 Bridge 遠端,區(qū)分靠回調(diào)有無。代價是必須有 Node runtime,跨語言綁定難——你想給 Java/Go 工具鏈做嵌入接入會非常別扭。
  • Codex 用單二進制多 mode + 跨語言 SDK:Rust 二進制可以獨立跑在沒 Node 的服務(wù)器上,但每個 mode 是獨立子命令;多語言 SDK 通過 JSON-RPC 接 daemon,所以 Java/Go/Python 后端都能直接調(diào)。
  • 共同點:主循環(huán)代碼完全不 care 自己跑在哪個環(huán)境。Claude Code 用"回調(diào)存在與否"實現(xiàn)這一點,Codex 用"SessionSource enum + mode 子命令"實現(xiàn)。兩條路都避免了 if mode == 'repl' else if mode == 'sdk' 這種到處分叉的反模式。
  • 隱含的代價不一樣:Claude Code 的可選回調(diào)寫起來輕,但維護時一旦多端行為不一致,bug 散在調(diào)用點;Codex 的 mode 子命令把行為分支顯式集中在 main.rs dispatcher,但增加一種 mode 要動整條編譯鏈。

哪種更好?如果目標(biāo)是"要一個 npx 一行就跑起來的開發(fā)工具",Claude Code 的 Node 路線讓 hook/skill 這種 markdown 擴展非常順。如果目標(biāo)是"要讓 Java/Go/Python 后端都能調(diào)用這個 agent",Codex 的 Rust + JSON-RPC 路線天然友好。

Takeaway:跨端不是從 day 1 決定的,是被部署形態(tài)逼出來的。兩邊都把"主循環(huán)不要 if mode == ‘X’"這條原則做到了——一個用可選回調(diào),一個用枚舉 SessionSource。

十、維度 8:成本與可觀測性——cache 字段對齊 vs 三種壓縮實現(xiàn)

前 7 個維度看的是"功能對錯"——能不能跑、安不安全、能不能擴。這一個維度看的是"花多少錢、出問題能不能查",是把 agent 推上 fleet 之后真正決定生死的層。

Claude Code:把 prompt cache 字段對齊當(dāng)頭號優(yōu)化

src/utils/forkedAgent.ts L57-68 的 CacheSafeParams 之前提過,再貼一次:

export type CacheSafeParams = {
  systemPrompt: SystemPrompt
  userContext: { [k: string]: string }
  systemContext: { [k: string]: string }
  toolUseContext: ToolUseContext
  forkContextMessages: Message[]
}

這 5 個字段必須字節(jié)級和父 agent 一致,子 agent 才能命中父的 prompt cache。Anthropic 官方對這個機制的說明是:cache read 通常只要 cache write 的 10% 價格(見參考章 Anthropic Prompt Caching docs)。所以當(dāng)你 fleet 每周有 3400 萬次 Explore agent spawn 的時候(runAgent.ts L385-410 注釋里的真實數(shù)字),命中或不命中 cache 是幾個零的成本差距。

實操上有幾個隱藏地雷:

  • thinking budget 是 cache key 的一部分。切個 budget_tokens 整個 cache 就失效。所以切模型時 Claude Code 會顯式 strip thinking signatures。
  • Explore / Plan agent 主動剝離 claudeMd 和 gitStatusrunAgent.ts L385-410 注釋里寫"saves ~5-15 Gtok/week")。原因是這兩 agent 是只讀的,不需要"commit message 要遵循 conventional commits"這種 rule,但每次都喂 30-40K tokens 進去太虧。
  • Compact 調(diào)用刻意共享父 prompt prefix(同一段注釋里)。壓縮本身不便宜,但只要 cache 命中,compute 費用比 cache miss 時低一個量級——這是和上一條同等量級的 Gtok/week 節(jié)省。

可觀測性這邊,Claude Code 的事件隊列設(shè)計精巧。utils/hooks/hookEvents.ts L20 / L57-78 之前提過的 MAX_PENDING_EVENTS = 100 + 早期事件先入隊列、handler 上線再 flush——這意味著 hook 系統(tǒng)絕對不會丟啟動事件,就算你的 plugin 是延遲加載的。Terminal.reason 10 種 + Continue.reason 7 種再加上 27 種 hook event,這套枚舉體系直接對接 OpenTelemetry 的 attribute 維度都不需要適配層。

Codex:把"壓縮在哪兒跑"也做成枚舉

Codex 沒有把 prompt cache 抽象成像 CacheSafeParams 這么顯式的類型——但它在另一個維度做了取舍:壓縮動作的執(zhí)行位置可枚舉?;乜?compact.rs 的三個實現(xiàn):

run_inline_auto_compact_task          —— 本地 inline
run_inline_remote_auto_compact_task   —— 遠程
run_inline_remote_auto_compact_task_v2 —— 遠程 v2

為什么搞三種?因為 Codex 的部署形態(tài)是"本地二進制 + 遠端 daemon + IDE 客戶端"三種疊加。壓縮有時候在你筆記本里跑(inline),有時候在 daemon 里跑(remote),v2 是后來重做的版本。這三種實現(xiàn)共享同一份 SUMMARIZATION_PROMPT 模板(include_str!("../templates/compact/prompt.md")),但運行環(huán)境不同——這是把"成本結(jié)構(gòu)"作為一種 enum 編進代碼。

COMPACT_USER_MESSAGE_MAX_TOKENS = 20_000 這個常量看起來很普通,但它定義了"任何一條 user message 超過 20K 都會被預(yù)先壓"——是一種成本側(cè)的 fail-closed。不是等到 prompt_too_long 再處理,是預(yù)防性切。

可觀測性這邊,Codex 的 AgentStatus(7 種 lifecycle)+ TurnAbortReason(4 種)+ SessionSource(8 種)+ CompactionPhase(3 種)合起來同樣可以投到 metric 維度上。但相對 Claude Code 那種"每條 transition 都有 reason"的力度就粗一些。

差別在哪

  • Claude Code 的成本觀是字段對齊:每一處 fork / compact / sub-agent spawn 都在壓榨 cache 命中??捎^測性是 reason 枚舉密集鋪,“每條 continue 都能被 dashboard 切片”。
  • Codex 的成本觀是位置枚舉:把 inline / remote / remote-v2 當(dāng)成不同壓縮實現(xiàn),按部署形態(tài)選。可觀測性是 lifecycle 枚舉,“每個 turn 都能被 status 標(biāo)記”。

什么場景偏哪種?如果你的 agent 調(diào)用密度極高(每用戶每分鐘幾十次 turn),prompt cache 字段對齊這種細顆粒優(yōu)化非常劃算。如果你的 agent 是每個 task 幾分鐘、人工觸發(fā),三種壓縮位置反而更重要——你需要選"在哪兒跑"。

Takeaway:成本和可觀測性是同一件事的兩面。能精確度量的成本才能精確優(yōu)化,反過來能精確分桶的事件流才能精確歸因。Claude Code 選了字段對齊 + reason 密集;Codex 選了位置枚舉 + lifecycle 三態(tài)。兩者都比"反正把日志打到 ELK 里"強一個量級。

十一、要抄哪段?一張選型表

把前面 8 個維度的差異收成一張可執(zhí)行清單——你在做什么類型的產(chǎn)品,左邊一列定位自己,右邊一列直接告訴你抄哪邊。每一行背后都對應(yīng)著一種典型的項目場景,不是憑空對仗。

你在做的事推薦抄的部分
個人 / 小團隊的 coding 助手抄 Codex 的樸素 loop {} + TurnAbortReason,等坑出來再升 enum
走自托管的企業(yè) agent SaaS抄 Claude Code 的 Terminal.reason + transition.reason,從一開始就讓運維能按 reason 分桶
服務(wù)端無人值守 agent(無開發(fā)者本地 OS)抄 Codex 的 OS sandbox 三件套(Landlock / Seatbelt / WFP);只信內(nèi)核,別信應(yīng)用層
桌面 dev tool(開發(fā)者自愿安裝運行)抄 Claude Code 的 Bash AST + 三態(tài)權(quán)限規(guī)則,UX 上能讓用戶一鍵 always allow
多 LLM 任務(wù)編排框架抄 Codex 的 spawn_agent 內(nèi)核能力,把策略留給上層
面向終端用戶的產(chǎn)品化 agent抄 Claude Code 的 7 種 TaskType + sidechain transcript,讓 UI 能呈現(xiàn)"這是 Explore / Plan / Workflow"
上下文壓縮第一版抄 Codex 的單層 LLM 摘要 + Pre/Post hook,先把生命周期插槽留好
上下文壓縮 fleet 優(yōu)化抄 Claude Code 的 5 層漏斗,免費手段優(yōu)先;務(wù)必加 MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES 熔斷
多語言 SDK / IDE 集成抄 Codex 的 app-server-protocol JSON-RPC + Rust 單二進制
富 markdown 擴展生態(tài)(hook/skill/plugin)抄 Claude Code 的 27 個 hook + 懶加載 skill + plugin 打包
雙向 MCP 集成抄 Codex 的 mcp-server + rmcp-client 雙 crate 拆法

十二、5 條可遷移到自家 harness 的設(shè)計準(zhǔn)則

圖 10:5 條可遷移準(zhǔn)則按"項目階段"組成的金字塔——地基是 fail-closed 默認(rèn)值(保守度量),中層是熔斷 + 隔離(防爆 + 隔污),頂層是狀態(tài)機枚舉 + cache 對齊(可歸因 + 可優(yōu)化)。三層從下往上做,越上層越是規(guī)模紅利。

把兩份源碼的共識層提煉成 5 條,按"項目階段"排序——day 1 必做的兩條最重要,scale 后必做兩條,fleet 級別再上最后一條:

【Day 1 必做】1. fail-closed 默認(rèn)值貫穿一切。
Claude Code 的 TOOL_DEFAULTS 全部默認(rèn)保守、safeParse 失敗默認(rèn)不并發(fā)、callback 拋錯默認(rèn)不并發(fā);Codex 的 SandboxPolicy::ReadOnly { network_access: false }、AskForApproval::OnRequest。新增能力時如果默認(rèn)偏激進,遲早會有一個早晨醒來發(fā)現(xiàn)昨夜燒了幾十萬次 API 調(diào)用。

【Day 1 必做】2. 熔斷必須有,不要靠"下次能成"。
MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3、MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3、MAX_PENDING_EVENTS = 100、PARSE_TIMEOUT_MS = 50、MAX_NODES = 50_000——每一個常量都是踩坑得來的。任何重試邏輯必須有上限;任何隊列必須有 max size;任何解析必須有 timeout。

【Scale 后必做】3. 退出 / 繼續(xù)原因要做成 enum,不要做成日志字符串。
Claude Code 的 10 種 Terminal + 7 種 Continue、Codex 的 4 種 TurnAbortReason + 7 種 AgentStatus,都是把"為什么停 / 為什么繼續(xù)"顯式枚舉。這讓運維能按 reason 分桶,讓測試能精確斷言恢復(fù)路徑。日志字符串不行——一升級文案 dashboard 就崩。

【Scale 后必做】4. 隔離要默認(rèn)開,逃生通道要寫注釋。
Claude Code 的 setAppStateForTasks 是逃生通道但注釋明確寫"task registration/kill must reach root store"——讀代碼的人能立刻明白這個例外的意義。所有 mutable 默認(rèn) no-op,只顯式開必要的窗戶。

【Fleet 級再上】5. prompt cache(或等價的成本維度)要從設(shè)計階段就考慮。
Claude Code 的 CacheSafeParams 5 個字段必須字節(jié)級一致;Codex 的 compact 走 inline / remote / remote-v2 三套以適配不同部署。如果你跑大 fleet,這是真金白銀——runAgent.ts 里那一周 5-15 Gtok 的注釋不是 marketing,是優(yōu)化沉淀。

十三、收束:harness 是產(chǎn)品,不是腳手架

讀到這里,回頭看會發(fā)現(xiàn):Claude Code 和 Codex 在戰(zhàn)術(shù)上完全相反,在戰(zhàn)略上又驚人地一致

戰(zhàn)術(shù)上:

  • Claude Code 把 harness 不變量寫成 TS 類型 + 元數(shù)據(jù) + AST + 三態(tài)權(quán)限,是 加法。
  • Codex 把 harness 不變量壓到 OS syscall + 幾個緊致 enum 上,是 減法

戰(zhàn)略上:

  • 兩邊都明白 prompt 不是產(chǎn)品力,harness 才是產(chǎn)品力。
  • 兩邊都在每個關(guān)鍵決策點把"為什么"寫成枚舉或常量,讓人類協(xié)作者能 grep 到原因。
  • 兩邊都用 fail-closed 默認(rèn)值兜底,把"忘了顯式聲明"導(dǎo)向更保守的行為而非更激進的行為。

如果你只能帶走一句話:

Harness 承擔(dān)不變量,模型承擔(dān)決策。

剩下的所有差異——TS 還是 Rust、5 層漏斗還是單層摘要、27 hook 還是 MCP 雙向、應(yīng)用層 AST 還是 OS syscall——都只是這條原則在不同部署形態(tài)下的折中。

兩種源碼讀起來觀感完全不同。Claude Code 的源碼像一本寫滿批注的工具書:每一行注釋都在告訴讀者"這里曾經(jīng)踩過哪個坑"。Codex 的源碼像一本簡潔的教科書:每個 enum 的 variant 都齊整地排在一起,沒有多余的話。兩種風(fēng)格各有可取之處,背后映射的工程文化也是不同的——前者是 fleet 上跑出來的疤,后者是先把骨架立好的勇氣。

如果你打算自己寫一套 harness,建議把這兩份源碼的 Tool.ts / query.ts / core/src/session/turn.rs / core/src/agent/control.rs 都讀一遍,然后做一個清單:哪些是你的產(chǎn)品 day 1 就需要的,哪些是 fleet 大了再加的。

差異化的產(chǎn)品力不來自"調(diào)哪個 LLM"——這部分誰都能調(diào)到。差異化來自 harness 怎么把那個 LLM 卡進自己的工程不變量層。

參考

源碼引用:

  • Claude Code v2.1.88 src/ 鏡像(2026-03-31 npm sourcemap 公開),路徑見各章節(jié)
  • OpenAI Codex codex-rs/github.com/openai/codex(Apache 2.0)

公開材料:

  • Anthropic, “Effective context engineering for AI agents”, 2025-09-29
  • Anthropic, “Introducing Claude Opus 4.7”, 2026
  • Anthropic, “Prompt Caching” API 文檔(cache read 價格約為 cache write 的 10%)
  • Addy Osmani, “Agent Harness Engineering”, 2026
  • OpenAI, “Codex CLI” 官方文檔(codex-rs/docs/)

(本文所有文件路徑、行號、常量值、枚舉 variant 均來自上述源碼或公開材料,未作自創(chuàng)。)

到此這篇關(guān)于Claude Code 與 Codex Harness 設(shè)計對比分析:一種加法,一種減法的文章就介紹到這了,更多相關(guān)Claude Code 與 Codex Harness 對比內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持腳本之家!

相關(guān)文章

  • Claude Code對話自動導(dǎo)入的完全指南

    文章介紹了如何使用ChatCrystal導(dǎo)入和處理ClaudeCode的對話數(shù)據(jù),包括數(shù)據(jù)存儲位置、導(dǎo)入流程、噪音消息過濾、內(nèi)容清理、項目名提取、自定義數(shù)據(jù)目錄設(shè)置、自動導(dǎo)入機制等步
    2026-05-19
  • 一文徹底掌握.claude/目錄(讓Claude Code真正懂你的項目)

    如果你曾經(jīng)用過Claude Code,或許會發(fā)現(xiàn)項目根目錄下突然多出一個名為.claude的文件夾,下面這篇文章主要介紹了ClaudeCode中.claude/目錄的相關(guān)資料,文中通過代碼介紹的非常
    2026-05-19
  • 在Claude Code設(shè)置MCP服務(wù)器

    MCP是一種為Claude提供外部能力的機制,通過安裝不同功能的MCP服務(wù)器,可賦予Claude文件系統(tǒng)訪問、網(wǎng)頁抓取、瀏覽器自動化等能力,下面就來詳細的介紹一下如何安裝,感興趣
    2026-05-19
  • claudeCode安裝配置jetbrains教程

    本文主要介紹了安裝和配置Claude代碼助手的相關(guān)步驟,包括安裝官方包、配置環(huán)境變量、啟動Claude、關(guān)閉確認(rèn)提示等,具有一定的參考價值,感興趣的可以了解一下
    2026-05-19
  • Claude Code初學(xué)者的一些使用技巧總結(jié)

    Claude Code憑借任務(wù)驅(qū)動+終端原生的神仙特性,成了很多開發(fā)者的效率搭子,這篇文章主要介紹了Claude Code初學(xué)者的一些使用技巧,文中通過圖文介紹的非常詳細,需要的朋友可以
    2026-05-18
  • 一文詳解Claude Code中的五層架構(gòu):MCP、Skills、Agent、Subagents、Agent Teams怎么協(xié)

    5 月初 Anthropic 官方公布了 Claude Code 的五層架構(gòu)——MCP / Skills / Agent / Subagents / Agent Teams,這個分層不是營銷話術(shù),每層都有明確的職責(zé)邊界和協(xié)作方向,下面
    2026-05-18
  • VS Code與IDEA集成Claude Code的實戰(zhàn)指南

    本文介紹了如何在VSCode和IDEA中集成ClaudeCode,通過智譜AI的GLM模型提供AI輔助編碼能力,文中詳細描述了環(huán)境準(zhǔn)備、智譜AI平臺準(zhǔn)備、安裝ClaudeCode及其在VSCode和IDEA中的
    2026-05-18
  • 讓Claude Code的Token消耗爆降80%的7個實用技巧

    Claude Code 很強大,這在前面的實踐文章中我們已經(jīng)驗證過了,但與此同時,也有不少朋友說Token消耗過多,成本過高,這篇文章我們來講7個真正實用的方法,在不犧牲效率的前
    2026-05-18
  • Windows系統(tǒng)下Claude Code的安裝教程

    文章瀏覽閱讀150次,點贊4次,收藏2次。檢查網(wǎng)絡(luò)代理是否全局生效,確認(rèn)賬號已開通 Claude 付費訂閱。下載地址:https://nodejs.org/重啟電腦/配置 Node.js 系統(tǒng)環(huán)境變量。
    2026-05-17
  • 2026年Claude Code使用指南之高頻命令,快捷鍵,核心功能與實戰(zhàn)技巧詳解

    本文全面解析了Claude Code的核心功能與使用技巧,涵蓋了鍵盤快捷鍵(基礎(chǔ)操作/導(dǎo)航/編輯模式),斜杠命令(會話控制/配置管理/工具集成),CLI啟動參數(shù)(模型控制/調(diào)試選
    2026-05-17

最新評論

河源市| 沂南县| 新建县| 晴隆县| 普宁市| 堆龙德庆县| 游戏| 新宾| 望都县| 利辛县| 陆丰市| 泽州县| 阳泉市| 怀来县| 巴中市| 津市市| 宜良县| 永兴县| 福清市| 化隆| 平谷区| 革吉县| 金堂县| 兴义市| 惠安县| 卢龙县| 十堰市| 女性| 石阡县| 井冈山市| 宁化县| 西畴县| 泰安市| 临城县| 台前县| 嘉义县| 普宁市| 屏东市| 汕尾市| 高雄县| 尚义县|