Harness · Coding Agent Book11 / OpenAI Codex
研究总览
M11 · SOURCE-GROUNDED TUTORIAL

OpenAI Codex
从源码学会它怎么工作

安全、可恢复、可观测和多 Agent 控制面最均衡的企业级基座之一。 我们不把 README 当结论,而是沿主循环、工具、上下文、权限、扩展、协作和状态一路读到实现。

Rust · Secure Multi-Agent RuntimeApache-2.03418498f014227 个结论 · 63 处引用
这门课怎么读

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

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

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

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

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

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

turn 内多 step;不可漂移 snapshot;流式工具 future

上下文

COW 历史;配对/多模态成本;本地压缩自救

适用建设

企业内研发平台、安全执行、复杂长任务、多 Agent

M00.5 · TRACE

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

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

读图提醒

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

M01 · ORIENTATION

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

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

先用一个生活比喻

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

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

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

小练习 1

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

M02 · LOOP

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

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

先用一个生活比喻

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

这套实现先回答了什么?

一轮任务可以问模型很多次,但每一次“想一想并行动”的小步都先拍一张现场快照,避免工具清单和提示词在同一步里前后不一致。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
每个 turn 由多个 step 组成,step 内共享一次不可漂移的上下文快照codex-rs/core/src/session/turn.rs:153一轮任务可以问模型很多次,但每一次“想一想并行动”的小步都先拍一张现场快照,避免工具清单和提示词在同一步里前后不一致。
流式采样与工具 Future 同时推进,并可被新消息抢占codex-rs/core/src/session/turn.rs:2034模型说到一个完整动作就能开工,不必等整段回答结束;用户中途补充信息时,系统也能在安全位置收住并接新指令。
重试预算属于 turn-scoped client session,窗口超限不当作普通网络错误重试codex-rs/core/src/session/turn.rs:1176同一轮尽量复用连接和粘性状态;行李箱塞不下不会盲目重拨网络,而是交给压缩逻辑处理。
01
L1 · fact · codex-loop-001

每个 turn 由多个 step 组成,step 内共享一次不可漂移的上下文快照

先看源码事实

run_turn 在采样前处理压缩、skills/plugins、session/input hooks 和待处理输入;随后为一个 step 捕获 StepContext,并用同一视图构造上下文、工具与模型请求。

翻译成白话

一轮任务可以问模型很多次,但每一次“想一想并行动”的小步都先拍一张现场快照,避免工具清单和提示词在同一步里前后不一致。

为什么这对自研重要

配置热更新只能在安全边界生效,换来 prompt cache 和工具调用的确定性。

固定提交源码摘录
  153  pub(crate) async fn run_turn(
  154      sess: Arc<Session>,
  155      turn_context: Arc<TurnContext>,
  156      turn_extension_data: Arc<codex_extension_api::ExtensionData>,
  157      input: Vec<TurnInput>,
  158      prewarmed_client_session: Option<ModelClientSession>,
  159      cancellation_token: CancellationToken,
  160  ) -> CodexResult<Option<String>> {
  161      let mut client_session =
  162          prewarmed_client_session.unwrap_or_else(|| sess.services.model_client.new_session());
  163      // TODO(ccunningham): Pre-turn compaction runs before context updates and the
  164      // new user message are recorded. Estimate pending incoming items (context
  165      // diffs/full reinjection + user input) and trigger compaction preemptively
  166      // when they would push the thread over the compaction threshold.
  167      if let Err(err) = run_pre_sampling_compact(
  168          &sess,
  169          &turn_context,
  170          &mut client_session,
  171          &cancellation_token,
  172      )
  173      .await
  174      {
  175          if matches!(err.details(), CodexErrorDetails::TurnAborted) {
  176              run_hooks_and_record_inputs(&sess, &turn_context, &input).await;
  177              return Err(err);
      … 86 lines omitted; exact range 153–274 …
  264          if run_hooks_and_record_inputs(&sess, &turn_context, &pending_input).await {
  265              break;
  266          }
  267  
  268          let window_id = sess.current_window_id().await;
  269          super::rollout_budget::maybe_record_reminder(
  270              sess.as_ref(),
  271              turn_context.as_ref(),
  272              &window_id,
  273          )
  274          .await;
为什么相信这条结论?查看 2 处证据
02
L1 · fact · codex-loop-002

流式采样与工具 Future 同时推进,并可被新消息抢占

先看源码事实

try_run_sampling_request 消费流式 ResponseEvent;完整 tool item 到达后进入 FuturesOrdered,工具参数 diff 可边流边展示;若 commentary/reasoning 期间 mailbox 有消息,则返回 needs_follow_up。

翻译成白话

模型说到一个完整动作就能开工,不必等整段回答结束;用户中途补充信息时,系统也能在安全位置收住并接新指令。

为什么这对自研重要

响应更快,但对调用顺序、取消、配对和幂等要求很高。

固定提交源码摘录
 2034      tool_runtime: ToolCallRuntime,
 2035      sess: Arc<Session>,
 2036      turn_context: Arc<TurnContext>,
 2037      turn_store: Arc<codex_extension_api::ExtensionData>,
 2038      client_session: &mut ModelClientSession,
 2039      responses_metadata: &CodexResponsesMetadata,
 2040      turn_diff_tracker: SharedTurnDiffTracker,
 2041      prompt: &Prompt,
 2042      cancellation_token: CancellationToken,
 2043  ) -> CodexResult<SamplingRequestResult> {
 2044      feedback_tags!(
 2045          model = turn_context.model_info.slug.clone(),
 2046          approval_policy = turn_context.approval_policy.value(),
 2047          sandbox_policy = &turn_context.sandbox_policy(),
 2048          effort = turn_context.reasoning_effort,
 2049          auth_mode = sess.services.auth_manager.auth_mode(),
 2050          features = sess.features.enabled_features(),
 2051      );
 2052      let inference_trace = sess.services.rollout_thread_trace.inference_trace_context(
 2053          turn_context.sub_id.as_str(),
 2054          turn_context.model_info.slug.as_str(),
 2055          turn_context.provider.info().name.as_str(),
 2056      );
 2057      let sampling_timing_guard = turn_context.turn_timing_state.begin_sampling();
 2058      let uses_sequential_cutoff_reasoning_summaries = turn_context
      … 99 lines omitted; exact range 2034–2168 …
 2158                      let item_id = previous.id();
 2159                      flush_assistant_text_segments_for_item(
 2160                          &sess,
 2161                          &turn_context,
 2162                          plan_mode_state.as_mut(),
 2163                          &mut assistant_message_stream_parsers,
 2164                          &item_id,
 2165                      )
 2166                      .await;
 2167                  }
 2168                  if let Some(state) = plan_mode_state.as_mut()
为什么相信这条结论?查看 2 处证据
03
L1 · fact · codex-loop-003

重试预算属于 turn-scoped client session,窗口超限不当作普通网络错误重试

先看源码事实

run_sampling_request 在一个 turn 内复用 ModelClientSession;上下文超限和 usage limit 直接上抛,其他可重试错误才按 Provider 的 stream retry 上限重试。

翻译成白话

同一轮尽量复用连接和粘性状态;行李箱塞不下不会盲目重拨网络,而是交给压缩逻辑处理。

为什么这对自研重要

把语义恢复与传输恢复分开,避免无效重试放大费用。

固定提交源码摘录
 1176  async fn run_sampling_request(
 1177      sess: Arc<Session>,
 1178      step_context: Arc<StepContext>,
 1179      turn_store: Arc<codex_extension_api::ExtensionData>,
 1180      turn_diff_tracker: SharedTurnDiffTracker,
 1181      client_session: &mut ModelClientSession,
 1182      responses_metadata: &CodexResponsesMetadata,
 1183      input: Vec<ResponseItem>,
 1184      cancellation_token: CancellationToken,
 1185  ) -> CodexResult<(SamplingRequestResult, Vec<ResponseItem>)> {
 1186      let turn_context = Arc::clone(&step_context.turn);
 1187      let router = Arc::clone(&step_context.tool_router);
 1188  
 1189      let base_instructions = sess.get_base_instructions().await;
 1190  
 1191      let tool_runtime = ToolCallRuntime::new(
 1192          Arc::clone(&router),
 1193          Arc::clone(&sess),
 1194          Arc::clone(&step_context),
 1195          Arc::clone(&turn_diff_tracker),
 1196      );
 1197      let _code_mode_worker = sess.services.code_mode_service.start_turn_worker(
 1198          &sess,
 1199          Arc::clone(&step_context),
 1200          Arc::clone(&router),
      … 62 lines omitted; exact range 1176–1273 …
 1263              max_retries,
 1264              err,
 1265              client_session,
 1266              &sess,
 1267              &turn_context,
 1268              ResponsesStreamRequest::Sampling,
 1269          )
 1270          .await?;
 1271          turn_context.turn_timing_state.record_sampling_retry();
 1272      }
 1273  }
为什么相信这条结论?查看 2 处证据
小练习 2

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

M03 · MODEL

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

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

先用一个生活比喻

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

这套实现先回答了什么?

它允许换“接线地址和门禁方式”,但要求对方都说 Responses 这门语言;不是任意 Chat Completions 方言翻译器。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
模型协议只保留 Responses API,但 Provider 端点与认证可扩展codex-rs/model-provider-info/src/lib.rs:54它允许换“接线地址和门禁方式”,但要求对方都说 Responses 这门语言;不是任意 Chat Completions 方言翻译器。
传输层同时支持 SSE 与可复用 WebSocket,并带 turn 粘性状态codex-rs/core/src/client.rs:1每轮对话尽量占用一条可复用的高速通道,还带着本轮路由票据;热身失败不会把整轮任务判死。
04
L1 · fact · codex-provider-001

模型协议只保留 Responses API,但 Provider 端点与认证可扩展

先看源码事实

WireApi 枚举只有 Responses,反序列化 chat 会报迁移错误;ModelProviderInfo 允许自定义 base URL、环境密钥、命令认证、AWS SigV4、headers、query 和重试。

翻译成白话

它允许换“接线地址和门禁方式”,但要求对方都说 Responses 这门语言;不是任意 Chat Completions 方言翻译器。

为什么这对自研重要

兼容面更一致,第三方 Provider 必须实现 Responses 语义而非只暴露 chat/completions。

固定提交源码摘录
   54  /// Wire protocol that the provider speaks.
   55  #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, JsonSchema)]
   56  #[serde(rename_all = "lowercase")]
   57  pub enum WireApi {
   58      /// The Responses API exposed by OpenAI at `/v1/responses`.
   59      #[default]
   60      Responses,
   61  }
   62  
   63  impl fmt::Display for WireApi {
   64      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
   65          let value = match self {
   66              Self::Responses => "responses",
   67          };
   68          f.write_str(value)
   69      }
   70  }
   71  
   72  impl<'de> Deserialize<'de> for WireApi {
   73      fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   74      where
   75          D: serde::Deserializer<'de>,
   76      {
   77          let value = String::deserialize(deserializer)?;
   78          match value.as_str() {
   79              "responses" => Ok(Self::Responses),
   80              "chat" => Err(serde::de::Error::custom(CHAT_WIRE_API_REMOVED_ERROR)),
   81              _ => Err(serde::de::Error::unknown_variant(&value, &["responses"])),
   82          }
   83      }
   84  }
为什么相信这条结论?查看 2 处证据
05
L1 · fact · codex-provider-002

传输层同时支持 SSE 与可复用 WebSocket,并带 turn 粘性状态

先看源码事实

ModelClientSession 按 turn 创建,延迟缓存 Responses WebSocket 和 x-codex-turn-state;预热是 generate=false 的 v2 response.create,失败后由常规重试/回退处理。

翻译成白话

每轮对话尽量占用一条可复用的高速通道,还带着本轮路由票据;热身失败不会把整轮任务判死。

为什么这对自研重要

交互延迟低,但 Provider 必须准确声明 WebSocket 能力,AWS 认证当前明确不能与 WebSocket 同开。

固定提交源码摘录
    1  //! Session- and turn-scoped helpers for talking to model provider APIs.
    2  //!
    3  //! `ModelClient` is intended to live for the lifetime of a Codex session and holds the stable
    4  //! configuration and state needed to talk to a provider (auth, provider selection, conversation id,
    5  //! and transport fallback state).
    6  //!
    7  //! Per-turn settings (model selection, reasoning controls, telemetry context, and turn metadata)
    8  //! are passed explicitly to streaming and unary methods so that the turn lifetime is visible at the
    9  //! call site.
   10  //!
   11  //! A [`ModelClientSession`] is created per turn and is used to stream one or more Responses API
   12  //! requests during that turn. It caches a Responses WebSocket connection (opened lazily) and stores
   13  //! per-turn state such as the `x-codex-turn-state` token used for sticky routing.
   14  //!
   15  //! WebSocket prewarm is a v2-only `response.create` with `generate=false`; it waits for completion
   16  //! so the next request can reuse the same connection and `previous_response_id`.
   17  //!
   18  //! Turn execution performs prewarm as a best-effort step before the first stream request so the
   19  //! subsequent request can reuse the same connection.
   20  //!
   21  //! ## Retry-Budget Tradeoff
   22  //!
   23  //! WebSocket prewarm is treated as the first websocket connection attempt for a turn. If it
   24  //! fails, normal stream retry/fallback logic handles recovery on the same turn.
为什么相信这条结论?查看 2 处证据
小练习 3

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

M04 · TOOLS

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

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

先用一个生活比喻

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

这套实现先回答了什么?

每个新工具不只是写一个 execute 函数,还要说明如何取消、怎么记日志、钩子看什么、参数流到一半时如何展示。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
工具有统一 typed runtime 契约,hooks、观测和流式参数 diff 都是一级能力codex-rs/core/src/tools/registry.rs:48每个新工具不只是写一个 execute 函数,还要说明如何取消、怎么记日志、钩子看什么、参数流到一半时如何展示。
并发工具用读写锁实现“并行组 + 全局排他工具”codex-rs/core/src/tools/parallel.rs:74能并行的工具像多人同时读资料;危险或有状态的工具拿独占钥匙,等所有并行动作结束后自己运行。
工具暴露与分发分离,兼容旧 shell 又不污染模型可见清单codex-rs/core/src/tools/spec_plan_tests.rs:637后台可以保留旧插座做兼容,但模型眼前只摆当前应该用的工具,避免同功能多把扳手。
06
L1 · fact · codex-tools-001

工具有统一 typed runtime 契约,hooks、观测和流式参数 diff 都是一级能力

先看源码事实

CoreToolRuntime 扩展 ToolExecutor,统一声明 payload 匹配、取消清理、telemetry tags、pre/post hook payload、hook 输入改写和参数 diff consumer。

翻译成白话

每个新工具不只是写一个 execute 函数,还要说明如何取消、怎么记日志、钩子看什么、参数流到一半时如何展示。

为什么这对自研重要

扩展成本高于轻量 Agent,但跨工具的安全和观测语义更一致。

固定提交源码摘录
   48  /// Typed runtime contract for locally executed tools.
   49  ///
   50  /// Implementers provide the shared `ToolExecutor` behavior plus optional
   51  /// core-owned metadata for hooks, telemetry, tool search, and argument diffs.
   52  pub(crate) trait CoreToolRuntime: ToolExecutor<ToolInvocation> {
   53      fn matches_kind(&self, payload: &ToolPayload) -> bool {
   54          matches!(
   55              payload,
   56              ToolPayload::Function { .. } | ToolPayload::ToolSearch { .. }
   57          )
   58      }
   59  
   60      /// Whether cancellation should let the handler finish teardown before the
   61      /// host returns an aborted tool response.
   62      fn waits_for_runtime_cancellation(&self) -> bool {
   63          false
   64      }
   65  
   66      fn telemetry_tags<'a>(
   67          &'a self,
   68          _invocation: &'a ToolInvocation,
   69      ) -> BoxFuture<'a, ToolTelemetryTags> {
   70          Box::pin(async { Vec::new() })
   71      }
   72  
      … 66 lines omitted; exact range 48–149 …
  139          Ok(ToolInvocation {
  140              payload: ToolPayload::Function { arguments },
  141              ..invocation
  142          })
  143      }
  144  
  145      /// Creates an optional consumer for streamed tool argument diffs.
  146      fn create_diff_consumer(&self) -> Option<Box<dyn ToolArgumentDiffConsumer>> {
  147          None
  148      }
  149  }
为什么相信这条结论?查看 2 处证据
07
L1 · fact · codex-tools-002

并发工具用读写锁实现“并行组 + 全局排他工具”

先看源码事实

ToolCallRuntime 对支持并发的 handler 取读锁,不支持并发的取写锁;取消时按 handler 语义等待清理或 abort,并输出结构化 aborted result 与分段时延。

翻译成白话

能并行的工具像多人同时读资料;危险或有状态的工具拿独占钥匙,等所有并行动作结束后自己运行。

为什么这对自研重要

不是简单 Promise.all,避免非并行工具与其他调用交叉污染环境。

固定提交源码摘录
   74      #[instrument(level = "trace", skip_all)]
   75      pub(crate) fn handle_tool_call(
   76          self,
   77          call: ToolCall,
   78          cancellation_token: CancellationToken,
   79      ) -> impl std::future::Future<Output = Result<ResponseInputItem, CodexErr>> {
   80          let error_call = call.clone();
   81          let future =
   82              self.handle_tool_call_with_source(call, ToolCallSource::Direct, cancellation_token);
   83          async move {
   84              match future.await {
   85                  Ok(response) => Ok(response.into_response()),
   86                  Err(FunctionCallError::Fatal(message)) => Err(CodexErr::Fatal(message)),
   87                  Err(other) => Ok(Self::failure_response(error_call, other)),
   88              }
   89          }
   90          .in_current_span()
   91      }
   92  
   93      #[instrument(level = "trace", skip_all)]
   94      pub(crate) fn handle_tool_call_with_source(
   95          self,
   96          call: ToolCall,
   97          source: ToolCallSource,
   98          cancellation_token: CancellationToken,
      … 36 lines omitted; exact range 74–145 …
  135                  } else {
  136                      Either::Right(lock.write().await)
  137                  };
  138                  // Admission through the parallel-execution gate marks the end
  139                  // of dispatch waiting and the start of handler execution.
  140                  if let Some(execution_started_at) = execution_started_at {
  141                      let _ = execution_started_at.set(Instant::now());
  142                  }
  143  
  144                  router
  145                      .dispatch_tool_call_with_terminal_outcome(
为什么相信这条结论?查看 2 处证据
08
L3 · fact · codex-tools-003

工具暴露与分发分离,兼容旧 shell 又不污染模型可见清单

先看源码事实

工具规划测试确认 unified exec 可把 exec_command/write_stdin 暴露给模型,同时保留隐藏的 legacy shell_command 仅供分发;apply_patch、web search 和动态工具也按环境、模型及 Provider capability gate。

翻译成白话

后台可以保留旧插座做兼容,但模型眼前只摆当前应该用的工具,避免同功能多把扳手。

为什么这对自研重要

迁移期兼容性强,且 prompt tool schema 保持紧凑稳定。

固定提交源码摘录
  637  async fn shell_family_registers_visible_unified_exec_and_hidden_legacy_shell() {
  638      let plan = probe(|turn| {
  639          set_features(turn, &[Feature::ShellTool, Feature::UnifiedExec]);
  640          set_feature(turn, Feature::ShellZshFork, /*enabled*/ false);
  641          turn.model_info.shell_type = ConfigShellToolType::ShellCommand;
  642      })
  643      .await;
  644  
  645      plan.assert_visible_contains(&["exec_command", "write_stdin"]);
  646      plan.assert_visible_lacks(&["shell_command"]);
  647      plan.assert_registered_contains(&["exec_command", "write_stdin", "shell_command"]);
  648      assert_eq!(plan.exposure("shell_command"), ToolExposure::Hidden);
  649      assert!(has_parameter(plan.visible_spec("exec_command"), "shell"));
  650  }
  651  
  652  #[tokio::test]
  653  async fn shell_zsh_fork_stays_standalone_until_unified_exec_composition_is_enabled() {
  654      let standalone = probe(|turn| {
  655          set_features(turn, &[Feature::ShellTool, Feature::UnifiedExec]);
  656          set_feature(turn, Feature::ShellZshFork, /*enabled*/ true);
  657          set_feature(turn, Feature::UnifiedExecZshFork, /*enabled*/ false);
  658          turn.model_info.shell_type = ConfigShellToolType::ShellCommand;
  659      })
  660      .await;
  661  
      … 36 lines omitted; exact range 637–708 …
  698      let plan = probe(|turn| {
  699          set_features(
  700              turn,
  701              &[
  702                  Feature::ShellTool,
  703                  Feature::UnifiedExec,
  704                  Feature::ShellZshFork,
  705                  Feature::UnifiedExecZshFork,
  706              ],
  707          );
  708          turn.unified_exec_shell_mode =
为什么相信这条结论?查看 3 处证据
小练习 4

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

M05 · CONTEXT

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

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

先用一个生活比喻

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

这套实现先回答了什么?

像有版本号的账本:读取者共享同一份快照,真要改时才复制,且每笔工具调用都要能对上回执。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
历史是带版本号的 Copy-on-Write 账本,不是随手拼接的消息数组codex-rs/core/src/context_manager/history.rs:38像有版本号的账本:读取者共享同一份快照,真要改时才复制,且每笔工具调用都要能对上回执。
上下文裁剪同时维护工具调用配对并计算多模态成本codex-rs/core/src/context_manager/history.rs:189剪历史不能只撕掉一页:工具问题和答案必须一起处理;图片、音频和加密内容也不能当作零体积。
本地压缩会自救:压缩请求自身超限时逐项删旧记录再重试codex-rs/core/src/compact.rs:240连“请帮我整理行李”这句话都塞不进去时,它会先扔掉最旧且成对的票据,直到能完成整理。
09
L1 · fact · codex-context-001

历史是带版本号的 Copy-on-Write 账本,不是随手拼接的消息数组

先看源码事实

ContextManager 用 Arc<Vec<ResponseItem>> 保存历史并维护 history_version、token info、reference context 和 world-state baseline;写入前过滤非 API item、截断并在取 prompt 时规范化。

翻译成白话

像有版本号的账本:读取者共享同一份快照,真要改时才复制,且每笔工具调用都要能对上回执。

为什么这对自研重要

适合并发读取和回滚,也让 compaction、fork 与增量世界状态有明确一致性边界。

固定提交源码摘录
   38  /// Transcript of thread history
   39  #[derive(Debug, Clone, Default)]
   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  }
为什么相信这条结论?查看 2 处证据
10
L1 · fact · codex-context-002

上下文裁剪同时维护工具调用配对并计算多模态成本

先看源码事实

remove_oldest_item 会同步移除对应 call/output 并清 world baseline;normalize 修复配对并过滤不支持的图像/音频,token 估算覆盖 reasoning、compaction、图像、音频和 encrypted content。

翻译成白话

剪历史不能只撕掉一页:工具问题和答案必须一起处理;图片、音频和加密内容也不能当作零体积。

为什么这对自研重要

上下文预算比纯文本字符计数可靠,降低 Provider 因协议不完整而拒绝请求的概率。

固定提交源码摘录
  189      pub(crate) fn remove_first_item(&mut self) {
  190          if !self.items.is_empty() {
  191              // Remove the oldest item (front of the list). Items are ordered from
  192              // oldest → newest, so index 0 is the first entry recorded.
  193              let items = Arc::make_mut(&mut self.items);
  194              let removed = items.remove(0);
  195              // If the removed item participates in a call/output pair, also remove
  196              // its corresponding counterpart to keep the invariants intact without
  197              // running a full normalization pass.
  198              normalize::remove_corresponding_for(items, &removed);
  199              self.world_state_baseline = None;
  200          }
  201      }
  202  
  203      pub(crate) fn replace(&mut self, items: Vec<ResponseItem>) {
  204          self.items = Arc::new(items);
  205          self.history_version = self.history_version.saturating_add(1);
  206          self.world_state_baseline = None;
  207      }
  208  
  209      /// Drop the last `num_turns` instruction turns from this history.
  210      ///
  211      /// Instruction turns are history messages that should behave like a new prompt boundary:
  212      /// ordinary user messages and structured assistant inter-agent instructions.
  213      ///
      … 24 lines omitted; exact range 189–248 …
  238          let mut cut_idx = if n_from_end >= user_positions.len() {
  239              first_instruction_turn_idx
  240          } else {
  241              user_positions[user_positions.len() - n_from_end]
  242          };
  243  
  244          cut_idx =
  245              self.trim_pre_turn_context_updates(&snapshot, first_instruction_turn_idx, cut_idx);
  246  
  247          self.replace(snapshot[..cut_idx].to_vec());
  248      }
为什么相信这条结论?查看 3 处证据
11
L1 · fact · codex-context-003

本地压缩会自救:压缩请求自身超限时逐项删旧记录再重试

先看源码事实

compact 使用模型生成摘要;若压缩调用本身超出窗口,就删除最老 item 及其配对继续尝试,成功后保留真实 user 消息、插入摘要前缀、推进窗口并重算 usage。

翻译成白话

连“请帮我整理行李”这句话都塞不进去时,它会先扔掉最旧且成对的票据,直到能完成整理。

为什么这对自研重要

长会话更耐用;源码也明确提示多次摘要会逐步降低准确性。

固定提交源码摘录
  240  async fn run_compact_task_inner_impl(
  241      sess: Arc<Session>,
  242      turn_context: Arc<TurnContext>,
  243      input: Vec<UserInput>,
  244      initial_context_injection: InitialContextInjection,
  245      compaction_metadata: CompactionTurnMetadata,
  246  ) -> CodexResult<String> {
  247      let compaction_item = TurnItem::ContextCompaction(ContextCompactionItem::new());
  248      sess.emit_turn_item_started(&turn_context, &compaction_item)
  249          .await;
  250      let initial_input_for_turn: ResponseInputItem = ResponseInputItem::from(input);
  251  
  252      let mut history = sess.clone_history().await;
  253      history.record_items(
  254          &[initial_input_for_turn.into()],
  255          turn_context.model_info.truncation_policy.into(),
  256      );
  257  
  258      let max_retries = turn_context.provider.info().stream_max_retries();
  259      let mut retries = 0;
  260      let mut client_session = sess.services.model_client.new_session();
  261      // Reuse one client session so turn-scoped state (sticky routing, websocket incremental
  262      // request tracking)
  263      // survives retries within this compact turn.
  264      let window_id = sess.current_window_id().await;
      … 43 lines omitted; exact range 240–318 …
  308              }
  309              Err(e) if matches!(e.details(), CodexErrorDetails::ContextWindowExceeded) => {
  310                  if turn_input_len > 1 {
  311                      // Trim from the beginning to preserve cache (prefix-based) and keep recent messages intact.
  312                      error!(
  313                          "Context window exceeded while compacting; removing oldest history item. Error: {e}"
  314                      );
  315                      history.remove_first_item();
  316                      retries = 0;
  317                      continue;
  318                  }
为什么相信这条结论?查看 2 处证据
小练习 5

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

M06 · SECURITY

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

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

先用一个生活比喻

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

这套实现先回答了什么?

一条轴决定要不要敲门,另一条轴决定进门后活动范围;“不用问”不等于“拥有整台机器”。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
审批策略把“何时问”与“允许做什么”分成两条轴codex-rs/protocol/src/protocol.rs:890一条轴决定要不要敲门,另一条轴决定进门后活动范围;“不用问”不等于“拥有整台机器”。
审批缺失默认中止,且可授予一次、本会话或规则/网络修订codex-rs/core/src/session/mod.rs:2295授权不是一个“永远允许”按钮;可以只放这次、放本会话、或把精确规则写进政策,没人回答则停下。
沙箱按平台变换真实进程:macOS Seatbelt、Linux seccomp/bwrap/landlock、Windows restricted tokencodex-rs/sandboxing/src/manager.rs:34不是在提示词里说“请别乱动”,而是在启动进程前给命令套上操作系统能执行的限制器。
自定义 permission profile 默认从受限文件系统和受限网络开始codex-rs/core/src/config/permissions.rs:203自定义政策从“什么都别给”开始逐项开门,而不是先全开再查漏补缺。
12
L2 · fact · codex-permission-001

审批策略把“何时问”与“允许做什么”分成两条轴

先看源码事实

AskForApproval 有 UnlessTrusted、OnRequest、Granular 和 Never;SandboxPolicy 独立描述 DangerFullAccess、ReadOnly、ExternalSandbox 与 WorkspaceWrite。

翻译成白话

一条轴决定要不要敲门,另一条轴决定进门后活动范围;“不用问”不等于“拥有整台机器”。

为什么这对自研重要

企业策略能单独收紧提权频率与文件/网络能力。

固定提交源码摘录
  890  /// Determines the conditions under which the user is consulted to approve
  891  /// running the command proposed by Codex.
  892  #[derive(
  893      Debug,
  894      Clone,
  895      Copy,
  896      Default,
  897      PartialEq,
  898      Eq,
  899      Hash,
  900      Serialize,
  901      Deserialize,
  902      Display,
  903      JsonSchema,
  904      TS,
  905  )]
  906  #[serde(rename_all = "kebab-case")]
  907  #[strum(serialize_all = "kebab-case")]
  908  pub enum AskForApproval {
  909      /// Under this policy, only "known safe" commands—as determined by
  910      /// `is_safe_command()`—that **only read files** are auto‑approved.
  911      /// Everything else will ask the user to approve.
  912      #[serde(rename = "untrusted")]
  913      #[strum(serialize = "untrusted")]
  914      UnlessTrusted,
      … 7 lines omitted; exact range 890–932 …
  922      ///
  923      /// When a field is `true`, commands in that category are allowed. When it
  924      /// is `false`, those requests are automatically rejected instead of shown
  925      /// to the user.
  926      #[strum(serialize = "granular")]
  927      Granular(GranularApprovalConfig),
  928  
  929      /// Never ask the user to approve commands. Failures are immediately returned
  930      /// to the model, and never escalated to the user for approval.
  931      Never,
  932  }
为什么相信这条结论?查看 2 处证据
13
L1 · fact · codex-permission-002

审批缺失默认中止,且可授予一次、本会话或规则/网络修订

先看源码事实

命令审批包含 command、cwd、reason、network amendment、execpolicy amendment、additional permissions 和 plugin provenance;等待通道消失时默认 Abort。ReviewDecision 支持单次、session、execpolicy/network amendment、拒绝、超时和 abort。

翻译成白话

授权不是一个“永远允许”按钮;可以只放这次、放本会话、或把精确规则写进政策,没人回答则停下。

为什么这对自研重要

失败关闭且授权可结构化沉淀,适合审计。

固定提交源码摘录
 2295      pub async fn request_command_approval(
 2296          &self,
 2297          turn_context: &TurnContext,
 2298          call_id: String,
 2299          approval_id: Option<String>,
 2300          environment_id: Option<String>,
 2301          command: Vec<String>,
 2302          cwd: AbsolutePathBuf,
 2303          reason: Option<String>,
 2304          network_approval_context: Option<NetworkApprovalContext>,
 2305          proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,
 2306          additional_permissions: Option<AdditionalPermissionProfile>,
 2307          available_decisions: Option<Vec<ReviewDecision>>,
 2308          plugin_attribution_override: Option<PluginCommandAttribution>,
 2309      ) -> ReviewDecision {
 2310          let _elicitation = self.services.elicitations.register();
 2311          //  command-level approvals use `call_id`.
 2312          // `approval_id` is only present for subcommand callbacks (execve intercept)
 2313          let effective_approval_id = approval_id.clone().unwrap_or_else(|| call_id.clone());
 2314          // Add the tx_approve callback to the map before sending the request.
 2315          let (tx_approve, rx_approve) = oneshot::channel();
 2316          let prev_entry = {
 2317              let mut active = self.active_turn.lock().await;
 2318              match active.as_mut() {
 2319                  Some(at) => {
      … 46 lines omitted; exact range 2295–2376 …
 2366              cwd,
 2367              reason,
 2368              network_approval_context,
 2369              proposed_execpolicy_amendment,
 2370              proposed_network_policy_amendments,
 2371              additional_permissions,
 2372              available_decisions: Some(available_decisions),
 2373              parsed_cmd,
 2374          });
 2375          self.send_event(turn_context, event).await;
 2376          rx_approve.await.unwrap_or(ReviewDecision::Abort)
为什么相信这条结论?查看 2 处证据
14
L1 · fact · codex-sandbox-001

沙箱按平台变换真实进程:macOS Seatbelt、Linux seccomp/bwrap/landlock、Windows restricted token

先看源码事实

SandboxManager 将抽象策略映射到平台后端;Auto 只在策略要求时选沙箱,Require/Forbid 明确覆盖选择,随后把命令变换为对应 launcher。

翻译成白话

不是在提示词里说“请别乱动”,而是在启动进程前给命令套上操作系统能执行的限制器。

为什么这对自研重要

强度取决于平台与后端可用性;报告不把 unsupported 平台推断成同等隔离。

固定提交源码摘录
   34  #[derive(Clone, Copy, Debug, PartialEq, Eq)]
   35  pub enum SandboxType {
   36      None,
   37      MacosSeatbelt,
   38      LinuxSeccomp,
   39      WindowsRestrictedToken,
   40  }
   41  
   42  impl SandboxType {
   43      pub fn as_metric_tag(self) -> &'static str {
   44          match self {
   45              SandboxType::None => "none",
   46              SandboxType::MacosSeatbelt => "seatbelt",
   47              SandboxType::LinuxSeccomp => "seccomp",
   48              SandboxType::WindowsRestrictedToken => "windows_sandbox",
   49          }
   50      }
   51  }
   52  
   53  #[derive(Clone, Copy, Debug, PartialEq, Eq)]
   54  pub enum SandboxablePreference {
   55      Auto,
   56      Require,
   57      Forbid,
   58  }
      … 4 lines omitted; exact range 34–73 …
   63      } else if cfg!(target_os = "linux") {
   64          Some(SandboxType::LinuxSeccomp)
   65      } else if cfg!(target_os = "windows") {
   66          if windows_sandbox_enabled {
   67              Some(SandboxType::WindowsRestrictedToken)
   68          } else {
   69              None
   70          }
   71      } else {
   72          None
   73      }
为什么相信这条结论?查看 2 处证据
15
L1 · fact · codex-sandbox-002

自定义 permission profile 默认从受限文件系统和受限网络开始

先看源码事实

custom profile 编译器先建立 restricted filesystem 与 restricted network,再应用条目;网络只有显式 true 才启用,WorkspaceWrite 默认也关闭网络,并保护 .git hooks 等元数据子路径。

翻译成白话

自定义政策从“什么都别给”开始逐项开门,而不是先全开再查漏补缺。

为什么这对自研重要

默认拒绝更适合作为组织级安全基线。

固定提交源码摘录
  203  fn extensible_builtin_parent_profile(profile_name: &str) -> Option<PermissionProfileToml> {
  204      let file_system = match profile_name {
  205          BUILT_IN_READ_ONLY_PROFILE => FileSystemSandboxPolicy::read_only(),
  206          BUILT_IN_WORKSPACE_PROFILE => FileSystemSandboxPolicy::workspace_write(
  207              &[],
  208              /*exclude_tmpdir_env_var*/ false,
  209              /*exclude_slash_tmp*/ false,
  210          ),
  211          _ => return None,
  212      };
  213      Some(permission_profile_toml_from_file_system_policy(file_system))
为什么相信这条结论?查看 3 处证据
小练习 6

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

M07 · ECOSYSTEM

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

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

先用一个生活比喻

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

这套实现先回答了什么?

它不是每轮临时扫一遍外接工具,而是维护一套有版本、有健康状态、有认证身份的连接池。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
MCP 是带复用、认证、required gate 和 catalog revision 的运行时codex-rs/codex-mcp/src/connection_manager.rs:66它不是每轮临时扫一遍外接工具,而是维护一套有版本、有健康状态、有认证身份的连接池。
模型只能看到显式可见且能绑定到同一目录版本的 MCP 工具codex-rs/codex-mcp/src/connection_manager/tool_catalog.rs:34展示给模型的工具名和真正执行它的连接必须来自同一版目录,不能拿新版菜单去点旧版厨房。
AGENTS.md 从项目根向 cwd 分层合并,局部 override 优先且有总字节预算codex-rs/core/src/agents_md.rs:1公司规章先读,走进子目录后再叠加本地规章;同一层的 override 像贴在门上的最新通知。
Skills、plugins 和 extensions 都在初始上下文构建期受预算与来源控制codex-rs/core/src/session/mod.rs:3336扩展不是把所有说明一股脑塞进提示词,而是先做预算,再按可信来源和消息层级装配。
16
L1 · fact · codex-mcp-001

MCP 是带复用、认证、required gate 和 catalog revision 的运行时

先看源码事实

McpConnectionSet 同时管理 server、required servers、工具目录版本、Codex Apps cache、plugin provenance、工具命名前缀和 elicitation;连接配置/OAuth 凭据一致且存活时才复用。

翻译成白话

它不是每轮临时扫一遍外接工具,而是维护一套有版本、有健康状态、有认证身份的连接池。

为什么这对自研重要

工具清单可稳定捕获到 step snapshot,连接变化不会悄悄污染正在执行的一步。

固定提交源码摘录
   66  pub(crate) struct McpServerConnection {
   67      identity: Option<McpServerConnectionIdentity>,
   68      client: AsyncManagedClient,
   69  }
   70  
   71  impl McpServerConnection {
   72      async fn reusable_client(
   73          &self,
   74          desired: &McpServerConnectionIdentity,
   75      ) -> Option<ManagedClient> {
   76          let current = self.identity.as_ref()?;
   77          if !current.has_same_connection_config(desired) {
   78              return None;
   79          }
   80          if !self.client.startup_complete.load(Ordering::Acquire) {
   81              return None;
   82          }
   83          let client = self.client.client().await.ok()?;
   84          if client.client.is_closed().await {
   85              return None;
   86          }
   87          let Ok(desired_credentials) = desired.oauth_credentials() else {
   88              return Some(client);
   89          };
   90          let reusable = match client.client.managed_oauth_credentials().await {
      … 16 lines omitted; exact range 66–117 …
  107      fn cancel_startup(&self) {
  108          if !self.client.startup_complete.load(Ordering::Acquire) {
  109              self.client.cancel_token.cancel();
  110          }
  111      }
  112  }
  113  
  114  impl Drop for McpServerConnection {
  115      fn drop(&mut self) {
  116          self.client.cancel_token.cancel();
  117      }
为什么相信这条结论?查看 2 处证据
17
L1 · fact · codex-mcp-002

模型只能看到显式可见且能绑定到同一目录版本的 MCP 工具

先看源码事实

tool_catalog 过滤 UI visibility,不含 metadata 时默认可见;capture_binding 在读锁下捕获 revision、ready client、过滤后的 tools 和 prepared calls,无法精确绑定的工具被省略。

翻译成白话

展示给模型的工具名和真正执行它的连接必须来自同一版目录,不能拿新版菜单去点旧版厨房。

为什么这对自研重要

减少热刷新导致的工具错配和 TOCTOU 风险。

固定提交源码摘录
   34  /// Returns whether a tool may be included in model-facing tool declarations.
   35  ///
   36  /// Tools without visibility metadata remain visible. Tools with visibility
   37  /// metadata are hidden unless they explicitly include `model`.
   38  ///
   39  /// <https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx#resource-discovery>
   40  pub fn tool_is_model_visible(tool: &ToolInfo) -> bool {
   41      let Some(visibility) = tool
   42          .tool
   43          .meta
   44          .as_deref()
   45          .and_then(|meta| meta.get(MCP_UI_META_KEY))
   46          .and_then(serde_json::Value::as_object)
   47          .and_then(|ui| ui.get(MCP_UI_VISIBILITY_META_KEY))
   48          .and_then(serde_json::Value::as_array)
   49      else {
   50          return true;
   51      };
   52      visibility
   53          .iter()
   54          .any(|target| target.as_str() == Some(MCP_UI_MODEL_VISIBILITY))
   55  }
为什么相信这条结论?查看 2 处证据
18
L1 · fact · codex-instructions-001

AGENTS.md 从项目根向 cwd 分层合并,局部 override 优先且有总字节预算

先看源码事实

发现逻辑以 project root marker 截断上行范围,从根到 cwd 每层最多选 AGENTS.override.md、AGENTS.md 或 fallback 文件之一;按总预算读取,超出部分截断。

翻译成白话

公司规章先读,走进子目录后再叠加本地规章;同一层的 override 像贴在门上的最新通知。

为什么这对自研重要

目录作用域明确,但超预算时越靠后的深层说明更可能被截断,需要可视化告警。

固定提交源码摘录
    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.
为什么相信这条结论?查看 3 处证据
19
L1 · fact · codex-extension-001

Skills、plugins 和 extensions 都在初始上下文构建期受预算与来源控制

先看源码事实

build_initial_context 按 context window 计算 skill metadata budget,超限发 warning;plugins 先按配置加载,再生成推荐插件上下文;extension contributors 可注入 developer、contextual user 或独立 developer fragment。

翻译成白话

扩展不是把所有说明一股脑塞进提示词,而是先做预算,再按可信来源和消息层级装配。

为什么这对自研重要

可扩展性强且照顾 prompt cache;插件推荐与已安装插件被区分。

固定提交源码摘录
 3336      pub(crate) async fn build_initial_context_with_world_state(
 3337          &self,
 3338          turn_context: &TurnContext,
 3339          world_state: &WorldState,
 3340      ) -> Vec<ResponseItem> {
 3341          let mut developer_sections = Vec::<String>::with_capacity(8);
 3342          let mut contextual_user_sections = Vec::<String>::with_capacity(2);
 3343          let mut separate_developer_sections = Vec::<String>::new();
 3344          let (session_source, auto_compact_window_ids) = {
 3345              let state = self.state.lock().await;
 3346              (
 3347                  state.session_configuration.session_source.clone(),
 3348                  state.auto_compact_window_ids(),
 3349              )
 3350          };
 3351          let separate_guardian_developer_message =
 3352              crate::guardian::is_guardian_reviewer_source(&session_source);
 3353          // Keep the guardian policy prompt out of the aggregated developer bundle so it
 3354          // stays isolated as its own top-level developer message for guardian subagents.
 3355          if !separate_guardian_developer_message
 3356              && let Some(developer_instructions) = turn_context.developer_instructions.as_deref()
 3357              && !developer_instructions.is_empty()
 3358          {
 3359              developer_sections.push(developer_instructions.to_string());
 3360          }
      … 26 lines omitted; exact range 3336–3397 …
 3387                          msg: EventMsg::Warning(WarningEvent {
 3388                              message: warning_message,
 3389                          }),
 3390                      })
 3391                      .await;
 3392                  }
 3393                  if !host_catalog_in_world_state {
 3394                      developer_sections.push(skills_instructions.render());
 3395                  }
 3396              }
 3397          }
为什么相信这条结论?查看 2 处证据
20
L1 · fact · codex-hooks-001

Hooks 覆盖 session、prompt、permission、tool、compact、stop 与 subagent 生命周期

先看源码事实

runtime 为 session/subagent start、PreToolUse、PermissionRequest、PostToolUse、UserPromptSubmit 等构造稳定 payload;hook 可阻断、改写输入或注入额外上下文,完成事件同时进入指标和 analytics。

翻译成白话

钩子既能当门卫,也能当翻译器和旁路记录员;每次执行都有开始、结束和耗时记录。

为什么这对自研重要

组织可插入治理逻辑,但 hook 本身应被当作有权限的代码并受项目信任策略保护。

固定提交源码摘录
  103  pub(crate) async fn run_pending_session_start_hooks(
  104      sess: &Arc<Session>,
  105      turn_context: &Arc<TurnContext>,
  106  ) -> bool {
  107      while let Some(session_start_source) = sess.take_pending_session_start_source().await {
  108          // Pending session-start hooks are reused to dispatch thread-spawn subagent
  109          // starts. Other subagent sessions are internal/system work and do not run
  110          // start hooks.
  111          let target = match &turn_context.session_source {
  112              SessionSource::SubAgent(SubAgentSource::ThreadSpawn { agent_role, .. })
  113                  if matches!(
  114                      session_start_source,
  115                      codex_hooks::SessionStartSource::Startup
  116                  ) =>
  117              {
  118                  let context = subagent_hook_context(sess, agent_role);
  119                  StartHookTarget::SubagentStart {
  120                      turn_id: turn_context.sub_id.clone(),
  121                      agent_id: context.agent_id,
  122                      agent_type: context.agent_type,
  123                  }
  124              }
  125              SessionSource::SubAgent(_) => return false,
  126              _ => StartHookTarget::SessionStart {
  127                  source: session_start_source,
      … 82 lines omitted; exact range 103–220 …
  210      {
  211          PreToolUseHookResult::Blocked(format!(
  212              "Command blocked by PreToolUse hook: {reason}. Command: {command}"
  213          ))
  214      } else {
  215          PreToolUseHookResult::Blocked(format!(
  216              "Tool call blocked by PreToolUse hook: {reason}. Tool: {}",
  217              tool_name.name()
  218          ))
  219      }
  220  }
为什么相信这条结论?查看 3 处证据
小练习 7

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

M08 · COLLABORATION

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

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

先用一个生活比喻

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

这套实现先回答了什么?

每个子 Agent 都有自己的会话账本,但兄弟们共用一张组织架构表、并发配额和总预算。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
多 Agent 是共享控制面的线程树,不是主循环里的递归函数codex-rs/core/src/agent/control.rs:70每个子 Agent 都有自己的会话账本,但兄弟们共用一张组织架构表、并发配额和总预算。
fork 可选全历史、最近 N 轮或空白;消息可只入队也可触发 turncodex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs:39派工时可把整本案卷、最近几页或一张白纸交给下属;便签可以只塞进邮箱,也可以按门铃让他立即处理。
驻留上限与同时执行上限分离,V2 子 Agent 才受执行 limitercodex-rs/core/src/config/mod.rs:1547可以让许多子会话留在通讯录里,但只有有限几个同时开工;已经在干活的人收到补充便签不会再算一个工位。
21
L1 · fact · codex-agent-001

多 Agent 是共享控制面的线程树,不是主循环里的递归函数

先看源码事实

每棵 root thread 树共享 AgentControl、registry、V2 residency、execution limiter 和 rollout budget;spawn 通过 ThreadManager 创建独立 thread,子线程继承环境与 exec policy。

翻译成白话

每个子 Agent 都有自己的会话账本,但兄弟们共用一张组织架构表、并发配额和总预算。

为什么这对自研重要

隔离了对话状态,同时能做全树限流、恢复和协作观测。

固定提交源码摘录
   70      pub(crate) fork_parent_spawn_call_id: Option<String>,
   71      pub(crate) fork_mode: Option<SpawnAgentForkMode>,
   72      pub(crate) parent_thread_id: Option<ThreadId>,
   73      pub(crate) environments: Option<Vec<TurnEnvironmentSelection>>,
   74  }
   75  
   76  #[derive(Clone, Debug)]
   77  pub(crate) struct LiveAgent {
   78      pub(crate) thread_id: ThreadId,
   79      pub(crate) metadata: AgentMetadata,
   80      pub(crate) status: AgentStatus,
   81  }
   82  
   83  #[derive(Clone, Debug, Serialize, PartialEq, Eq)]
   84  pub(crate) struct ListedAgent {
   85      pub(crate) agent_name: String,
   86      pub(crate) agent_status: AgentStatus,
   87  }
   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`.
      … 6 lines omitted; exact range 70–111 …
  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 {
为什么相信这条结论?查看 2 处证据
22
L1 · fact · codex-agent-002

fork 可选全历史、最近 N 轮或空白;消息可只入队也可触发 turn

先看源码事实

spawn_agent V2 解析 fork_turns none/all/正整数,默认 all;send_message 只投递队列,followup 类通信可触发空闲 agent 开新 turn,wait 同时监听 mailbox、steer 和 timeout。

翻译成白话

派工时可把整本案卷、最近几页或一张白纸交给下属;便签可以只塞进邮箱,也可以按门铃让他立即处理。

为什么这对自研重要

上下文成本可控,并避免普通消息总是打断正在执行的子任务。

固定提交源码摘录
   39  async fn handle_spawn_agent(
   40      invocation: ToolInvocation,
   41  ) -> Result<SpawnAgentResult, FunctionCallError> {
   42      let ToolInvocation {
   43          session,
   44          turn,
   45          payload,
   46          call_id,
   47          ..
   48      } = invocation;
   49      let arguments = function_arguments(payload)?;
   50      let args: SpawnAgentArgs = parse_arguments(&arguments)?;
   51      let fork_mode = args.fork_mode()?;
   52      let role_name = args
   53          .agent_type
   54          .as_deref()
   55          .map(str::trim)
   56          .filter(|role| !role.is_empty());
   57  
   58      let message = message_content(args.message)?;
   59      let session_source = turn.session_source.clone();
   60      let child_depth = next_thread_spawn_depth(&session_source);
   61      let mut config =
   62          build_agent_spawn_config(&session.get_base_instructions().await, turn.as_ref())?;
   63      if let Some(service_tier) = args.service_tier.as_ref() {
      … 91 lines omitted; exact range 39–165 …
  155  
  156      let hide_agent_metadata = turn.config.multi_agent_v2.hide_spawn_agent_metadata;
  157      if hide_agent_metadata {
  158          Ok(SpawnAgentResult::HiddenMetadata { task_name })
  159      } else {
  160          Ok(SpawnAgentResult::WithNickname {
  161              task_name,
  162              nickname,
  163          })
  164      }
  165  }
为什么相信这条结论?查看 3 处证据
23
L1 · fact · codex-agent-003

驻留上限与同时执行上限分离,V2 子 Agent 才受执行 limiter

先看源码事实

V2 的 effective_agent_max_threads 从 max_concurrent_threads_per_session 减去 root;AgentExecutionLimiter 只在 V2 SubAgent 启动新 turn 时检查 active 数,已有 active turn 的消息不重复占槽,guard drop 释放名额。

翻译成白话

可以让许多子会话留在通讯录里,但只有有限几个同时开工;已经在干活的人收到补充便签不会再算一个工位。

为什么这对自研重要

资源治理比单一 agent count 精细,恢复/驻留和运行并发可以独立调优。

固定提交源码摘录
 1547      pub(crate) fn effective_agent_max_threads(
 1548          &self,
 1549          multi_agent_version: MultiAgentVersion,
 1550      ) -> Option<usize> {
 1551          match multi_agent_version {
 1552              MultiAgentVersion::V2 => Some(
 1553                  self.multi_agent_v2
 1554                      .max_concurrent_threads_per_session
 1555                      .saturating_sub(1),
 1556              ),
 1557              MultiAgentVersion::Disabled | MultiAgentVersion::V1 => {
 1558                  self.agent_max_threads.or(DEFAULT_AGENT_MAX_THREADS)
 1559              }
 1560          }
为什么相信这条结论?查看 2 处证据
小练习 8

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

M09 · STATE

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

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

先用一个生活比喻

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

这套实现先回答了什么?

先把每一步写成可重放流水账,后台书记员负责落盘;书记员一旦坏掉,后续调用会记得这次故障而不是假装成功。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
会话采用 JSONL rollout 作为事件事实源,后台 writer 支持 persist、flush 与失败记忆codex-rs/rollout/src/recorder.rs:93先把每一步写成可重放流水账,后台书记员负责落盘;书记员一旦坏掉,后续调用会记得这次故障而不是假装成功。
SQLite 是可查询镜像,并把状态、日志、目标和记忆拆库降低锁竞争codex-rs/state/src/lib.rs:1流水账负责忠实记录,SQLite 像索引卡片箱,负责快速搜索;不同类型卡片分柜,避免大家抢同一把锁。
观测横跨模型、工具、hooks、MCP、rollout 与 SQLite,不只是一份 CLI 日志codex-rs/core/src/client.rs:74既能看模型这段花了多久,也能拆出排队、工具处理、钩子、连接器和数据库的时间与故障。
24
L1 · fact · codex-persistence-001

会话采用 JSONL rollout 作为事件事实源,后台 writer 支持 persist、flush 与失败记忆

先看源码事实

RolloutRecorder 将创建/恢复参数与 RolloutItem 送入后台命令队列;writer task 保存 terminal failure,公开 Persist、Flush、Shutdown acknowledgement。

翻译成白话

先把每一步写成可重放流水账,后台书记员负责落盘;书记员一旦坏掉,后续调用会记得这次故障而不是假装成功。

为什么这对自研重要

支持 resume/fork/审计,也给崩溃恢复和一致性测试提供稳定基线。

固定提交源码摘录
   93  pub enum RolloutRecorderParams {
   94      Create {
   95          session_id: SessionId,
   96          conversation_id: ThreadId,
   97          forked_from_id: Option<ThreadId>,
   98          parent_thread_id: Option<ThreadId>,
   99          source: Box<SessionSource>,
  100          thread_source: Option<ThreadSource>,
  101          originator: String,
  102          base_instructions: BaseInstructions,
  103          dynamic_tools: Vec<DynamicToolSpec>,
  104          selected_capability_roots: Vec<SelectedCapabilityRoot>,
  105          multi_agent_version: Option<MultiAgentVersion>,
  106          history_mode: ThreadHistoryMode,
  107          history_base: Option<HistoryPosition>,
  108          subagent_history_start_ordinal: Option<u64>,
  109          initial_window_id: Option<String>,
  110      },
  111      Resume {
  112          path: PathBuf,
  113      },
  114  }
  115  
  116  enum RolloutCmd {
  117      AddItems(Vec<RolloutItem>),
      … 43 lines omitted; exact range 93–171 …
  161      }
  162  
  163      /// Return the terminal writer-task failure, if the task exited with an error.
  164      fn terminal_failure(&self) -> Option<IoError> {
  165          let guard = self
  166              .terminal_failure
  167              .lock()
  168              .unwrap_or_else(std::sync::PoisonError::into_inner);
  169          guard.as_ref().map(|err| clone_io_error(err.as_ref()))
  170      }
  171  }
为什么相信这条结论?查看 2 处证据
25
L1 · fact · codex-persistence-002

SQLite 是可查询镜像,并把状态、日志、目标和记忆拆库降低锁竞争

先看源码事实

state crate 从 JSONL rollout 提取元数据镜像到 SQLite;StateRuntime 分别打开 state、logs、goals、memories 数据库,并明确把日志和分页历史分离以降低锁竞争。

翻译成白话

流水账负责忠实记录,SQLite 像索引卡片箱,负责快速搜索;不同类型卡片分柜,避免大家抢同一把锁。

为什么这对自研重要

兼顾可恢复事件源与 UI 查询性能,代价是要处理 backfill/reconciliation。

固定提交源码摘录
    1  //! SQLite-backed state for rollout metadata.
    2  //!
    3  //! This crate is intentionally small and focused: it extracts rollout metadata
    4  //! from JSONL rollouts and mirrors it into a local SQLite database. Backfill
    5  //! orchestration and rollout scanning live in `codex-core`.
    6  
    7  const _: () = assert!(
    8      libsqlite3_sys::SQLITE_VERSION_NUMBER >= 3_051_003,
    9      "bundled SQLite must include the WAL-reset corruption fix",
   10  );
为什么相信这条结论?查看 3 处证据
26
L1 · fact · codex-observe-001

观测横跨模型、工具、hooks、MCP、rollout 与 SQLite,不只是一份 CLI 日志

先看源码事实

模型 client 注入 SessionTelemetry 与 rollout inference trace;工具 runtime 记录 dispatch/handler/total timing;hooks 发开始/完成事件与 duration;MCP 记录 tool list/refresh;state 定义初始化、错误、回填与 fallback 指标。

翻译成白话

既能看模型这段花了多久,也能拆出排队、工具处理、钩子、连接器和数据库的时间与故障。

为什么这对自研重要

适合做端到端性能归因和故障回放,但需要明确遥测数据的隐私与导出策略。

固定提交源码摘录
   74  use codex_otel::SessionTelemetry;
   75  use codex_otel::current_span_w3c_trace_context;
   76  use codex_protocol::auth::AuthMode;
   77  
   78  use codex_protocol::ThreadId;
   79  use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
   80  use codex_protocol::config_types::Verbosity as VerbosityConfig;
   81  use codex_protocol::models::ContentItem;
   82  use codex_protocol::models::ResponseItem;
   83  use codex_protocol::openai_models::ModelInfo;
   84  use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
   85  use codex_protocol::protocol::InternalSessionSource;
   86  use codex_protocol::protocol::SessionSource;
   87  use codex_protocol::protocol::W3cTraceContext;
   88  use codex_rollout_trace::CompactionTraceContext;
   89  use codex_rollout_trace::InferenceTraceAttempt;
   90  use codex_rollout_trace::InferenceTraceContext;
   91  use codex_tools::create_tools_json_for_responses_api;
为什么相信这条结论?查看 3 处证据
小练习 9

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

M10 · ENGINEERING

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

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

先用一个生活比喻

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

这套实现先回答了什么?

它不是一段 CLI 脚本,而是一套带协议、TUI、app server、状态库、插件和跨平台后端的系统工程。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
这是大型、多前端、测试密集的 Rust Harness,许可证为 Apache-2.0LICENSE:1它不是一段 CLI 脚本,而是一套带协议、TUI、app server、状态库、插件和跨平台后端的系统工程。
27
L3 · fact · codex-maturity-001

这是大型、多前端、测试密集的 Rust Harness,许可证为 Apache-2.0

先看源码事实

固定快照含约 2,780 个 Rust 文件、645 个 TypeScript 文件;源码树中约 487 个 test 命名文件、1,170 个含 Rust test attribute 的文件,根许可证为 Apache License 2.0。

翻译成白话

它不是一段 CLI 脚本,而是一套带协议、TUI、app server、状态库、插件和跨平台后端的系统工程。

为什么这对自研重要

可借鉴性高,但直接复刻意味着承担很大的平台与回归测试成本。

边界与风险
  • 文件数与测试文件数是对该固定 checkout 的机械统计,不等于测试覆盖率。
固定提交源码摘录
    1                                   Apache License
    2                             Version 2.0, January 2004
    3                          http://www.apache.org/licenses/
    4  
    5  TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    6  
    7  1.  Definitions.
    8  
    9      "License" shall mean the terms and conditions for use, reproduction,
   10      and distribution as defined by Sections 1 through 9 of this document.
   11  
   12      "Licensor" shall mean the copyright owner or entity authorized by
   13      the copyright owner that is granting the License.
   14  
   15      "Legal Entity" shall mean the union of the acting entity and all
   16      other entities that control, are controlled by, or are under common
   17      control with that entity. For the purposes of this definition,
   18      "control" means (i) the power, direct or indirect, to cause the
   19      direction or management of such entity, whether by contract or
   20      otherwise, or (ii) ownership of fifty percent (50%) or more of the
   21      outstanding shares, or (iii) beneficial ownership of such entity.
   22  
   23      "You" (or "Your") shall mean an individual or Legal Entity
   24      exercising permissions granted by this License.
   25  
   26      "Source" form shall mean the preferred form for making modifications,
   27      including but not limited to software source code, documentation
   28      source, and configuration files.
为什么相信这条结论?查看 3 处证据
小练习 10

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

M11 · PRACTICE

把读懂变成会判断

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

Q1

每个 turn 由多个 step 组成,step 内共享一次不可漂移的上下文快照

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

参考答案

配置热更新只能在安全边界生效,换来 prompt cache 和工具调用的确定性。

证据:codex-rs/core/src/session/turn.rs:153
Q2

流式采样与工具 Future 同时推进,并可被新消息抢占

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

参考答案

响应更快,但对调用顺序、取消、配对和幂等要求很高。

证据:codex-rs/core/src/session/turn.rs:2034
Q3

重试预算属于 turn-scoped client session,窗口超限不当作普通网络错误重试

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

参考答案

把语义恢复与传输恢复分开,避免无效重试放大费用。

证据:codex-rs/core/src/session/turn.rs:1176
Q4

模型协议只保留 Responses API,但 Provider 端点与认证可扩展

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

参考答案

兼容面更一致,第三方 Provider 必须实现 Responses 语义而非只暴露 chat/completions。

证据:codex-rs/model-provider-info/src/lib.rs:54
Q5

传输层同时支持 SSE 与可复用 WebSocket,并带 turn 粘性状态

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

参考答案

交互延迟低,但 Provider 必须准确声明 WebSocket 能力,AWS 认证当前明确不能与 WebSocket 同开。

证据:codex-rs/core/src/client.rs:1
APPENDIX · SOURCE INDEX

本课读过的实现文件

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

  1. 01codex-rs/core/src/session/turn.rsL153–274, L275–455, L2034–2168, L2169–2265, L1176–1273
  2. 02codex-rs/core/src/client.rsL1–24, L1–24, L74–91
  3. 03codex-rs/model-provider-info/src/lib.rsL54–84, L86–144, L156–186
  4. 04codex-rs/core/src/context_manager/history.rsL38–60, L123–146, L189–248, L295–386, L493–558
  5. 05codex-rs/core/src/compact.rsL240–318, L319–392
  6. 06codex-rs/core/src/tools/registry.rsL48–149, L151–188
  7. 07codex-rs/core/src/tools/parallel.rsL74–145, L146–202
  8. 08codex-rs/core/src/tools/spec_plan_tests.rsL637–708, L637–708
  9. 09codex-rs/core/src/tools/spec_plan.rsL310–328, L917–936
  10. 10codex-rs/protocol/src/protocol.rsL890–932, L995–1043, L4094–4135
  11. 11codex-rs/core/src/session/mod.rsL2295–2376, L3336–3397, L3398–3440
  12. 12codex-rs/sandboxing/src/manager.rsL34–73, L280–367
  13. 13codex-rs/core/src/config/permissions.rsL203–213, L347–407, L507–520
  14. 14codex-rs/codex-mcp/src/connection_manager.rsL66–117, L143–199
  15. 15codex-rs/codex-mcp/src/connection_manager/tool_catalog.rsL34–55, L127–230
  16. 16codex-rs/core/src/agents_md.rsL1–16, L89–150, L207–247
  17. 17codex-rs/core/src/hook_runtime.rsL103–220, L222–285, L649–705, L649–691
  18. 18codex-rs/core/src/agent/control.rsL70–111
  19. 19codex-rs/core/src/agent/control/spawn.rsL365–445
  20. 20codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rsL39–165, L173–220
  21. 21codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rsL37–118
  22. 22codex-rs/core/src/config/mod.rsL1547–1560
  23. 23codex-rs/core/src/agent/control/execution.rsL14–118
  24. 24codex-rs/rollout/src/recorder.rsL93–171, L177–290
  25. 25codex-rs/state/src/lib.rsL1–10, L81–95
  26. 26codex-rs/state/src/runtime.rsL71–125, L126–170
  27. 27LICENSEL1–28
  28. 28codex-rs/core/src/agent/control_tests.rsL2047–2174
下一步

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

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

查看 OpenAI Codex 报告 ↗