Harness · Coding Agent Book04 / Open Interpreter
研究总览
M04 · SOURCE-GROUNDED TUTORIAL

Open Interpreter
从源码学会它怎么工作

Codex 安全底盘之上做多 Harness 协议仿真;独特价值是“统一 IR,模型原生手感”。 我们不把 README 当结论,而是沿主循环、工具、上下文、权限、扩展、协作和状态一路读到实现。

Rust · Multi-Harness Compatibility LayerApache-2.0aaf0aebf820326 个结论 · 72 处引用
这门课怎么读

先建立直觉,再沿一条任务链钻进代码

参考教程的做法不是把 API 名称罗列出来,而是从一个小白能理解的问题开始,先解释“为什么需要这个机制”,再用概念对比、执行链路和固定提交的源码回答“它究竟怎么做”。本页把 Open Interpreter 的 26 个源码结论重新编排成十节课;每个结论都保留证据等级、文件路径、行号和可点击源码。

你会得到一张可复述的架构地图一次完整任务的链路追踪能迁移到自研 Harness 的设计判断
你不会得到把 README 功能当成已验证事实把 prompt 约束说成 OS 沙箱把一次双模型调用夸成多 Agent 平台
M00 · MAP

先看全景:这个 Agent 的控制面在哪里

下面的图不是产品宣传图,而是把固定提交里最关键的入口、循环、模型、工具、安全、状态和协作节点放在一张地图上。

架构图全屏打开 ↗
一轮执行链路全屏打开 ↗
核心机制

Codex turn/step 快照循环;Harness 前后置整形

上下文

调用配对、多模态裁剪、Harness-aware compact

适用建设

多模型 A/B、Harness 研究、Codex 生态兼容

M00.5 · TRACE

跟踪一个任务:从输入到交付

把下面九步当成你读源码时的“地图坐标”。每到一个节点,都能回到后面的章节查具体实现。

读图提醒

箭头只表示控制面之间的关系,不代表每个实现都同步、串行或拥有 OS 隔离;真正的边界要以对应章节的源码摘录和 caveat 为准。

M01 · ORIENTATION

先把 Agent 看成一台会交付的机器

如果只看 README,你知道它能做什么;钻进源码后,我们要知道它为什么能做、什么时候会停、失败后谁负责收拾。

先用一个生活比喻

把 Agent 想成一间带传送带的工作室:入口收任务,主循环决定下一步,模型负责提出动作,工具负责动手,状态账本负责让下一班人接着干。

这套实现先回答了什么?

它不是在旧 Python 循环上继续打补丁,而是把一套 Codex 级 Rust Harness 换了产品入口,再加入自己的多 Harness 能力。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
当前源码不是经典 Python Open Interpreter,而是 Rust/Codex 兼容分叉codex-rs/product-info/src/lib.rs:45它不是在旧 Python 循环上继续打补丁,而是把一套 Codex 级 Rust Harness 换了产品入口,再加入自己的多 Harness 能力。
兼容性是被测试的产品契约,不只是目录遗留scripts/test-codex-sdk-compat.sh:21“还能被 Codex SDK 当成 Codex 用”是 CI 级目标,不是偶然能跑。
优势是“一个安全底盘,多种模型原生手感”;主要成本是兼容矩阵爆炸codex-rs/core/src/harness/request.rs:75它最像“Coding Agent 兼容器”:同一套沙箱和会话底盘,让不同模型吃到熟悉的提示词与工具格式。
01
L1 · fact · oi-identity-001

当前源码不是经典 Python Open Interpreter,而是 Rust/Codex 兼容分叉

先看源码事实

核心目录、crate、app-server 协议和 Python/TypeScript SDK 仍以 codex/openai-codex 命名;Open Interpreter 通过 Product::OpenInterpreter、interpreter/i 入口、.openinterpreter HOME 和 open-interpreter 包变体建立产品身份。

翻译成白话

它不是在旧 Python 循环上继续打补丁,而是把一套 Codex 级 Rust Harness 换了产品入口,再加入自己的多 Harness 能力。

为什么这对自研重要

评估时必须把它与同批次的 openai/codex 分开:共享基座很多,但 Open Interpreter 的差异主要落在品牌、provider/harness 选择和协议仿真。

固定提交源码摘录
   45  /// Product channel information used by branded package variants.
   46  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
   47  pub enum Product {
   48      Codex,
   49      OpenInterpreter,
   50  }
   51  
   52  impl Product {
   53      /// A binary shipped inside an Open Interpreter package is Open
   54      /// Interpreter unconditionally — identity must never depend on what the
   55      /// executable happens to be named or aliased to. The argv0 and env-var
   56      /// checks only exist for development builds run straight out of the
   57      /// cargo target directory.
   58      pub fn current() -> Self {
   59          if std::env::var_os(OPEN_INTERPRETER_BRAND_ENV_VAR).is_some()
   60              || is_open_interpreter_argv0()
   61              || is_open_interpreter_install()
   62          {
   63              Self::OpenInterpreter
   64          } else {
   65              Self::Codex
   66          }
   67      }
   68  
   69      pub fn display_name(self) -> &'static str {
   70          match self {
   71              Product::Codex => "OpenAI Codex",
   72              Product::OpenInterpreter => "Open Interpreter",
   73          }
   74      }
   75  
   76      pub fn command_name(self) -> &'static str {
   77          match self {
   78              Product::Codex => "codex",
   79              Product::OpenInterpreter => "interpreter",
为什么相信这条结论?查看 3 处证据
02
L3 · fact · oi-identity-002

兼容性是被测试的产品契约,不只是目录遗留

先看源码事实

仓库用 interpreter 二进制替换 CODEX_EXEC_PATH,运行 @openai/codex-sdk 的 thread resume 测试;包布局测试也检查 interpreter 与 i alias 以及 code-mode host。

翻译成白话

“还能被 Codex SDK 当成 Codex 用”是 CI 级目标,不是偶然能跑。

为什么这对自研重要

企业接入可复用 Codex 客户端生态;代价是内部命名和上游演进耦合很深。

固定提交源码摘录
   21  cd "${repo_root}"
   22  
   23  if [[ ! -d node_modules || ! -d sdk/typescript/node_modules ]]; then
   24    "${pnpm_command[@]}" install --frozen-lockfile --filter @openai/codex-sdk...
   25  fi
   26  
   27  "${pnpm_command[@]}" --filter @openai/codex-sdk run build
   28  
   29  CODEX_EXEC_PATH="${interpreter_bin}" "${pnpm_command[@]}" \
   30    --dir sdk/typescript \
   31    exec jest \
   32    --runInBand \
   33    tests/run.test.ts \
   34    --testNamePattern "resumes thread by id"
   35  
   36  echo "Codex SDK compatibility passed with ${interpreter_bin}"
为什么相信这条结论?查看 2 处证据
03
L5 · inference · oi-maturity-001

优势是“一个安全底盘,多种模型原生手感”;主要成本是兼容矩阵爆炸

先看源码事实

本提交同时维护 15 类 Harness enum、三种 wire protocol、多个独立 request builder、数千行 alias/adapter 与 Harness-aware compaction,并以大量路由/快照/包测试约束行为。

翻译成白话

它最像“Coding Agent 兼容器”:同一套沙箱和会话底盘,让不同模型吃到熟悉的提示词与工具格式。

为什么这对自研重要

建设自有 Agent 时可借鉴“内部统一 IR + 外部 Harness adapter”;但必须为路由、历史翻译、工具参数、压缩和错误恢复建立组合测试,否则适配越多越难证明正确。

边界与风险
  • 这是基于当前源码形态的工程判断,不代表每种 Harness 在目标 benchmark 上一定优于 Native。
固定提交源码摘录
   75  pub(crate) fn build_chat_harness_request(
   76      route: ChatHarnessRoute,
   77      turn: ChatHarnessTurn<'_>,
   78  ) -> Result<ChatHarnessRequest, CodexErr> {
   79      let ChatHarnessTurn {
   80          prompt,
   81          harness,
   82          harness_guidance,
   83          model_info,
   84          effort,
   85          thread_id,
   86          session_source,
   87      } = turn;
   88      let guided_prompt = prompt_with_harness_guidance(prompt, harness, harness_guidance);
   89      let yolo_mode = prompt
   90          .base_instructions
   91          .text
   92          .contains("Approval policy is currently never.");
   93      let (request_body, tool_kinds, title_request, postprocess) = match route {
   94          ChatHarnessRoute::DeepSeekTui => {
   95              let (request_body, tool_kinds) = build_deepseek_tui_request(&guided_prompt, model_info)
   96                  .map_err(|err| {
   97                      CodexErr::InvalidRequest(format!("invalid deepseek-tui request: {err}"))
   98                  })?;
   99              (request_body, tool_kinds, None, ChatHarnessPostprocess::None)
      … 121 lines omitted; exact range 75–231 …
  221              inject_mini_swe_agent_no_tool_call_format_error(stream)
  222          }
  223          ChatHarnessPostprocess::SweAgentActionCalls { has_submit_review } => {
  224              inject_swe_agent_action_calls(stream, has_submit_review)
  225          }
  226          ChatHarnessPostprocess::Terminus2ActionCalls {
  227              request_kind,
  228              pending_completion,
  229          } => inject_terminus_2_action_calls(stream, request_kind, pending_completion),
  230      }
  231  }
为什么相信这条结论?查看 4 处证据
小练习 1

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M02 · LOOP

主循环:模型为什么会继续动

一次模型调用为什么会变成十几步?循环靠什么继续,靠什么停止?

先用一个生活比喻

像一个会看回执的快递员:模型先写行动单,工具返回回执,主循环把回执放回桌面,再让模型决定下一张行动单。

这套实现先回答了什么?

同一个底盘能换不同“驾驶舱”:改变系统提示词、消息格式、工具名称与参数,去适配最合适的模型习惯。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
核心差异是一层多 Harness 仿真目录codex-rs/core/src/harness/mod.rs:1同一个底盘能换不同“驾驶舱”:改变系统提示词、消息格式、工具名称与参数,去适配最合适的模型习惯。
Provider、Wire API、Harness 是三层独立选择codex-rs/app-server-protocol/src/protocol/v2/interpreter.rs:8“连哪家服务器”“用哪个模型”“让请求长得像哪个 Coding Agent”不是同一个开关。
路由矩阵在代码里硬校验,不兼容组合直接报错codex-rs/core/src/harness/routing.rs:5不是把任意协议和任意外壳随便相乘;路由表会在发请求前拦住不支持的组合。
请求仿真的单一入口统一处理前置与后置整形codex-rs/core/src/harness/request.rs:1每种外壳的怪癖被收在一个总路由器里:发出前改请求,回来后必要时把文本动作翻译成工具动作。
04
L1 · fact · oi-harness-001

核心差异是一层多 Harness 仿真目录

先看源码事实

源码内有 Claude Code/full+bare、DeepSeek TUI、Kimi CLI、Kimi Code、Little Coder、mini-swe-agent、OpenCode、Pi、Qwen Code、SWE-agent、Terminus 2、ZCode 与 Minimal 等独立请求构造器;app-server 将主要可选项按 wire API 编成 catalog。

翻译成白话

同一个底盘能换不同“驾驶舱”:改变系统提示词、消息格式、工具名称与参数,去适配最合适的模型习惯。

为什么这对自研重要

这比普通 provider adapter 更深,但也不是把这些项目的完整运行时都嵌入进来。

边界与风险
  • catalog 的可选项与底层 enum 并非一一相等,例如 ZCode 存在于底层实现但未列入这一段 UI catalog。
固定提交源码摘录
    1  pub(crate) mod claude_code;
    2  mod claude_code_prompt;
    3  pub(crate) mod deepseek_tui;
    4  pub(crate) mod guidance;
    5  pub(crate) mod kimi_cli;
    6  pub(crate) mod kimi_code;
    7  pub(crate) mod little_coder;
    8  pub(crate) mod mini_swe_agent;
    9  pub(crate) mod minimal;
   10  pub(crate) mod opencode;
   11  pub(crate) mod pi;
   12  pub(crate) mod qwen_code;
   13  pub(crate) mod request;
   14  pub(crate) mod routing;
   15  pub(crate) mod session_skills;
   16  pub(crate) mod swe_agent;
   17  pub(crate) mod terminus_2;
   18  pub(crate) mod zcode;
为什么相信这条结论?查看 3 处证据
05
L2 · fact · oi-harness-002

Provider、Wire API、Harness 是三层独立选择

先看源码事实

app-server DTO 分开暴露 Provider、Model、Harness;Provider 包含 base URL/env key/wire API,Harness 是可推荐的独立选项。默认推断按 provider/model/base URL 把 Claude→claude-code、Kimi→kimi-code、Qwen→qwen-code、DeepSeek→claude-code-bare。

翻译成白话

“连哪家服务器”“用哪个模型”“让请求长得像哪个 Coding Agent”不是同一个开关。

为什么这对自研重要

可做跨模型的 Harness A/B;默认映射是产品经验规则,不代表所有组合都兼容。

固定提交源码摘录
    8  #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
    9  #[serde(rename_all = "lowercase")]
   10  #[ts(export_to = "v2/")]
   11  pub enum WireApiDto {
   12      Responses,
   13      Chat,
   14      Messages,
   15  }
   16  
   17  #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
   18  #[serde(rename_all = "camelCase")]
   19  #[ts(export_to = "v2/")]
   20  pub struct InterpreterProvider {
   21      pub id: String,
   22      pub name: String,
   23      pub description: String,
   24      pub is_current: bool,
   25      #[ts(optional)]
   26      pub base_url: Option<String>,
   27      #[ts(optional)]
   28      pub wire_api: Option<WireApiDto>,
   29      #[ts(optional)]
   30      pub env_key: Option<String>,
   31      pub configured: bool,
   32      pub is_default: bool,
      … 1 lines omitted; exact range 8–44 …
   34  
   35  #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
   36  #[serde(rename_all = "camelCase")]
   37  #[ts(export_to = "v2/")]
   38  pub struct InterpreterHarness {
   39      #[ts(optional)]
   40      pub id: Option<String>,
   41      pub label: String,
   42      pub description: String,
   43      pub is_recommended: bool,
   44  }
为什么相信这条结论?查看 3 处证据
06
L3 · fact · oi-harness-003

路由矩阵在代码里硬校验,不兼容组合直接报错

先看源码事实

Responses 原生走 Responses;Chat 非 Claude Harness 走 chat compatibility;Claude full/bare 可对 Responses/Chat 做 profile shaping;Messages 只允许 Claude 与 ZCode 原生路径,其他 Harness 和 Native Messages 返回 InvalidRequest。单元测试覆盖这些分支。

翻译成白话

不是把任意协议和任意外壳随便相乘;路由表会在发请求前拦住不支持的组合。

为什么这对自研重要

错误更早、更可诊断;catalog 的 Native/ALL 表述仍需与 routing 的 Native+Messages 拒绝一起理解。

边界与风险
  • app-server catalog 把 Native 标成 ALL_WIRE_APIS,但底层 routing 对 Native+Messages 明确报错;这是当前提交可见的接口不一致。
固定提交源码摘录
    5  #[derive(Debug, Clone, Copy, Eq, PartialEq)]
    6  pub(crate) enum MessagesHarnessRoute {
    7      ClaudeCode,
    8      ZCode,
    9  }
   10  
   11  /// Which claude-code tool/prompt surface a non-Messages wire should shape.
   12  ///
   13  /// `claude-code` and `claude-code-bare` share the same shaping primitives but
   14  /// differ in system prompt and tool set; this carries that distinction onto the
   15  /// Responses and Chat transports.
   16  #[derive(Debug, Clone, Copy, Eq, PartialEq)]
   17  pub(crate) enum ClaudeCodeProfileRoute {
   18      Full,
   19      Bare,
   20  }
   21  
   22  #[derive(Debug, Clone, Copy, Eq, PartialEq)]
   23  pub(crate) enum ChatHarnessRoute {
   24      DeepSeekTui,
   25      KimiCode,
   26      KimiCli,
   27      LittleCoder,
   28      MiniSweAgent,
   29      Minimal,
      … 111 lines omitted; exact range 5–151 …
  141          )),
  142          (WireApi::Messages, Harness::Native) => Err(CodexErr::InvalidRequest(
  143              "wire_api = \"messages\" requires a harness-native transport; configure harness = \"claude-code\" or \"claude-code-bare\" for Anthropic-style sessions"
  144                  .to_string(),
  145          )),
  146          (WireApi::Messages, Harness::Other(harness_name)) => Err(CodexErr::InvalidRequest(
  147              format!(
  148                  "wire_api = \"messages\" is not supported by harness = \"{harness_name}\""
  149              ),
  150          )),
  151      }
为什么相信这条结论?查看 2 处证据
07
L1 · fact · oi-harness-004

请求仿真的单一入口统一处理前置与后置整形

先看源码事实

request.rs 明确声明为 model client 与 chat harness emulation 的单一集成点;它按 route 调各 builder,判断 YOLO、可加 Harness guidance、为 OpenCode 生成独立 title request,并为 mini-SWE/SWE-agent/Terminus 2 注入响应流后处理。

翻译成白话

每种外壳的怪癖被收在一个总路由器里:发出前改请求,回来后必要时把文本动作翻译成工具动作。

为什么这对自研重要

新增 Harness 的改动面较集中;后处理型工具提取比原生 function call 更脆弱,必须依赖格式测试。

固定提交源码摘录
    1  //! The single integration point between the model client and
    2  //! chat-completions harness emulation.
    3  //!
    4  //! `client.rs` resolves a [`ChatHarnessRoute`] and calls
    5  //! [`build_chat_harness_request`]; everything harness-specific — guidance
    6  //! injection, per-harness request building, and response-stream
    7  //! postprocessing — lives here. Adding a chat harness means adding a route
    8  //! arm in this module, not editing the client.
为什么相信这条结论?查看 3 处证据
08
L1 · fact · oi-harness-005

各 Harness 不只换 prompt,还重写历史和工具协议

先看源码事实

例如 Pi builder 生成自己的 system prompt/tools/thinking 配置,把 ResponseItem 历史改成 Chat messages,跳过 contextual user message、配对工具调用与结果、保留 reasoning_content,并把 LocalShell/Custom tool call 映射成 Pi 认识的名字;Little Coder 与 OpenCode 各自拥有不同 system prompt、tool kinds 和 title/search-agent 逻辑。

翻译成白话

这不是贴一张角色卡,而是把整段对话和工具说明书翻译成目标 Agent 的“母语”。

为什么这对自研重要

模型行为更接近目标 Harness;翻译层必须维护 call/result 配对和隐藏上下文,升级成本高。

固定提交源码摘录
   21      })];
   22      messages.extend(build_messages(prompt.get_formatted_input())?);
   23      let tools = build_tools();
   24      let tool_kinds = tools
   25          .iter()
   26          .filter_map(|tool| {
   27              tool.get("function")
   28                  .and_then(|function| function.get("name"))
   29                  .and_then(Value::as_str)
   30                  .map(|name| (name.to_string(), ToolOutputKind::Function))
   31          })
   32          .collect();
   33  
   34      Ok((
   35          json!({
   36              "model": model_info.slug,
   37              "messages": messages,
   38              "stream": true,
   39              "stream_options": {
   40                  "include_usage": true,
   41              },
   42              "store": false,
   43              "tools": tools,
   44              "thinking": {
   45                  "type": "enabled",
      … 64 lines omitted; exact range 21–120 …
  110                                      "text": message_content,
  111                                  }
  112                              ],
  113                          }));
  114                      }
  115                  }
  116                  "developer" => {}
  117                  _ => {}
  118              },
  119              ResponseItem::FunctionCall {
  120                  name,
为什么相信这条结论?查看 3 处证据
09
L1 · fact · oi-loop-001

共享内核仍是 turn 内多 step 的流式工具循环

先看源码事实

run_turn 先做预压缩、注入 skills/plugins、运行 session/user hooks并记录输入;随后循环捕获 step context、构造历史、流式 sampling。模型要求工具或有 pending input 就继续,否则运行 stop/after-agent hook 后结束。

翻译成白话

一次用户请求会反复经历“取固定快照—问模型—跑工具—把结果放回去”,直到模型真正收尾。

为什么这对自研重要

Harness 仿真共享同一个成熟执行底盘,不需要每种 Harness 重写 agent loop。

固定提交源码摘录
  140  ///
  141  /// - If the model requests a function call, we execute it and send the output
  142  ///   back to the model in the next sampling request.
  143  /// - If the model sends only an assistant message, we record it in the
  144  ///   conversation history and consider the turn complete.
  145  ///
  146  pub(crate) async fn run_turn(
  147      sess: Arc<Session>,
  148      turn_context: Arc<TurnContext>,
  149      turn_extension_data: Arc<codex_extension_api::ExtensionData>,
  150      input: Vec<TurnInput>,
  151      prewarmed_client_session: Option<ModelClientSession>,
  152      cancellation_token: CancellationToken,
  153  ) -> CodexResult<Option<String>> {
  154      let mut client_session =
  155          prewarmed_client_session.unwrap_or_else(|| sess.services.model_client.new_session());
  156      // TODO(ccunningham): Pre-turn compaction runs before context updates and the
  157      // new user message are recorded. Estimate pending incoming items (context
  158      // diffs/full reinjection + user input) and trigger compaction preemptively
  159      // when they would push the thread over the compaction threshold.
  160      if let Err(err) = run_pre_sampling_compact(&sess, &turn_context, &mut client_session).await {
  161          if matches!(err, CodexErr::TurnAborted) {
  162              return Err(err);
  163          }
  164          let error = err.to_codex_protocol_error();
      … 53 lines omitted; exact range 140–228 …
  218          TurnDiffTracker::with_environment_display_roots(display_roots),
  219      ));
  220  
  221      // `ModelClientSession` is turn-scoped and caches WebSocket + sticky routing state, so we reuse
  222      // one instance across retries within this turn.
  223      // Pending input is drained into history before building the next model request.
  224      // However, we defer that drain until after sampling in two cases:
  225      // 1. At the start of a turn, so the fresh turn input in `input` gets sampled first.
  226      // 2. After auto-compact, when model/tool continuation needs to resume before any steer.
  227  
  228      let mut next_step_context = Some(first_step_context);
为什么相信这条结论?查看 3 处证据
10
L1 · fact · oi-loop-002

step 快照让上下文、工具清单与工具执行看到同一世界

先看源码事实

每个循环只 capture 一次 StepContext,注释明确要求 context、advertised tools 和 tool calls 共享同一 request view;world state 变化被记录到 rollout,下一 step 才重新采样环境。

翻译成白话

模型看到的工具说明和真正执行工具时的权限/目录不会在同一步里偷偷漂移。

为什么这对自研重要

降低并发配置变化导致的 TOCTOU 类语义错位;动态变更需要等下一 step 生效。

固定提交源码摘录
  243          let window_id = sess.current_window_id().await;
  244          super::rollout_budget::maybe_record_reminder(
  245              sess.as_ref(),
  246              turn_context.as_ref(),
  247              &window_id,
  248          )
  249          .await;
  250  
  251          // Capture once so context, advertised tools, and tool calls share one request view.
  252          let step_context = match next_step_context.take() {
  253              Some(step_context) => step_context,
  254              None => sess.capture_step_context(Arc::clone(&turn_context)).await,
  255          };
  256          let sampling_request_result: CodexResult<_> = async {
  257              super::time_reminder::maybe_record_current_time_reminder(
  258                  sess.as_ref(),
  259                  turn_context.as_ref(),
  260                  &window_id,
  261              )
  262              .await?;
  263  
  264              world_state = sess
  265                  .record_step_world_state_if_changed(&world_state, step_context.as_ref())
  266                  .await;
  267  
      … 14 lines omitted; exact range 243–292 …
  282              run_sampling_request(
  283                  Arc::clone(&sess),
  284                  Arc::clone(&step_context),
  285                  Arc::clone(&turn_extension_data),
  286                  Arc::clone(&turn_diff_tracker),
  287                  &mut client_session,
  288                  &responses_metadata,
  289                  sampling_request_input,
  290                  cancellation_token.child_token(),
  291              )
  292              .await
为什么相信这条结论?查看 2 处证据
小练习 2

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M03 · MODEL

模型调用:流式输出如何变成可执行步骤

模型输出的文字、思考、工具调用和错误,经过哪些转换才进入 Agent 状态?

先用一个生活比喻

模型像电话另一端的同事:你听到的不是一整段录音,而是一串实时片段;Harness 要边听边拼装,还要能在电话断线时留下可恢复的记录。

本节阅读法先问问题读事实看代码做迁移判断
读源码时先问本课的判断方式
这一层有没有独立证据?没有就不把其他层的能力冒充成默认行为;回到固定提交继续追调用链。
谁拥有最终控制权?区分模型输出、框架规则、用户审批和 OS 隔离四种不同力量。
这一章先建立通用概念

固定提交账本没有把该维度单独拆出,但它会在其他章节的源码路径中体现。先沿执行链路阅读,再回到报告页核对证据。

小练习 3

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M04 · TOOLS

工具系统:Agent 的手脚怎样被注册和调度

模型看见的工具说明,和真正执行工具的代码,是不是同一个东西?并发、编辑和失败结果怎么处理?

先用一个生活比喻

工具系统像机场:模型提交登机牌,注册表确认航班,权限闸机检查证件,调度器决定跑道,最后才允许真正起飞。

这套实现先回答了什么?

给模型看的菜单和厨房真正能接的订单不是同一张表;旧别名可以藏在后厨,不污染新模型的菜单。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
模型可见工具与内部可分发 runtime 分离codex-rs/core/src/tools/spec_plan.rs:240给模型看的菜单和厨房真正能接的订单不是同一张表;旧别名可以藏在后厨,不污染新模型的菜单。
Harness aliases 将十余套工具名收敛到共享执行器codex-rs/core/src/tools/handlers/harness_aliases.rs:102Claude 叫 Read、Pi 叫 read、OpenCode 叫 task,都可以落到同一套真实能力上。
Harness 写文件也进入统一 FileChange lifecyclecodex-rs/core/src/tools/handlers/harness_aliases.rs:1749即使模型说的是别家 Agent 的 Write/Edit,前端仍能看到正式的文件改动卡片、diff、成功或失败状态。
工具并发用读写锁表达,而不是一律并发codex-rs/core/src/tools/parallel.rs:42多个搜索可以一起跑,但写文件或问用户会像闸门一样独占执行通道。
11
L1 · fact · oi-tools-001

模型可见工具与内部可分发 runtime 分离

先看源码事实

Tool plan 先按 exposure 去重、生成模型可见 spec,再由全部 runtimes 建 ToolRegistry;dispatch-only handler 可以不可见但仍接住兼容工具名。Code mode 又把允许的工具 schema 包进 execute/wait 两个工具。

翻译成白话

给模型看的菜单和厨房真正能接的订单不是同一张表;旧别名可以藏在后厨,不污染新模型的菜单。

为什么这对自研重要

兼容多 Harness 时能避免重复工具;隐藏 handler 仍属于攻击面,必须受同一权限检查。

固定提交源码摘录
  240      let mut seen_tool_names = HashSet::new();
  241      for runtime in &runtimes {
  242          let tool_name = runtime.tool_name();
  243          if !seen_tool_names.insert(tool_name.clone()) {
  244              continue;
  245          }
  246          let exposure = runtime.exposure();
  247          if exposure.is_direct() && !is_hidden_by_code_mode_only(turn_context, &tool_name, exposure)
  248          {
  249              let spec = runtime.spec();
  250              specs.push(spec_for_model_request(
  251                  turn_context,
  252                  exposure,
  253                  &tool_name,
  254                  spec,
  255              ));
  256          }
  257      }
  258      specs.extend(hosted_specs);
  259  
  260      let registry = ToolRegistry::from_tools(runtimes);
  261      let model_visible_specs = merge_into_namespaces(specs)
  262          .into_iter()
  263          .filter(|spec| {
  264              namespace_tools_enabled(turn_context) || !matches!(spec, ToolSpec::Namespace(_))
  265          })
  266          .collect();
  267  
  268      (model_visible_specs, registry)
为什么相信这条结论?查看 3 处证据
12
L1 · fact · oi-tools-002

Harness aliases 将十余套工具名收敛到共享执行器

先看源码事实

HarnessAliasHandler 覆盖 Bash/Read/Write/Edit/Glob/Grep、Claude task、Kimi goal/cron、DeepSeek diagnostics/git/file、OpenCode task/todo、ZCode plan/skill 等;spec plan 按当前 Harness 添加可见或 dispatch-only alias。

翻译成白话

Claude 叫 Read、Pi 叫 read、OpenCode 叫 task,都可以落到同一套真实能力上。

为什么这对自研重要

跨 Harness 复用强;别名层很大,参数语义和错误文本需要逐 Harness 回归。

固定提交源码摘录
  102  #[derive(Clone, Copy)]
  103  pub enum HarnessAliasHandler {
  104      Agent,
  105      Bash,
  106      BashLower,
  107      Read,
  108      ReadLower,
  109      ReadMediaFile,
  110      Write,
  111      WriteLower,
  112      Edit,
  113      EditLower,
  114      Glob,
  115      GlobLower,
  116      Grep,
  117      GrepLower,
  118      AskUserQuestion,
  119      TaskList,
  120      TaskOutput,
  121      TaskStop,
  122      ChecklistAdd,
  123      ChecklistList,
  124      ChecklistUpdate,
  125      ChecklistWrite,
  126      DeepSeekDiagnostics,
      … 117 lines omitted; exact range 102–254 …
  244              Self::DeepSeekToolSearch => handle_deepseek_tool_search(invocation).await,
  245              Self::DeepSeekWriteFile => handle_deepseek_write_file(invocation).await,
  246              Self::OpenCodeTask => handle_opencode_task(invocation).await,
  247              Self::OpenCodeTodoWrite => handle_opencode_todowrite(invocation).await,
  248              Self::ZCodeTodoRead => handle_zcode_todo_read(invocation).await,
  249              Self::ZCodeTodoWrite => handle_zcode_todo_write(invocation).await,
  250              Self::ZCodeEnterPlanMode => handle_zcode_enter_plan_mode(invocation).await,
  251              Self::ZCodeExitPlanMode => handle_zcode_exit_plan_mode(invocation).await,
  252              Self::ZCodeReadSessionContext => handle_zcode_read_session_context(invocation).await,
  253              Self::ZCodeSkill => handle_zcode_skill(invocation).await,
  254          }
为什么相信这条结论?查看 2 处证据
13
L1 · fact · oi-tools-003

Harness 写文件也进入统一 FileChange lifecycle

先看源码事实

Write/Edit alias 先读取旧内容构建 Add/Update unified diff,发 Started,再执行写入;成功发 Completed,失败发 Failed。事件通过 TurnItem::FileChange 进入会话与 rollout,而不是只返回一句工具文本。

翻译成白话

即使模型说的是别家 Agent 的 Write/Edit,前端仍能看到正式的文件改动卡片、diff、成功或失败状态。

为什么这对自研重要

兼容层不会绕过观测与审计;这是当前提交的主变更点。

固定提交源码摘录
 1749  #[derive(Deserialize)]
 1750  struct WriteArgs {
 1751      #[serde(alias = "file_path", alias = "filePath")]
 1752      path: String,
 1753      content: String,
 1754      #[serde(default)]
 1755      mode: Option<String>,
 1756  }
 1757  
 1758  #[derive(Clone, Copy)]
 1759  enum HarnessFileChangeStage<'a> {
 1760      Started,
 1761      Completed,
 1762      Failed(&'a str),
 1763  }
 1764  
 1765  fn harness_file_change(
 1766      path: &Path,
 1767      previous_content: Option<&str>,
 1768      updated_content: &str,
 1769  ) -> HashMap<PathBuf, FileChange> {
 1770      let change = match previous_content {
 1771          Some(previous_content) => FileChange::Update {
 1772              unified_diff: similar::TextDiff::from_lines(previous_content, updated_content)
 1773                  .unified_diff()
      … 37 lines omitted; exact range 1749–1821 …
 1811                  .emit_turn_item_started(&invocation.turn, &item)
 1812                  .await;
 1813          }
 1814          HarnessFileChangeStage::Completed | HarnessFileChangeStage::Failed(_) => {
 1815              invocation
 1816                  .session
 1817                  .emit_turn_item_completed(&invocation.turn, item)
 1818                  .await;
 1819          }
 1820      }
 1821  }
为什么相信这条结论?查看 3 处证据
14
L1 · fact · oi-tools-004

工具并发用读写锁表达,而不是一律并发

先看源码事实

ToolCallRuntime 查询 handler 是否支持并发;并发工具取得共享 read lock,写入/交互等排他工具取得 write lock,因此同组读工具可并行,任何排他工具会等待并阻塞后续组。

翻译成白话

多个搜索可以一起跑,但写文件或问用户会像闸门一样独占执行通道。

为什么这对自研重要

兼顾吞吐和副作用顺序;alias 自己也声明 Edit/Write/AskUserQuestion 不并行。

固定提交源码摘录
   42  pub(crate) struct ToolCallRuntime {
   43      router: Arc<ToolRouter>,
   44      session: Arc<Session>,
   45      // Tool calls may run later, so retain the step whose tool list advertised them.
   46      step_context: Arc<StepContext>,
   47      tracker: SharedTurnDiffTracker,
   48      parallel_execution: Arc<RwLock<()>>,
   49  }
   50  
   51  impl ToolCallRuntime {
   52      pub(crate) fn new(
   53          router: Arc<ToolRouter>,
   54          session: Arc<Session>,
   55          step_context: Arc<StepContext>,
   56          tracker: SharedTurnDiffTracker,
   57      ) -> Self {
   58          Self {
   59              router,
   60              session,
   61              step_context,
   62              tracker,
   63              parallel_execution: Arc::new(RwLock::new(())),
   64          }
   65      }
   66  
      … 61 lines omitted; exact range 42–138 …
  128          );
  129          let abort_dispatch_span = dispatch_span.clone();
  130  
  131          let mut dispatch_handle: AbortOnDropHandle<Result<AnyToolResult, FunctionCallError>> =
  132              AbortOnDropHandle::new(tokio::spawn(async move {
  133                  let _guard = if supports_parallel {
  134                      Either::Left(lock.read().await)
  135                  } else {
  136                      Either::Right(lock.write().await)
  137                  };
  138                  // Admission through the parallel-execution gate marks the end
为什么相信这条结论?查看 2 处证据
小练习 4

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M05 · CONTEXT

上下文:有限窗口怎样装下长任务

当对话、工具输出、计划和记忆越来越多,系统怎样决定留下什么、折叠什么、放到哪里?

先用一个生活比喻

上下文不是聊天记录,而是一张会整理的工作台:常用零件放桌面,旧材料装进档案盒,必要时只留下索引卡。

这套实现先回答了什么?

上下文不是简单 messages 数组;它会修账,保证工具订单和回执成对,并避免把模型根本不能看的媒体继续塞回去。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
历史管理维护调用配对、多模态能力和可见 token 成本codex-rs/core/src/context_manager/history.rs:40上下文不是简单 messages 数组;它会修账,保证工具订单和回执成对,并避免把模型根本不能看的媒体继续塞回去。
压缩是 Harness-aware 的,而不是统一摘要模板codex-rs/core/src/compact.rs:299换了驾驶舱,压缩后的“交接便笺”也要换写法,否则目标模型会读不懂或丢失刚看过的文件。
15
L1 · fact · oi-context-001

历史管理维护调用配对、多模态能力和可见 token 成本

先看源码事实

ContextManager 记录 ResponseItem 后按输入模态规范化:补齐缺失 call output、删除孤儿 output、剥离不支持的图片/音频;工具输出按策略截断,并用模型可见字节估算 token。

翻译成白话

上下文不是简单 messages 数组;它会修账,保证工具订单和回执成对,并避免把模型根本不能看的媒体继续塞回去。

为什么这对自研重要

恢复和压缩后的协议更稳定;token 估算是启发式下界,不等于 provider tokenizer。

固定提交源码摘录
   40  pub(crate) struct ContextManager {
   41      /// The oldest items are at the beginning of the vector. Snapshots share the vector until a
   42      /// caller needs to mutate it, avoiding deep copies for read-only history consumers.
   43      items: Arc<Vec<ResponseItem>>,
   44      /// Bumped whenever history is rewritten, such as compaction or rollback.
   45      history_version: u64,
   46      token_info: Option<TokenUsageInfo>,
   47      /// Reference context snapshot used for diffing and producing model-visible
   48      /// settings update items.
   49      ///
   50      /// This is the baseline for the next regular model turn, and may already
   51      /// match the current turn after context updates are persisted.
   52      ///
   53      /// When this is `None`, settings diffing treats the next turn as having no
   54      /// baseline and emits a full reinjection of context state. Rollback may
   55      /// also clear this when it trims a mixed initial-context developer bundle
   56      /// whose non-diff fragments no longer exist in the surviving history.
   57      reference_context_item: Option<TurnContextItem>,
   58      /// World state most recently appended to model-visible history.
   59      world_state_baseline: Option<WorldStateSnapshot>,
   60  }
   61  
   62  impl ContextManager {
   63      pub(crate) fn new() -> Self {
   64          Self {
      … 111 lines omitted; exact range 40–186 …
  176      ) -> Option<i64> {
  177          let base_tokens =
  178              i64::try_from(approx_token_count(&base_instructions.text)).unwrap_or(i64::MAX);
  179  
  180          let items_tokens = self
  181              .items
  182              .iter()
  183              .map(estimate_item_token_count)
  184              .fold(0i64, i64::saturating_add);
  185  
  186          Some(base_tokens.saturating_add(items_tokens))
为什么相信这条结论?查看 3 处证据
16
L1 · fact · oi-context-002

压缩是 Harness-aware 的,而不是统一摘要模板

先看源码事实

压缩先用当前 Harness 生成摘要请求,然后 build_harness_compacted_history:ZCode、Claude Code bare 等可走各自 replacement history;普通路径用 summary prefix。还会从 harness read 调用重建相关文件内容,并识别 no-truncate 标记。

翻译成白话

换了驾驶舱,压缩后的“交接便笺”也要换写法,否则目标模型会读不懂或丢失刚看过的文件。

为什么这对自研重要

长会话兼容性更好;每新增 Harness 都必须测试压缩与恢复,否则短任务能跑、长任务会失真。

固定提交源码摘录
  299                      history.remove_first_item();
  300                      retries = 0;
  301                      continue;
  302                  }
  303                  sess.set_total_tokens_full(turn_context.as_ref()).await;
  304                  sess.track_turn_codex_error(turn_context.as_ref(), &e);
  305                  let event = EventMsg::Error(e.to_error_event(/*message_prefix*/ None));
  306                  sess.send_event(&turn_context, event).await;
  307                  return Err(e);
  308              }
  309              Err(e) => {
  310                  if retries < max_retries {
  311                      retries += 1;
  312                      let delay = backoff(retries);
  313                      sess.notify_stream_error(
  314                          turn_context.as_ref(),
  315                          format!("Reconnecting... {retries}/{max_retries}"),
  316                          e,
  317                      )
  318                      .await;
  319                      tokio::time::sleep(delay).await;
  320                      continue;
  321                  } else {
  322                      sess.track_turn_codex_error(turn_context.as_ref(), &e);
  323                      let event = EventMsg::Error(e.to_error_event(/*message_prefix*/ None));
      … 55 lines omitted; exact range 299–389 …
  379      )
  380      .await;
  381      sess.recompute_token_usage(&turn_context).await;
  382  
  383      sess.emit_turn_item_completed(&turn_context, compaction_item)
  384          .await;
  385      let warning = EventMsg::Warning(WarningEvent {
  386          message: "Heads up: Long threads and multiple compactions can cause the model to be less accurate. Start a new thread when possible to keep threads small and targeted.".to_string(),
  387      });
  388      sess.send_event(&turn_context, warning).await;
  389      Ok(summary_suffix)
为什么相信这条结论?查看 3 处证据
小练习 5

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M06 · SECURITY

权限与沙箱:能做什么,在哪里做

审批按钮、规则引擎、容器和操作系统沙箱分别解决什么问题?为什么“问过用户”不等于“隔离了风险”?

先用一个生活比喻

审批像门卫问你有没有预约,沙箱像把访客关在指定房间;前者决定是否放行,后者限制放行后能摸到什么。

这套实现先回答了什么?

即使换成 Claude/Pi 风格的 Read/Write,也不能因为路径里有软链接或奇怪别名绕过沙箱。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
Harness 文件工具先过策略,且同时检查原路径与规范化路径codex-rs/core/src/tools/handlers/harness_fs.rs:39即使换成 Claude/Pi 风格的 Read/Write,也不能因为路径里有软链接或奇怪别名绕过沙箱。
审批和能力授权是两条轴,OS 沙箱是真实进程变换codex-rs/core/src/config/permissions.rs:170“要不要先问你”与“即使你同意,它最多能碰哪里”是两回事;后者不是提示词,而是操作系统级限制。
17
L1 · fact · oi-security-001

Harness 文件工具先过策略,且同时检查原路径与规范化路径

先看源码事实

checked_read_path/checked_write_path 解析相对 cwd 后调用 file_system_sandbox_policy;policy_candidates_for_path 同时检查输入路径、canonical path,目标不存在时检查 canonical parent+filename,防止 symlink/别名绕过。

翻译成白话

即使换成 Claude/Pi 风格的 Read/Write,也不能因为路径里有软链接或奇怪别名绕过沙箱。

为什么这对自研重要

兼容层与原生工具共享安全边界;搜索 walk 还跳过 symlink、限制 64 层和 5 万项。

固定提交源码摘录
   39  pub(crate) fn resolve_model_path(
   40      invocation: &ToolInvocation,
   41      path: &str,
   42  ) -> Result<PathBuf, FunctionCallError> {
   43      let path = normalize_model_path_text(path);
   44      let path = PathBuf::from(path);
   45      if path.is_absolute() {
   46          Ok(path)
   47      } else {
   48          Ok(primary_cwd(invocation).join(path))
   49      }
   50  }
   51  
   52  pub(crate) fn checked_read_path(
   53      invocation: &ToolInvocation,
   54      path: &str,
   55      operation: &str,
   56  ) -> Result<PathBuf, FunctionCallError> {
   57      let path = resolve_model_path(invocation, path)?;
   58      ensure_read_allowed(invocation, &path, operation)?;
   59      Ok(path)
   60  }
   61  
   62  pub(crate) fn checked_write_path(
   63      invocation: &ToolInvocation,
      … 20 lines omitted; exact range 39–94 …
   84  ) -> Result<(), FunctionCallError> {
   85      ensure_allowed(invocation, path, AccessKind::Write, operation)
   86  }
   87  
   88  pub(crate) fn read_search_file(path: &Path) -> Option<String> {
   89      let metadata = fs::symlink_metadata(path).ok()?;
   90      let file_type = metadata.file_type();
   91      if !file_type.is_file() || file_type.is_symlink() || metadata.len() > MAX_SEARCH_FILE_BYTES {
   92          return None;
   93      }
   94      fs::read_to_string(path).ok()
为什么相信这条结论?查看 3 处证据
18
L1 · fact · oi-security-002

审批和能力授权是两条轴,OS 沙箱是真实进程变换

先看源码事实

配置将 approval policy 与 permission profile 分开编译;执行时根据平台把命令变换成 macOS sandbox-exec/Seatbelt、Linux seccomp+bubblewrap/landlock 或 Windows restricted token/private desktop,SandboxType::None 才是不变换。

翻译成白话

“要不要先问你”与“即使你同意,它最多能碰哪里”是两回事;后者不是提示词,而是操作系统级限制。

为什么这对自研重要

这是该分叉继承的最重要企业能力之一;选择 danger/full access 或 SandboxType::None 会主动撤掉这层保护。

固定提交源码摘录
  170          unix_sockets: feature_config.unix_sockets.as_ref().map(|unix_sockets| {
  171              NetworkUnixSocketPermissionsToml {
  172                  entries: unix_sockets
  173                      .iter()
  174                      .map(|(path, permission)| {
  175                          let permission = match permission {
  176                              NetworkProxyUnixSocketPermissionToml::Allow => {
  177                                  NetworkUnixSocketPermissionToml::Allow
  178                              }
  179                              NetworkProxyUnixSocketPermissionToml::Deny => {
  180                                  NetworkUnixSocketPermissionToml::Deny
  181                              }
  182                          };
  183                          (path.clone(), permission)
  184                      })
  185                      .collect(),
  186              }
  187          }),
  188          allow_local_binding: feature_config.allow_local_binding,
  189          mitm: None,
  190      }
  191      .apply_to_network_proxy_config(config);
  192  }
  193  
  194  pub(crate) fn resolve_permission_profile(
      … 55 lines omitted; exact range 170–260 …
  250              insert_special_filesystem_permission_toml(entries, value, entry.access);
  251          }
  252      }
  253  }
  254  
  255  fn insert_special_filesystem_permission_toml(
  256      entries: &mut BTreeMap<String, FilesystemPermissionToml>,
  257      value: FileSystemSpecialPath,
  258      access: FileSystemAccessMode,
  259  ) {
  260      match value {
为什么相信这条结论?查看 3 处证据
小练习 6

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M07 · ECOSYSTEM

指令、MCP、Skills 与插件:能力如何接进来

一条系统指令、一个 Skill、一个 MCP server 和一个插件,分别在什么时候进入上下文和执行路径?

先用一个生活比喻

这像给工作室接设备:说明书不是设备,设备也不等于电源;成熟 Harness 会分别治理发现、信任、加载、调用和卸载。

这套实现先回答了什么?

连接器不是随时从全局表里飘进来,而是在这一轮、这一步被拍成快照后再装进工具箱。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
MCP、Apps、Plugins、Extensions 都在 step 工具计划中受快照控制codex-rs/core/src/session/turn.rs:546连接器不是随时从全局表里飘进来,而是在这一轮、这一步被拍成快照后再装进工具箱。
AGENTS.md 从项目根到 cwd 合并,局部 override 优先且有总预算codex-rs/core/src/agents_md.rs:1仓库总规矩先读,子目录的局部规矩后读;局部 override 能盖住普通 AGENTS 文件,所有说明不能无限占上下文。
Skills developer block 不允许被 Harness 转换悄悄丢掉codex-rs/core/src/harness/session_skills.rs:1换成 Claude/Kimi/Pi 外壳时,用户装的技能仍要跟过去,不能因为消息格式翻译而消失。
Hooks 覆盖会话、输入、权限、工具、压缩、停止和子 Agent 生命周期codex-rs/hooks/src/types.rs:1外部治理系统能在关键关口插卡:开始前补上下文,危险动作前审批,工具后审计,结束前决定是否继续。
19
L1 · fact · oi-mcp-001

MCP、Apps、Plugins、Extensions 都在 step 工具计划中受快照控制

先看源码事实

turn 开始根据用户明确提及的 plugins/apps 构建注入;step context 持有 MCP 配置快照,spec plan 再把 MCP resource、extension、dynamic tools 与 core tools 合并,模型可见 spec 和实际 registry 同步生成。

翻译成白话

连接器不是随时从全局表里飘进来,而是在这一轮、这一步被拍成快照后再装进工具箱。

为什么这对自研重要

可复现性和权限边界较清楚;插件配置变化通常下一 step 才生效。

固定提交源码摘录
  546      step_context: &StepContext,
  547      input: &[TurnInput],
  548      cancellation_token: &CancellationToken,
  549  ) -> Option<(Vec<ResponseItem>, HashSet<String>)> {
  550      let turn_context = step_context.turn.as_ref();
  551      // Guardian input embeds the parent transcript as untrusted evidence. Do not interpret skill or
  552      // plugin mentions from that generated prompt as requests to inject additional instructions.
  553      if crate::guardian::is_guardian_reviewer_source(&turn_context.session_source) {
  554          return Some((Vec::new(), HashSet::new()));
  555      }
  556  
  557      let user_input = input
  558          .iter()
  559          .filter_map(|item| match item {
  560              TurnInput::UserInput { content, .. } => Some(content.as_slice()),
  561              TurnInput::ResponseItem(_) | TurnInput::InterAgentCommunication(_) => None,
  562          })
  563          .flatten()
  564          .cloned()
  565          .collect::<Vec<_>>();
  566      let tracking = build_track_events_context(
  567          turn_context.model_info.slug.clone(),
  568          sess.thread_id.to_string(),
  569          turn_context.sub_id.clone(),
  570          turn_context.originator.clone(),
      … 69 lines omitted; exact range 546–650 …
  640      } = build_skill_injections(
  641          &mentioned_skills,
  642          Some(skills_outcome),
  643          Some(&turn_context.session_telemetry),
  644          &sess.services.analytics_events_client,
  645          tracking.clone(),
  646      )
  647      .await;
  648  
  649      for message in skill_warnings {
  650          sess.send_event(turn_context, EventMsg::Warning(WarningEvent { message }))
为什么相信这条结论?查看 2 处证据
20
L1 · fact · oi-instructions-001

AGENTS.md 从项目根到 cwd 合并,局部 override 优先且有总预算

先看源码事实

loader 先定位 project root,再从根到 cwd 搜索 AGENTS.override.md/AGENTS.md;每层只取第一个候选,按顺序拼接并受 project_doc_max_bytes 限制,读取通过环境文件系统抽象。

翻译成白话

仓库总规矩先读,子目录的局部规矩后读;局部 override 能盖住普通 AGENTS 文件,所有说明不能无限占上下文。

为什么这对自研重要

适合 monorepo 分层治理;超预算内容会截断,因此关键安全规则不应只放在大文件末尾。

固定提交源码摘录
    1  //! AGENTS.md discovery and user instruction assembly.
    2  //!
    3  //! Project-level documentation is primarily stored in files named `AGENTS.md`.
    4  //! Additional fallback filenames can be configured via `project_doc_fallback_filenames`.
    5  //! We include the concatenation of all files found along the path from the
    6  //! project root to the current working directory as follows:
    7  //!
    8  //! 1.  Determine the project root by walking upwards from the current working
    9  //!     directory until a configured `project_root_markers` entry is found.
   10  //!     When `project_root_markers` is unset, the default marker list is used
   11  //!     (`.git`). If no marker is found, only the current working directory is
   12  //!     considered. An empty marker list disables parent traversal.
   13  //! 2.  Collect every `AGENTS.md` found from the project root down to the
   14  //!     current working directory (inclusive) and concatenate their contents in
   15  //!     that order.
   16  //! 3.  We do **not** walk past the project root.
   17  
   18  use crate::config::Config;
   19  use crate::context::UserInstructions as ContextUserInstructions;
   20  use crate::environment_selection::TurnEnvironmentSnapshot;
   21  use codex_config::ConfigLayerSource;
   22  use codex_config::ConfigLayerStackOrdering;
   23  use codex_config::default_project_root_markers;
   24  use codex_config::merge_toml_values;
   25  use codex_config::project_root_markers_from_config;
      … 13 lines omitted; exact range 1–49 …
   39  /// Preferred local override for AGENTS.md instructions.
   40  pub const LOCAL_AGENTS_MD_FILENAME: &str = "AGENTS.override.md";
   41  
   42  /// When both user and project AGENTS.md docs are present, they will be
   43  /// concatenated with the following separator.
   44  const AGENTS_MD_SEPARATOR: &str = "\n\n--- project-doc ---\n\n";
   45  
   46  // Metadata probes are cheap and the exec-server transport already bounds total in-flight calls.
   47  // This covers typical project hierarchies in one remote round trip without monopolizing that
   48  // transport when independent startup discovery runs concurrently.
   49  const MAX_CONCURRENT_ANCESTOR_PROBES: usize = 256;
为什么相信这条结论?查看 3 处证据
21
L3 · fact · oi-instructions-002

Skills developer block 不允许被 Harness 转换悄悄丢掉

先看源码事实

session_skills.rs 明确规定 runtime skills 在 Harness 上层组装成 developer block,每个 Harness 必须映射到最接近的原生格式;解析器支持多行描述、plugin:skill 名称和预算压力下的 rN alias,并有单元测试。

翻译成白话

换成 Claude/Kimi/Pi 外壳时,用户装的技能仍要跟过去,不能因为消息格式翻译而消失。

为什么这对自研重要

跨 Harness 行为更一致;每个 builder 仍需正确调用这些 helper。

固定提交源码摘录
    1  //! Helpers for surfacing the session's `<skills_instructions>` developer
    2  //! block in emulated harness requests.
    3  //!
    4  //! The workspace harness instruction-role rule says runtime skills are
    5  //! assembled above the harness layer as a `<skills_instructions>` developer
    6  //! block in `prompt.input` and must never be dropped per-harness; each harness
    7  //! maps the block to the closest shape it supports. These helpers locate the
    8  //! block in the prompt input and parse its `### Available skills` entries so
    9  //! harnesses can re-render them in their native skills format.
   10  
   11  use codex_protocol::models::ContentItem;
   12  use codex_protocol::models::ResponseItem;
   13  use codex_protocol::protocol::SKILLS_INSTRUCTIONS_CLOSE_TAG;
   14  use codex_protocol::protocol::SKILLS_INSTRUCTIONS_OPEN_TAG;
   15  
   16  /// A skill entry parsed from the `### Available skills` list of the session's
   17  /// `<skills_instructions>` developer block. Entries render natively as
   18  /// `- name: description (file: path)`; `path` may be an absolute path or an
   19  /// `rN/...` alias path when the skills list was rendered under budget
   20  /// pressure.
   21  #[derive(Debug, Clone, PartialEq, Eq)]
   22  pub(crate) struct SessionSkill {
   23      pub(crate) name: String,
   24      pub(crate) description: String,
   25      pub(crate) path: String,
      … 29 lines omitted; exact range 1–65 …
   55      (!body.is_empty()).then_some(body)
   56  }
   57  
   58  /// Parses the session's skills from the `<skills_instructions>` developer
   59  /// block in the prompt input. Returns an empty list when the session has no
   60  /// skills block.
   61  pub(crate) fn parse_session_skills(items: &[ResponseItem]) -> Vec<SessionSkill> {
   62      find_skills_instructions_text(items)
   63          .map(parse_skills_instructions)
   64          .unwrap_or_default()
   65  }
为什么相信这条结论?查看 3 处证据
22
L2 · fact · oi-hooks-001

Hooks 覆盖会话、输入、权限、工具、压缩、停止和子 Agent 生命周期

先看源码事实

hook event 类型与 runtime 调用点覆盖 SessionStart、UserPromptSubmit、PermissionRequest、PreToolUse/PostToolUse/PostToolUseFailure、PreCompact、Stop、SubagentStart/SubagentStop;hook 可返回附加上下文、权限决定或阻止继续。

翻译成白话

外部治理系统能在关键关口插卡:开始前补上下文,危险动作前审批,工具后审计,结束前决定是否继续。

为什么这对自研重要

适合企业策略和观测;hook 自身是高权限扩展点,配置来源必须受信。

固定提交源码摘录
    1  use std::sync::Arc;
    2  
    3  use chrono::DateTime;
    4  use chrono::SecondsFormat;
    5  use chrono::Utc;
    6  use codex_protocol::ThreadId;
    7  use codex_utils_absolute_path::AbsolutePathBuf;
    8  use futures::future::BoxFuture;
    9  use serde::Serialize;
   10  use serde::Serializer;
   11  
   12  pub type HookFn = Arc<dyn for<'a> Fn(&'a HookPayload) -> BoxFuture<'a, HookResult> + Send + Sync>;
   13  
   14  #[derive(Debug)]
   15  pub enum HookResult {
   16      /// Success: hook completed successfully.
   17      Success,
   18      /// FailedContinue: hook failed, but other subsequent hooks should still execute and the
   19      /// operation should continue.
   20      FailedContinue(Box<dyn std::error::Error + Send + Sync + 'static>),
   21      /// FailedAbort: hook failed, other subsequent hooks should not execute, and the operation
   22      /// should be aborted.
   23      FailedAbort(Box<dyn std::error::Error + Send + Sync + 'static>),
   24  }
   25  
      … 116 lines omitted; exact range 1–152 …
  142                  "event_type": "after_agent",
  143                  "thread_id": thread_id.to_string(),
  144                  "turn_id": "turn-1",
  145                  "input_messages": ["hello"],
  146                  "last_assistant_message": "hi",
  147              },
  148          });
  149  
  150          assert_eq!(actual, expected);
  151      }
  152  }
为什么相信这条结论?查看 2 处证据
小练习 7

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M08 · COLLABORATION

子 Agent:把一个大任务拆成可治理的协作

什么时候是普通工具调用,什么时候才算子 Agent?子 Agent 的上下文、预算、取消和结果怎样回到父 Agent?

先用一个生活比喻

不是把同事叫来聊天就叫协作;真正的协作要有工单、权限、截止时间、交付物和回收机制。

这套实现先回答了什么?

子 Agent 不是主函数里临时递归一下,而是有独立会话和持久关系的线程树。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
多 Agent 是共享控制面的线程树codex-rs/core/src/agent/control.rs:88子 Agent 不是主函数里临时递归一下,而是有独立会话和持久关系的线程树。
Harness 自己的 Agent/Task 工具复用同一子 Agent 系统codex-rs/core/src/tools/spec_plan.rs:616外面看像 Claude 的 Agent 工具或 OpenCode 的 task,里面其实都在同一棵线程树上派工。
23
L1 · fact · oi-agent-001

多 Agent 是共享控制面的线程树

先看源码事实

AgentControl 在 root session 创建并共享给所有子 Agent,维护 thread id、parent/child edges、spawn depth、状态与执行 limiter;spawn、message、interrupt、wait、resume 都落到同一 CodexThreadManager。

翻译成白话

子 Agent 不是主函数里临时递归一下,而是有独立会话和持久关系的线程树。

为什么这对自研重要

可以跨步等待、续跑和观测;也需要全局驻留数、运行数和深度限制。

固定提交源码摘录
   88  
   89  /// Control-plane handle for multi-agent operations.
   90  /// `AgentControl` is held by each session (via `SessionServices`). It provides capability to
   91  /// spawn new agents and the inter-agent communication layer.
   92  /// An `AgentControl` instance is intended to be created at most once per root thread/session
   93  /// tree. That same `AgentControl` is then shared with every sub-agent spawned from that root,
   94  /// which keeps the registry scoped to that root thread rather than the entire `ThreadManager`.
   95  #[derive(Clone, Default)]
   96  pub(crate) struct AgentControl {
   97      /// ID shared by the whole agent control session. This means every sub-agents from a common
   98      /// root share the same session ID.
   99      session_id: SessionId,
  100      /// Weak handle back to the global thread registry/state.
  101      /// This is `Weak` to avoid reference cycles and shadow persistence of the form
  102      /// `ThreadManagerState -> CodexThread -> Session -> SessionServices -> ThreadManagerState`.
  103      manager: Weak<ThreadManagerState>,
  104      state: Arc<AgentRegistry>,
  105      v2_residency: Arc<V2Residency>,
  106      agent_execution_limiter: Arc<AgentExecutionLimiter>,
  107      /// Session-scoped state shared by the root thread and every cloned sub-agent control handle.
  108      rollout_budget: Arc<RolloutBudget>,
  109  }
  110  
  111  impl AgentControl {
  112      /// Construct a new `AgentControl` that can spawn/message agents via the given manager state.
      … 57 lines omitted; exact range 88–180 …
  170          agent_id: ThreadId,
  171          communication: InterAgentCommunication,
  172          agent_communication_context: AgentCommunicationContext,
  173      ) -> CodexResult<String> {
  174          let state = self.upgrade()?;
  175          self.ensure_execution_capacity_for_turn_start(agent_id, communication.trigger_turn)
  176              .await?;
  177          self.send_inter_agent_communication_after_capacity_check(
  178              agent_id,
  179              &state,
  180              communication,
为什么相信这条结论?查看 3 处证据
24
L1 · fact · oi-agent-002

Harness 自己的 Agent/Task 工具复用同一子 Agent 系统

先看源码事实

Claude/Kimi/ZCode alias 可把 Agent、TaskOutput、TaskStop 等目标工具名映射到内部 multi-agent handler;OpenCode task 也走专用 alias。模型看到的是目标 Harness 语言,后台仍是同一个 AgentControl。

翻译成白话

外面看像 Claude 的 Agent 工具或 OpenCode 的 task,里面其实都在同一棵线程树上派工。

为什么这对自研重要

协作能力不会因换 Harness 丢失;目标 Harness 原生的所有细节并不保证完全复刻。

固定提交源码摘录
  616      if harness.is_claude_code() {
  617          planned_tools.add(HarnessAliasHandler::Agent);
  618          planned_tools.add_dispatch_only(HarnessAliasHandler::TaskOutput);
  619          planned_tools.add_dispatch_only(HarnessAliasHandler::TaskStop);
  620      }
  621      planned_tools.add(HarnessAliasHandler::Bash);
  622      planned_tools.add(HarnessAliasHandler::BashLower);
  623      planned_tools.add(HarnessAliasHandler::Read);
  624      planned_tools.add(HarnessAliasHandler::ReadLower);
  625      planned_tools.add(HarnessAliasHandler::Write);
  626      planned_tools.add(HarnessAliasHandler::WriteLower);
  627      planned_tools.add(HarnessAliasHandler::Edit);
  628      planned_tools.add(HarnessAliasHandler::EditLower);
  629      planned_tools.add(HarnessAliasHandler::Glob);
  630      planned_tools.add(HarnessAliasHandler::GlobLower);
  631      planned_tools.add(HarnessAliasHandler::Grep);
  632      planned_tools.add(HarnessAliasHandler::GrepLower);
  633      planned_tools.add(HarnessAliasHandler::AskUserQuestion);
  634      if matches!(harness, Harness::KimiCode) {
  635          planned_tools.add_dispatch_only(HarnessAliasHandler::Agent);
  636          planned_tools.add_dispatch_only(HarnessAliasHandler::ReadMediaFile);
  637          planned_tools.add_dispatch_only(HarnessAliasHandler::TaskList);
  638          planned_tools.add_dispatch_only(HarnessAliasHandler::TaskOutput);
  639          planned_tools.add_dispatch_only(HarnessAliasHandler::TaskStop);
  640          planned_tools.add_dispatch_only(HarnessAliasHandler::ZCodeEnterPlanMode);
      … 34 lines omitted; exact range 616–685 …
  675      }
  676      if matches!(harness, Harness::ZCode) {
  677          planned_tools.add_dispatch_only(HarnessAliasHandler::Agent);
  678          planned_tools.add_dispatch_only(HarnessAliasHandler::ZCodeTodoRead);
  679          planned_tools.add_dispatch_only(HarnessAliasHandler::ZCodeTodoWrite);
  680          planned_tools.add_dispatch_only(HarnessAliasHandler::ZCodeEnterPlanMode);
  681          planned_tools.add_dispatch_only(HarnessAliasHandler::ZCodeExitPlanMode);
  682          planned_tools.add_dispatch_only(HarnessAliasHandler::ZCodeReadSessionContext);
  683          planned_tools.add_dispatch_only(HarnessAliasHandler::ZCodeSkill);
  684      }
  685  }
为什么相信这条结论?查看 3 处证据
小练习 8

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M09 · STATE

会话、持久化与观测:让一次运行变成可追溯事实

如果进程崩了、用户刷新了、任务跑了一夜,系统凭什么恢复并解释“刚才究竟发生了什么”?

先用一个生活比喻

内存像白板,数据库像目录,append-only journal 像监控录像;可靠 Harness 不只保存最后答案,还保存每次转弯。

这套实现先回答了什么?

先保存完整流水账,再从流水账还原聊天、文件、工具和子 Agent 发生了什么。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
JSONL rollout 是可恢复事件事实源,SQLite/trace 是查询与诊断层codex-rs/core/src/session/rollout_reconstruction.rs:116先保存完整流水账,再从流水账还原聊天、文件、工具和子 Agent 发生了什么。
Open Interpreter 使用独立分析端点,默认启用但可显式关闭codex-rs/analytics/src/client.rs:54默认会发产品使用事件到 Open Interpreter 的后端,用户可以在配置里关掉;遥测故障不会卡住编码任务。
25
L1 · fact · oi-observe-001

JSONL rollout 是可恢复事件事实源,SQLite/trace 是查询与诊断层

先看源码事实

会话把 SessionMeta、TurnContext、WorldState、ResponseItem、Compacted、EventMsg 和 inter-agent communication 记录为 RolloutItem;重建器按 turn complete/abort/rollback/compact 复原历史,rollout-trace 再把原始事件归约成会话、工具和推理视图。

翻译成白话

先保存完整流水账,再从流水账还原聊天、文件、工具和子 Agent 发生了什么。

为什么这对自研重要

恢复与审计能力强;事件 schema 演进、敏感字段保留和磁盘生命周期需要治理。

固定提交源码摘录
  116          rollout_items: &[RolloutItem],
  117      ) -> RolloutReconstruction {
  118          // Replay metadata should already match the shape of the future lazy reverse loader, even
  119          // while history materialization still uses an eager bridge. Scan newest-to-oldest,
  120          // stopping once a surviving replacement-history checkpoint and the required resume metadata
  121          // are both known; then replay only the buffered surviving tail forward to preserve exact
  122          // history semantics.
  123          let has_legacy_compaction_without_window_number =
  124              rollout_items.iter().any(|item| {
  125                  matches!(item, RolloutItem::Compacted(compacted) if compacted.window_number.is_none())
  126              });
  127          let initial_window = if has_legacy_compaction_without_window_number {
  128              None
  129          } else {
  130              rollout_items.iter().find_map(|item| match item {
  131                  RolloutItem::SessionMeta(session_meta) => session_meta
  132                      .meta
  133                      .context_window
  134                      .as_ref()
  135                      .and_then(reconstructed_window_from_session_context_window),
  136                  _ => None,
  137              })
  138          };
  139          let mut base_replacement_history: Option<&[ResponseItem]> = None;
  140          let mut previous_turn_settings = None;
      … 137 lines omitted; exact range 116–288 …
  278                          active_segment.get_or_insert_with(ActiveReplaySegment::default);
  279                      active_segment.counts_as_user_turn = true;
  280                  }
  281                  RolloutItem::EventMsg(_)
  282                  | RolloutItem::SessionMeta(_)
  283                  | RolloutItem::InterAgentCommunicationMetadata { .. } => {}
  284              }
  285  
  286              if base_replacement_history.is_some()
  287                  && previous_turn_settings.is_some()
  288                  && !matches!(reference_context_item, TurnReferenceContextItem::NeverSet)
为什么相信这条结论?查看 3 处证据
26
L1 · risk · oi-observe-002

Open Interpreter 使用独立分析端点,默认启用但可显式关闭

先看源码事实

AnalyticsEventsClient 在 analytics_enabled != Some(false) 时创建队列;Open Interpreter 把任何 provider 的事件发送到 oi-new-api.fly.dev 的固定端点,配置 [analytics] enabled=false 可关闭;队列满会丢事件而不阻塞主循环。

翻译成白话

默认会发产品使用事件到 Open Interpreter 的后端,用户可以在配置里关掉;遥测故障不会卡住编码任务。

为什么这对自研重要

企业部署应在基线配置里明确关闭或审查字段,而不是假设换了自建模型就没有遥测。

固定提交源码摘录
   54  const ANALYTICS_EVENTS_QUEUE_SIZE: usize = 256;
   55  const ANALYTICS_EVENTS_TIMEOUT: Duration = Duration::from_secs(10);
   56  const ANALYTICS_EVENT_DEDUPE_MAX_KEYS: usize = 4096;
   57  // Open Interpreter analytics endpoint. This replaces upstream Codex's
   58  // `{chatgpt_base_url}/codex/analytics-events/events` so events from any
   59  // provider land in our infra. Disabled the same way Codex does it:
   60  // set `[analytics] enabled = false` in `~/.codex/config.toml`.
   61  const INTERPRETER_ANALYTICS_URL: &str = "https://oi-new-api.fly.dev/v0/interpreter-events";
   62  const LOCAL_ANALYTICS_EVENTS_PATH: &str = "/codex/analytics-events/events";
   63  
   64  #[derive(Clone)]
   65  pub(crate) struct AnalyticsEventsQueue {
   66      pub(crate) sender: mpsc::Sender<AnalyticsFact>,
   67      pub(crate) app_used_emitted_keys: Arc<Mutex<HashSet<(String, String)>>>,
   68      pub(crate) plugin_used_emitted_keys: Arc<Mutex<HashSet<(String, String)>>>,
   69  }
   70  
   71  #[derive(Clone)]
   72  pub struct AnalyticsEventsClient {
   73      queue: Option<AnalyticsEventsQueue>,
   74  }
   75  
   76  #[derive(Clone, Debug, Eq, PartialEq)]
   77  enum AnalyticsEventsDestination {
   78      Http {
      … 16 lines omitted; exact range 54–105 …
   95          if let Some(path) = capture_file {
   96              if let Err(err) = crate::analytics_capture::initialize(&path) {
   97                  tracing::error!(
   98                      path = %path.display(),
   99                      "failed to initialize analytics event capture; network delivery remains disabled: {err}"
  100                  );
  101              }
  102              tracing::warn!(
  103                  path = %path.display(),
  104                  "analytics event capture enabled; network delivery is disabled"
  105              );
为什么相信这条结论?查看 3 处证据
小练习 9

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M10 · ENGINEERING

测试、恢复与工程取舍:把漂亮机制变成可靠产品

哪些行为有测试证明?哪些只是配置或 prompt 约定?当安全、性能、可恢复性冲突时,源码选择了什么?

先用一个生活比喻

这像验收一座桥:图纸说明结构,测试证明承重,故障演练证明断电后还能不能让人安全回来。

本节阅读法先问问题读事实看代码做迁移判断
读源码时先问本课的判断方式
这一层有没有独立证据?没有就不把其他层的能力冒充成默认行为;回到固定提交继续追调用链。
谁拥有最终控制权?区分模型输出、框架规则、用户审批和 OS 隔离四种不同力量。
这一章先建立通用概念

固定提交账本没有把该维度单独拆出,但它会在其他章节的源码路径中体现。先沿执行链路阅读,再回到报告页核对证据。

小练习 10

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M11 · PRACTICE

把读懂变成会判断

下面的练习不要求你先写一个完整 Agent,而是训练你检查设计边界:事实是什么、推断是什么、如果换成自研产品要补哪一层。

Q1

当前源码不是经典 Python Open Interpreter,而是 Rust/Codex 兼容分叉

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

评估时必须把它与同批次的 openai/codex 分开:共享基座很多,但 Open Interpreter 的差异主要落在品牌、provider/harness 选择和协议仿真。

证据:codex-rs/product-info/src/lib.rs:45
Q2

兼容性是被测试的产品契约,不只是目录遗留

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

企业接入可复用 Codex 客户端生态;代价是内部命名和上游演进耦合很深。

证据:scripts/test-codex-sdk-compat.sh:21
Q3

核心差异是一层多 Harness 仿真目录

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

这比普通 provider adapter 更深,但也不是把这些项目的完整运行时都嵌入进来。

证据:codex-rs/core/src/harness/mod.rs:1
Q4

Provider、Wire API、Harness 是三层独立选择

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

可做跨模型的 Harness A/B;默认映射是产品经验规则,不代表所有组合都兼容。

证据:codex-rs/app-server-protocol/src/protocol/v2/interpreter.rs:8
Q5

路由矩阵在代码里硬校验,不兼容组合直接报错

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

错误更早、更可诊断;catalog 的 Native/ALL 表述仍需与 routing 的 Native+Messages 拒绝一起理解。

证据:codex-rs/core/src/harness/routing.rs:5
APPENDIX · SOURCE INDEX

本课读过的实现文件

文件索引帮助你在课程外继续追踪调用链;每一条路径来自固定提交的证据账本。

  1. 01codex-rs/product-info/src/lib.rsL45–79
  2. 02scripts/install/install-open-interpreter.shL13–25
  3. 03scripts/codex_package/targets.pyL60–85
  4. 04scripts/test-codex-sdk-compat.shL21–36
  5. 05scripts/codex_package/test_layout.pyL19–63
  6. 06codex-rs/core/src/harness/mod.rsL1–18
  7. 07codex-rs/app-server/src/interpreter_catalog.rsL29–118
  8. 08codex-rs/tools/src/harness.rsL1–99
  9. 09codex-rs/app-server-protocol/src/protocol/v2/interpreter.rsL8–44, L78–137
  10. 10codex-rs/model-provider-info/src/lib.rsL161–223
  11. 11codex-rs/core/src/harness/routing.rsL5–151, L185–270
  12. 12codex-rs/core/src/harness/request.rsL1–8, L42–73, L75–231, L75–231
  13. 13codex-rs/core/src/harness/pi.rsL21–120, L121–225
  14. 14codex-rs/core/src/harness/opencode.rsL1–180
  15. 15codex-rs/core/src/session/turn.rsL140–228, L229–324, L355–460, L243–292, L546–650
  16. 16codex-rs/core/src/session/step_context.rsL1–54
  17. 17codex-rs/core/src/context_manager/history.rsL40–186, L328–368, L493–505
  18. 18codex-rs/core/src/compact.rsL299–389, L600–689, L718–815, L600–815
  19. 19codex-rs/core/src/tools/spec_plan.rsL240–268, L432–495, L719–735, L610–685, L590–608, L616–685
  20. 20codex-rs/core/src/tools/handlers/harness_aliases.rsL102–254, L1749–1821, L1823–1925, L1927–2025, L196–205, L264–420, L102–254
  21. 21codex-rs/core/src/tools/parallel.rsL42–138
  22. 22codex-rs/core/src/tools/handlers/harness_fs.rsL39–94, L97–149, L158–204
  23. 23codex-rs/core/src/config/permissions.rsL170–260
  24. 24codex-rs/sandboxing/src/manager.rsL280–352, L357–410
  25. 25codex-rs/core/src/agents_md.rsL1–49, L51–150, L153–231
  26. 26codex-rs/core/src/harness/session_skills.rsL1–65, L67–116, L118–199
  27. 27codex-rs/hooks/src/types.rsL1–152
  28. 28codex-rs/core/src/hook_runtime.rsL1–260
  29. 29codex-rs/core/src/agent/control.rsL88–180, L520–640, L659–750
  30. 30codex-rs/core/src/tools/handlers/multi_agents.rsL1–100
  31. 31codex-rs/core/src/session/rollout_reconstruction.rsL116–288
  32. 32codex-rs/rollout/src/recorder.rsL1–260
  33. 33codex-rs/rollout-trace/src/reducer/mod.rsL1–220
  34. 34codex-rs/analytics/src/client.rsL54–105, L140–215
  35. 35codex-rs/analytics/src/client_tests.rsL240–290
  36. 36LICENSEL1–20
下一步

从课程回到报告,做一次反向核验

教程负责让你读懂,报告负责让你查证。打开报告页,任选一个章节,尝试只靠源码摘录复述它的边界。

查看 Open Interpreter 报告 ↗