Codex 系统手册

Rollout trace 记录、schema 与 replay reducers

stage-20.424 个文件

这一阶段像系统的“行车记录仪”和“案卷整理员”,属于幕后支撑。运行时,配置先说明项目和数据放哪;bundle 定好一包轨迹在磁盘上的样子;writer 把原始事件和大块内容不断写下。thread、inference、tool、code cell、compaction、MCP 等模块分别盯住线程、模型请求、工具、代码执行、历史压缩和外部调用。事后 reducer 再把流水账按顺序整理成可回放的对话、线程、工具和终端时间线。

本阶段的文件24

crate 接口与 schema

这些文件定义公共 crate 接口,以及记录和重放都依赖的原始、bundle 和精简数据 schema。

rollout-trace/src/bundle.rs源码 ↗
data_modeltrace bundle creation and loading

一次 rollout 的追踪数据不是单独一个文件,而是一小包文件:有清单 manifest.json,有原始事件日志 trace.jsonl,还有存放额外内容的 payloads 文件夹。这个文件把这些名字统一定下来,避免不同地方各写各的,最后互相找不到文件。核心类型是 TraceBundleManifest,也就是轨迹包清单。它记录版本号、trace_id、rollout_id、根线程 ID、开始时间,以及原始日志和 payload 目录的位置。这里的“序列化/反序列化”指把这个结构变成 JSON,或者从 JSON 读回来,所以它可以直接写进 manifest.json。特别重要的是 root_thread_id:注释里说明,回放时不能随便编一个根线程,因为后面整理出来的对象都要挂回这棵线程树。

函数细节1
TraceBundleManifest::new33–48 ↗
fn new(
        trace_id: String,
        rollout_id: String,
        root_thread_id: AgentThreadId,
        started_at_unix_ms: i64,
    ) -> Self

作用:这个函数用来创建一份标准格式的轨迹包清单。调用者只需要给出这次追踪的几个关键信息,它会自动填好固定的版本号、日志文件名和 payload 文件夹名。

数据流:进去的是 trace_id、rollout_id、root_thread_id 和开始时间 started_at_unix_ms → 函数把它们放进 TraceBundleManifest,并补上 schema_version、trace.jsonl、payloads 这些标准值 → 出来的是一份完整的清单对象,之后可以写成 manifest.json;它本身不读写磁盘,只负责组装数据。

调用关系:它在创建轨迹包时被 create 调用,相当于 create 准备打包数据前先让它填一张标准清单。它不再把活交给别的函数,只是把统一规则集中在这里,避免创建流程到处硬编码文件名和版本号。

调用图:被 1 处调用(create)。

rollout-trace/src/lib.rs源码 ↗
othercross-cutting

这个库用来记录和回放 Codex 运行过程中的“轨迹包”。可以把它想成飞机的黑匣子:运行时发生了哪些请求、工具调用、模型推理、线程事件、压缩检查点等,都会按统一格式记下来,之后还能重放并整理成更好读的状态。这个文件本身不写具体记录逻辑,而是像前台接待台一样,把 bundle、writer、reducer、thread、tool_dispatch 等内部房间连接起来,并把外部真正该用的名字公开出去。这样做的好处是,别的代码不用知道每个东西藏在哪个文件里,只要依赖这个库的公开入口就行。它还通过注释说明了边界:高频运行路径只应该用这里提供的小型写入接口,而更复杂的语义回放和展示逻辑留在 codex-core 外面,避免核心代码变得臃肿。

rollout-trace/src/model/conversation.rs源码 ↗
data_modelcross-cutting

这个文件本身不做计算,主要是在规定数据格式。可以把它理解成一套“对话档案表格”。系统运行时,模型请求、模型回复、用户输入、工具调用、代码执行、历史压缩等事情都会产生内容;如果不统一格式,后面做调试、回放、展示时就很难知道一段话从哪来、给谁看过、和哪次请求有关。这里的核心是 ConversationItem,表示时间线里的一条内容或一个边界标记。它记录角色,比如用户、助手、工具;记录频道,比如分析、最终回答;记录正文,正文可以是文字、摘要、代码、原始大 payload 的引用等。ProducerRef 说明这条内容是谁“造出来”的,比如用户输入、一次模型推理、工具调用。InferenceCall 则记录一次发给模型的请求和返回,包括用了哪个模型、输入输出包含哪些对话项、token 用量以及原始请求响应的引用。整体上,它给系统提供了一种稳定的“账本”,方便之后查清每一步发生了什么。

rollout-trace/src/model/mod.rs源码 ↗
data_modelcross-cutting

可以把这个文件理解成一张“运行事故现场的总档案表”。一次 Codex rollout,也就是一次完整运行,会产生很多东西:用户和模型的对话、每次发给模型的请求、模型写出的代码单元、工具调用、终端会话、历史压缩记录,还有原始 JSON 数据。这个文件里的 RolloutTrace 就是最外层的大盒子,把这些内容按 ID 分类放好。这里大量使用 BTreeMap,可以理解成“按编号整理好的文件柜”,每个编号对应一份具体记录。文件还定义了一批 ID 类型,比如线程 ID、工具调用 ID、终端操作 ID 等,虽然底层都是字符串,但名字不同能提醒开发者:这些编号代表的东西不一样,不能随便混用。它也把 conversation、runtime、session 三个子模块重新导出,让外部只需要引用这个 model 模块,就能拿到整套运行记录模型。

函数细节1
RolloutTrace::new94–122 ↗
fn new(
        schema_version: u32,
        trace_id: String,
        rollout_id: String,
        root_thread_id: AgentThreadId,
        started_at_unix_ms: i64,
    ) -> Self

作用:这个函数创建一份空的运行记录,像先拿出一本空档案夹,写好封面信息,再等后续流程往里面填内容。它主要给回放或整理流程使用,用来作为收集结果的起点。

数据流:输入的是格式版本号、这份追踪档案的 ID、被观察的运行 ID、根对话线程 ID,以及开始时间。函数把这些基础信息写进 RolloutTrace,把结束时间设为空,把状态设为 Running,也就是“还在运行”。然后它为线程、模型请求、工具调用、终端操作等各类内容创建一批空的 BTreeMap,也就是空文件柜。输出是一份结构完整但内容尚未填充的 RolloutTrace。

调用关系:在整体流程里,replay_bundle 会调用 RolloutTrace::new 来先搭好这份追踪记录的骨架。这个函数自己不解析日志、不读取文件,也不判断运行结果;它只把初始容器准备好,并通过标准库的 new 创建各个空映射表,后面的回放整理步骤再把真实数据逐项放进去。

调用图:被 1 处调用(replay_bundle);外部调用 1 个(new)。

rollout-trace/src/raw_event.rs源码 ↗
data_modelcross-cutting:记录、回放和校验追踪日志时都会用到

这个文件解决的是“运行过程怎么可靠记下来”的问题。系统里会发生很多事:一次任务开始和结束、线程启动、模型请求、工具调用、代码单元执行、压缩请求、协议事件等。如果每种事都随便写,后面读取时很难判断顺序、版本和数据是否完整。所以这里先定义一个统一外壳 RawTraceEvent:里面有格式版本、递增编号、时间、rollout 标识、线程和回合信息,再放入真正的事件内容 RawTraceEventPayload。RawTraceEventPayload 是一个枚举,可以理解成“事件种类菜单”,每个种类带自己需要的字段。文件还定义了 RawTraceEventContext,用来携带写入事件时的上下文,以及 RawToolCallRequester,用来说明工具调用是模型发起的,还是代码单元发起的。一个重要点是:大块原始数据不直接塞进事件里,而是用 RawPayloadRef 这种“引用”指过去,像账本里写“凭证编号”。raw_payload_refs 会告诉写入器:这个事件依赖哪些凭证,追加事件前必须先确认这些凭证存在。

函数细节1
RawTraceEventPayload::raw_payload_refs236–311 ↗
fn raw_payload_refs(&self) -> Vec<&RawPayloadRef>

作用:这个函数找出某个原始事件里引用了哪些外部原始载荷。它的作用像检查报销单前先看看所有发票编号有没有列出来,避免事件写进日志后才发现它指向的数据不存在。

数据流:进去的是一个 RawTraceEventPayload,也就是某一种具体事件。函数根据事件类型逐一判断:没有外部载荷的事件返回空列表;有一个请求、响应、运行时记录或协议载荷的事件返回这一项;有可选载荷的事件只有在真的存在时才返回;Other 这种临时扩展事件会返回它带着的全部载荷引用。出来的是一个 RawPayloadRef 引用列表;它不修改事件本身,只把事件依赖的外部数据摘出来给调用者检查。

调用关系:它处在“写入原始事件之前的校验”这一步。上层写日志的代码可以先调用它,拿到事件需要的所有 RawPayloadRef,再确认这些载荷已经保存好,最后才安全地追加事件。函数内部只用空列表和列表创建这类基础操作,不把工作再交给复杂流程。

调用图:外部调用 2 个(new, vec!)。

rollout/src/config.rs源码 ↗
configconfig load / startup / cross-cutting

这个文件像是给程序准备的一张“随身信息卡”。程序启动和初始化时,需要知道几个基本位置:Codex 的家目录、SQLite 数据库放哪里、当前工作目录在哪里,还要知道模型提供方的名字,以及是否开启“生成记忆”这个开关。这里先定义了 RolloutConfigView 这个接口,也就是一组统一的“读取按钮”;再定义 RolloutConfig 这个真正装数据的结构。RolloutConfig::from_view 可以把任何符合这套接口的配置,复制成一份独立的 RolloutConfig,避免后面还依赖原来的来源。文件还特意支持普通引用和 Arc。Arc 是一种“多人共享同一份数据的智能指针”,像多人看同一张公告牌,不需要每人复印一份。这样无论代码手里拿的是配置本体、借来的配置,还是共享配置,都能用同样的方法读配置。

函数细节16
RolloutConfig::from_view25–33 ↗
fn from_view(view: &impl RolloutConfigView) -> Self

作用:把任何“看起来像配置”的东西复制成一份标准的 RolloutConfig。初始化代码会用它来固定住当时的配置,后面就能稳定地传来传去。

数据流:进去的是一个实现了 RolloutConfigView 的配置视图;它依次读取 codex_home、sqlite_home、cwd、model_provider_id 和 generate_memories,把路径和字符串复制成自己拥有的数据;出来的是一份新的 RolloutConfig,不会再借用原来的配置对象。

调用关系:它会在 init 和 try_init 这类初始化流程里被调用。它自己不做复杂判断,只向传入的配置视图索要各项值,然后组装成标准配置,供后续运行阶段使用。

调用图:被 2 处调用(init, try_init);外部调用 5 个(codex_home, cwd, generate_memories, model_provider_id, sqlite_home)。

RolloutConfig::codex_home37–39 ↗
fn codex_home(&self) -> &Path

作用:返回 Codex 的家目录路径。别的代码需要找 Codex 相关文件时,就通过这个入口拿位置。

数据流:进去的是 RolloutConfig 自己;它把内部保存的 PathBuf 转成只读的 Path 视图;出来的是一个路径引用,不复制文件,也不修改配置。

调用关系:这是 RolloutConfig 对 RolloutConfigView 接口的实现之一。需要读取 Codex 主目录的代码会通过统一接口调用它。

调用图:外部调用 1 个(as_path)。

RolloutConfig::sqlite_home41–43 ↗
fn sqlite_home(&self) -> &Path

作用:返回 SQLite 数据库相关文件所在的目录。这样数据库位置不会散落在各处硬编码。

数据流:进去的是 RolloutConfig 自己;它把内部的 sqlite_home 路径拿出来,转换成只读路径引用;出来的是这个目录的位置,不改变任何数据。

调用关系:它是配置读取接口的一部分。需要知道数据库目录的初始化或存储代码,会通过 RolloutConfigView 这一层来拿它。

调用图:外部调用 1 个(as_path)。

RolloutConfig::cwd45–47 ↗
fn cwd(&self) -> &Path

作用:返回当前工作目录,也就是程序当前认为自己正在操作的目录。很多相对路径都要靠它解释。

数据流:进去的是 RolloutConfig;它读取内部 cwd 字段,并把它作为只读路径引用交出去;出来的是当前工作目录路径,不会移动或创建目录。

调用关系:它让调用者不用直接碰 RolloutConfig 的字段,而是通过统一接口取当前工作目录。

调用图:外部调用 1 个(as_path)。

RolloutConfig::model_provider_id49–51 ↗
fn model_provider_id(&self) -> &str

作用:返回模型提供方的标识,比如程序应该用哪一家或哪一种模型后端。它是选择模型服务时用的名字。

数据流:进去的是 RolloutConfig;它读取内部字符串 model_provider_id;出来的是字符串引用,不复制也不修改。

调用关系:它是 RolloutConfigView 的读取按钮之一。需要决定使用哪个模型服务的代码,会通过这个接口拿到提供方标识。

RolloutConfig::generate_memories53–55 ↗
fn generate_memories(&self) -> bool

作用:告诉程序是否要生成“记忆”。这是一个开关,决定相关功能开还是关。

数据流:进去的是 RolloutConfig;它读取内部布尔值 generate_memories;出来的是 true 或 false,不产生其他副作用。

调用关系:它提供给后续流程做功能开关判断。调用者只关心结果,不需要知道这个开关原本从哪里加载来。

T::codex_home59–61 ↗
fn codex_home(&self) -> &Path

作用:让“配置的引用”也能像配置本体一样返回 Codex 家目录。这样代码拿到借来的配置时,不用额外拆开它。

数据流:进去的是一个指向某个配置对象的引用;它把请求转交给被引用的那个真实配置对象;出来的是 Codex 家目录的只读路径引用。

调用关系:这是给 &T 实现 RolloutConfigView 的转发方法。它的作用像代收窗口:自己不保存信息,只把问题问给里面真正的配置。

T::sqlite_home63–65 ↗
fn sqlite_home(&self) -> &Path

作用:让配置引用也能返回 SQLite 目录。这样函数参数写成借用配置时,仍然能按同一套接口读取。

数据流:进去的是配置对象的引用;它调用里面真实对象的 sqlite_home;出来的是数据库目录路径引用,没有额外复制。

调用关系:它属于 &T 的接口转发层。凡是代码手里拿的是借来的配置,而不是配置本体时,这层就能保持接口一致。

T::cwd67–69 ↗
fn cwd(&self) -> &Path

作用:让配置引用也能返回当前工作目录。调用者不用关心自己拿到的是原件还是借用。

数据流:进去的是一个配置引用;它把 cwd 读取请求交给实际配置对象;出来的是当前工作目录的只读路径引用。

调用关系:它是 &T 对 RolloutConfigView 的适配。适配的意思是把一种拿法包装成另一种统一用法。

T::model_provider_id71–73 ↗
fn model_provider_id(&self) -> &str

作用:让配置引用也能返回模型提供方标识。这样选择模型服务的代码可以接受更灵活的配置输入。

数据流:进去的是配置引用;它转身询问真实配置对象的 model_provider_id;出来的是模型提供方字符串引用。

调用关系:它在借用配置的场景里工作,把读取动作转发给真正实现 RolloutConfigView 的对象。

T::generate_memories75–77 ↗
fn generate_memories(&self) -> bool

作用:让配置引用也能读取“是否生成记忆”的开关。这样借来的配置也能用于功能开关判断。

数据流:进去的是配置引用;它调用被引用对象的 generate_memories;出来的是一个布尔值,表示开关开或关。

调用关系:它是 &T 的统一接口转发方法。上层代码可以不区分配置是直接传入还是借用传入。

Arc::codex_home81–83 ↗
fn codex_home(&self) -> &Path

作用:让包在 Arc 里的共享配置也能返回 Codex 家目录。Arc 可以理解成多人共用同一份配置的安全包装。

数据流:进去的是 Arc 包着的配置;它先拿到里面真实配置的只读引用,再读取 codex_home;出来的是 Codex 家目录路径引用。

调用关系:这是给 Arc<T> 实现 RolloutConfigView 的方法。多个组件共享同一份配置时,仍然可以像读普通配置一样读它。

Arc::sqlite_home85–87 ↗
fn sqlite_home(&self) -> &Path

作用:让共享配置也能返回 SQLite 数据目录。这样数据库相关代码不需要知道配置外面包了一层 Arc。

数据流:进去的是 Arc<T>;它访问 Arc 里面的真实配置对象,并转发 sqlite_home 请求;出来的是数据库目录路径引用。

调用关系:它属于 Arc 配置的适配层。共享配置被传到需要 RolloutConfigView 的地方时,这个方法保证接口仍然可用。

Arc::cwd89–91 ↗
fn cwd(&self) -> &Path

作用:让共享配置也能返回当前工作目录。多人共享同一份配置时,各处仍然读到同一个 cwd。

数据流:进去的是 Arc 包裹的配置;它通过 as_ref 看到里面的配置,再读取 cwd;出来的是当前工作目录路径引用。

调用关系:它把 Arc<T> 伪装成一个普通的 RolloutConfigView。上层流程不必为了共享指针写特殊代码。

Arc::model_provider_id93–95 ↗
fn model_provider_id(&self) -> &str

作用:让共享配置也能返回模型提供方标识。需要选择模型后端的地方可以直接读取,不用先解开 Arc。

数据流:进去的是 Arc<T>;它取得内部配置的引用,并转发 model_provider_id 请求;出来的是模型提供方的字符串引用。

调用关系:它服务于配置被多个部分共同持有的场景。读取请求从共享外壳传到真实配置对象。

Arc::generate_memories97–99 ↗
fn generate_memories(&self) -> bool

作用:让共享配置也能读取“生成记忆”开关。多个组件共用配置时,都能用同一套方式判断功能是否开启。

数据流:进去的是 Arc<T>;它访问里面的配置对象,读取 generate_memories;出来的是 true 或 false,不改动共享配置。

调用关系:它是 Arc<T> 的 RolloutConfigView 实现的一部分。它让共享配置在初始化后和普通配置一样参与后续流程。

跟踪写入骨架

这些文件提供低层事件转换和持久化机制,供更高层的跟踪上下文用于序列化 payload 并追加原始事件。

rollout-trace/src/protocol_event.rs源码 ↗
domain_logicrequest handling / cross-cutting

可以把这个文件理解成一个“事件翻译员”。Codex 运行时会吐出各种事件,比如一轮对话开始了、结束了、命令开始执行了、补丁应用成功或失败了、协作子代理做了什么等。但追踪系统不想原样记录所有东西,只关心能说明运行边界和结果的关键事件。这个文件就逐个检查 EventMsg,也就是“协议事件的大信封”,挑出需要的部分,转成更小、更稳定的追踪格式。它还会把不同来源的成功、失败、取消统一成 ExecutionStatus,避免后面统计时各说各话。一个重要细节是:很多 match 分支看起来只是返回 None,但这是故意写全的。这样以后协议新增事件时,编译器会提醒开发者:这个新事件到底要不要进追踪。

函数细节7
codex_turn_trace_event38–79 ↗
fn codex_turn_trace_event(
    thread_id: AgentThreadId,
    default_turn_id: &str,
    event: &EventMsg,
) -> Option<CodexTurnTraceEvent>

作用:这个函数专门看“一轮 Codex 对话”的开始、完成、被中断这些事件,并把它们变成追踪系统能记录的轮次事件。别人想知道某一轮什么时候开始、什么时候结束、结果怎样,就会用到它。

数据流:进去的是线程编号、默认轮次编号,以及一个协议事件。它先判断事件是不是 TurnStarted、TurnComplete 或 TurnAborted;如果是,就取出轮次 ID,包装成 RawTraceEventPayload;如果是中断,还会根据中断原因算出是取消还是别的状态。出来的是一个可记录的 CodexTurnTraceEvent;如果这个协议事件和轮次生命周期无关,就出来 None,不产生记录。

调用关系:它由 record_codex_turn_event 调用,位置很像“轮次记录入口后面的筛选器”。遇到被中断的轮次时,它会把状态判断这件小事交给 execution_status_for_abort_reason,自己负责把最终结果装进追踪事件。

调用图:调用 1 个内部函数(execution_status_for_abort_reason);被 1 处调用(record_codex_turn_event)。

ToolRuntimePayload::serialize118–139 ↗
fn serialize(&self, serializer: S) -> Result<S::Ok, S::Error>

作用:这个函数让 ToolRuntimePayload 可以被序列化,也就是变成能写入文件或发送出去的数据格式。它的重点是保留原始协议事件的样子,不先复制一份,也不先改造成松散的 JSON。

数据流:进去的是一个 ToolRuntimePayload,里面借用了某个原始工具事件,比如命令开始、补丁结束、MCP 工具调用结束等。它根据具体是哪一种事件,把序列化工作转交给那个原始事件自己的 serialize 方法。出来的是序列化结果;它不改动原事件,只是按原样把内容写给序列化器。

调用关系:它在追踪记录需要保存工具运行细节时被 serde 使用。serde 是 Rust 常用的“把数据结构转成 JSON 等格式”的工具。这个函数不主动调别人做业务判断,只是在保存数据时充当透明包装,让外层追踪事件和原始协议内容能一起落盘。

tool_runtime_trace_event142–290 ↗
fn tool_runtime_trace_event(event: &EventMsg) -> Option<ToolRuntimeTraceEvent<'_>>

作用:这个函数判断一个协议事件是不是“工具运行”的开始或结束,并转成追踪系统里的工具运行事件。比如执行 shell 命令、应用补丁、调用 MCP 工具、协作代理交互等,都在这里被识别。

数据流:进去的是一个协议事件。它逐项判断事件类型:如果是工具开始,就拿 call_id 当作这次工具调用的编号,并附上原始事件内容;如果是工具结束,就再算出完成、失败或取消的状态;如果是不需要记录的事件,就返回 None。它还特别跳过来自 UserShell 的命令,因为那是用户自己的终端操作,不算代理工具运行边界。

调用关系:它由 record_tool_call_event 调用,是工具追踪记录的主要翻译器。它会使用 ExecCommandStatus::trace_execution_status 和 PatchApplyStatus::trace_execution_status 这类状态转换能力;对协作代理相关事件,它直接把开始或结束事件包装成 ToolRuntimePayload。后面的记录器拿到它的结果后,才真正写入追踪。

调用图:被 1 处调用(record_tool_call_event);外部调用 15 个(CollabAgentInteractionBegin, CollabAgentInteractionEnd, CollabAgentSpawnBegin, CollabAgentSpawnEnd, CollabCloseBegin, CollabCloseEnd, CollabWaitingBegin, CollabWaitingEnd, ExecCommandBegin, ExecCommandEnd (+5 more))。

wrapped_protocol_event_type292–371 ↗
fn wrapped_protocol_event_type(event: &EventMsg) -> Option<&'static str>

作用:这个函数给少数需要“原样包起来记录”的协议事件取一个简短类型名。比如 session_configured、turn_started、error 这种事件,可以用统一名字写进追踪。

数据流:进去的是一个协议事件。它检查这个事件是否属于允许包装记录的少数类型;如果是,就返回一个固定的字符串名称;如果不是,就返回 None。它不复制事件内容,也不改变状态,只负责回答“这个事件要不要包起来,以及名字叫什么”。

调用关系:它由 record_protocol_event 调用,像一个白名单检查器。record_protocol_event 想记录通用协议事件时,先问它这个事件有没有可记录的类型名;有名字才继续包装和保存,没有就跳过。

调用图:被 1 处调用(record_protocol_event)。

ExecCommandStatus::trace_execution_status378–384 ↗
fn trace_execution_status(&self) -> ExecutionStatus

作用:这个函数把“执行命令”的原始状态,换成追踪系统统一使用的状态。这样后面看追踪时,不用关心命令模块内部怎么命名成功、失败或被拒绝。

数据流:进去的是 ExecCommandStatus,也就是命令执行结果:Completed、Failed 或 Declined。它把 Completed 变成追踪里的 Completed,把 Failed 变成 Failed,把 Declined 变成 Cancelled。出来的是统一的 ExecutionStatus,不改动原来的状态值。

调用关系:它在 tool_runtime_trace_event 处理 ExecCommandEnd 时被使用。工具追踪翻译器负责识别“命令结束了”,这个函数负责把命令自己的结果语言翻译成追踪系统的结果语言。

PatchApplyStatus::trace_execution_status388–394 ↗
fn trace_execution_status(&self) -> ExecutionStatus

作用:这个函数把“应用补丁”的原始状态,换成追踪系统统一使用的状态。它让补丁成功、失败、被拒绝这些结果能和其他工具调用放在同一套统计口径里。

数据流:进去的是 PatchApplyStatus,也就是补丁应用结果:Completed、Failed 或 Declined。它把 Completed 对应到 Completed,Failed 对应到 Failed,Declined 对应到 Cancelled。出来的是统一的 ExecutionStatus;它只做翻译,不做记录。

调用关系:它在 tool_runtime_trace_event 处理 PatchApplyEnd 时被使用。tool_runtime_trace_event 负责判断这是一次补丁工具运行结束,这个函数负责把补丁模块的结果说法换成追踪系统的说法。

execution_status_for_abort_reason397–404 ↗
fn execution_status_for_abort_reason(reason: &TurnAbortReason) -> ExecutionStatus

作用:这个函数把“一轮对话为什么被中止”的原因,翻译成追踪系统里的结束状态。目前这些中止原因都被看作取消,而不是成功或失败。

数据流:进去的是 TurnAbortReason,也就是轮次中断原因,比如被打断、被替换、审查结束、预算受限。它把这些原因统一映射为 ExecutionStatus::Cancelled。出来的是一个追踪状态,不改变输入原因。

调用关系:它只被 codex_turn_trace_event 调用。当 codex_turn_trace_event 遇到 TurnAborted 事件时,会把“中断原因如何算状态”这件事交给它,从而保持轮次事件翻译逻辑更清楚。

调用图:被 1 处调用(codex_turn_trace_event)。

rollout-trace/src/writer.rs源码 ↗
io_transportcross-cutting

这个文件定义了 TraceWriter,也就是本地追踪包的写入器。它解决的问题是:程序运行时会不断产生事件,比如某个线程开始、一次模型请求开始和结束、某段原始请求或响应内容需要保存。如果只放在内存里,程序一退出就没了;如果随便写文件,可能顺序乱、引用丢、并发写坏。这里的做法是先创建一个追踪文件夹,写入清单文件,再打开一份按行追加的事件日志。较大的 JSON 内容会先写进 payloads 目录,再在事件里保存它的引用,这样回放时不会看到“事件指向了一个还没写出来的文件”。TraceWriter 内部用互斥锁(一把锁,防止两个任务同时改同一份数据)保护序号、文件句柄和 payload 编号,确保事件按顺序写入。它还有一个特别的容错点:即使之前写追踪时发生过 panic(程序崩溃式错误),锁被标记为“中毒”,它也会继续取回内部数据,尽量保住后续诊断记录。

函数细节8
TraceWriter::create51–83 ↗
fn create(
        bundle_dir: impl AsRef<Path>,
        trace_id: String,
        rollout_id: String,
        root_thread_id: AgentThreadId,
    ) -> Result<Self>

作用:创建一个新的追踪包写入器。它会准备好目录、清单文件和事件日志文件,让后面的运行过程可以不断往里面写记录。

数据流:进去的是追踪包目录、trace_id、rollout_id 和根线程 ID。它先建出 payloads 子目录,取当前时间,生成一份清单 manifest 并写成 JSON 文件,然后打开原始事件日志文件,最后返回一个带锁的 TraceWriter;这个写入器从事件序号 1、payload 编号 1 开始工作。

调用关系:这是整个写入流程的起点。测试和上层追踪代码会在一次运行或一次请求开始前调用它;它内部会用 unix_time_ms 取时间,用 write_json_file 写清单。后续的 write_json_payload 和 append_with_context 都依赖它准备好的目录和日志文件。

调用图:调用 3 个内部函数(new, unix_time_ms, write_json_file);被 8 处调用(started_inference_attempt, responses_websocket_request_prewarm_traces_logical_request, enabled_attempt_adds_inference_request_header, enabled_context_records_replayable_inference_attempt, create_started_writer_for_thread, child_thread_metadata_creates_spawn_origin_without_delivery_edge, start_root_in_root, writer_records_payload_refs_and_replays_rollout_status);外部调用 6 个(as_ref, join, new, new, new, create_dir_all)。

TraceWriter::write_json_payload86–106 ↗
fn write_json_payload(
        &self,
        kind: RawPayloadKind,
        value: &impl Serialize,
    ) -> Result<RawPayloadRef>

作用:把一段较大的 JSON 原始内容写成单独文件,并返回一个可以放进事件里的引用。这样事件日志保持轻巧,同时又能找到完整原文。

数据流:进去的是 payload 类型和一个可以序列化成 JSON 的值。它先锁住内部状态,拿到下一个 payload 编号,生成类似 raw_payload:1 的 ID 和 payloads/1.json 这样的路径;接着把 JSON 真实写到磁盘,最后返回 RawPayloadRef,里面包含 ID、类型和相对路径。它会推进下一个 payload 编号。

调用关系:上层在记录模型请求、模型响应、协议事件、工具调用等大块内容时会调用它。它先通过 lock_inner 拿到安全访问权,再交给 write_json_file 真正落盘。之后调用方通常会把返回的引用塞进 append 或 append_with_context 写出的事件里。

调用图:调用 2 个内部函数(lock_inner, write_json_file);被 9 处调用(write_json_payload_best_effort, write_json_payload_best_effort, write_json_payload_best_effort, append_completed_inference, append_inference_request, append_spawn_agent_tool_lifecycle, append_followup_with_tool_output, append_inference_with_tool_call, write_json_payload_best_effort);外部调用 1 个(format!)。

TraceWriter::append109–111 ↗
fn append(&self, payload: RawTraceEventPayload) -> Result<RawTraceEvent>

作用:追加一条没有额外上下文的原始事件。调用方只关心“发生了什么”,不需要手动指定线程或回合信息时,就用它。

数据流:进去的是一个 RawTraceEventPayload,也就是事件的具体内容。它补上默认上下文,然后把事情转交给 append_with_context;出来的是完整 RawTraceEvent,里面已经带上序号、时间、rollout_id 等外壳信息,并且事件已经写进日志。

调用关系:这是 append_with_context 的简化入口。线程开始、回合开始、推理开始和一些测试场景会调用它;它自己不直接写文件,而是把默认上下文和 payload 一起交给 append_with_context。

调用图:调用 1 个内部函数(append_with_context);被 6 处调用(append_inference_completion, append_inference_start_for_thread, start_thread, start_turn_for_thread, append_followup_with_tool_output, append_inference_with_tool_call);外部调用 1 个(default)。

TraceWriter::append_with_context114–134 ↗
fn append_with_context(
        &self,
        context: RawTraceEventContext,
        payload: RawTraceEventPayload,
    ) -> Result<RawTraceEvent>

作用:追加一条带上下文的原始事件。这里的上下文指这条事件属于哪个线程、哪个 Codex 回合,方便之后按运行脉络回放。

数据流:进去的是 RawTraceEventContext 和事件 payload。它锁住内部状态,取当前毫秒时间,拿 manifest 里的 rollout_id,组装出完整事件;然后事件序号加一,把事件用 JSON 写到事件日志里,再写一个换行并立即 flush(刷新到文件),最后返回这条完整事件。

调用关系:这是事件写入的核心通道。append 会把简单事件转给它,上层记录完成的推理、工具生命周期等带上下文的事件也会直接调用它。它会用 lock_inner 保证一次只写一条,用 unix_time_ms 补时间,用 serde_json 把事件编码成 JSON。

调用图:调用 2 个内部函数(lock_inner, unix_time_ms);被 3 处调用(append_completed_inference, append_spawn_agent_tool_lifecycle, append);外部调用 1 个(to_writer)。

TraceWriter::lock_inner136–141 ↗
fn lock_inner(&self) -> MutexGuard<'_, TraceWriterInner>

作用:安全拿到 TraceWriter 内部状态的访问权。它的重点不是做复杂计算,而是防止多个写入动作同时改序号或同时写日志。

数据流:进去的是 TraceWriter 自身。它尝试锁住内部互斥锁;如果锁曾因为 panic 被标记为异常,它不会直接放弃,而是取回里面的数据继续用。出来的是一个 MutexGuard,也就是临时独占内部状态的“钥匙”。

调用关系:write_json_payload 和 append_with_context 都会先调用它,再修改编号或写文件。这个函数让整个写入器在并发场景下保持顺序,也让追踪系统在出错后尽量继续记录后续诊断信息。

调用图:被 2 处调用(append_with_context, write_json_payload)。

write_json_file144–148 ↗
fn write_json_file(path: &Path, value: &impl Serialize) -> Result<()>

作用:把一个 Rust 数据值写成漂亮格式的 JSON 文件。它是这个文件里的通用小工具,用来写 manifest 和 payload 文件。

数据流:进去的是目标文件路径和一个可序列化的值。它先创建文件,如果创建失败会附上“创建哪个文件失败”的说明;然后把值写成带缩进的 JSON,如果写失败也会附上文件路径。成功时不返回内容,只表示文件已经写好。

调用关系:TraceWriter::create 用它写清单文件,TraceWriter::write_json_payload 用它写 payload 文件。它把底层的文件创建和 JSON 编码细节包起来,让上层只关心“把这份数据写到这个路径”。

调用图:被 2 处调用(create, write_json_payload);外部调用 2 个(create, to_writer_pretty)。

unix_time_ms150–155 ↗
fn unix_time_ms() -> i64

作用:取得当前 Unix 时间的毫秒数。Unix 时间就是从 1970 年 1 月 1 日零点到现在经过了多久,常用来给事件打时间戳。

数据流:进去没有参数。它读取系统当前时间,计算相对 UNIX_EPOCH 的时长,把毫秒数转成 i64;如果系统时间异常早于纪元,就用默认值;如果数值大到放不下,就用 i64 的最大值。出来的是一个毫秒级时间戳。

调用关系:TraceWriter::create 用它记录追踪包开始时间,TraceWriter::append_with_context 用它给每条事件打写入时刻。它是整个追踪时间线的时间来源。

调用图:被 2 处调用(append_with_context, create);外部调用 2 个(now, try_from)。

tests::writer_records_payload_refs_and_replays_rollout_status171–264 ↗
fn writer_records_payload_refs_and_replays_rollout_status() -> anyhow::Result<()>

作用:这是一个自动测试,用来确认 TraceWriter 写出的追踪包真的能被回放,并且 payload 引用、运行状态、线程和推理记录都对得上。

数据流:它先创建临时目录和 TraceWriter,然后依次写入 rollout 开始、线程开始、回合开始、推理开始、推理完成、回合结束、rollout 结束等事件;中间还写入 metadata、请求、响应这些 JSON payload。之后它调用 replay_bundle 读取刚写出的文件,最后用断言检查回放结果是否包含正确状态、线程路径、回合归属、payload ID 和 payload 路径。

调用关系:这个测试模拟了一个小而完整的追踪生命周期。它直接调用 TraceWriter::create、write_json_payload、append,并把写出的文件交给 replay_bundle 验证;如果写入顺序、payload 引用或日志格式坏了,这个测试会暴露问题。

调用图:调用 1 个内部函数(create);外部调用 4 个(new, assert_eq!, replay_bundle, json!)。

记录上下文

这些文件暴露面向运行时的线程跟踪 API,以及其专门的子活动,例如推理、压缩、代码单元、MCP 调用和工具分发。

rollout-trace/src/thread.rs源码 ↗
domain_logiccross-cutting

一次 rollout 可以理解成一次完整任务执行,里面可能有主线程,也可能派生出子线程。这个文件就是给每个线程发一张“身份卡”和一本“流水账”。如果环境变量 CODEX_ROLLOUT_TRACE_ROOT 没有打开,它会变成空操作,所有记录请求都直接忽略,不拖慢正常流程;如果打开了,它会创建一个 trace bundle,也就是一包可回放的追踪文件。它会尽量写入事件,但写失败只打警告,不让 Codex 会话崩掉。主要部件是 ThreadTraceContext:外部代码只拿它调用各种 record 或 start 方法;真正启用时,里面的 EnabledThreadTraceContext 会通过 TraceWriter 把事件和大块 JSON 载荷写到磁盘。这个文件还特别区分根线程和子线程:根线程结束才代表整个 rollout 结束,子线程结束只代表自己结束。

函数细节24
ThreadTraceContext::disabled95–99 ↗
fn disabled() -> Self

作用:创建一个“什么都不记录”的追踪句柄。这样调用方不用到处判断追踪有没有开启,照常调用也不会产生文件。

数据流:进去没有参数 → 它把内部状态设成 Disabled → 出来一个 ThreadTraceContext,之后所有记录动作都会快速跳过,不改动磁盘也不产生事件。

调用关系:很多会话创建和测试路径会直接用它,比如交互线程、各种 session 构造函数和测试辅助函数。它是追踪系统的安全兜底:没有追踪时,其他代码仍然可以拿到同样形状的对象。

调用图:被 9 处调用(run_codex_thread_interactive, guardian_subagent_does_not_inherit_parent_exec_policy_rules, make_session_and_context, make_session_and_context_with_auth_config_home_and_rx, make_session_with_config_and_rx, make_session_with_history_source_and_agent_control_and_rx, session_new_fails_when_zsh_fork_enabled_without_packaged_zsh, parent_rollout_thread_trace_for_source, disabled_thread_context_accepts_trace_calls_without_writing)。

ThreadTraceContext::start_root_or_disabled106–118 ↗
fn start_root_or_disabled(metadata: ThreadStartedTraceMetadata) -> Self

作用:尝试为一个根线程开启追踪;如果环境变量没设置或初始化失败,就自动退回到不记录模式。它保证“追踪坏了也不能影响主业务”。

数据流:进去一份线程启动元数据 → 它读取 CODEX_ROLLOUT_TRACE_ROOT 环境变量;有目录就调用 start_root_in_root 创建追踪包,失败就写警告 → 出来一个启用或禁用的 ThreadTraceContext。

调用关系:它会在 new 创建会话时被调用。它把真正建目录、建写入器的工作交给 start_root_in_root;如果外部环境没打开追踪,就交给 ThreadTraceContext::disabled。

调用图:调用 1 个内部函数(start_root_in_root);被 1 处调用(new);外部调用 4 个(from, disabled, var_os, warn!)。

ThreadTraceContext::start_root_in_root_for_test124–129 ↗
fn start_root_in_root_for_test(
        root: &Path,
        metadata: ThreadStartedTraceMetadata,
    ) -> anyhow::Result<Self>

作用:给测试用的根追踪启动入口。测试可以直接指定目录,不需要改整个进程的环境变量。

数据流:进去一个根目录和线程启动元数据 → 它原样转交给 start_root_in_root → 出来成功的 ThreadTraceContext,或者返回创建失败的错误。

调用关系:它被 attach_trace_bundle、attach_test_trace 以及多个追踪回放测试使用。它本身不做额外逻辑,只是把测试场景接到正式的 start_root_in_root 上,保证测试和真实路径一致。

调用图:调用 1 个内部函数(start_root_in_root);被 5 处调用(attach_trace_bundle, attach_test_trace, create_in_root_writes_replayable_lifecycle_events, protocol_wrapper_records_selected_events_as_raw_payloads, spawned_thread_start_appends_to_root_bundle)。

ThreadTraceContext::start132–146 ↗
fn start(
        writer: Arc<TraceWriter>,
        root_thread_id: AgentThreadId,
        metadata: ThreadStartedTraceMetadata,
    ) -> Self

作用:在已有追踪包里启动一个线程的追踪生命周期。根线程和子线程都会通过它写下“线程开始了”。

数据流:进去共享的 TraceWriter、根线程 id、当前线程元数据 → 它组装 EnabledThreadTraceContext,并调用 record_thread_started 写启动事件 → 出来一个启用状态的 ThreadTraceContext。

调用关系:start_root_in_root 创建完根追踪包后会调用它。子线程也会通过 start_child_thread_trace_or_disabled 间接调用它。它把第一条线程启动记录交给 record_thread_started 完成。

调用图:调用 1 个内部函数(record_thread_started);被 1 处调用(start_root_in_root);外部调用 1 个(Enabled)。

ThreadTraceContext::is_enabled153–155 ↗
fn is_enabled(&self) -> bool

作用:告诉调用方这个追踪句柄现在会不会真的写文件。它主要用来避免在追踪关闭时提前准备昂贵的大数据。

数据流:进去当前 ThreadTraceContext → 它检查内部状态是不是 Enabled → 出来 true 或 false,不改动任何状态。

调用关系:它是给外部调用方做轻量判断的工具。大多数记录函数自己已经会检查启用状态,所以只有在准备追踪数据本身很费劲时才需要先问它。

调用图:外部调用 1 个(matches!)。

ThreadTraceContext::start_child_thread_trace_or_disabled162–174 ↗
fn start_child_thread_trace_or_disabled(
        &self,
        metadata: ThreadStartedTraceMetadata,
    ) -> Self

作用:为当前 rollout 里的新子线程开一份追踪。这样主线程和子线程会写进同一个追踪包,事后能看出一棵完整的执行树。

数据流:进去父上下文和子线程启动元数据 → 如果父上下文关闭追踪,就返回 disabled;如果开启,就复用同一个 TraceWriter 和根线程 id,调用 start 写子线程启动事件 → 出来子线程自己的 ThreadTraceContext。

调用关系:它在 new 创建子线程会话时被用到。它会调用 ThreadTraceContext::start,和根线程共用同一个追踪包;如果父追踪没开,就调用 ThreadTraceContext::disabled。

调用图:被 1 处调用(new);外部调用 3 个(clone, disabled, start)。

ThreadTraceContext::record_ended181–192 ↗
fn record_ended(&self, status: RolloutStatus)

作用:记录一个线程正常结束。它还会在根线程结束时额外记录整个 rollout 结束。

数据流:进去一个结束状态 → 如果追踪关闭就什么也不做;如果开启,就写 ThreadEnded 事件;如果当前线程就是根线程,再写 RolloutEnded 事件 → 磁盘上的追踪文件多出结束标记。

调用关系:会话收尾时会调用它。它不把工作交给其他公开函数,而是直接通过启用上下文追加事件;这样子线程结束和根任务结束能被清楚地区分。

调用图:外部调用 1 个(clone)。

ThreadTraceContext::record_protocol_event198–214 ↗
fn record_protocol_event(&self, event: &EventMsg)

作用:把一部分协议事件保存成原始追踪线索。协议事件可以理解成系统内部传来传去的消息,这里只挑重要的保存,避免把高频噪声全写进去。

数据流:进去一个 EventMsg → 如果追踪关闭就跳过;如果事件类型不需要包装也跳过;否则先把完整事件写成 JSON 载荷,再追加 ProtocolEventObserved 事件 → 出来的是追踪文件里的一个引用和一条观察记录。

调用关系:它会调用 wrapped_protocol_event_type 判断这个协议事件值不值得记录,然后通过启用上下文写 JSON 载荷和追加事件。它位于协议消息和追踪文件之间,像一个筛选器。

调用图:调用 1 个内部函数(wrapped_protocol_event_type)。

ThreadTraceContext::record_codex_turn_event217–230 ↗
fn record_codex_turn_event(&self, default_turn_id: &str, event: &EventMsg)

作用:把协议里的生命周期消息转换成更清楚的 Codex turn 事件。turn 可以理解成一次模型交互回合。

数据流:进去默认 turn id 和一个协议事件 → 如果追踪关闭就跳过;否则调用 codex_turn_trace_event 尝试翻译;翻译成功后,带着线程 id 和 turn id 写入追踪 → 追踪里出现结构化的回合事件。

调用关系:它依赖 codex_turn_trace_event 做“协议消息到追踪事件”的翻译。翻译失败说明这个事件不是 turn 生命周期事件,就不会写入。

调用图:调用 1 个内部函数(codex_turn_trace_event)。

ThreadTraceContext::record_tool_call_event237–248 ↗
fn record_tool_call_event(&self, codex_turn_id: impl Into<CodexTurnId>, event: &EventMsg)

作用:记录工具运行过程中的开始、结束等事件。这里的工具指 Codex 调用的外部能力,比如执行命令或访问某个服务。

数据流:进去一个 Codex turn id 和协议事件 → 如果追踪关闭就跳过;否则用 tool_runtime_trace_event 从协议事件里提取工具运行信息,再转成原始追踪载荷,最后带 turn id 写入 → 追踪里多出工具运行的结构化记录。

调用关系:它先把识别工作交给 tool_runtime_trace_event,再把载荷转换交给 EnabledThreadTraceContext::raw_tool_runtime_payload。它补充的是“工具实际运行时发生了什么”,不是工具调度边界本身。

调用图:调用 1 个内部函数(tool_runtime_trace_event);外部调用 1 个(into)。

ThreadTraceContext::record_agent_result_interaction256–283 ↗
fn record_agent_result_interaction(
        &self,
        child_codex_turn_id: impl Into<CodexTurnId>,
        parent_thread_id: impl Into<AgentThreadId>,
        payload: &AgentResultTracePayload<'_

作用:记录子代理完成后把结果通知父代理这件事。它把这次通知明确写成一条“从子线程到父线程”的边,方便事后看清多代理之间如何传话。

数据流:进去子 turn id、父线程 id 和结果载荷 → 如果追踪关闭就跳过;否则把载荷尽量写成 JSON,生成一个边 id,然后追加 AgentResultObserved 事件 → 追踪里出现子线程、父线程、消息内容和可选原始载荷。

调用关系:它用于子任务结果送回父任务邮箱时。它不让后续分析器去猜这条关系,而是在运行时直接写明这条图上的连接。

调用图:外部调用 3 个(clone, into, format!)。

ThreadTraceContext::record_codex_turn_started290–302 ↗
fn record_codex_turn_started(&self, codex_turn_id: impl Into<CodexTurnId>)

作用:显式记录某个 Codex turn 开始了。它主要方便追踪相关测试构造合法的输入,不一定依赖完整会话循环。

数据流:进去一个 turn id → 如果追踪关闭就跳过;否则把 turn id 和当前线程 id 组成 CodexTurnStarted 事件,并带上下文写入 → 追踪里有了这个回合的开始点。

调用关系:它是一个小钩子,供生产路径或测试路径在需要时直接打点。写入动作通过启用上下文的 append_with_context_best_effort 完成。

调用图:外部调用 2 个(clone, into)。

ThreadTraceContext::start_code_cell_trace305–315 ↗
fn start_code_cell_trace(
        &self,
        codex_turn_id: impl Into<CodexTurnId>,
        runtime_cell_id: impl Into<String>,
        model_visible_call_id: impl Into<String>,
        source_js:

作用:开始记录一个 code-mode 单元格的生命周期,并返回后续继续记录这个单元格的句柄。code-mode 单元格可以理解成一次可执行代码片段。

数据流:进去 turn id、运行时单元格 id、模型可见调用 id 和源码 → 它先调用 code_cell_trace_context 拿到单元格追踪上下文,再记录 started 事件 → 出来 CodeCellTraceContext,后续可以继续记录这个单元格的执行过程。

调用关系:它把创建上下文的工作交给 ThreadTraceContext::code_cell_trace_context,然后立刻调用返回对象的 record_started。它是“创建并马上写开始事件”的便捷入口。

调用图:调用 1 个内部函数(code_cell_trace_context)。

ThreadTraceContext::code_cell_trace_context318–332 ↗
fn code_cell_trace_context(
        &self,
        codex_turn_id: impl Into<CodexTurnId>,
        runtime_cell_id: impl Into<String>,
    ) -> CodeCellTraceContext

作用:为一个已经存在或即将记录的代码单元格创建追踪句柄。它只准备句柄,不自动写开始事件。

数据流:进去 turn id 和运行时单元格 id → 如果追踪关闭,返回禁用的 CodeCellTraceContext;如果开启,就把共享写入器、线程 id、turn id 和单元格 id装进去 → 出来可用或空操作的单元格追踪上下文。

调用关系:它被 start_code_cell_trace 调用,也可给已经开始的单元格补建句柄。它会根据状态选择 CodeCellTraceContext::enabled 或 CodeCellTraceContext::disabled。

调用图:调用 2 个内部函数(disabled, enabled);被 1 处调用(start_code_cell_trace);外部调用 1 个(clone)。

ThreadTraceContext::start_tool_dispatch_trace339–350 ↗
fn start_tool_dispatch_trace(
        &self,
        invocation: impl FnOnce() -> Option<ToolDispatchInvocation>,
    ) -> ToolDispatchTraceContext

作用:开始记录一次工具调度层面的生命周期。调度层面指“系统决定要调用哪个工具、带什么参数”这个边界。

数据流:进去一个延迟执行的 invocation 构造函数 → 如果追踪关闭,直接返回禁用工具追踪;如果开启,才调用这个构造函数,拿不到 invocation 也返回禁用;拿到后调用 ToolDispatchTraceContext::start → 出来工具调度追踪句柄。

调用关系:它通常在工具即将被派发时使用。重要点是 invocation 是懒加载的:只有追踪开启才会构造,避免在正常热路径上复制大参数。

调用图:调用 2 个内部函数(disabled, start);外部调用 1 个(clone)。

ThreadTraceContext::inference_trace_context357–373 ↗
fn inference_trace_context(
        &self,
        codex_turn_id: impl Into<CodexTurnId>,
        model: impl Into<String>,
        provider_name: impl Into<String>,
    ) -> InferenceTraceContext

作用:为一次模型推理回合准备追踪上下文。它还不是一次具体请求,只是把线程、turn、模型和服务商信息先放好。

数据流:进去 turn id、模型名、服务商名 → 如果追踪关闭,返回禁用的 InferenceTraceContext;如果开启,就把共享写入器、线程 id 和这些信息装进启用上下文 → 出来给后续模型请求尝试使用的追踪句柄。

调用关系:模型传输代码会拿到这个上下文,但真正的请求尝试由传输层稍后启动。它把线程级追踪和具体模型请求追踪衔接起来。

调用图:调用 2 个内部函数(disabled, enabled);外部调用 2 个(clone, into)。

ThreadTraceContext::compaction_trace_context381–399 ↗
fn compaction_trace_context(
        &self,
        codex_turn_id: impl Into<CodexTurnId>,
        compaction_id: impl Into<CompactionId>,
        model: impl Into<String>,
        provider_name: impl

作用:为一次远程压缩检查点准备追踪上下文。压缩这里指把历史对话整理成更短的新历史,所以需要清楚记录请求、响应和最终替换历史的时刻。

数据流:进去 turn id、压缩 id、模型名、服务商名 → 如果追踪关闭,返回禁用的 CompactionTraceContext;如果开启,就把共享写入器、线程 id 和这些标识装进去 → 出来可用于记录压缩过程的上下文。

调用关系:它连接线程追踪和远程 compaction 子流程。后续 compaction 代码会用返回的上下文记录具体尝试和检查点事件。

调用图:调用 2 个内部函数(disabled, enabled);外部调用 2 个(clone, into)。

ThreadTraceContext::start_mcp_call_trace406–417 ↗
fn start_mcp_call_trace(&self, tool_call_id: impl Into<ToolCallId>) -> McpCallTraceContext

作用:为一次 MCP 后端请求生成跨进程关联 id。MCP 可以理解成连接外部工具服务的桥,UUID 是一串几乎不会撞车的随机编号,方便把不同日志对上。

数据流:进去工具调用 id → 如果追踪关闭,返回禁用的 McpCallTraceContext;如果开启,就生成一个新的 UUID,创建启用的 MCP 追踪上下文,并写入 tool_call_id 到 mcp_call_id 的对应关系 → 出来可带到后端请求里的追踪句柄。

调用关系:它在真正发起 MCP 后端请求前使用。它会调用 McpCallTraceContext::enabled 创建桥接句柄,并把关联关系追加到 rollout 追踪里。

调用图:调用 2 个内部函数(disabled, enabled);外部调用 2 个(into, new_v4)。

start_root_in_root420–444 ↗
fn start_root_in_root(
    root: &Path,
    metadata: ThreadStartedTraceMetadata,
) -> anyhow::Result<ThreadTraceContext>

作用:在指定目录下创建一个新的根追踪包。它是真正建文件夹、建 TraceWriter、写 rollout 开始事件的地方。

数据流:进去根目录和线程启动元数据 → 它生成 trace id,用 trace id 和线程 id 拼出 bundle 目录,创建 TraceWriter,先尝试写 RolloutStarted,再调用 ThreadTraceContext::start 写 ThreadStarted → 出来启用的 ThreadTraceContext,或者创建失败的错误。

调用关系:它被 start_root_or_disabled 和 start_root_in_root_for_test 调用。它负责底层启动工作,完成后把线程生命周期交给 ThreadTraceContext::start。

调用图:调用 2 个内部函数(start, create);被 2 处调用(start_root_in_root_for_test, start_root_or_disabled);外部调用 6 个(new, join, new_v4, debug!, format!, warn!)。

record_thread_started446–457 ↗
fn record_thread_started(
    context: &EnabledThreadTraceContext,
    metadata: ThreadStartedTraceMetadata,
)

作用:写入“线程已经启动”的追踪事件,并把启动时的详细元数据另存成 JSON 载荷。

数据流:进去启用上下文和线程启动元数据 → 它先尽量把元数据写成 SessionMetadata 载荷,再追加 ThreadStarted 事件,事件里带线程 id、agent 路径和载荷引用 → 追踪包里有了线程起点。

调用关系:它只在 ThreadTraceContext::start 里被调用。它把大块元数据写入交给 write_json_payload_best_effort,把事件追加交给 append_best_effort。

调用图:调用 2 个内部函数(append_best_effort, write_json_payload_best_effort);被 1 处调用(start)。

EnabledThreadTraceContext::write_json_payload_best_effort460–472 ↗
fn write_json_payload_best_effort(
        &self,
        kind: RawPayloadKind,
        payload: &impl Serialize,
    ) -> Option<RawPayloadRef>

作用:尽量把一份可序列化的数据写成 JSON 载荷。写失败只报警,不中断主流程。

数据流:进去载荷类别和要写的数据 → 它调用 TraceWriter 写 JSON;成功就返回 RawPayloadRef,也就是以后能找到这份载荷的引用;失败就写警告并返回 None → 不会抛出错误给上层。

调用关系:record_thread_started 和 raw_tool_runtime_payload 会用它保存较大的原始信息。它是所有“附带详细 JSON 内容”的安全写入入口。

调用图:被 2 处调用(raw_tool_runtime_payload, record_thread_started);外部调用 1 个(warn!)。

EnabledThreadTraceContext::raw_tool_runtime_payload474–504 ↗
fn raw_tool_runtime_payload(
        &self,
        trace_event: crate::protocol_event::ToolRuntimeTraceEvent<'_>,
    ) -> Option<RawTraceEventPayload>

作用:把工具运行事件转换成 rollout 追踪系统能写的原始事件载荷。它会把详细工具信息单独存成 JSON,再在事件里引用它。

数据流:进去一个 ToolRuntimeTraceEvent,可能是 Started 或 Ended → 它根据类型写 ToolRuntimeEvent JSON 载荷;成功后组装 ToolCallRuntimeStarted 或 ToolCallRuntimeEnded → 出来 RawTraceEventPayload,写载荷失败则返回 None。

调用关系:record_tool_call_event 会通过它把协议层提取出的工具事件变成最终可写入的追踪事件。它内部依赖 write_json_payload_best_effort 保存详细内容。

调用图:调用 1 个内部函数(write_json_payload_best_effort)。

EnabledThreadTraceContext::append_best_effort506–510 ↗
fn append_best_effort(&self, payload: RawTraceEventPayload)

作用:尽量追加一条不带额外上下文的追踪事件。失败时只写警告,保证追踪不会拖垮正常运行。

数据流:进去一条 RawTraceEventPayload → 它调用 TraceWriter 追加事件;成功就写进追踪文件;失败就记录警告 → 没有返回业务结果,也不改变调用方流程。

调用关系:record_thread_started 会用它写 ThreadStarted。文件里多处启用路径也采用同样的“尽力而为”思路追加事件。

调用图:被 1 处调用(record_thread_started);外部调用 1 个(warn!)。

EnabledThreadTraceContext::append_with_context_best_effort512–524 ↗
fn append_with_context_best_effort(
        &self,
        codex_turn_id: CodexTurnId,
        payload: RawTraceEventPayload,
    )

作用:尽量追加一条带线程 id 和 Codex turn id 的追踪事件。这样事后能知道这条事件属于哪个线程、哪个对话回合。

数据流:进去 turn id 和事件载荷 → 它先组装 RawTraceEventContext,里面放当前线程 id 和传入的 turn id,再调用 TraceWriter 写入;失败就写警告 → 追踪文件里多出一条带上下文的事件,或在失败时保持主流程继续。

调用关系:记录 turn、工具运行、子代理结果等需要定位到具体回合的地方会用它。它是线程级上下文和底层 TraceWriter 之间的带标签写入通道。

调用图:外部调用 2 个(clone, warn!)。

rollout-trace/src/code_cell.rs源码 ↗
domain_logic代码模式单元运行期间,贯穿 start、initial response、end 等生命周期点

可以把一次代码运行想成餐厅里的一张订单:下单、先上了一部分、最后结账。这个文件就是给这张订单盖时间戳、保存小票的人。它提供一个 CodeCellTraceContext,外面执行代码时只要拿着它,在开始、首次响应、结束时各喊一声,它就会把事件写进 trace(运行痕迹记录)。如果当前不需要记录,它也能变成 disabled,也就是“空操作”,外面照样调用但什么都不写。它还会把运行时返回的原始响应单独保存成 JSON 负载,并把状态翻译成更好理解的几类:让出控制权、已终止、成功完成、失败。一个重要特点是“尽力而为”:写记录失败只打警告,不会因为追踪系统出错而打断真正的代码执行。

函数细节9
CodeCellTraceContext::disabled57–61 ↗
fn disabled() -> Self

作用:创建一个“不记录任何东西”的追踪上下文。这样调用方不用到处判断追踪有没有开启,直接调用记录函数也不会出事。

数据流:进去没有额外输入 → 它生成一个状态为 Disabled 的 CodeCellTraceContext → 出来的是一个安全的空追踪句柄,之后所有记录动作都会直接跳过,不改任何外部数据。

调用关系:它由 code_cell_trace_context 在发现当前不该或不能记录追踪时使用。后续 record_started、record_initial_response、record_ended 被调用时,会先看到这是 Disabled,然后马上返回。

调用图:被 1 处调用(code_cell_trace_context)。

CodeCellTraceContext::enabled64–78 ↗
fn enabled(
        writer: Arc<TraceWriter>,
        thread_id: impl Into<AgentThreadId>,
        codex_turn_id: impl Into<CodexTurnId>,
        runtime_cell_id: impl Into<String>,
    ) -> Self

作用:创建一个真正会写追踪记录的上下文。它把写入器、线程编号、对话轮次编号、运行单元编号这些关键信息绑在一起,方便后面每条记录都能落到正确位置。

数据流:进去的是 TraceWriter(一支写日志的笔)、线程 ID、轮次 ID、代码单元 ID → 它把这些值转换成内部统一格式并保存起来 → 出来的是 Enabled 状态的 CodeCellTraceContext,之后可以写开始、响应和结束事件。

调用关系:它由 code_cell_trace_context 在追踪可用时创建。创建好之后,真正执行代码的流程会带着这个上下文,在不同生命周期节点调用 record_started、record_initial_response 或 record_ended。

调用图:被 1 处调用(code_cell_trace_context);外部调用 2 个(into, Enabled)。

CodeCellTraceContext::record_started81–97 ↗
fn record_started(
        &self,
        model_visible_call_id: impl Into<ModelVisibleCallId>,
        source_js: impl Into<String>,
    )

作用:记录一个代码单元已经开始运行。它会把模型能看到的调用 ID 和实际要执行的 JavaScript 源码一起记下来。

数据流:进去的是模型可见的调用 ID 和 JavaScript 源码 → 如果上下文是 Disabled 就什么都不做;如果是 Enabled,就组装一条 CodeCellStarted 事件 → 最后交给 append_with_context_best_effort 写入追踪记录。

调用关系:它通常在 JavaScript 代码真正开始跑、并且可能发起嵌套工具调用之前被调用。它不直接写文件,而是把事件交给 append_with_context_best_effort,让后者补上线程和轮次信息后写入。

调用图:调用 1 个内部函数(append_with_context_best_effort);外部调用 1 个(into)。

CodeCellTraceContext::record_initial_response105–117 ↗
fn record_initial_response(&self, response: &RuntimeResponse)

作用:记录公开的代码执行工具第一次返回给外界的响应。这个响应可能只是“先把控制权还给模型,代码还在跑”,也可能已经带着最终结果。

数据流:进去的是 RuntimeResponse(运行时返回的一份结果)→ 如果追踪关闭就跳过;否则先用 code_cell_status_for_runtime_response 判断状态,再用 code_cell_response_payload 保存原始响应内容 → 最后组装 CodeCellInitialResponse 事件并写入追踪。

调用关系:它在代码执行工具拿到第一次响应时被调用。它会请 code_cell_status_for_runtime_response 翻译状态,请 code_cell_response_payload 保存详细响应,再交给 append_with_context_best_effort 追加事件。

调用图:调用 3 个内部函数(append_with_context_best_effort, code_cell_response_payload, code_cell_status_for_runtime_response)。

CodeCellTraceContext::record_ended120–132 ↗
fn record_ended(&self, response: &RuntimeResponse)

作用:记录一个代码单元的最终结束点。它告诉追踪系统:这次运行最后到底是成功、失败、终止,还是别的终态。

数据流:进去的是最终 RuntimeResponse → 如果上下文关闭就不写;如果开启,就把响应转换成生命周期状态,并尝试保存完整响应 JSON → 最后写入 CodeCellEnded 事件,表示这个运行单元到此结束。

调用关系:它由调用方在代码单元真正结束时调用,通常跟在终态响应之后。它和 record_initial_response 使用同一套状态判断与负载保存函数,最后同样通过 append_with_context_best_effort 写事件。

调用图:调用 3 个内部函数(append_with_context_best_effort, code_cell_response_payload, code_cell_status_for_runtime_response)。

code_cell_status_for_runtime_response135–147 ↗
fn code_cell_status_for_runtime_response(response: &RuntimeResponse) -> CodeCellRuntimeStatus

作用:把运行时的原始响应翻译成追踪系统更容易理解的状态。比如有错误文本就算失败,没有错误就算完成。

数据流:进去的是 RuntimeResponse → 它查看响应是哪一类:Yielded、Terminated,还是 Result,并在 Result 里检查有没有 error_text → 出来的是 CodeCellRuntimeStatus,例如 Yielded、Terminated、Failed 或 Completed。

调用关系:它服务于 record_initial_response 和 record_ended。两者都需要先知道这次响应代表什么状态,再把状态写进追踪事件里。

调用图:被 2 处调用(record_ended, record_initial_response)。

code_cell_response_payload149–158 ↗
fn code_cell_response_payload(
    context: &EnabledCodeCellTraceContext,
    response: &RuntimeResponse,
) -> Option<RawPayloadRef>

作用:把代码运行时返回的原始响应保存成一份可引用的 JSON 负载。这样事件本身不用塞满大段内容,只要保存一个引用就行。

数据流:进去的是已启用的追踪上下文和 RuntimeResponse → 它把响应包进 CodeCellResponseTracePayload,并标记为 ToolResult 类型 → 出来的是一个 RawPayloadRef 引用;如果保存失败,就返回 None。

调用关系:它被 record_initial_response 和 record_ended 使用,用来给这些生命周期事件附上详细响应内容。真正写 JSON 的工作继续交给 write_json_payload_best_effort。

调用图:调用 1 个内部函数(write_json_payload_best_effort);被 2 处调用(record_ended, record_initial_response)。

write_json_payload_best_effort160–172 ↗
fn write_json_payload_best_effort(
    writer: &TraceWriter,
    kind: RawPayloadKind,
    payload: &impl Serialize,
) -> Option<RawPayloadRef>

作用:尽量把一份数据写成 JSON 负载,但失败时不让主流程崩掉。它的设计目标是:追踪坏了可以报警,不能影响代码真正执行。

数据流:进去的是 TraceWriter、负载类型和可序列化的数据(可序列化就是能变成 JSON 这类格式)→ 它调用 writer.write_json_payload 尝试写入 → 成功就返回负载引用;失败就记录 warn 警告并返回 None。

调用关系:它是 code_cell_response_payload 的底层帮手。上层只关心有没有拿到负载引用,写入失败的细节由这里用警告日志说明。

调用图:调用 1 个内部函数(write_json_payload);被 1 处调用(code_cell_response_payload);外部调用 1 个(warn!)。

append_with_context_best_effort174–185 ↗
fn append_with_context_best_effort(
    context: &EnabledCodeCellTraceContext,
    payload: RawTraceEventPayload,
)

作用:把一条追踪事件连同“它属于哪个线程、哪个对话轮次”一起追加到记录里。失败时只报警,不打断正在运行的代码。

数据流:进去的是已启用的代码单元上下文和一条 RawTraceEventPayload 事件 → 它先组装 RawTraceEventContext,填入 thread_id 和 codex_turn_id → 然后调用 writer.append_with_context 写入;如果失败,就输出 warn 警告。

调用关系:record_started、record_initial_response 和 record_ended 都把最终事件交给它。它相当于统一的出口,保证每条代码单元事件都带着同样的上下文信息写入追踪系统。

调用图:被 3 处调用(record_ended, record_initial_response, record_started);外部调用 1 个(warn!)。

rollout-trace/src/compaction.rs源码 ↗
domain_logiccompaction request handling / cross-cutting tracing

这里的“压缩”不是压缩文件,而是把一长串对话历史整理成更短、更适合继续使用的历史。这个过程会向上游模型服务发请求,可能失败,也可能重试。这个文件就像给每次压缩配了一个随身记录员:先记下请求内容,再记下成功返回或失败原因,最后如果压缩结果真的替换了当前对话历史,还会记下这个“安装检查点”。它有一个重要设计:可以启用,也可以关闭。关闭时,调用方照样调用这些函数,但什么都不会写,避免业务代码到处判断“要不要记录”。真正写入时,它会把大块 JSON 内容先交给 TraceWriter 保存,再追加一条带有线程、回合、压缩编号的事件。写请求和响应时是“尽力而为”:失败就默默放弃,避免追踪系统影响主流程;安装检查点失败时会打警告,因为这一步更关键。

函数细节12
CompactionTraceContext::disabled91–95 ↗
fn disabled() -> Self

作用:创建一个“空记录员”。调用方可以放心把它传来传去、照常调用记录函数,但它不会真的写任何追踪数据。

数据流:进去没有额外数据 → 它构造一个状态为 Disabled 的 CompactionTraceContext → 出来一个可用但不记录东西的上下文,也不改动外部状态。

调用关系:它通常由 compaction_trace_context 在发现追踪功能不可用或不需要时使用。后面如果有人调用 start_attempt 或 record_installed,这个上下文会让这些操作直接跳过。

调用图:被 1 处调用(compaction_trace_context)。

CompactionTraceContext::enabled98–116 ↗
fn enabled(
        writer: Arc<TraceWriter>,
        thread_id: AgentThreadId,
        codex_turn_id: CodexTurnId,
        compaction_id: CompactionId,
        model: String,
        provider_name: S

作用:创建一个真正会记录压缩过程的上下文。它把写日志用的工具、当前线程、当前回合、压缩编号、模型名和服务商名都装在一起。

数据流:进去的是 TraceWriter、线程编号、回合编号、压缩编号、模型名、服务商名 → 它把这些信息放进 EnabledCompactionTraceContext → 出来一个 Enabled 状态的 CompactionTraceContext,之后每次记录都会带上这些身份信息。

调用关系:它由 compaction_trace_context 在追踪功能开启时调用。后续 compact_conversation_history 会拿这个上下文开始一次或多次上游压缩请求。

调用图:被 1 处调用(compaction_trace_context);外部调用 1 个(Enabled)。

CompactionTraceContext::start_attempt119–132 ↗
fn start_attempt(&self, request: &impl Serialize) -> CompactionTraceAttempt

作用:开始记录一次向上游模型服务发出的压缩请求。一次压缩可能会重试,所以每次尝试都会拿到一个新的请求编号。

数据流:进去的是当前上下文和要发送的请求内容 → 如果上下文是关闭的,就返回一个不会记录的 attempt;如果是开启的,就生成新的 compaction_request_id,创建一次请求尝试,并立刻记录“请求已开始”和请求原文 → 出来一个 CompactionTraceAttempt,后面用它记录成功或失败。

调用关系:它由 compact_conversation_history 在准备调用压缩接口时使用。它内部会在关闭状态下走 CompactionTraceAttempt::disabled,在开启状态下调用 next_compaction_request_id 生成编号,并创建 Enabled 状态的尝试对象。

调用图:调用 2 个内部函数(disabled, next_compaction_request_id);被 1 处调用(compact_conversation_history);外部调用 1 个(Enabled)。

CompactionTraceContext::record_installed138–166 ↗
fn record_installed(&self, checkpoint: &CompactionCheckpointTracePayload<'_>)

作用:记录压缩结果真正替换当前对话历史的那一刻。也就是说,它记录的是“这份新历史正式上岗了”。

数据流:进去的是一个 checkpoint,里面有压缩前选中的历史和压缩后要替换成的历史 → 如果追踪关闭就直接返回;如果开启,就先把 checkpoint 写成 JSON payload,再追加一条 CompactionInstalled 事件 → 成功后追踪文件里多出一份检查点内容和一条安装事件;失败时会用 warn! 打警告。

调用关系:它不属于某一次请求尝试,而属于整个压缩生命周期,因为多次请求重试最终只会安装一个检查点。它直接使用 TraceWriter 写 payload 和追加事件,没有走 attempt 那条记录成功/失败的流程。

调用图:外部调用 1 个(warn!)。

CompactionTraceAttempt::disabled171–175 ↗
fn disabled() -> Self

作用:创建一个“空的请求尝试记录器”。它让调用方即使在追踪关闭时,也能继续调用记录成功、失败等函数而不用额外判断。

数据流:进去没有额外数据 → 它构造一个状态为 Disabled 的 CompactionTraceAttempt → 出来一个不会写任何追踪数据的 attempt。

调用关系:它由 CompactionTraceContext::start_attempt 在发现上下文是关闭状态时调用。之后 record_completed、record_failed、record_result 这些操作都会因为 Disabled 状态而直接跳过。

调用图:被 1 处调用(start_attempt)。

CompactionTraceAttempt::record_started177–201 ↗
fn record_started(&self, request: &impl Serialize)

作用:记录一次压缩请求刚发起时的情况,尤其是请求的完整内容。这样以后排查问题时能看到当时到底给模型服务发了什么。

数据流:进去的是一次请求尝试和请求对象 → 如果 attempt 关闭就不做事;如果开启,就把请求序列化成 JSON payload,写成功后再追加一条 CompactionRequestStarted 事件,里面带上压缩编号、请求编号、线程、回合、模型和服务商 → 出来没有直接返回值,但追踪记录中会多出请求内容和开始事件;写失败时会安静放弃。

调用关系:它是 start_attempt 创建启用状态的 attempt 后用来打第一条记录的步骤。它把大块请求内容交给 write_json_payload_best_effort,再把事件追加工作交给 append_with_context_best_effort。

调用图:调用 2 个内部函数(append_with_context_best_effort, write_json_payload_best_effort)。

CompactionTraceAttempt::record_completed208–231 ↗
fn record_completed(&self, output_items: &[ResponseItem])

作用:记录一次压缩请求成功返回后的内容。它保存模型返回的输出项,作为之后审计和排错的证据。

数据流:进去的是一组 ResponseItem,也就是模型返回的结果项 → 它先把每个结果项转换成适合追踪保存的 JSON,再写成 CompactionResponse payload,最后追加一条 CompactionRequestCompleted 事件 → 出来没有直接返回值,但追踪中会多出响应内容和完成事件;如果写 payload 失败,就不追加完成事件。

调用关系:它由 record_result 在结果是 Ok 时调用。它和 record_started 一样,依赖 write_json_payload_best_effort 保存大块 JSON,再依赖 append_with_context_best_effort 追加带上下文的事件。

调用图:调用 2 个内部函数(append_with_context_best_effort, write_json_payload_best_effort);被 1 处调用(record_result);外部调用 1 个(iter)。

CompactionTraceAttempt::record_result234–239 ↗
fn record_result(&self, result: Result<&[ResponseItem], E>)

作用:给调用方一个省事入口:把压缩接口的结果直接交进来,它会自动判断该记录成功还是失败。

数据流:进去的是 Result:成功时里面是输出项列表,失败时里面是错误 → 它匹配结果,成功就调用 record_completed,失败就调用 record_failed → 出来没有直接返回值,但会根据结果留下成功响应记录或失败记录。

调用关系:它位于调用方和具体记录函数之间,减少调用方写 if/else。它把成功路径交给 CompactionTraceAttempt::record_completed,把失败路径交给 CompactionTraceAttempt::record_failed。

调用图:调用 2 个内部函数(record_completed, record_failed)。

CompactionTraceAttempt::record_failed242–254 ↗
fn record_failed(&self, error: impl Display)

作用:记录一次压缩请求在拿到正常响应之前失败了。它保存错误文字,方便之后知道失败原因。

数据流:进去的是一次请求尝试和一个可显示成文字的错误 → 如果 attempt 关闭就不做事;如果开启,就把错误转成字符串,并追加一条 CompactionRequestFailed 事件 → 出来没有直接返回值,但追踪中会多出失败事件。

调用关系:它由 record_result 在结果是 Err 时调用,也可以被调用方直接用来记录失败。它不需要写大块 JSON payload,只把事件交给 append_with_context_best_effort 追加。

调用图:调用 1 个内部函数(append_with_context_best_effort);被 1 处调用(record_result);外部调用 1 个(to_string)。

next_compaction_request_id257–260 ↗
fn next_compaction_request_id() -> CompactionRequestId

作用:生成一个全局递增的压缩请求编号。这样同一次压缩中的多次重试也能被区分开。

数据流:进去没有参数 → 它从全局原子计数器里取出当前数字并加一;原子计数器可以理解成多个线程也能安全使用的计数器 → 出来一个形如 compaction_request:数字 的 CompactionRequestId,同时全局计数器前进一位。

调用关系:它由 CompactionTraceContext::start_attempt 在开启一次新的上游请求尝试时调用。生成的编号会跟压缩编号一起写进开始、完成或失败事件里。

调用图:被 1 处调用(start_attempt);外部调用 1 个(format!)。

write_json_payload_best_effort262–268 ↗
fn write_json_payload_best_effort(
    writer: &TraceWriter,
    kind: RawPayloadKind,
    payload: &impl Serialize,
) -> Option<crate::RawPayloadRef>

作用:尽力把一块数据写成 JSON 追踪 payload。它的特点是失败不报错给上层,避免追踪系统拖垮正常压缩流程。

数据流:进去的是 TraceWriter、payload 类型和要序列化的数据 → 它调用 writer.write_json_payload 写入 JSON → 成功就出来一个 RawPayloadRef,表示这块内容存在哪里;失败就出来 None。

调用关系:它被 record_started 和 record_completed 使用,分别保存请求正文和响应正文。保存成功后,调用方才会继续追加对应事件。

调用图:调用 1 个内部函数(write_json_payload);被 2 处调用(record_completed, record_started)。

append_with_context_best_effort270–279 ↗
fn append_with_context_best_effort(
    context: &EnabledCompactionTraceContext,
    payload: RawTraceEventPayload,
)

作用:尽力追加一条带上下文的追踪事件。这里的上下文主要是线程编号和回合编号,让以后能知道这条事件属于哪段对话。

数据流:进去的是启用状态的压缩上下文和一条事件 payload → 它组装 RawTraceEventContext,把 thread_id 和 codex_turn_id 放进去,然后调用 writer.append_with_context → 出来没有返回值;追加失败也会被忽略。

调用关系:它被 record_started、record_completed 和 record_failed 共用,是这些事件最后写入追踪流的统一出口。这样每条压缩请求事件都会带上相同的线程和回合信息。

调用图:被 3 处调用(record_completed, record_failed, record_started)。

rollout-trace/src/inference.rs源码 ↗
domain_logicrequest handling

一次对上游模型的请求,可能因为重试、鉴权恢复、WebSocket 失败后改走 HTTP 等原因,实际发出好几次。这个文件给每一次“尝试”生成独立编号,记录请求内容、成功响应、失败原因或被取消时已经收到的部分输出。它像给每趟快递贴一个单号:不管最后送到了、半路丢了,还是被叫停了,都能查到这趟发生过什么。核心是 InferenceTraceContext 和 InferenceTraceAttempt。前者代表一个 Codex 回合里的追踪环境,后者代表一次具体请求尝试。关闭追踪时它们什么都不写,但调用方式不变。文件还很小心:写追踪失败不会影响真正的模型请求;结束事件用原子布尔值(一种线程安全的小开关)保证只记一次,避免重复写“成功/失败/取消”。

函数细节20
InferenceTraceContext::disabled96–100 ↗
fn disabled() -> Self

作用:创建一个“空的追踪上下文”。别人可以照常调用它,但它不会写任何追踪数据,适合未开启追踪或测试时使用。

数据流:进去不需要任何参数 → 它构造一个状态为 Disabled 的 InferenceTraceContext → 出来的是一个可传来传去、但所有记录动作都会自动忽略的上下文。

调用关系:很多请求路径会先拿到这个空上下文,比如预热 WebSocket、远程压缩请求或认证请求。这样调用方不用写一堆“如果开启追踪才记录”的分支。

调用图:被 18 处调用(prewarm_websocket, drain_to_completed, run_remote_compaction_request_v2, responses_respects_model_info_overrides_from_config, responses_stream_includes_subagent_header_on_other, responses_stream_includes_subagent_header_on_review, azure_responses_request_includes_store_and_reasoning_ids, send_provider_auth_request, responses_websocket_emits_rate_limit_events, responses_websocket_emits_reasoning_included_event (+8 more))。

InferenceTraceContext::enabled103–119 ↗
fn enabled(
        writer: Arc<TraceWriter>,
        thread_id: AgentThreadId,
        codex_turn_id: CodexTurnId,
        model: String,
        provider_name: String,
    ) -> Self

作用:创建一个真正会写追踪数据的上下文。它把写入器、线程编号、当前 Codex 回合编号、模型名和供应商名都放在一起,后续每次模型请求都会带上这些背景信息。

数据流:进去的是 TraceWriter、线程 ID、回合 ID、模型名和供应商名 → 它把这些信息包成 EnabledInferenceTraceContext → 出来的是一个 Enabled 状态的 InferenceTraceContext。

调用关系:会在真正需要记录模型调用的地方创建,也被相关测试使用。之后 InferenceTraceContext::start_attempt 会从它派生出一次具体请求尝试。

调用图:被 5 处调用(started_inference_attempt, responses_websocket_request_prewarm_traces_logical_request, enabled_attempt_adds_inference_request_header, enabled_context_records_replayable_inference_attempt, inference_trace_context);外部调用 1 个(Enabled)。

InferenceTraceContext::start_attempt122–134 ↗
fn start_attempt(&self) -> InferenceTraceAttempt

作用:为当前回合开始记录一次新的上游模型请求尝试。每次调用都会给这次尝试生成一个新的调用编号。

数据流:进去的是当前追踪上下文 → 如果追踪关闭,就返回一个空尝试;如果开启,就复制上下文、生成 UUID 形式的新 inference_call_id,并放入一个“结束只记一次”的开关 → 出来的是 InferenceTraceAttempt。

调用关系:stream_responses_api 和 stream_responses_websocket 在准备真正请求模型时会调用它。它会用 next_inference_call_id 生成编号,关闭时会退回 InferenceTraceAttempt::disabled。

调用图:调用 2 个内部函数(disabled, next_inference_call_id);被 2 处调用(stream_responses_api, stream_responses_websocket);外部调用 2 个(new, Enabled)。

InferenceTraceAttempt::disabled139–143 ↗
fn disabled() -> Self

作用:创建一个不会记录任何东西的请求尝试。它让后续代码仍然可以调用加请求头、记录开始、记录完成等方法,而不会产生实际追踪。

数据流:进去不需要参数 → 它把尝试状态设为 Disabled → 出来的是一个安全的空 InferenceTraceAttempt。

调用关系:当上下文关闭追踪时,start_attempt 会返回它。WebSocket 流程和测试也会直接用它来确认关闭状态不会偷偷加头或写记录。

调用图:被 4 处调用(stream_responses_websocket, response_stream_records_last_model_feedback_ids, start_attempt, disabled_attempt_adds_no_request_headers)。

InferenceTraceAttempt::inference_call_id145–152 ↗
fn inference_call_id(&self) -> Option<&str>

作用:取出这次模型请求尝试的追踪编号。如果追踪没开,就明确返回没有编号。

数据流:进去的是一次 InferenceTraceAttempt → 它查看内部状态;Disabled 就返回 None,Enabled 就拿出 inference_call_id 的字符串引用 → 出来是可选的编号。

调用关系:InferenceTraceAttempt::add_request_headers 会先调用它,只有拿到编号时才会给上游请求加追踪请求头。

调用图:被 1 处调用(add_request_headers)。

InferenceTraceAttempt::add_request_headers155–167 ↗
fn add_request_headers(&self, headers: &mut HeaderMap)

作用:给即将发往模型供应商的 HTTP 请求加一个追踪请求头。这个头像快递单号一样,让上游请求和本地追踪记录能对上。

数据流:进去的是可修改的 HTTP HeaderMap → 它先取 inference_call_id,再把这个字符串转成合法的 HeaderValue,成功后插入 x-codex-inference-call-id → 出来时 headers 可能多了一个追踪头;如果追踪关闭或转换失败,headers 不变。

调用关系:它依赖 InferenceTraceAttempt::inference_call_id。即使理论上 UUID 不该转 header 失败,这里也选择静默跳过,因为追踪不能害真正的模型请求失败。

调用图:调用 1 个内部函数(inference_call_id);外部调用 2 个(insert, from_str)。

InferenceTraceAttempt::record_started174–197 ↗
fn record_started(&self, request: &impl Serialize)

作用:记录一次模型请求已经开始,并保存模型实际能看到的请求内容。之后回放或排查时,就能知道当时到底发了什么。

数据流:进去的是一个可序列化的 request → 如果追踪关闭就不做事;如果开启,就尽力把 request 写成 JSON 原始载荷,再追加一条 InferenceStarted 事件,里面带调用编号、线程、回合、模型和供应商信息 → 出来没有返回值,但可能新增一个请求载荷和一条开始事件。

调用关系:它把写 JSON 的活交给 write_json_payload_best_effort,把追加事件的活交给 append_with_context_best_effort。调用方通常在上游请求真正发出前后用它留下起点证据。

调用图:调用 2 个内部函数(append_with_context_best_effort, write_json_payload_best_effort)。

InferenceTraceAttempt::record_completed205–234 ↗
fn record_completed(
        &self,
        response_id: &str,
        upstream_request_id: Option<&str>,
        token_usage: &Option<TokenUsage>,
        output_items: &[ResponseItem],
    )

作用:记录模型请求成功结束,并保存这次响应里的主要输出。这样追踪里不只有“成功了”,还有模型回了什么。

数据流:进去的是 response_id、可选的上游请求 ID、可选 token 用量和输出项列表 → 它先通过 take_terminal_attempt 抢占“结束事件名额”;如果已经记过结束就跳过;否则把响应整理成原始载荷,再追加 InferenceCompleted 事件 → 出来没有返回值,但可能新增响应载荷和完成事件。

调用关系:map_response_events 在读到模型流结束时会调用它。它依赖 write_response_payload_best_effort 写响应摘要,再用 append_with_context_best_effort 写事件。

调用图:调用 3 个内部函数(take_terminal_attempt, append_with_context_best_effort, write_response_payload_best_effort);被 1 处调用(map_response_events)。

InferenceTraceAttempt::record_failed237–266 ↗
fn record_failed(
        &self,
        error: impl Display,
        upstream_request_id: Option<&str>,
        output_items: &[ResponseItem],
    )

作用:记录一次模型请求失败,比如还没拿到完整响应就报错。若失败前已经收到一些完整输出项,也会把这些部分结果留下。

数据流:进去的是错误信息、可选上游请求 ID 和已收到的输出项 → 它先确保结束事件还没被记录;如果有部分输出,就写一份部分响应载荷;然后追加 InferenceFailed 事件,包含调用编号、错误文字和部分响应引用 → 出来没有返回值,但可能新增失败事件和部分响应载荷。

调用关系:map_response_events 在流处理中遇到错误时会调用它。它和 record_completed、record_cancelled 共用 take_terminal_attempt,保证一次尝试不会同时被记成成功和失败。

调用图:调用 3 个内部函数(take_terminal_attempt, append_with_context_best_effort, write_response_payload_best_effort);被 1 处调用(map_response_events);外部调用 2 个(to_string, is_empty)。

InferenceTraceAttempt::record_cancelled273–302 ↗
fn record_cancelled(
        &self,
        reason: impl Display,
        upstream_request_id: Option<&str>,
        output_items: &[ResponseItem],
    )

作用:记录一次请求不是失败,而是 Codex 主动不再继续读了,比如用户中断或新消息抢占了当前请求。它会保留取消前已经完整收到的输出。

数据流:进去的是取消原因、可选上游请求 ID 和已收到的输出项 → 它先用 take_terminal_attempt 确认还能写结束事件;有部分输出就写部分响应载荷;最后追加 InferenceCancelled 事件,带上原因和部分响应引用 → 出来没有返回值,但可能新增取消事件和部分响应载荷。

调用关系:map_response_events 在发现当前模型流被主动停止消费时会调用它。它复用 write_response_payload_best_effort 和 append_with_context_best_effort,和失败、成功走同一套收尾通道。

调用图:调用 3 个内部函数(take_terminal_attempt, append_with_context_best_effort, write_response_payload_best_effort);被 1 处调用(map_response_events);外部调用 2 个(to_string, is_empty)。

InferenceTraceAttempt::take_terminal_attempt304–313 ↗
fn take_terminal_attempt(&self) -> Option<&EnabledInferenceTraceAttempt>

作用:拿到这次尝试的“最终记录权”。它防止同一次模型请求被重复记成成功、失败或取消。

数据流:进去的是一次 InferenceTraceAttempt → 如果追踪关闭,返回 None;如果开启,就用原子布尔值把 terminal_recorded 从 false 改成 true;如果之前已经是 true,说明别人已经记过结尾,就返回 None → 出来是可选的启用态尝试引用。

调用关系:record_completed、record_failed 和 record_cancelled 都先调用它。它是三种结尾事件之间的守门员。

调用图:被 3 处调用(record_cancelled, record_completed, record_failed)。

trace_response_item_json321–345 ↗
fn trace_response_item_json(item: &ResponseItem) -> JsonValue

作用:把模型返回的一个响应项转成适合追踪保存的 JSON。特别是推理内容,它会补回普通序列化可能故意省掉的可读内容。

数据流:进去的是 ResponseItem → 它先尝试用 serde_json 转成 JSON;如果失败,就生成带 serialization_error 的 JSON;如果这是带 content 的 Reasoning 项,就把 content 手动塞回 JSON 对象 → 出来是一份更适合留证据的 JsonValue。

调用关系:测试 tests::traced_response_item_preserves_reasoning_content_omitted_by_normal_serializer 会直接验证它。响应载荷整理时也依赖这个规则,确保追踪看到的是 Codex 收到的内容,而不只是未来请求复用时的简化版本。

调用图:被 1 处调用(traced_response_item_preserves_reasoning_content_omitted_by_normal_serializer);外部调用 1 个(to_value)。

next_inference_call_id347–349 ↗
fn next_inference_call_id() -> InferenceCallId

作用:生成一次模型请求尝试的新编号。这个编号用 UUID,也就是几乎不会重复的一串随机标识。

数据流:进去不需要参数 → 它调用 UUID v4 生成器,再转成字符串 → 出来是一个 InferenceCallId。

调用关系:InferenceTraceContext::start_attempt 在创建启用态尝试时调用它,用来给每次上游请求贴唯一追踪标签。

调用图:被 1 处调用(start_attempt);外部调用 1 个(new_v4)。

write_json_payload_best_effort351–357 ↗
fn write_json_payload_best_effort(
    writer: &TraceWriter,
    kind: RawPayloadKind,
    payload: &impl Serialize,
) -> Option<crate::RawPayloadRef>

作用:尽力把一份数据写成追踪用的 JSON 原始载荷。所谓“尽力”,就是写失败也不报错给主流程。

数据流:进去的是 TraceWriter、载荷类型和可序列化数据 → 它调用 writer.write_json_payload;成功就返回载荷引用,失败就把错误吞掉并返回 None → 出来是可选的 RawPayloadRef。

调用关系:record_started 用它写请求载荷,write_response_payload_best_effort 用它写响应载荷。它体现了这个文件的重要原则:追踪不能影响真正业务。

调用图:调用 1 个内部函数(write_json_payload);被 2 处调用(record_started, write_response_payload_best_effort)。

write_response_payload_best_effort359–377 ↗
fn write_response_payload_best_effort(
    attempt: &EnabledInferenceTraceAttempt,
    response_id: Option<&str>,
    upstream_request_id: Option<&str>,
    token_usage: Option<&TokenUsage>,
    outpu

作用:把一次模型响应整理成追踪里的响应摘要,并尽力写到原始载荷区。它保存的是完整输出项摘要,而不是每个流式小片段。

数据流:进去的是启用态尝试、可选 response_id、可选上游请求 ID、可选 token 用量和输出项列表 → 它组装 TracedResponseStreamOutput,把输出项转换成追踪用 JSON,再交给 write_json_payload_best_effort 写入 → 出来是可选的响应载荷引用。

调用关系:record_completed、record_failed 和 record_cancelled 都会在需要保存完整或部分输出时调用它。它位于“模型事件”到“可回放证据”的中间。

调用图:调用 1 个内部函数(write_json_payload_best_effort);被 3 处调用(record_cancelled, record_completed, record_failed);外部调用 1 个(iter)。

append_with_context_best_effort379–388 ↗
fn append_with_context_best_effort(
    context: &EnabledInferenceTraceContext,
    payload: RawTraceEventPayload,
)

作用:给追踪日志追加一条事件,并自动带上当前线程和 Codex 回合信息。失败时也不打扰主流程。

数据流:进去的是启用态上下文和一条 RawTraceEventPayload → 它先做一个 RawTraceEventContext,填入 thread_id 和 codex_turn_id,再调用 writer.append_with_context;写入结果被忽略 → 出来没有返回值,但可能新增一条带上下文的追踪事件。

调用关系:record_started、record_completed、record_failed 和 record_cancelled 都用它写事件。它是本文件所有追踪事件落盘前的统一出口。

调用图:被 4 处调用(record_cancelled, record_completed, record_failed, record_started)。

tests::disabled_attempt_adds_no_request_headers405–411 ↗
fn disabled_attempt_adds_no_request_headers()

作用:验证关闭追踪时,不会给 HTTP 请求偷偷加任何追踪头。

数据流:进去是测试里新建的空 HeaderMap → 它创建 disabled attempt 并调用 add_request_headers → 最后断言 headers 仍然为空。

调用关系:这个测试覆盖 InferenceTraceAttempt::disabled 和 InferenceTraceAttempt::add_request_headers 的关闭路径,保证无追踪时对真实请求没有影响。

调用图:调用 1 个内部函数(disabled);外部调用 2 个(new, assert!)。

tests::enabled_attempt_adds_inference_request_header414–440 ↗
fn enabled_attempt_adds_inference_request_header() -> anyhow::Result<()>

作用:验证开启追踪后,一次请求尝试会把自己的调用编号放进 HTTP 请求头里,并且这个编号是合法 UUID。

数据流:进去是临时目录和新建的 TraceWriter → 它创建 enabled context,开始一次 attempt,把请求头加到 HeaderMap → 最后检查请求头存在、值等于 attempt 的编号,并能按 UUID 解析。

调用关系:这个测试串起 TraceWriter::create、InferenceTraceContext::enabled、start_attempt 和 add_request_headers,确认追踪编号能从本地尝试传到上游请求。

调用图:调用 2 个内部函数(enabled, create);外部调用 5 个(new, new, new, assert!, assert_eq!)。

tests::enabled_context_records_replayable_inference_attempt443–494 ↗
fn enabled_context_records_replayable_inference_attempt() -> anyhow::Result<()>

作用:验证开启追踪后,一次完整模型调用能被记录成可回放的资料,包括开始、完成、请求载荷和响应载荷。

数据流:进去是临时目录和一个 TraceWriter → 它先写线程开始和回合开始事件,再创建 enabled context,记录请求开始和请求完成 → 然后读取 replay_bundle,检查里面确实有一次已完成的 inference call,线程、回合、上游请求 ID 和原始载荷数量都正确。

调用关系:这个测试把 record_started、record_completed 和 replay_bundle 连起来,证明本文件写出的追踪数据能被后续回放/归约流程理解。

调用图:调用 2 个内部函数(enabled, create);外部调用 5 个(new, new, assert_eq!, replay_bundle, json!)。

tests::traced_response_item_preserves_reasoning_content_omitted_by_normal_serializer497–523 ↗
fn traced_response_item_preserves_reasoning_content_omitted_by_normal_serializer()

作用:验证追踪保存响应项时,会保留普通序列化省略掉的推理正文。这样排查问题时不会丢掉关键可读信息。

数据流:进去是测试构造的 Reasoning 响应项,里面有 summary、content 和 encrypted_content → 它分别做普通 JSON 序列化和 trace_response_item_json 转换 → 最后断言普通结果没有 content,而追踪结果包含 content。

调用关系:这个测试直接保护 trace_response_item_json 的特殊行为,防止以后改代码时把推理正文从追踪证据里弄丢。

调用图:调用 1 个内部函数(trace_response_item_json);外部调用 3 个(assert_eq!, to_value, vec!)。

rollout-trace/src/tool_dispatch.rs源码 ↗
domain_logicrequest handling

在这个系统里,模型或代码单元会去调用各种工具,比如执行命令、搜索网页、生成图片等。这个文件像一位旁边记流水账的书记员:工具刚要被派出去时,它写一条“开始”;工具回来或出错时,它再写一条“结束”。它还会把工具输入和结果整理成统一格式,方便后面做追踪、调试和审计。为了不影响主流程,它采用“尽力而为”的写法:如果记录失败,只打警告日志,不让工具调用本身失败。它也有一个“关闭记录”的上下文,外部代码可以照常调用,但什么都不会写,避免到处写判断。

函数细节19
ToolDispatchTraceContext::disabled131–135 ↗
fn disabled() -> Self

作用:创建一个“空的追踪句柄”。别人可以拿它来调用记录函数,但它不会真的写任何记录。

数据流:进去没有额外输入 → 它把内部状态设成 Disabled,也就是关闭状态 → 出来一个 ToolDispatchTraceContext,后续记录开始、结束或失败时都会直接跳过。

调用关系:外部的 start_tool_dispatch_trace 会在不需要追踪时使用它。ToolDispatchTraceContext::start 发现这次工具调用应该被隐藏时,也会走到这个关闭状态。

调用图:被 1 处调用(start_tool_dispatch_trace)。

ToolDispatchTraceContext::is_enabled141–143 ↗
fn is_enabled(&self) -> bool

作用:告诉调用方:这个追踪句柄现在是不是真的会写记录。这样外部代码可以避免白白整理一大段结果文本。

数据流:进去的是当前追踪句柄里的状态 → 它检查状态是不是 Enabled,也就是已开启 → 出来 true 或 false,不改动任何东西。

调用关系:外部的 record_completed 会用它判断有没有必要准备和记录结果。它只做判断,不把工作交给别的内部函数。

调用图:被 1 处调用(record_completed);外部调用 1 个(matches!)。

ToolDispatchTraceContext::start146–161 ↗
fn start(writer: Arc<TraceWriter>, invocation: ToolDispatchInvocation) -> Self

作用:开始记录一次工具调用。它会决定这次要不要记,并在需要时写下“工具调用开始”的事件。

数据流:进去的是写追踪用的 TraceWriter,以及一次工具调用的完整信息,比如线程、回合、工具名、输入和发起者 → 它先检查这次调用是否应该被压制;如果要压制,就返回空句柄;否则保存上下文并调用 record_started 写开始事件 → 出来一个句柄,后面用它记录成功、失败或结束。

调用关系:外部的 start_tool_dispatch_trace 会调用它来开启一次工具调用生命周期。它先问 suppresses_tool_dispatch_trace 是否要跳过,真正要记录时再把开始事件交给 record_started。

调用图:调用 2 个内部函数(record_started, suppresses_tool_dispatch_trace);被 1 处调用(start_tool_dispatch_trace);外部调用 2 个(disabled, Enabled)。

ToolDispatchTraceContext::record_completed164–177 ↗
fn record_completed(&self, status: ExecutionStatus, result: ToolDispatchResult)

作用:记录一次工具调用已经正常结束,并把工具返回给调用方的结果写进追踪。

数据流:进去的是执行状态,比如成功或失败,以及工具结果,可能是普通响应,也可能是代码模式的 JSON 值 → 如果当前上下文关闭,就什么也不做;如果开启,就把结果包装成追踪用的响应格式 → 最后写出一条“工具调用结束”事件。

调用关系:外部的 record_completed 在工具有正常结果后调用它。它不直接写文件,而是把收尾事件交给 append_tool_call_ended。

调用图:调用 1 个内部函数(append_tool_call_ended);被 1 处调用(record_completed)。

ToolDispatchTraceContext::record_failed180–191 ↗
fn record_failed(&self, error: impl Display)

作用:记录工具还没给出正常结果就失败了,比如调度阶段出错或执行前崩了。

数据流:进去的是一个能显示成文字的错误 → 如果追踪关闭,就跳过;如果开启,就把错误转成字符串,并标成 Failed 状态 → 出来没有返回值,但会尝试写一条带错误信息的结束事件。

调用关系:外部的 record_failed 在工具调用异常中断时使用它。它把错误包装好后,也交给 append_tool_call_ended 统一写结束事件。

调用图:调用 1 个内部函数(append_tool_call_ended);被 1 处调用(record_failed);外部调用 1 个(to_string)。

suppresses_tool_dispatch_trace194–198 ↗
fn suppresses_tool_dispatch_trace(invocation: &ToolDispatchInvocation) -> bool

作用:判断某些工具调用是不是不该在这里重复记录。主要是避免同一件事在代码模式下被记两遍。

数据流:进去的是一次工具调用的名字、命名空间和输入类型 → 它检查是否是公开的代码模式工具、没有命名空间、并且输入是 Custom → 出来 true 表示跳过记录,false 表示照常记录。

调用关系:ToolDispatchTraceContext::start 会先调用它。它像入口处的筛子,先把不该进入这套追踪边界的调用挡掉。

调用图:被 1 处调用(start);外部调用 1 个(matches!)。

record_started200–233 ↗
fn record_started(context: &EnabledToolDispatchTraceContext, invocation: ToolDispatchInvocation)

作用:真正写下“工具调用开始”的追踪事件。它把业务里的工具调用信息整理成追踪系统能保存的标准样子。

数据流:进去的是已开启的追踪上下文,以及一次工具调用的完整信息 → 它算出工具类别、显示标签、输入预览,把完整输入转成 JSON,并写成原始载荷引用;同时整理发起者信息 → 最后追加一条 ToolCallStarted 事件。

调用关系:ToolDispatchTraceContext::start 在确认要记录后调用它。它会依次借助 dispatched_tool_kind、dispatched_tool_label、requester_fields、write_json_payload_best_effort 和 append_with_context_best_effort 完成分类、打包和写入。

调用图:调用 5 个内部函数(append_with_context_best_effort, dispatched_tool_kind, dispatched_tool_label, requester_fields, write_json_payload_best_effort);被 1 处调用(start)。

requester_fields235–259 ↗
fn requester_fields(
    requester: ToolDispatchRequester,
) -> (
    Option<ModelVisibleCallId>,
    Option<CodeModeRuntimeToolId>,
    RawToolCallRequester,
)

作用:把“是谁发起了工具调用”拆成追踪事件需要的几个字段。发起者可能是模型,也可能是代码单元。

数据流:进去的是 ToolDispatchRequester → 如果是模型,就拿出模型可见的调用 ID,并标记 requester 为 Model;如果是代码单元,就拿出运行时工具调用 ID 和单元 ID,并标记 requester 为 CodeCell → 出来三个字段,供开始事件使用。

调用关系:record_started 在写开始事件前调用它。它只负责翻译发起者身份,不负责写追踪。

调用图:被 1 处调用(record_started)。

dispatched_tool_kind261–277 ↗
fn dispatched_tool_kind(tool_name: &str, _payload: &ToolDispatchPayload) -> ToolCallKind

作用:根据工具名字给工具调用分门别类,比如命令执行、网页搜索、图片生成、代理任务等。

数据流:进去的是工具名和输入载荷 → 它按工具名匹配一组已知类别;如果没认出来,就放进 Other,并保留原始名字 → 出来一个 ToolCallKind,方便追踪界面或分析工具按类别查看。

调用关系:record_started 用它来给 ToolCallStarted 事件填“工具类型”。它是分类器,不参与实际调用工具。

调用图:被 1 处调用(record_started)。

dispatched_tool_label279–288 ↗
fn dispatched_tool_label(
    tool_name: &str,
    tool_namespace: Option<&str>,
    _payload: &ToolDispatchPayload,
) -> String

作用:生成一个人能看懂的工具显示名。没有命名空间时只显示工具名,有命名空间时显示成“命名空间.工具名”。

数据流:进去的是工具名、可选命名空间和输入载荷 → 它判断命名空间是否存在;存在就拼接,不存在就原样使用工具名 → 出来一个字符串标签,用在追踪摘要里。

调用关系:record_started 调用它来生成开始事件里的摘要标签。它的输出会出现在 ToolCallSummary 里,让人回看记录时更容易识别工具。

调用图:被 1 处调用(record_started);外部调用 1 个(format!)。

ToolDispatchPayload::log_payload_preview291–298 ↗
fn log_payload_preview(&self) -> String

作用:从工具输入里截取一小段预览文字。这样日志里能看到大概输入,但不会塞进过长内容。

数据流:进去的是不同种类的工具输入:函数参数、搜索参数、自定义输入或本地命令 → 它挑出最适合展示的那段文字,比如搜索词或命令行 → 再交给 truncate_preview 截短,出来一段最多约 160 个字符的预览。

调用关系:record_started 需要生成开始事件的 input_preview 时会用它。它把具体截短工作交给 truncate_preview。

调用图:调用 1 个内部函数(truncate_preview)。

ToolDispatchPayload::into_json_payload300–333 ↗
fn into_json_payload(self) -> JsonValue

作用:把各种不同形状的工具输入统一转成 JSON。JSON 可以理解成一种通用的数据包装格式,方便保存和后续读取。

数据流:进去的是一个 ToolDispatchPayload,可能是函数参数、搜索请求、自定义输入或本地 shell 命令 → 它按类型放入 type 字段,并把对应内容放进标准字段里 → 出来一个 JsonValue,后面可以写入追踪载荷。

调用关系:record_started 在保存完整调用输入时使用它。它只负责把输入变成统一数据形状,写入动作由 write_json_payload_best_effort 完成。

调用图:外部调用 1 个(json!)。

truncate_preview336–344 ↗
fn truncate_preview(value: &str) -> String

作用:把一段文字缩短到适合放进摘要的长度。它防止很长的工具输入把追踪摘要撑爆。

数据流:进去的是任意字符串 → 它按字符取前 160 个字符;如果原文还有更多内容,就在末尾加上省略号 → 出来一段短预览,不改动原文。

调用关系:ToolDispatchPayload::log_payload_preview 会调用它。它是一个小工具函数,专门服务输入预览。

调用图:被 1 处调用(log_payload_preview)。

append_tool_call_ended346–361 ↗
fn append_tool_call_ended(
    context: &EnabledToolDispatchTraceContext,
    status: ExecutionStatus,
    response: &DispatchedToolTraceResponse<'_>,
)

作用:统一写“工具调用结束”的事件,不管结果是成功、失败,还是错误包装后的失败。

数据流:进去的是追踪上下文、执行状态和响应内容 → 它先尝试把响应写成 JSON 载荷,得到一个载荷引用;然后把工具调用 ID、状态和结果引用组成 ToolCallEnded 事件 → 最后把事件追加到追踪里。

调用关系:ToolDispatchTraceContext::record_completed 和 ToolDispatchTraceContext::record_failed 都会调用它。它再把写载荷交给 write_json_payload_best_effort,把追加事件交给 append_with_context_best_effort。

调用图:调用 2 个内部函数(append_with_context_best_effort, write_json_payload_best_effort);被 2 处调用(record_completed, record_failed)。

write_json_payload_best_effort363–375 ↗
fn write_json_payload_best_effort(
    writer: &TraceWriter,
    kind: RawPayloadKind,
    payload: &impl Serialize,
) -> Option<RawPayloadRef>

作用:尽量把一段 JSON 载荷写进追踪存储里。写失败时只报警,不打断主流程。

数据流:进去的是 TraceWriter、载荷种类和可序列化的数据;可序列化就是能变成保存格式的数据 → 它调用 writer.write_json_payload 尝试写入 → 成功时出来一个 RawPayloadRef,像文件收据;失败时打印警告并返回 None。

调用关系:record_started 用它写工具输入,append_tool_call_ended 用它写工具结果。它是追踪写入的安全垫,保证记录失败不会影响工具执行。

调用图:调用 1 个内部函数(write_json_payload);被 2 处调用(append_tool_call_ended, record_started);外部调用 1 个(warn!)。

append_with_context_best_effort377–388 ↗
fn append_with_context_best_effort(
    context: &EnabledToolDispatchTraceContext,
    payload: RawTraceEventPayload,
)

作用:尽量把一条追踪事件连同线程和回合信息写出去。失败时只记警告,不让系统因为追踪失败而停下。

数据流:进去的是已开启的追踪上下文和一条原始追踪事件 → 它从上下文里取出 thread_id 和 codex_turn_id,组成事件上下文 → 调用 writer.append_with_context 追加事件;如果出错,就打印警告。

调用关系:record_started 用它写开始事件,append_tool_call_ended 用它写结束事件。它负责把事件挂到正确的对话线程和回合下面。

调用图:被 2 处调用(append_tool_call_ended, record_started);外部调用 1 个(warn!)。

tests::suppresses_only_noncanonical_dispatch_boundaries395–426 ↗
fn suppresses_only_noncanonical_dispatch_boundaries()

作用:测试哪些工具调用会被跳过记录,哪些不会。它防止以后有人改错过滤规则,导致重要调用被漏记或重复记。

数据流:进去的是测试里构造的几种工具调用场景 → 它分别调用 suppresses_tool_dispatch_trace 判断结果 → 用断言确认只有特定的代码模式公开工具调用会被压制,其他自定义工具或带命名空间的工具不会被压制。

调用关系:这是测试代码,运行测试时由测试框架调用。它依赖 tests::invocation 构造输入,核心检查对象是 suppresses_tool_dispatch_trace。

调用图:外部调用 1 个(assert!)。

tests::classifies_interrupt_agent_as_close_agent429–439 ↗
fn classifies_interrupt_agent_as_close_agent()

作用:测试 interrupt_agent 这个工具名会被归到 CloseAgent 类别。这样追踪里“中断代理”和“关闭代理”会被当成同类行为看待。

数据流:进去的是测试构造的工具名 interrupt_agent 和一个函数输入载荷 → 它调用 dispatched_tool_kind 做分类 → 用断言确认结果等于 ToolCallKind::CloseAgent。

调用关系:这是测试代码,运行测试时由测试框架调用。它专门保护 dispatched_tool_kind 的一个重要分类规则。

调用图:外部调用 1 个(assert_eq!)。

tests::invocation441–456 ↗
fn invocation(
        tool_name: &str,
        tool_namespace: Option<String>,
        requester: ToolDispatchRequester,
        payload: ToolDispatchPayload,
    ) -> ToolDispatchInvocation

作用:帮测试快速造出一份 ToolDispatchInvocation。这样测试不用每次重复填写线程、回合和工具调用 ID。

数据流:进去的是工具名、可选命名空间、发起者和输入载荷 → 它补上固定的 thread_id、codex_turn_id 和 tool_call_id,并把工具名转成字符串 → 出来一份完整的 ToolDispatchInvocation 测试数据。

调用关系:tests::suppresses_only_noncanonical_dispatch_boundaries 会用它准备不同场景。它只服务测试,不参与真实运行时的追踪记录。

rollout-trace/src/mcp.rs源码 ↗
domain_logicrequest handling / cross-cutting

这个文件做的事很小,但很关键:当系统真的要执行一次 MCP 后端调用时,它保存一个由追踪系统生成的唯一编号,并在发请求前把这个编号塞进请求的 metadata(附带信息)里。可以把它想成给包裹贴快递单号:包裹内容不变,但以后查物流、对账、排错都靠这个号。核心结构是 McpCallTraceContext,它可能是“启用”的,也可能是“关闭”的。关闭时什么都不改,保证不会影响正常请求;启用时,它会把 codex_bridge_mcp_call_id 这个内部专用字段加到 JSON metadata 对象里。如果 metadata 本来没有,它会新建一个;如果 metadata 不是对象这种预期格式,它宁愿不动它,也不冒险把请求弄坏。这体现了一个重要原则:追踪是尽力而为,不能为了记录信息破坏真实业务。

函数细节6
McpCallTraceContext::disabled20–22 ↗
fn disabled() -> Self

作用:创建一个“关闭追踪”的 MCP 调用上下文。有人在不需要追踪、或当前不能追踪时用它,这样后续代码仍然可以照常调用同一套接口。

数据流:进去没有参数 → 它生成一个不带 MCP 调用编号的 McpCallTraceContext → 出来的是一个安全的空上下文,之后给请求加 metadata 时不会改动任何东西。

调用关系:它会被 start_mcp_call_trace 在决定不启用 MCP 追踪时使用。这样上层流程不用到处写特殊判断,只要拿到一个上下文,后面照常走 add_request_meta 这类步骤即可。

调用图:被 1 处调用(start_mcp_call_trace)。

McpCallTraceContext::enabled25–29 ↗
fn enabled(mcp_call_id: McpCallId) -> Self

作用:创建一个“启用追踪”的 MCP 调用上下文,并把这次真实 MCP 调用的唯一编号放进去。这个编号之后会被写进请求附带信息里,用来做关联。

数据流:进去一个 mcp_call_id,也就是这次 MCP 调用的追踪编号 → 它把编号保存到 McpCallTraceContext 里 → 出来的是一个带编号的上下文,后续可以用它给外发请求贴上追踪标记。

调用关系:它会被 start_mcp_call_trace 在确定要追踪一次 MCP 调用时使用;测试 tests::enabled_mcp_trace_adds_bridge_correlation_meta 也会调用它,确认启用后确实能把编号加到请求 metadata 中。

调用图:被 2 处调用(enabled_mcp_trace_adds_bridge_correlation_meta, start_mcp_call_trace)。

McpCallTraceContext::mcp_call_id32–34 ↗
fn mcp_call_id(&self) -> Option<&str>

作用:取出当前上下文里保存的 MCP 调用编号。如果追踪没开,它就告诉调用者“没有编号”。

数据流:进去的是当前 McpCallTraceContext 自己 → 它查看内部有没有 mcp_call_id → 出来是一个可选的字符串引用:有追踪就返回编号,没有追踪就返回空。

调用关系:它主要服务于 McpCallTraceContext::add_request_meta。add_request_meta 先问它有没有编号;没有就不改请求,有就继续把编号写进 metadata。

调用图:被 1 处调用(add_request_meta)。

McpCallTraceContext::add_request_meta37–63 ↗
fn add_request_meta(&self, meta: Option<JsonValue>) -> Option<JsonValue>

作用:把 MCP 调用追踪编号加到一条即将发出的请求 metadata 里。metadata 可以理解成请求的“备注栏”,这里专门放一个内部关联用的编号。

数据流:进去一个可能存在的 JSON metadata,以及当前上下文里可能有的追踪编号 → 它先读取 mcp_call_id;如果没有编号,就原样返回 metadata;如果有编号,并且 metadata 是 JSON 对象,就往里面加入 codex_bridge_mcp_call_id;如果原来没有 metadata,就新建一个对象再加入编号;如果 metadata 是意外的非对象格式,就保持不变 → 出来的是新的或原样的 metadata,请求内容本身不被破坏。

调用关系:它是这个文件真正把追踪信息接到 MCP 请求上的地方。它内部会调用 McpCallTraceContext::mcp_call_id 来判断是否需要工作;需要写入时会借助 JSON 对象、字符串和新建 map 这些 serde_json 提供的工具完成。

调用图:调用 1 个内部函数(mcp_call_id);外部调用 3 个(Object, String, new)。

tests::disabled_mcp_trace_leaves_request_meta_unchanged74–81 ↗
fn disabled_mcp_trace_leaves_request_meta_unchanged()

作用:验证关闭追踪时不会偷偷改请求 metadata。这很重要,因为追踪功能不能影响正常请求。

数据流:进去是一段测试用 metadata,比如 source 为 test → 测试创建关闭状态的上下文,并调用 add_request_meta → 出来应该还是完全相同的 metadata;如果有任何变化,测试就会失败。

调用关系:这是给 McpCallTraceContext::disabled 和 add_request_meta 的安全网。它用 json! 构造测试数据,用 assert_eq! 检查关闭追踪时结果必须原样返回。

调用图:外部调用 2 个(assert_eq!, json!)。

tests::enabled_mcp_trace_adds_bridge_correlation_meta84–98 ↗
fn enabled_mcp_trace_adds_bridge_correlation_meta()

作用:验证启用追踪时,系统会把内部关联编号正确加到请求 metadata 里,同时不丢掉原有字段。

数据流:进去是一个固定的 MCP 调用编号和一段已有 metadata → 测试用 McpCallTraceContext::enabled 创建带编号的上下文,再调用 add_request_meta → 出来的 metadata 应该仍有原来的 source 字段,并额外多出 codex_bridge_mcp_call_id 字段,值就是追踪编号。

调用关系:这是启用追踪路径的检查。它先调用 McpCallTraceContext::enabled 造出带编号的上下文,再通过断言确认 add_request_meta 的结果符合预期。

调用图:调用 1 个内部函数(enabled);外部调用 2 个(assert_eq!, json!)。

reducer 入口与对话重建

这些文件建立确定性重放,并根据规范化的 payload 内容以及推理或压缩历史,重建模型可见的对话记录。

rollout-trace/src/reducer/conversation.rs源码 ↗
domain_logicrequest handling / trace reduction

模型请求和响应里常常带着一份“当前对话快照”,但这些快照可能重复旧内容、只追加新内容,或者在压缩上下文后换成摘要。如果不仔细对齐,同一句话可能被当成两条,或者新内容误用了旧编号。这个文件就是对话整理器。它先读取原始 JSON 载荷,把里面的模型消息标准化;再和线程里上一份快照比对,能复用旧对话项编号就复用,确实是新内容才创建新编号。模型回答会被立刻追加到对话里,因为这是模型刚刚产生的内容。压缩检查点会额外插入一个“边界标记”,说明旧历史到这里结束,后面安装的是压缩后的新历史。文件里还特别处理推理内容:有时同一段推理一次带可读文本,一次只带加密内容,它会用加密内容认定“这是同一个东西”,并尽量合并可读信息。

函数细节21
TraceReducer::reduce_inference_request34–129 ↗
fn reduce_inference_request(
        &mut self,
        wall_time_unix_ms: i64,
        inference_call_id: &InferenceCallId,
        thread_id: &str,
        codex_turn_id: &str,
        request_paylo

作用:把一次发给模型的请求整理成模型可见的对话项列表。它确保重复出现的历史不会被当成新消息,同时新增内容能得到自己的编号。

数据流:输入是时间、推理调用编号、线程编号、回合编号和请求载荷引用。它先读取载荷 JSON,取出 input 数组并标准化;如果请求声明了 previous_response_id,就从上一轮请求和响应拼出省略的历史前缀,再只追加本次新增部分;如果是完整快照,就和当前线程快照或压缩后的替换快照做比对。最后它把得到的对话项编号追加到线程里,更新线程当前快照,并返回这些编号。

调用关系:这是处理模型请求时的入口之一。它把标准化工作交给 normalize_model_items,把“对齐旧项还是创建新项”的核心工作交给 TraceReducer::reconcile_conversation_items,最后用 TraceReducer::append_thread_conversation_items 写回线程对话列表。

调用图:调用 3 个内部函数(append_thread_conversation_items, reconcile_conversation_items, normalize_model_items);外部调用 3 个(new, Ok, bail!)。

TraceReducer::reduce_inference_response132–191 ↗
fn reduce_inference_response(
        &mut self,
        wall_time_unix_ms: i64,
        inference_call_id: &InferenceCallId,
        response_payload: &RawPayloadRef,
    ) -> Result<Vec<String>>

作用:把模型返回的回答整理成新的对话项。因为这些内容是模型刚产生的,所以它们会立刻进入对话时间线。

数据流:输入是时间、推理调用编号和响应载荷引用。它读取 JSON,取出 output_items 数组;再从已记录的推理调用里找到所属线程和回合;随后标准化输出项,从当前对话末尾开始追加。它还会读取 token_usage,也就是这次调用消耗了多少 token,并写回对应的推理调用记录。最后返回模型回答对应的对话项编号。

调用关系:这是处理模型响应时的入口。它调用 normalize_model_items 清洗模型输出,再调用 TraceReducer::reconcile_conversation_items 创建或确认对话项,然后通过 TraceReducer::append_thread_conversation_items 把结果挂到线程上。

调用图:调用 3 个内部函数(append_thread_conversation_items, reconcile_conversation_items, normalize_model_items);外部调用 3 个(Ok, bail!, vec!)。

TraceReducer::reconcile_conversation_items193–277 ↗
fn reconcile_conversation_items(
        &mut self,
        items: Vec<NormalizedConversationItem>,
        context: ReconcileItems<'_>,
    ) -> Result<Vec<String>>

作用:把一批标准化后的对话项和已有快照对齐。简单说,就是判断“这是以前见过的同一条”,还是“这是一条新消息”。

数据流:输入是一批标准化对话项,以及线程、回合、时间、生产来源、起始位置、对齐模式等上下文。它逐条检查:先确认 call_id 没有被同一线程拿来表示不同内容;再按位置或按内容寻找可复用的旧编号;找不到时创建新对话项。每处理一条,还会更新生产来源、连接工具或代码单元相关关系,并处理等待中的边。最后清理等待中的代码单元开始记录,返回本批对话项编号。

调用关系:它是请求和响应共用的核心齿轮,由 TraceReducer::reduce_inference_request 和 TraceReducer::reduce_inference_response 调用。它内部会用 TraceReducer::item_matches、TraceReducer::find_matching_snapshot_item 判断是否同一项,用 TraceReducer::create_conversation_item 创建新项,用 TraceReducer::update_conversation_item_from_sighting 补充已见信息。

调用图:调用 5 个内部函数(create_conversation_item, ensure_call_id_consistency, find_matching_snapshot_item, item_matches, update_conversation_item_from_sighting);被 2 处调用(reduce_inference_request, reduce_inference_response);外部调用 4 个(with_capacity, Ok, bail!, matches!)。

TraceReducer::reduce_compaction_checkpoint283–358 ↗
fn reduce_compaction_checkpoint(
        &mut self,
        wall_time_unix_ms: i64,
        thread_id: &str,
        codex_turn_id: &str,
        compaction_id: &CompactionId,
        checkpoint_paylo

作用:处理一次“压缩对话历史”的检查点。它记录压缩前的输入历史、插入一个边界标记,并安装压缩后的替换历史。

数据流:输入是时间、线程编号、回合编号、压缩编号和检查点载荷引用。它读取 JSON,要求里面有 input_history 和 replacement_history 两个数组;把两边都标准化。旧输入历史会尽量和当前快照里的项对齐;随后创建一个空内容的压缩边界标记;替换历史则作为压缩后的新历史安装,故意不复用压缩前的旧编号。最后把输入项、边界标记、替换项依次追加到线程对话里,并返回这三组编号。

调用关系:这是压缩检查点进入对话系统的入口。它用 required_array 检查必要字段,用 normalize_model_items 标准化内容,用 TraceReducer::reconcile_detached_conversation_items 对齐不直接代表当前快照的历史片段,并用 TraceReducer::create_conversation_item 创建压缩边界标记。

调用图:调用 5 个内部函数(append_thread_conversation_items, create_conversation_item, reconcile_detached_conversation_items, normalize_model_items, required_array);外部调用 4 个(new, Ok, from_ref, vec!)。

TraceReducer::reconcile_detached_conversation_items360–402 ↗
fn reconcile_detached_conversation_items(
        &mut self,
        items: Vec<NormalizedConversationItem>,
        context: DetachedReconcileItems<'_>,
    ) -> Result<Vec<String>>

作用:对齐一批“离线”的对话项,也就是它们不是当前实时快照的一部分,但仍需要识别是否和候选旧项相同。

数据流:输入是一批标准化对话项,以及线程、回合、时间、生产来源和候选旧编号。它逐条确认 call_id 没冲突,然后在候选列表里找内容相同且还没用过的项;找不到就创建新项。之后它更新该项的已见信息,连接工具或代码单元关系,处理等待中的边。最后返回这一批编号。

调用关系:它主要服务于 TraceReducer::reduce_compaction_checkpoint,用来处理压缩前输入历史和压缩后替换历史。它和 TraceReducer::reconcile_conversation_items 做的事很像,但不依赖线程当前快照的位置。

调用图:调用 3 个内部函数(ensure_call_id_consistency, find_matching_snapshot_item, update_conversation_item_from_sighting);被 1 处调用(reduce_compaction_checkpoint);外部调用 2 个(with_capacity, Ok)。

TraceReducer::create_conversation_item404–430 ↗
fn create_conversation_item(
        &mut self,
        thread_id: &str,
        codex_turn_id: Option<String>,
        first_seen_at_unix_ms: i64,
        item: NormalizedConversationItem,
        pr

作用:创建一条全新的对话项记录,并放进总的 rollout 数据里。可以把它理解成给新消息办一张身份证。

数据流:输入是线程编号、可选回合编号、首次看到的时间、标准化后的消息内容,以及是谁产生了它。它先通过 TraceReducer::next_conversation_item_id 生成新编号,然后把角色、频道、类型、正文、call_id、来源等信息组装成 ConversationItem 存入 rollout.conversation_items。输出是新编号。

调用关系:当对齐逻辑找不到可复用旧项时,就会调用它。它被 TraceReducer::reconcile_conversation_items 和 TraceReducer::reduce_compaction_checkpoint 使用,是所有新对话项落库的地方。

调用图:调用 1 个内部函数(next_conversation_item_id);被 2 处调用(reconcile_conversation_items, reduce_compaction_checkpoint)。

TraceReducer::update_conversation_item_from_sighting432–451 ↗
fn update_conversation_item_from_sighting(
        &mut self,
        item_id: &str,
        normalized: &NormalizedConversationItem,
        produced_by: &[ProducerRef],
    ) -> Result<()>

作用:当同一条对话项再次在载荷里出现时,用这次看到的信息补充旧记录。尤其会处理推理内容的合并。

数据流:输入是已有对话项编号、这次看到的标准化内容,以及这次内容的生产来源。它先找到已有记录;如果是推理项,就调用 merge_reasoning_body 把可读文本、摘要和加密内容尽量合在一起;然后把新的生产来源补进 produced_by,避免重复添加。成功时不返回新数据,只修改已有记录。

调用关系:它由 TraceReducer::reconcile_conversation_items 和 TraceReducer::reconcile_detached_conversation_items 在每次确认一项后调用。它让“同一项多次出现”不会丢掉后来看见的补充信息。

调用图:调用 1 个内部函数(merge_reasoning_body);被 2 处调用(reconcile_conversation_items, reconcile_detached_conversation_items);外部调用 2 个(Ok, bail!)。

TraceReducer::append_thread_conversation_items453–465 ↗
fn append_thread_conversation_items(
        &mut self,
        thread_id: &str,
        item_ids: &[String],
    ) -> Result<()>

作用:把一批对话项编号挂到某个线程的对话列表末尾,并避免重复挂同一个编号。

数据流:输入是线程编号和对话项编号列表。它找到对应线程,逐个检查编号是否已经在线程列表里;没有的话就追加进去。输出只是成功或失败状态,实际变化是线程的 conversation_item_ids 变长。

调用关系:请求、响应和压缩检查点处理完后都会调用它。它是把全局对话项真正串成“某个线程里的聊天记录”的最后一步。

调用图:被 3 处调用(reduce_compaction_checkpoint, reduce_inference_request, reduce_inference_response);外部调用 1 个(Ok)。

TraceReducer::find_matching_snapshot_item467–479 ↗
fn find_matching_snapshot_item(
        &self,
        previous_snapshot: &[String],
        used_item_ids: &[String],
        normalized: &NormalizedConversationItem,
    ) -> Option<String>

作用:在旧快照里找一条内容相同、并且本次还没被用过的对话项编号。

数据流:输入是旧快照编号列表、本次已经使用过的编号列表,以及一条标准化内容。它遍历旧快照,跳过已经用过的编号,然后用 TraceReducer::item_matches 判断内容是否一致。找到就返回旧编号,找不到返回空。

调用关系:它被 TraceReducer::reconcile_conversation_items 和 TraceReducer::reconcile_detached_conversation_items 调用。它解决的是“同样内容在快照里换了位置,还能不能认出它”的问题。

调用图:被 2 处调用(reconcile_conversation_items, reconcile_detached_conversation_items)。

TraceReducer::ensure_call_id_consistency481–499 ↗
fn ensure_call_id_consistency(
        &self,
        thread_id: &str,
        normalized: &NormalizedConversationItem,
    ) -> Result<()>

作用:检查同一个线程里,模型可见的 call_id 没有被拿来表示两段不同内容。call_id 可以理解成一次工具调用或代码调用的标签,标签不能乱贴。

数据流:输入是线程编号和一条标准化对话项。如果这条没有 call_id,它直接成功;如果有,它会扫描已有对话项,找同线程、同 call_id、同类型的记录。只要发现内容不同,就报错;否则成功。

调用关系:它在 TraceReducer::reconcile_conversation_items 和 TraceReducer::reconcile_detached_conversation_items 处理每条项前都会被调用。它内部用 conversation_item_matches 做严格内容比较,是防止历史串线的重要保险。

调用图:调用 1 个内部函数(conversation_item_matches);被 2 处调用(reconcile_conversation_items, reconcile_detached_conversation_items);外部调用 2 个(Ok, bail!)。

TraceReducer::item_matches501–506 ↗
fn item_matches(&self, item_id: &str, normalized: &NormalizedConversationItem) -> bool

作用:判断某个已存在的对话项编号,是否和当前标准化内容代表同一条消息。

数据流:输入是已有对话项编号和标准化内容。它先从 rollout.conversation_items 里取出旧记录;如果编号不存在,就返回 false;存在的话调用 conversation_item_matches 比较角色、类型、正文、call_id 等信息。输出是匹配或不匹配。

调用关系:它被 TraceReducer::reconcile_conversation_items 用来按位置确认旧快照项是否还能复用,也被间接用于寻找可复用项的判断链路。

调用图:调用 1 个内部函数(conversation_item_matches);被 1 处调用(reconcile_conversation_items)。

TraceReducer::next_conversation_item_id508–512 ↗
fn next_conversation_item_id(&mut self) -> String

作用:生成下一个对话项编号。它让每条新对话项都有一个不会撞车的名字。

数据流:它读取 reducer 里的 next_conversation_item_ordinal 数字,把当前数字格式化成 conversation_item:数字;随后把计数器加一。输出是生成好的字符串编号。

调用关系:它只被 TraceReducer::create_conversation_item 调用。每当系统确认需要新建一条对话项时,这个函数负责发号。

调用图:被 1 处调用(create_conversation_item);外部调用 1 个(format!)。

required_array556–567 ↗
fn required_array(
    payload: &'a Value,
    key: &str,
    raw_payload: &RawPayloadRef,
) -> Result<&'a Vec<Value>>

作用:从 JSON 载荷里取出一个必须存在的数组字段。少了或类型不对,就给出带载荷编号的错误信息。

数据流:输入是 JSON 对象、字段名和原始载荷引用。它尝试按字段名读取并确认是数组;成功就返回数组引用,失败就返回说明哪个压缩检查点载荷缺了哪个数组的错误。

调用关系:它被 TraceReducer::reduce_compaction_checkpoint 调用,用来检查 input_history 和 replacement_history。它让压缩检查点格式问题能尽早、清楚地暴露出来。

调用图:被 1 处调用(reduce_compaction_checkpoint);外部调用 1 个(get)。

conversation_item_matches569–587 ↗
fn conversation_item_matches(
    item: &ConversationItem,
    normalized: &NormalizedConversationItem,
) -> bool

作用:完整判断一条已保存的对话项和一条标准化内容是否是同一项。

数据流:输入是已保存的 ConversationItem 和标准化内容。它先比较正文:普通正文走 conversation_body_matches,推理正文走 reasoning_body_matches;然后再比较角色、频道、类型、代理消息、call_id 等字段。所有关键字段都一致才返回 true。

调用关系:它是多个一致性检查的基础,被 TraceReducer::ensure_call_id_consistency 和 TraceReducer::item_matches 调用。它把“同一条消息”的规则集中在一个地方。

调用图:调用 2 个内部函数(conversation_body_matches, reasoning_body_matches);被 2 处调用(ensure_call_id_consistency, item_matches)。

conversation_body_matches589–608 ↗
fn conversation_body_matches(left: &ConversationBody, right: &ConversationBody) -> bool

作用:比较两段对话正文是否相同。它对 JSON 正文做了特别处理:只比较摘要,不把原始载荷编号差异当成内容差异。

数据流:输入是左右两份 ConversationBody。它先要求两边片段数量相同,再逐段比较;如果两段都是 JSON 片段,就只比 summary;其他片段按完整内容比较。输出是正文是否一致。

调用关系:它被 conversation_item_matches 用于普通正文比较,也被 reasoning_body_matches 和 merge_reasoning_body 用来处理推理正文。它是正文相等判断的底层尺子。

调用图:被 3 处调用(conversation_item_matches, merge_reasoning_body, reasoning_body_matches)。

reasoning_body_matches610–628 ↗
fn reasoning_body_matches(left: &ConversationBody, right: &ConversationBody) -> bool

作用:判断两段推理正文是否代表同一段推理。推理内容有时可读文本不同,但加密内容相同,所以不能只按表面文本判断。

数据流:输入是两份推理正文。它先尝试用 conversation_body_matches 直接比较;如果不完全相同,就分别找出加密片段,也就是 Encoded 部分;两边都有且标签和值都相同,就认为是同一段推理。否则返回不匹配。

调用关系:它被 conversation_item_matches 用于推理项匹配,也被 merge_reasoning_body 在合并前确认两份内容确实属于同一推理项。它调用 reasoning_encoded_part 抽取加密身份。

调用图:调用 2 个内部函数(conversation_body_matches, reasoning_encoded_part);被 2 处调用(conversation_item_matches, merge_reasoning_body)。

merge_reasoning_body630–673 ↗
fn merge_reasoning_body(
    existing: &mut ConversationBody,
    incoming: &ConversationBody,
) -> Result<()>

作用:把同一段推理在不同载荷里出现的信息合并起来。这样既保留稳定的加密身份,也尽量保留可读文本和摘要。

数据流:输入是已有推理正文和新看到的推理正文。它先检查两者是否已经完全一样;如果不一样,就用 reasoning_body_matches 确认加密身份相同,不同则报错。接着分别取出现有和新来的文本片段、摘要片段、加密片段;已有文本或摘要优先,没有时用新来的补上。最后把文本、摘要、加密片段按顺序重新组成 existing.parts。

调用关系:它由 TraceReducer::update_conversation_item_from_sighting 调用,只在已有推理项再次出现时工作。它内部依赖 reasoning_text_parts、reasoning_summary_parts、reasoning_encoded_parts 等小工具拆分正文片段。

调用图:调用 5 个内部函数(conversation_body_matches, reasoning_body_matches, reasoning_encoded_parts, reasoning_summary_parts, reasoning_text_parts);被 1 处调用(update_conversation_item_from_sighting);外部调用 2 个(Ok, bail!)。

reasoning_text_parts675–680 ↗
fn reasoning_text_parts(body: &ConversationBody) -> Vec<&ConversationPart>

作用:从推理正文里挑出可读文本片段。

数据流:输入是一份 ConversationBody。它遍历其中所有 parts,只保留 Text 类型片段,返回这些片段的引用列表;原正文不被修改。

调用关系:它只被 merge_reasoning_body 调用,用来决定合并推理正文时有哪些可读文本可以保留。

调用图:被 1 处调用(merge_reasoning_body)。

reasoning_summary_parts682–687 ↗
fn reasoning_summary_parts(body: &ConversationBody) -> Vec<&ConversationPart>

作用:从推理正文里挑出摘要片段。

数据流:输入是一份 ConversationBody。它遍历正文片段,只收集 Summary 类型片段并返回引用列表;不会改动原数据。

调用关系:它只被 merge_reasoning_body 调用,用来在合并推理内容时补齐或保留摘要。

调用图:被 1 处调用(merge_reasoning_body)。

reasoning_encoded_parts689–694 ↗
fn reasoning_encoded_parts(body: &ConversationBody) -> Vec<&ConversationPart>

作用:从推理正文里挑出加密片段。加密片段是识别同一段推理的稳定身份。

数据流:输入是一份 ConversationBody。它遍历所有片段,只保留 Encoded 类型片段,返回这些片段的引用列表;原正文保持不变。

调用关系:它被 merge_reasoning_body 调用,用来在重组推理正文时保留加密身份信息。

调用图:被 1 处调用(merge_reasoning_body)。

reasoning_encoded_part696–704 ↗
fn reasoning_encoded_part(body: &ConversationBody) -> Option<(&str, &str)>

作用:从推理正文里找到第一个加密片段,并取出它的标签和值。

数据流:输入是一份 ConversationBody。它从前往后查找 Encoded 类型片段;找到后返回 label 和 value 两个字符串引用,找不到就返回空。

调用关系:它被 reasoning_body_matches 调用。reasoning_body_matches 靠它判断两份表面不同的推理正文是否其实拥有同一个加密身份。

调用图:被 1 处调用(reasoning_body_matches)。

rollout-trace/src/reducer/mod.rs源码 ↗
orchestration离线重放 trace bundle、生成精简运行图时

原始 trace 里是一行行事件,比如线程开始、模型请求开始、工具调用结束、代码单元返回结果等。如果直接看这些日志,很难知道一次运行到底发生了什么。这个文件先读取 bundle 里的清单文件,创建一个空的 RolloutTrace,然后逐行读取原始事件日志。每读到一个事件,就交给 TraceReducer::apply_event 按事件类型处理。它还会记录原始 payload 的引用,必要时再从磁盘读出 JSON 内容。这里有一些“暂存区”很重要:有些事件会先到,比如代码已经开始跑了,但对应的模型输出还没被整理出来;这时 reducer 会先排队,等证据齐了再落到最终图里。最后,它会补上那些一开始没法确定目标的跨线程边。没有这个文件,后面的工具只能面对杂乱日志,很多关系会断掉或指错。

函数细节4
replay_bundle44–86 ↗
fn replay_bundle(bundle_dir: impl AsRef<Path>) -> Result<RolloutTrace>

作用:从一个本地 trace bundle 目录里读出清单和事件日志,并生成最终的 RolloutTrace。别人想把原始日志变成可分析的运行图时,就会从这里开始。

数据流:进去的是一个 bundle 目录路径。它先打开清单文件,拿到 trace id、rollout id、根线程、开始时间等基本信息;再创建 TraceReducer 这个“整理员”;然后打开原始事件日志,一行一行解析成 RawTraceEvent,逐个交给 reducer.apply_event。全部读完后,它再处理延迟确定的跨线程连接,最后出来的是整理好的 RolloutTrace。

调用关系:这是本文件对外的入口。它负责搭好 TraceReducer,并驱动整个重放流程;真正每个事件怎么变成图里的节点和边,则交给 TraceReducer::apply_event 以及 reducer 内部的各类专门方法。

调用图:调用 1 个内部函数(new);外部调用 9 个(as_ref, join, to_path_buf, new, new, open, new, from_reader, from_str)。

TraceReducer::read_payload_json139–147 ↗
fn read_payload_json(&self, payload: &RawPayloadRef) -> Result<Value>

作用:按原始 payload 引用,从 bundle 目录里把对应的 JSON 文件读出来。它用于那些不能只靠事件外壳、还必须看看正文内容才能整理清楚的场景。

数据流:进去的是一个 RawPayloadRef,也就是“这个 payload 文件在哪里、它的编号是什么”的引用。函数把路径拼到 bundle 目录下面,打开文件,用 JSON 解析器读成 serde_json::Value。出来的是一份可查询的 JSON 内容;如果文件打不开或 JSON 有问题,就返回带上下文的错误。

调用关系:它是 TraceReducer 的辅助读文件工具。各个具体 reducer 子流程在需要查看原始请求、响应、检查点等正文时会用它;它不直接改 RolloutTrace,只负责把磁盘上的 payload 变成内存里的 JSON。

调用图:外部调用 3 个(open, join, from_reader)。

TraceReducer::apply_event149–480 ↗
fn apply_event(&mut self, event: RawTraceEvent) -> Result<()>

作用:把单个原始事件翻译进最终的 RolloutTrace。它像一个分拣员:先保留这个事件提到的原始材料,再看事件类型,把它送到线程、模型调用、工具调用、代码单元、压缩、多代理交互等对应处理流程。

数据流:进去的是一个 RawTraceEvent,里面有时间、序号、线程信息和具体事件内容。函数先把事件里引用的所有原始 payload 登记到 rollout.raw_payloads,防止证据丢失。接着根据事件种类更新状态:例如 rollout 开始或结束会改总状态,线程事件会创建或结束线程,推理事件会记录模型请求和结果,工具和代码单元事件会建立调用、运行和结束记录。出来没有单独返回值,但 TraceReducer 里的 rollout 和若干暂存队列会被更新;如果遇到无法处理或缺关键信息的事件,就返回错误。

调用关系:replay_bundle 每读到一行日志就调用它。它会先调用 TraceReducer::insert_raw_payload 保存原始 payload 引用,然后把更细的工作分派给 reducer 的其他专门方法。它是原始事件进入精简运行图的主要关口。

调用图:调用 1 个内部函数(insert_raw_payload);外部调用 1 个(bail!)。

TraceReducer::insert_raw_payload482–486 ↗
fn insert_raw_payload(&mut self, payload: &RawPayloadRef)

作用:把一个原始 payload 引用记到最终 trace 里。这样即使精简图不保存大段原文,也还能追溯到原始材料。

数据流:进去的是一个 RawPayloadRef。函数取出其中的 raw_payload_id 作为键,把整个引用复制一份,放进 rollout.raw_payloads 这张表里。出来没有返回值;结果是 RolloutTrace 多了一条“原始材料在哪里”的索引。

调用关系:它由 TraceReducer::apply_event 在处理每个事件前调用。这样所有事件携带的 payload 证据都会先统一登记,再由后续的具体 reducer 逻辑决定是否读取和解释这些 payload。

调用图:被 1 处调用(apply_event);外部调用 1 个(clone)。

rollout-trace/src/reducer/conversation/normalize.rs源码 ↗
domain_logicrequest handling / reducer normalization

模型返回的数据像一箱混放的单据:普通消息、工具调用、工具结果、推理内容、压缩摘要,字段名字和内容形状都不一样。这个文件做的事,就是先把这些单据分门别类,变成统一的 NormalizedConversationItem。它会判断每条 JSON 的 type,再决定它是用户消息、助手消息、工具输出,还是模型的推理过程。遇到文本就保存成文本,遇到图片或看不懂的复杂内容,就留下一个“原始载荷引用”,意思是:正文太复杂,不硬塞进对话里,但可以回头按编号找到原数据。它还会把 JSON 内容压成短摘要,避免手册或界面里被大块原始数据淹没。遇到缺少关键字段或类型不支持时,它会直接报错,这能防止坏数据悄悄混进后续流程。

函数细节18
normalize_model_items34–43 ↗
fn normalize_model_items(
    items: &[Value],
    raw_payload: &RawPayloadRef,
) -> Result<Vec<NormalizedConversationItem>>

作用:把一组模型返回的 JSON 条目,逐条整理成统一的对话条目列表。有人在处理请求、响应或压缩检查点时,会先调用它把原始数据变得好用。

数据流:进去的是一个 JSON 数组和原始载荷编号信息;它新建一个空列表,然后把每个条目交给 normalize_model_item 判断和转换;出来的是一组 NormalizedConversationItem,如果其中任何一条格式不对,就返回错误。

调用关系:它是这个文件对外的批量入口。reduce_compaction_checkpoint、reduce_inference_request、reduce_inference_response 在需要把模型数据并入对话历史时会调用它;它自己不细分类型,而是把每一条交给 normalize_model_item。

调用图:调用 1 个内部函数(normalize_model_item);被 3 处调用(reduce_compaction_checkpoint, reduce_inference_request, reduce_inference_response);外部调用 1 个(new)。

token_usage_from_value45–52 ↗
fn token_usage_from_value(value: &Value) -> Option<TokenUsage>

作用:从 JSON 里提取 token 用量。token 可以理解成模型计费和计算时使用的“小文字块”,这个函数把输入、缓存输入、输出、推理输出四类数量读出来。

数据流:进去的是一段 JSON;它分别读取 input_tokens、cached_input_tokens、output_tokens、reasoning_output_tokens 四个字段,并要求都能变成非负整数;出来的是 TokenUsage,缺任何一个字段或字段不合适就返回 None。

调用关系:它是一个独立的小转换器,专门服务于统计用量的地方。它把每个字段的读取工作交给 u64_field,自己负责把四个数字拼成完整的用量对象。

调用图:调用 1 个内部函数(u64_field)。

normalize_model_item54–151 ↗
fn normalize_model_item(
    item: &Value,
    raw_payload: &RawPayloadRef,
) -> Result<NormalizedConversationItem>

作用:把单个模型 JSON 条目变成统一的对话条目。它像分拣员一样先看 type,再决定这张“单据”该按哪种规则处理。

数据流:进去的是一个 JSON 条目和原始载荷编号;它先读取 type 字段,如果没有字符串类型就报错;然后按 type 创建对应角色、频道、内容、调用编号等信息;出来的是一个 NormalizedConversationItem,或者在类型不支持时返回错误。

调用关系:它由 normalize_model_items 逐条调用,是整个归一化流程的分发中心。普通消息交给 normalize_message_item,agent_message 交给 normalize_agent_message_item,推理内容交给 normalize_reasoning_item,工具调用和工具输出则借助 raw_text_or_json_body、tool_output_body、custom_tool_call_body、json_body、compaction_body 等函数装正文。

调用图:调用 8 个内部函数(compaction_body, custom_tool_call_body, json_body, normalize_agent_message_item, normalize_message_item, normalize_reasoning_item, raw_text_or_json_body, tool_output_body);被 1 处调用(normalize_model_items);外部调用 2 个(get, bail!)。

normalize_message_item153–182 ↗
fn normalize_message_item(
    item: &Value,
    raw_payload: &RawPayloadRef,
) -> Result<NormalizedConversationItem>

作用:处理最常见的普通 message 条目,也就是系统、开发者、用户、助手或工具说的一段话。它确保消息角色是项目认识的角色。

数据流:进去的是一个 message JSON 和原始载荷编号;它读取 role 字段,把字符串角色转换成内部角色,再根据 phase 尝试识别频道,最后把 content 拆成正文片段;出来的是一个普通消息类型的 NormalizedConversationItem。缺少 role 或角色不支持时会报错。

调用关系:它由 normalize_model_item 在 type 是 message 时调用。它把角色翻译交给 role_from_str,把正文拆分交给 content_parts,自己负责把这些结果组装成一条内部消息。

调用图:调用 2 个内部函数(content_parts, role_from_str);被 1 处调用(normalize_model_item);外部调用 2 个(get, bail!)。

normalize_agent_message_item184–226 ↗
fn normalize_agent_message_item(
    item: &Value,
    raw_payload: &RawPayloadRef,
) -> Result<NormalizedConversationItem>

作用:处理 agent_message,也就是带有作者、接收者等元信息的助手消息。它能保存这类消息的特殊身份信息,而不是把它当普通文本丢掉。

数据流:进去的是一个 JSON 条目和原始载荷编号;它先用协议模型把 JSON 解析成 ResponseItem,并确认确实是 AgentMessage;再把其中的文本内容变成文本片段,把加密内容变成带标签的编码片段;出来的是一条助手分析频道的消息。如果解析失败、类型不对或内容为空,就返回错误。

调用关系:它由 normalize_model_item 在遇到 agent_message 时调用。它依赖 codex_protocol 里的 ResponseItem 结构来严格理解这种消息,然后把结果塞回本项目的 ConversationPart 和 AgentMessageMetadata。

调用图:被 1 处调用(normalize_model_item);外部调用 2 个(clone, bail!)。

normalize_reasoning_item228–282 ↗
fn normalize_reasoning_item(
    item: &Value,
    raw_payload: &RawPayloadRef,
) -> Result<NormalizedConversationItem>

作用:处理模型的推理内容。推理内容可能有正文、摘要,也可能只有加密内容,这个函数把这些都整理成可放进对话里的片段。

数据流:进去的是 reasoning JSON 和原始载荷编号;它先从 content 里追加推理正文,再从 summary 里追加摘要,之后检查 encrypted_content 是否是字符串并加入编码片段;出来的是一条助手分析频道的 Reasoning 对话条目。如果三类内容都没有,或字段形状不对,就报错。

调用关系:它由 normalize_model_item 在 type 是 reasoning 时调用。它把 content 和 summary 两组列表的细节检查交给 append_reasoning_parts,自己负责合并这些片段,并处理额外的 encrypted_content。

调用图:调用 1 个内部函数(append_reasoning_parts);被 1 处调用(normalize_model_item);外部调用 3 个(get, new, bail!)。

append_reasoning_parts290–355 ↗
fn append_reasoning_parts(
    item: &Value,
    key: &str,
    kind: ReasoningPartKind,
    raw_payload: &RawPayloadRef,
    parts: &mut Vec<ConversationPart>,
) -> Result<()>

作用:从 reasoning 条目的某个列表字段里,把一段段推理文字或摘要追加到结果里。它像检查表格一样,逐项确认类型和 text 字段都正确。

数据流:进去的是整个 reasoning JSON、要读取的字段名、字段代表正文还是摘要、原始载荷编号,以及一个可追加的片段列表;它读取对应字段,允许正文 content 为 null,但其他非数组情况会报错;然后逐项检查 type 和 text;最后把文本片段或摘要片段追加到传入的列表中。

调用关系:它只在 normalize_reasoning_item 内部使用。normalize_reasoning_item 负责决定要读 content 和 summary,它负责把每个列表里的小条目安全地拆出来。

调用图:被 1 处调用(normalize_reasoning_item);外部调用 3 个(get, bail!, matches!)。

role_from_str357–366 ↗
fn role_from_str(role: &str) -> Option<ConversationRole>

作用:把 JSON 里的角色字符串,翻译成本项目内部使用的角色枚举。枚举可以理解成一组固定选项,能避免到处写随意字符串。

数据流:进去的是 role 字符串,比如 system、user、assistant;它用固定对照表查找;出来的是对应的 ConversationRole,遇到不认识的字符串就返回 None。

调用关系:它由 normalize_message_item 调用。normalize_message_item 需要它先确认角色合法,才能放心组装普通消息。

调用图:被 1 处调用(normalize_message_item)。

channel_from_phase368–375 ↗
fn channel_from_phase(phase: &str) -> Option<ConversationChannel>

作用:把消息里的 phase 字符串翻译成内部频道。频道可以理解成消息所在的区域,比如解释过程、最终答案、摘要。

数据流:进去的是 phase 字符串;它识别 commentary、final_answer、summary 这几种值;出来的是对应的 ConversationChannel,不认识的值就返回 None,表示不强行指定频道。

调用关系:它是普通消息归一化时用到的小翻译表。normalize_message_item 会通过它把外部字段 phase 变成内部能理解的频道。

content_parts377–402 ↗
fn content_parts(content: Option<&Value>, raw_payload: &RawPayloadRef) -> Vec<ConversationPart>

作用:把 content 字段拆成一组对话正文片段。它会尽量提取纯文本;遇到图片或未知内容,就留下原始载荷引用,避免信息丢失。

数据流:进去的是可选的 content JSON 和原始载荷编号;如果 content 不是数组,就生成一个指向原始 payload 的引用片段;如果是数组,就逐项读取 type,文本类取 text,图片或未知类型生成引用;出来的是 ConversationPart 列表,哪怕内容为空也会放一个 empty_content 引用。

调用关系:它由 normalize_message_item 处理普通消息正文时调用,也由 tool_output_body 在工具输出是数组时调用。它自己会用 payload_ref_part 创建“回头看原文”的占位片段。

调用图:调用 1 个内部函数(payload_ref_part);被 2 处调用(normalize_message_item, tool_output_body);外部调用 2 个(new, vec!)。

custom_tool_call_body404–422 ↗
fn custom_tool_call_body(item: &Value, raw_payload: &RawPayloadRef) -> ConversationBody

作用:处理自定义工具调用的正文。它会特别识别名为 exec 的调用,把输入当代码保存,这样展示时更像代码块而不是普通一句话。

数据流:进去的是自定义工具调用 JSON 和原始载荷编号;它先找 input 字符串,没有就把整个条目按 JSON 保存;如果 name 是 exec,就把 input 做成 JavaScript 代码片段;否则把 input 做成普通文本片段;出来的是 ConversationBody。

调用关系:它由 normalize_model_item 在遇到 custom_tool_call 时调用。遇到不能简单取 input 的情况,它把兜底工作交给 json_body,把完整 JSON 以摘要加原始引用的方式保存。

调用图:调用 1 个内部函数(json_body);被 1 处调用(normalize_model_item);外部调用 2 个(get, vec!)。

raw_text_or_json_body424–440 ↗
fn raw_text_or_json_body(value: Option<&Value>, raw_payload: &RawPayloadRef) -> ConversationBody

作用:把一段可能是纯文本、也可能是 JSON 的参数整理成正文。它常用于函数调用参数,因为这些参数有时是字符串包着的 JSON。

数据流:进去的是一个可选 JSON 值和原始载荷编号;如果是字符串,它先尝试把字符串当 JSON 解析,能解析就按 JSON 保存,不能解析就按文本保存;如果本来就是其他 JSON 值,就按 JSON 保存;如果没有值,就放一个 payload 引用片段;出来的是 ConversationBody。

调用关系:它由 normalize_model_item 在处理 function_call 的 arguments 时调用。需要保存结构化 JSON 时,它会交给 json_body。

调用图:调用 1 个内部函数(json_body);被 1 处调用(normalize_model_item);外部调用 1 个(vec!)。

tool_output_body442–455 ↗
fn tool_output_body(output: Option<&Value>, raw_payload: &RawPayloadRef) -> ConversationBody

作用:把工具返回结果整理成对话正文。工具输出可能是一段文字、一个内容数组,也可能是复杂 JSON,这个函数分别处理。

数据流:进去的是可选 output 值和原始载荷编号;字符串会变成文本片段,数组会交给 content_parts 拆分,其他 JSON 会交给 json_body 保存,没有 output 就生成 tool_output 原始载荷引用;出来的是 ConversationBody。

调用关系:它由 normalize_model_item 在处理 function_call_output 和 custom_tool_call_output 时调用。它负责把工具结果从各种形状统一变成 ConversationBody。

调用图:调用 2 个内部函数(content_parts, json_body);被 1 处调用(normalize_model_item);外部调用 1 个(vec!)。

compaction_body457–473 ↗
fn compaction_body(item: &Value, raw_payload: &RawPayloadRef) -> Result<ConversationBody>

作用:处理压缩摘要条目的正文。这里的压缩摘要是为了把长历史浓缩后继续传给模型,内容必须是加密字符串。

数据流:进去的是 compaction 类 JSON 和原始载荷编号;它读取 encrypted_content 字符串;成功时把它放进 Encoded 片段,并标上 encrypted_content;如果没有这个字符串,就返回错误;出来的是 ConversationBody。

调用关系:它由 normalize_model_item 在遇到 compaction、compaction_summary 或 context_compaction 时调用。它只保存加密摘要本身,不负责插入“历史在这里被截断”的结构标记。

调用图:被 1 处调用(normalize_model_item);外部调用 3 个(get, bail!, vec!)。

json_body475–482 ↗
fn json_body(value: &Value, raw_payload: &RawPayloadRef) -> ConversationBody

作用:把复杂 JSON 包成一个对话正文片段。它不会把整块 JSON 直接摊开,而是保存短摘要和原始载荷编号,方便看概况也能追溯原文。

数据流:进去的是一个 JSON 值和原始载荷编号;它生成这个 JSON 的简短说明,并带上 raw_payload_id;出来的是只含一个 Json 片段的 ConversationBody。

调用关系:它是多个兜底路径会用到的工具。normalize_model_item、custom_tool_call_body、raw_text_or_json_body、tool_output_body 在遇到复杂结构或不能简化成文本时,会把内容交给它保存。

调用图:被 4 处调用(custom_tool_call_body, normalize_model_item, raw_text_or_json_body, tool_output_body);外部调用 1 个(vec!)。

payload_ref_part484–489 ↗
fn payload_ref_part(label: &str, raw_payload: &RawPayloadRef) -> ConversationPart

作用:创建一个“这里请去看原始载荷”的引用片段。它用于内容太复杂、太大或不是文本时,保证不假装理解,也不丢掉来源。

数据流:进去的是一个标签和原始载荷编号;它把标签转成字符串,并复制 raw_payload_id;出来的是 ConversationPart::PayloadRef。

调用关系:它由 content_parts 调用。content_parts 在遇到图片、未知内容、空内容或非数组内容时,用它留下可追溯的占位。

调用图:被 1 处调用(content_parts)。

summarize_json491–500 ↗
fn summarize_json(value: &Value) -> String

作用:给 JSON 生成一段短短的文字摘要,避免界面或日志里塞进过长的原始 JSON。它最多保留固定长度,超出就加省略号。

数据流:进去的是一个 JSON 值;它先尝试把 JSON 转成字符串,失败时用 <unserializable json> 代替;如果字符串超过 240 个字符,就截断并补上 ...;出来的是摘要字符串。

调用关系:它是这个文件里专门给复杂 JSON 做“短标题”的小工具。凡是需要展示 JSON 概况而不是完整展开的流程,都依赖这种摘要思路。

调用图:外部调用 1 个(to_string)。

u64_field502–507 ↗
fn u64_field(value: &Value, field: &str) -> Option<u64>

作用:从 JSON 中读取一个整数用量字段,并转成无符号整数。无符号整数就是不能为负的整数,适合表示 token 数量这种计数。

数据流:进去的是 JSON 和字段名;它按字段名读取值,要求能按整数理解;如果读到负数,会按 0 处理;出来的是 u64 数字,读不到或类型不对就返回 None。

调用关系:它由 token_usage_from_value 调用。token_usage_from_value 需要连续读取四个 token 统计字段,每个字段的安全读取都交给它。

调用图:被 1 处调用(token_usage_from_value);外部调用 1 个(get)。

rollout-trace/src/reducer/inference.rs源码 ↗
domain_logictrace reduction / inference call lifecycle

这个文件处理的是推理调用,也就是程序把一段对话请求发给模型、再等模型返回结果的全过程。原始日志通常是一条条事件:开始了、完成了、失败了、取消了。这个文件像一个记账员,把这些零散事件合并成一张完整账单。开始时,它先确认这次调用没有重复、所属的对话轮次真的存在、线程也对得上,然后把请求内容整理成对话里的条目,再登记这次模型调用。结束时,它根据完成、失败或取消这些不同结局,补上结束时间、状态、响应编号、上游请求编号和返回内容。如果某个对话轮次已经结束,但模型流还没收到正式结束事件,它也会主动把仍在运行的调用关掉,防止系统手册或可视化图里显示“幽灵调用”。这里的错误会直接报出来,因为这些关系一旦错了,后面的调用链就不可信了。

函数细节3
TraceReducer::start_inference_call36–106 ↗
fn start_inference_call(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        started: StartedInferenceCall,
    ) -> Result<()>

作用:这个函数登记一次模型调用的开始。它不只是把“开始了”写进去,还会先检查这次调用是不是重复、是不是挂在正确的对话轮次和线程下面,并把请求内容整理成后面能理解的对话条目。

数据流:进去的是事件序号、发生时间,以及一个 StartedInferenceCall,里面有调用编号、线程编号、对话轮次编号、模型名、服务商名和原始请求内容。函数先查已有记录,发现同一个调用已经开始过就报错;再查对应的对话轮次,找不到或线程不匹配也报错。检查通过后,它把请求载荷转换成请求条目编号,确认线程存在,然后在 rollout 的 inference_calls 表里插入一条状态为 Running 的 InferenceCall 记录。出来的是成功或错误;成功时,系统里多了一条正在运行的模型调用记录。

调用关系:它通常在 reducer 读到“推理开始”事件时被调用,是一次模型调用进入系统记录的入口。它会借助外部错误机制 bail! 在发现脏数据时立刻中断,也会创建新的 InferenceCall 结构来保存这次调用;后续的 TraceReducer::complete_inference_call 会接着补齐这条记录的结束信息。

调用图:外部调用 2 个(new, bail!)。

TraceReducer::close_running_inference_calls_for_turn_end113–135 ↗
fn close_running_inference_calls_for_turn_end(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        codex_turn_id: &str,
        turn_status: &ExecutionStatus,
    )

作用:这个函数在一个对话轮次结束时,顺手关掉还没正式结束的模型调用。这样可以避免日志里出现对话已经结束、但模型调用还一直显示“运行中”的假象。

数据流:进去的是事件序号、发生时间、对话轮次编号,以及这个轮次最后的状态。函数先把轮次状态换算成模型调用该有的结束状态:如果轮次完成或取消,就把未结束的模型调用标成取消;如果轮次失败或中止,就照着标成失败或中止;如果轮次还在运行,就什么也不做。然后它扫描所有模型调用,找到属于这个轮次且仍是 Running 的记录,填上结束时间、结束事件序号和最终状态。出来没有返回值;它直接修改 rollout 里已有的调用记录。

调用关系:它在对话轮次收尾时发挥兜底作用。正常情况下,TraceReducer::complete_inference_call 会先把模型调用关掉;但如果外部模型流提前断了、取消事件来晚了,轮次结束流程就会调用这个函数,把漏关的调用补上一个合理结局。

TraceReducer::complete_inference_call138–226 ↗
fn complete_inference_call(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        payload: RawTraceEventPayload,
    ) -> Result<()>

作用:这个函数登记一次模型调用的结束。它能处理三种结局:正常完成、失败、取消,并且会尽量保留模型返回的完整或部分内容。

数据流:进去的是事件序号、发生时间和一条原始事件载荷。函数先判断载荷是不是推理结束类事件:完成就标成 Completed,失败就标成 Failed,取消就标成 Cancelled;如果传进来的不是结束事件,就报错。接着它确认对应的模型调用已经存在,否则报错。若事件里带有响应内容,它会把响应内容整理成响应条目编号。最后它回到那条 InferenceCall 记录,补上响应编号、上游请求编号、原始响应载荷编号、响应条目编号,并在调用还处于 Running 时写入结束时间、结束序号和最终状态。出来的是成功或错误;成功时,模型调用从“正在运行”变成有结局、有返回证据的完整记录。

调用关系:它通常在 reducer 读到“推理完成、失败或取消”事件时被调用,用来收尾 TraceReducer::start_inference_call 之前登记的调用。它会用 bail! 拦住不该传来的事件或找不到的调用;如果轮次收尾函数已经提前把调用关掉,它不会强行覆盖那个终止状态,但仍会补收后来到达的部分响应和请求编号。

调用图:外部调用 1 个(bail!)。

rollout-trace/src/reducer/compaction.rs源码 ↗
domain_logicevent reduction / compaction lifecycle

这里的“压缩”可以理解成:对话太长时,系统把旧内容整理成一个更短的检查点,方便后面继续用。这个文件不直接改写普通对话消息,而是专门管压缩请求和压缩安装的边界。它先记录一次远程压缩请求什么时候开始、用的哪个模型、原始请求内容放在哪里;等请求结束时,再补上结束时间、状态和原始响应内容。但这些都只是“远程调用的证据”,不代表对话已经换成压缩后的版本。只有收到“压缩检查点已安装”的事件时,它才会生成一条真正的 Compaction 记录,并把替换后的消息标记为待接入当前线程历史。整个过程像快递:下单、签收只是记录流程,真正把东西放进仓库,必须等“入库”这一步发生。

函数细节3
TraceReducer::start_compaction_request21–76 ↗
fn start_compaction_request(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        started: StartedCompactionRequest,
    ) -> Result<()>

作用:记录一次压缩请求刚刚开始。它会先检查这次请求是不是重复、线程和回合是否对得上,确认没问题后才把它写进总账。

数据流:进去的是事件序号、开始时间,以及 StartedCompactionRequest 里拆好的信息,比如压缩编号、请求编号、线程编号、回合编号、模型名、服务商名和原始请求内容的位置。函数先查已有记录,防止同一个请求编号被写两次;再确认线程存在,并确认这个压缩请求引用的 codex turn(一次系统处理回合)确实属于这个线程。之后它新建一条 CompactionRequest,把执行状态设为 Running(运行中),保存开始时间、开始事件序号和请求 payload 的编号。出来的结果是成功返回,或者发现数据前后矛盾时直接报错;它会改动 rollout.compaction_requests。

调用关系:它通常在 reducer 收到“压缩请求开始”事件时被用到,是压缩生命周期的第一笔账。它不会去联系远程服务,也不会改对话内容,只是把开始证据登记好。遇到重复请求、未知回合、线程不匹配这类问题时,它会通过 bail! 立刻中止,避免脏数据继续往后流。

调用图:外部调用 1 个(bail!)。

TraceReducer::complete_compaction_request82–111 ↗
fn complete_compaction_request(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        compaction_id: String,
        compaction_request_id: CompactionRequestId,

作用:记录一次压缩请求已经结束。它只补全这次远程调用的结果,不会把压缩内容安装进对话历史。

数据流:进去的是事件序号、结束时间、压缩编号、请求编号、最终状态,以及可选的原始响应内容位置。函数先用请求编号找到之前开始时登记的那条 CompactionRequest;如果找不到,说明结束事件没有对应的开始事件,就报错。然后它检查结束事件里的压缩编号和开始时记录的压缩编号是否一致。确认后,它把结束时间、结束事件序号、执行状态写进去,并把响应 payload 的编号保存下来。出来的是更新后的请求记录;如果引用错了请求或压缩编号对不上,就失败返回。

调用关系:它接在 start_compaction_request 之后,用来给同一条请求记录“收尾”。它的重要边界是:请求完成不等于对话内容已经改变,所以它不会碰 conversation history(对话历史)。如果数据不可信,它同样用 bail! 停下来,保证后面的压缩安装不会建立在错账上。

调用图:外部调用 1 个(bail!)。

TraceReducer::reduce_compaction_installed_event117–171 ↗
fn reduce_compaction_installed_event(
        &mut self,
        wall_time_unix_ms: i64,
        thread_id: String,
        codex_turn_id: String,
        compaction_id: String,
        checkpoint_pay

作用:处理“压缩检查点已经安装”的事件。只有走到这里,压缩后的替换内容才算真正进入当前线程的对话图里。

数据流:进去的是安装时间、线程编号、回合编号、压缩编号,以及检查点 payload 的位置。函数先确认这个压缩编号没有被安装过,再确认线程存在、引用的 codex turn 存在,并且这个回合确实属于这个线程。然后它读取并规整检查点内容,得到原来哪些消息被压缩、替换成了哪些消息,以及用于标记的 marker item。接着它找出所有属于这个压缩编号的请求编号,把替换消息记录到 pending_compaction_replacement_item_ids,最后在 rollout.compactions 里写入一条正式的 Compaction。出来的是一份已安装的压缩记录;如果发现重复安装或引用关系不对,就报错。

调用关系:它是压缩生命周期里的关键分界线:前面的请求开始和完成只是“远程服务尝试过”,这里才表示“新的对话检查点已经生效”。它会把检查点解析工作交给内部的 reduce_compaction_checkpoint 来完成,然后把结果接到总账里。和另外两个函数一样,它用 bail! 阻止重复安装、未知回合、线程错配等会破坏历史一致性的情况。

调用图:外部调用 1 个(bail!)。

rollout-trace/src/reducer/thread.rs源码 ↗
domain_logictrace event reduction

这里的 reducer,可以理解成“流水账整理员”:原始日志是一条条零散事件,它把这些事件折叠成清楚的状态。这个文件处理最基础的容器:线程,以及线程里的 Codex 回合。线程像一个工作间,其他对话、工具调用、推理调用都要挂在某个工作间下面,所以线程身份必须先记准。线程开始时,它会检查是否重复,读取可选元数据,判断这是根线程,还是由父线程派生出的子线程,并生成父子关系用的边编号。线程或回合结束时,它会记录结束时间、结束序号和最终状态。它也会防止明显错误,比如结束一个不存在的回合,或者回合结束事件写了不匹配的线程编号。

函数细节8
TraceReducer::start_thread30–104 ↗
fn start_thread(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        thread_id: String,
        agent_path: String,
        metadata_payload: Option<RawPayloadRef>,

作用:在整理日志时登记一个新线程。它不只是存下线程编号,还会尽量从元数据里识别这个线程的身份:是主线程,还是某个父线程派生出来的子任务。

数据流:进去的是事件序号、墙上时间、线程编号、事件里带的 agent_path,以及可选的原始元数据引用。它先确认这个线程以前没登记过;如果有元数据,就解析出昵称、模型、派生来源等信息;如果发现是子线程,就用父线程编号和当前线程编号生成一条派生关系的边编号,并推导任务名。最后它把一个新的 AgentThread 放进 rollout.threads,初始状态是 Running;如果重复或元数据有问题,就返回错误。

调用关系:当上层分发器看到“线程开始”事件时会走到这里。它会用 ThreadStartedMetadata::thread_spawn 看元数据里有没有子线程派生信息,并借助 spawn_edge_id 生成父子关系标识;如果发现重复线程,会用 bail! 直接报错,避免后续所有内容挂到错误的线程上。

调用图:外部调用 3 个(new, bail!, spawn_edge_id)。

TraceReducer::end_thread107–124 ↗
fn end_thread(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        thread_id: String,
        status: RolloutStatus,
    ) -> Result<()>

作用:把一个线程标记为结束。它只结束这个线程本身,不把子线程的关闭误当成整个 rollout 的完成。

数据流:进去的是事件序号、结束时间、线程编号,以及 rollout 层面的状态。它先通过 thread_mut 找到要改的线程,然后写入结束时间、结束事件序号,并把 Running、Completed、Failed、Aborted 这些状态转换成线程执行窗口里的状态。出来是成功结果;如果线程编号不存在,就返回错误。

调用关系:当日志里出现“线程结束”事件时,上层流程会调用它。它把找线程这件事交给 TraceReducer::thread_mut,这样所有“未知线程”的错误提示都保持一致。

调用图:调用 1 个内部函数(thread_mut)。

TraceReducer::start_codex_turn127–156 ↗
fn start_codex_turn(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        codex_turn_id: CodexTurnId,
        thread_id: String,
    ) -> Result<()>

作用:在已有线程里登记一个新的 Codex 回合。Codex 回合可以理解成一次模型处理输入、产生行动或回答的工作段。

数据流:进去的是事件序号、开始时间、回合编号和所属线程编号。它先检查这个回合编号是否已经存在,再确认所属线程真的存在。之后它创建一个 CodexTurn,状态设为 Running,输入项列表先为空,并放进 rollout.codex_turns。出来是成功;如果回合重复或线程不存在,就报错。

调用关系:当上层分发器看到“Codex 回合开始”事件时会调用它。它依赖 TraceReducer::thread_mut 做线程存在性检查;如果发现重复回合,会用 bail! 停下来,防止同一个回合被记录两次。

调用图:调用 1 个内部函数(thread_mut);外部调用 3 个(clone, new, bail!)。

TraceReducer::end_codex_turn159–197 ↗
fn end_codex_turn(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        thread_id: Option<String>,
        codex_turn_id: CodexTurnId,
        status: ExecutionStatus,

作用:把一个 Codex 回合标记为结束,并顺手校验事件里的线程编号有没有写错。这样可以防止日志里某个回合被错误地归到另一个线程下面。

数据流:进去的是事件序号、结束时间、可选线程编号、回合编号和结束状态。它先看事件如果带了线程编号,是否和这个回合登记时的线程一致;不一致就报错。接着找到这个回合,写入结束时间、结束序号和最终状态。之后还会把这个回合下仍在运行的相关工作收尾。出来是成功;如果回合不存在或线程不匹配,就返回错误。

调用关系:当日志里出现“Codex 回合结束”事件时会用到它。它自己完成校验和状态落盘;遇到不可信的事件内容时,通过 bail! 让整理流程立刻报错,而不是悄悄生成一份错误的时间线。

调用图:外部调用 2 个(bail!, clone)。

TraceReducer::thread_mut200–205 ↗
fn thread_mut(&mut self, thread_id: &str) -> Result<&mut AgentThread>

作用:按线程编号取出一个可以修改的线程记录。它是一个小守门员:只有线程确实存在,后面的代码才能继续改它。

数据流:进去的是一个线程编号。它去 rollout.threads 里查找对应的 AgentThread,并返回可修改引用;如果找不到,就附上一句清楚的错误说明,告诉调用者这个事件引用了未知线程。

调用关系:TraceReducer::end_thread 和 TraceReducer::start_codex_turn 都会用它。这样结束线程、开启回合等操作不用各自写一套查找和报错逻辑,错误口径也一致。

调用图:被 2 处调用(end_thread, start_codex_turn)。

TraceReducer::thread_started_metadata207–214 ↗
fn thread_started_metadata(
        &self,
        metadata_payload: &RawPayloadRef,
    ) -> Result<ThreadStartedMetadata>

作用:把线程开始事件附带的原始元数据读成程序能理解的结构。元数据里可能藏着昵称、默认模型、子线程来源等关键信息。

数据流:进去的是一个 RawPayloadRef,也就是指向原始载荷的引用。它先通过 reducer 读取这份载荷里的 JSON,再用 serde_json::from_value 把 JSON 转成 ThreadStartedMetadata。出来是解析好的元数据;如果 JSON 读不到或格式不对,就带着 payload 编号返回错误,方便定位坏数据。

调用关系:它主要服务于 TraceReducer::start_thread。start_thread 不直接关心原始 JSON 长什么样,而是把读取和解析交给这个函数,拿到干净的 ThreadStartedMetadata 后再决定线程身份。

调用图:外部调用 1 个(from_value)。

ThreadStartedMetadata::thread_spawn228–254 ↗
fn thread_spawn(&self) -> Option<ThreadSpawnMetadata>

作用:从线程元数据里判断“这个线程是不是子线程”。如果是,它会提取父线程编号、子任务名、角色和 agent_path 这些派生关系需要的信息。

数据流:进去的是已经解析好的 ThreadStartedMetadata。它会在 session_source 里寻找 subagent.thread_spawn 这一块;找不到就返回 None,表示不是子线程。找到了就读取 parent_thread_id,并按优先级补齐 agent_path、task_name 和 agent_role:优先用嵌套的派生信息,不够再用外层元数据,任务名还可以从 agent_path 最后一段推出来。出来是 ThreadSpawnMetadata。

调用关系:TraceReducer::start_thread 会用它来决定 AgentOrigin 是 Root 还是 Spawned。它相当于把复杂的多智能体元数据翻译成一句简单结论:这个线程从哪里来、叫什么任务。

task_name_from_agent_path264–270 ↗
fn task_name_from_agent_path(agent_path: &str) -> String

作用:从 agent_path 里取出一个适合作为任务名的短名字。它就像从文件路径里取最后一个文件夹名,方便人看。

数据流:进去的是一个类似路径的字符串。它从右往左按斜杠分段,找到最后一个非空片段;如果找不到,就退回使用整个原字符串。出来是一个 String,作为推导出的任务名。

调用关系:ThreadStartedMetadata::thread_spawn 会在元数据没有明确 task_name 时用它兜底;TraceReducer::start_thread 在处理子线程时也会用它补出任务名。这样即使日志没有直接写任务名,系统也能给子任务一个可读的名字。

运行时与工具重放

这些文件将运行时执行和工具活动归约为可重放的代码单元、工具调用、终端和多智能体交互模型,并关联回对话记录。

rollout-trace/src/reducer/code_cell.rs源码 ↗
domain_logictrace reduction / runtime event handling

这里的“code cell”可以理解成一次代码执行的小盒子:模型写了 exec 代码,运行时真的去执行,里面还可能再调用工具、等待结果、产生输出。麻烦在于,日志里的两种时间线不一定同步:模型对话记录是一条线,真实运行事件又是一条线。所以这个文件做的事像收快递时先放到临时架上:如果代码开始事件到了,但还找不到对应的对话来源,就先排队;等来源出现后,再把开始、首次响应、结束、嵌套工具、wait 等关系补齐。它还会检查线程和回合是否匹配,避免把别人的运行记录接错地方。最后形成的 CodeCell 既能说明“这段代码是谁写的”,也能说明“它运行到哪一步、结果如何、关联了哪些输出和工具”。

函数细节27
TraceReducer::start_or_queue_code_cell88–104 ↗
fn start_or_queue_code_cell(&mut self, pending: PendingCodeCellStart) -> Result<()>

作用:收到一次代码执行开始事件时,决定是立刻建立 CodeCell,还是先放进等待队列。这样可以避免代码已经运行、但对话里还没有出现对应 exec 记录时把关系接错。

数据流:输入是一个待开始的 code cell 事件,里面有线程、回合、运行时 cell id、模型可见 call id 和源码。它先查对话里是否已经有这次 exec 的来源项;有就创建正式 CodeCell,没有就存到 pending 队列;如果发现同一个 code cell 已经存在或已经排队,就报错。

调用关系:这是 code cell 开始流程的入口之一。它先请 TraceReducer::source_item_id_for_pending_code_cell 查来源是否存在;准备好时把活交给 TraceReducer::start_code_cell,没准备好时只排队,等 TraceReducer::flush_pending_code_cell_starts 后续再处理。

调用图:调用 2 个内部函数(source_item_id_for_pending_code_cell, start_code_cell);外部调用 1 个(bail!)。

TraceReducer::flush_pending_code_cell_starts110–128 ↗
fn flush_pending_code_cell_starts(&mut self) -> Result<()>

作用:把之前因为缺少对话来源而暂存的代码执行开始事件,重新检查一遍。只要对应的模型对话项已经出现,就把它们转成正式 CodeCell。

数据流:它读取 pending_code_cell_starts 队列,逐个判断来源项是否已经能找到。能找到的 id 会被收集出来,然后从队列里移除,并调用正式创建逻辑;不能找到的继续留在队列里。输出是更新后的 rollout,其中一部分等待项变成正式 code cell。

调用关系:它通常在推理结果或压缩结果被整理成对话项之后调用。它依赖 TraceReducer::source_item_id_for_pending_code_cell 判断是否解锁,再调用 TraceReducer::start_code_cell 完成真正建档。

调用图:调用 2 个内部函数(source_item_id_for_pending_code_cell, start_code_cell);外部调用 1 个(new)。

TraceReducer::start_code_cell131–211 ↗
fn start_code_cell(&mut self, pending: PendingCodeCellStart) -> Result<()>

作用:真正创建一条 CodeCell 记录,把“模型写的代码”和“运行时发生的事”接到同一个节点上。它是把临时事件落成正式运行档案的核心步骤。

数据流:输入是已经确认有来源的 PendingCodeCellStart。它检查是否重复、是否带有 Codex 回合 id、线程和回合是否匹配;再找到来源对话项和已有输出项,收集已出现的嵌套工具调用,创建 CodeCell 并写入 rollout;最后把输出项反向标记为由这个 CodeCell 产生,并回放之前排队的生命周期事件。

调用关系:它由 TraceReducer::start_or_queue_code_cell 或 TraceReducer::flush_pending_code_cell_starts 调用。内部会用 TraceReducer::validate_code_cell_turn 做安全检查,用 TraceReducer::source_item_id_for_code_cell_start 和 TraceReducer::model_visible_code_cell_item_ids 找对话关系,用 TraceReducer::add_code_cell_output_item 和 TraceReducer::flush_pending_code_cell_lifecycle_events 补齐后续关系。

调用图:调用 5 个内部函数(add_code_cell_output_item, flush_pending_code_cell_lifecycle_events, model_visible_code_cell_item_ids, source_item_id_for_code_cell_start, validate_code_cell_turn);被 2 处调用(flush_pending_code_cell_starts, start_or_queue_code_cell);外部调用 2 个(new, bail!)。

TraceReducer::source_item_id_for_pending_code_cell214–226 ↗
fn source_item_id_for_pending_code_cell(
        &self,
        pending: &PendingCodeCellStart,
    ) -> Result<Option<String>>

作用:检查一个等待中的 code cell 是否已经能在对话里找到“是谁发起了这次 exec”。它只做判断,不会修改数据。

数据流:输入是 PendingCodeCellStart。它用线程 id、模型可见 call id 和 CustomToolCall 类型去对话项里查找;找到就返回第一个 item id,找不到就返回空。

调用关系:它是排队机制的探测器。TraceReducer::start_or_queue_code_cell 用它决定是否立刻创建,TraceReducer::flush_pending_code_cell_starts 用它决定哪些等待项可以解锁。

调用图:调用 1 个内部函数(model_visible_code_cell_item_ids);被 2 处调用(flush_pending_code_cell_starts, start_or_queue_code_cell)。

TraceReducer::record_or_queue_code_cell_initial_response235–267 ↗
fn record_or_queue_code_cell_initial_response(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        code_cell_id: CodeCellId,
        runtime_cell_id: String,
        s

作用:记录 code cell 的第一次运行时响应,比如开始运行、运行中、已经 yield 等状态。若正式 CodeCell 还没创建,但开始事件正在排队,它会把这次响应也先排队。

数据流:输入包括事件序号、时间、code cell id、运行时 cell id 和运行状态。它先看正式 CodeCell 是否存在;存在就直接写入首次响应信息,不存在但开始事件在排队就保存为待回放事件;两边都没有就报错。

调用关系:这是运行时首次响应事件的入口。它可能调用 TraceReducer::queue_code_cell_lifecycle_event 暂存,也可能调用 TraceReducer::record_code_cell_initial_response 立刻写入;暂存的事件会在 TraceReducer::flush_pending_code_cell_lifecycle_events 中被重放。

调用图:调用 2 个内部函数(queue_code_cell_lifecycle_event, record_code_cell_initial_response);外部调用 1 个(bail!)。

TraceReducer::record_code_cell_initial_response269–292 ↗
fn record_code_cell_initial_response(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        code_cell_id: CodeCellId,
        runtime_cell_id: String,
        status: Co

作用:把 code cell 的第一次运行时响应写进正式记录里。它会保留首次响应的时间,并更新当前运行状态。

数据流:输入是事件序号、时间、code cell id、运行时 cell id 和状态。它找到对应 CodeCell 后,写入 runtime_cell_id;如果此前没有首次响应时间,就填上;如果状态是 Yielded,也记录 yield 的时间;最后更新 runtime_status。找不到 cell 就报错。

调用关系:它是真正落库的内部函数。TraceReducer::record_or_queue_code_cell_initial_response 在 cell 已存在时调用它;TraceReducer::flush_pending_code_cell_lifecycle_events 在回放排队事件时也调用它。

调用图:被 2 处调用(flush_pending_code_cell_lifecycle_events, record_or_queue_code_cell_initial_response);外部调用 1 个(bail!)。

TraceReducer::end_or_queue_code_cell300–322 ↗
fn end_or_queue_code_cell(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        code_cell_id: CodeCellId,
        status: CodeCellRuntimeStatus,
    ) -> Result<()>

作用:处理 code cell 结束事件。若 CodeCell 已经存在,就结束它;若它的开始事件还在等对话来源,就先把结束事件存起来,避免快速失败的代码丢失结尾。

数据流:输入是事件序号、时间、code cell id 和最终运行状态。它检查正式 cell 是否存在;存在就写入结束信息,不存在但开始事件已排队就把结束事件加入待回放列表;完全未知则报错。

调用关系:这是运行时结束事件的入口。它把已知 cell 交给 TraceReducer::end_code_cell,未成形但已知的 cell 交给 TraceReducer::queue_code_cell_lifecycle_event 暂存。

调用图:调用 2 个内部函数(end_code_cell, queue_code_cell_lifecycle_event);外部调用 1 个(bail!)。

TraceReducer::end_code_cell324–344 ↗
fn end_code_cell(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        code_cell_id: CodeCellId,
        status: CodeCellRuntimeStatus,
    ) -> Result<()>

作用:把一个正式 CodeCell 标记为结束,并把运行时状态转换成更通用的执行状态。比如运行时说 Completed,这里会写成执行已完成。

数据流:输入是事件序号、结束时间、code cell id 和运行时状态。它找到 CodeCell,必要时补上首次响应时间;然后写入结束时间、结束序号、执行状态和运行时状态。找不到对应 cell 就报错。

调用关系:它是结束 code cell 的底层写入函数。TraceReducer::end_or_queue_code_cell、TraceReducer::flush_pending_code_cell_lifecycle_events 和 TraceReducer::terminate_running_code_cells_for_turn_end 都会调用它;它用 execution_status_for_code_cell 做状态翻译。

调用图:调用 1 个内部函数(execution_status_for_code_cell);被 3 处调用(end_or_queue_code_cell, flush_pending_code_cell_lifecycle_events, terminate_running_code_cells_for_turn_end);外部调用 1 个(bail!)。

TraceReducer::terminate_running_code_cells_for_turn_end353–382 ↗
fn terminate_running_code_cells_for_turn_end(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        codex_turn_id: &str,
        turn_status: &ExecutionStatus,
    ) ->

作用:当一个 Codex 回合失败、取消或中止时,把这个回合里还在跑的 code cell 关掉。这样最终 trace 不会看起来还有任务永远悬着。

数据流:输入是事件序号、时间、回合 id 和回合结束状态。若回合是正常运行或完成,它不动任何 cell;若是失败或取消类状态,它找出该回合中仍处于 Running 的 CodeCell,并逐个写成失败或终止。

调用关系:它在回合收尾时发挥作用。它不会因为正常完成回合而关闭 yielded 的 cell,因为这些 cell 可能之后被 wait 继续等待;真正需要强制收尾时,它调用 TraceReducer::end_code_cell。

调用图:调用 1 个内部函数(end_code_cell)。

TraceReducer::queue_code_cell_lifecycle_event384–395 ↗
fn queue_code_cell_lifecycle_event(
        &mut self,
        code_cell_id: CodeCellId,
        event: PendingCodeCellLifecycleEvent,
    )

作用:把还不能写入正式 CodeCell 的生命周期事件先存起来。生命周期事件就是“首次响应”“结束”这类描述运行进度的事件。

数据流:输入是 code cell id 和待保存事件。它把事件放进该 cell 的 pending 列表里,并按事件序号排序,保证以后回放时仍接近原始发生顺序。

调用关系:它是排队机制的小仓库。TraceReducer::record_or_queue_code_cell_initial_response 和 TraceReducer::end_or_queue_code_cell 在发现 cell 还没正式创建但开始事件已知时,会把事件交给它保存。

调用图:被 2 处调用(end_or_queue_code_cell, record_or_queue_code_cell_initial_response)。

TraceReducer::flush_pending_code_cell_lifecycle_events397–422 ↗
fn flush_pending_code_cell_lifecycle_events(&mut self, code_cell_id: &str) -> Result<()>

作用:当 CodeCell 正式创建后,把之前暂存的首次响应、结束等事件按顺序补写进去。这样快进快出的代码也不会漏状态。

数据流:输入是 code cell id。它从 pending_code_cell_lifecycle_events 里取出该 cell 的所有事件;没有就直接结束;有的话按保存顺序逐个判断类型,并调用对应的写入函数。处理完后,这些待处理事件会从队列消失。

调用关系:它只在 TraceReducer::start_code_cell 创建正式 CodeCell 后调用。它把 InitialResponse 交给 TraceReducer::record_code_cell_initial_response,把 Ended 交给 TraceReducer::end_code_cell。

调用图:调用 2 个内部函数(end_code_cell, record_code_cell_initial_response);被 1 处调用(start_code_cell)。

TraceReducer::attach_model_visible_code_cell_item505–526 ↗
fn attach_model_visible_code_cell_item(
        &mut self,
        item_id: &str,
        call_id: Option<&str>,
        kind: &ConversationItemKind,
    ) -> Result<()>

作用:当稍后才看到某个模型可见的 code cell 输出项时,把这个输出项补挂到对应 CodeCell 上。它解决“输出回到模型的记录晚于运行时 cell 创建”的情况。

数据流:输入是对话 item id、可选 call id 和 item 类型。它只处理 CustomToolCallOutput 类型且有 call id 的项;先用 call id 算出 code cell id,确认该 cell 已存在后,把输出项加入 cell,并给对话项加上“由这个 CodeCell 产生”的反向标记。

调用关系:它在对话项被后续观察到时使用。它先通过 TraceReducer::reduced_code_cell_id_for_model_visible_call 得到稳定 id,再调用 TraceReducer::add_code_cell_output_item 完成双向连接。

调用图:调用 2 个内部函数(add_code_cell_output_item, reduced_code_cell_id_for_model_visible_call)。

TraceReducer::code_cell_event_thread_id533–555 ↗
fn code_cell_event_thread_id(
        &self,
        thread_id: Option<String>,
        codex_turn_id: Option<&str>,
        runtime_cell_id: &str,
        event_name: &str,
    ) -> Result<String>

作用:为 code cell 运行时事件确定所属线程。新事件通常直接带线程 id;老事件可能只带回合 id,这个函数负责兜底查出来。

数据流:输入是可选线程 id、可选 Codex 回合 id、运行时 cell id 和事件名。若线程 id 已有就直接返回;否则要求必须有回合 id,并从已记录的回合里查线程 id;查不到或信息不足就报错。

调用关系:它是多个 code cell 事件处理路径可共用的定位工具。它避免每个事件分支都重复写“从回合找线程”的逻辑,并让错误信息带上事件名和 runtime cell id。

调用图:外部调用 1 个(bail!)。

TraceReducer::reduced_code_cell_id_for_model_visible_call558–566 ↗
fn reduced_code_cell_id_for_model_visible_call(
        &self,
        model_visible_call_id: &str,
    ) -> CodeCellId

作用:把模型对话里的 exec call id 转成系统内部稳定使用的 code cell id。这样后面不依赖容易变化的运行时 cell id。

数据流:输入是模型可见 call id。它在前面加上 code_cell: 前缀,输出一个稳定的 CodeCellId 字符串,不修改任何状态。

调用关系:它是 id 命名规则的集中位置。TraceReducer::attach_model_visible_code_cell_item 会用它从对话 call id 找到可能对应的 CodeCell。

调用图:被 1 处调用(attach_model_visible_code_cell_item);外部调用 1 个(format!)。

TraceReducer::record_runtime_code_cell_id572–591 ↗
fn record_runtime_code_cell_id(
        &mut self,
        thread_id: &str,
        runtime_cell_id: &str,
        code_cell_id: &str,
    ) -> Result<()>

作用:记录“某个线程里的运行时 cell id”对应哪个稳定 CodeCellId。运行时 id 可能在不同线程重复,所以线程 id 必须一起参与匹配。

数据流:输入是线程 id、运行时 cell id 和稳定 code cell id。它组成一个复合 key;如果以前没记录,就写入映射;如果已经记录且一致,就当作成功;如果同一个运行时 id 被映射到不同 CodeCell,就报错。

调用关系:它建立运行时世界和整理后图结构之间的桥。后续 TraceReducer::code_cell_id_for_runtime_cell_id_if_known 和 TraceReducer::code_cell_id_for_runtime_cell_id 会靠这个映射解析事件归属。

调用图:调用 1 个内部函数(runtime_code_cell_key);外部调用 1 个(bail!)。

TraceReducer::code_cell_id_for_runtime_cell_id594–607 ↗
fn code_cell_id_for_runtime_cell_id(
        &self,
        thread_id: &str,
        runtime_cell_id: &str,
        event_name: &str,
    ) -> Result<CodeCellId>

作用:把运行时 cell id 查成稳定的 CodeCellId,并在查不到时给出明确错误。它适合用于必须知道归属的事件。

数据流:输入是线程 id、运行时 cell id 和事件名。它查询映射表;找到就返回 CodeCellId,找不到就返回带事件名、线程和 runtime id 的错误。

调用关系:它是严格版查询。TraceReducer::reduce_tool_call_requester 在把原始工具请求者转成整理后请求者时会调用它;内部实际查询交给 TraceReducer::code_cell_id_for_runtime_cell_id_if_known。

调用图:调用 1 个内部函数(code_cell_id_for_runtime_cell_id_if_known);被 1 处调用(reduce_tool_call_requester)。

TraceReducer::code_cell_id_for_runtime_cell_id_if_known609–617 ↗
fn code_cell_id_for_runtime_cell_id_if_known(
        &self,
        thread_id: &str,
        runtime_cell_id: &str,
    ) -> Option<CodeCellId>

作用:温和地查询运行时 cell id 对应的稳定 CodeCellId。查不到时不报错,只返回空。

数据流:输入是线程 id 和运行时 cell id。它用这两个值组成 key,到 code_cell_ids_by_runtime 映射表里找;找到就复制出 CodeCellId,找不到就返回 None。

调用关系:它是非强制查询工具。TraceReducer::code_cell_id_for_runtime_cell_id 用它实现严格查询;TraceReducer::link_wait_tool_call_from_request_payload 用它处理“暂时不知道也可以跳过”的 wait 关系。

调用图:调用 1 个内部函数(runtime_code_cell_key);被 2 处调用(code_cell_id_for_runtime_cell_id, link_wait_tool_call_from_request_payload)。

TraceReducer::reduce_tool_call_requester623–638 ↗
fn reduce_tool_call_requester(
        &self,
        thread_id: &str,
        requester: RawToolCallRequester,
    ) -> Result<ToolCallRequester>

作用:把原始日志里的工具调用发起者,转换成整理后图结构能理解的发起者。尤其是把“某运行时 cell 发起”转换成“某稳定 CodeCell 发起”。

数据流:输入是线程 id 和原始请求者。若请求者是 Model,就直接输出模型请求者;若请求者是 CodeCell,它拿 runtime_cell_id 查询稳定 code_cell_id,并输出 CodeCell 请求者。查不到对应 cell 时会报错。

调用关系:它是工具调用整理流程和 code cell 映射之间的边界。它调用 TraceReducer::code_cell_id_for_runtime_cell_id,确保嵌套工具调用最终挂到稳定 CodeCell 上,而不是挂到临时运行时 id 上。

调用图:调用 1 个内部函数(code_cell_id_for_runtime_cell_id)。

TraceReducer::validate_code_cell_turn640–655 ↗
fn validate_code_cell_turn(&self, thread_id: &str, codex_turn_id: &str) -> Result<()>

作用:检查 code cell 开始事件里的线程和 Codex 回合是否真实存在,并且是否互相匹配。它防止把一次代码执行接到错误的回合上。

数据流:输入是线程 id 和回合 id。它先确认线程存在,再确认回合存在,最后确认这个回合确实属于该线程;任何一步不对都会报错,全部通过则不修改状态并返回成功。

调用关系:它是 TraceReducer::start_code_cell 创建正式记录前的安全门。只有通过这个检查,CodeCell 才会被写入 rollout。

调用图:被 1 处调用(start_code_cell);外部调用 1 个(bail!)。

TraceReducer::model_visible_code_cell_item_ids657–673 ↗
fn model_visible_code_cell_item_ids(
        &self,
        thread_id: &str,
        call_id: &str,
        kind: ConversationItemKind,
    ) -> Vec<String>

作用:在已经整理好的对话项中,找出属于某个 code cell 调用的 item id。可以按类型找 exec 本身,也可以找 exec 的输出。

数据流:输入是线程 id、call id 和对话项类型。它遍历 conversation_items,筛出线程相同、call_id 相同、类型相同的项,并返回这些 item_id 列表,不改动任何数据。

调用关系:它是查找模型可见对话关系的通用小工具。TraceReducer::source_item_id_for_pending_code_cell、TraceReducer::source_item_id_for_code_cell_start 和 TraceReducer::start_code_cell 都依赖它。

调用图:被 3 处调用(source_item_id_for_code_cell_start, source_item_id_for_pending_code_cell, start_code_cell)。

TraceReducer::source_item_id_for_code_cell_start675–694 ↗
fn source_item_id_for_code_cell_start(
        &self,
        thread_id: &str,
        code_cell_id: &str,
        model_visible_call_id: &str,
    ) -> Result<String>

作用:为即将创建的 CodeCell 找到它的来源对话项,也就是模型发出的那条 custom tool call。找不到时会明确报错。

数据流:输入是线程 id、code cell id 和模型可见 call id。它查找 CustomToolCall 类型的对话项,返回第一个匹配的 item id;如果没有匹配项,就产生说明性的错误。

调用关系:它是 TraceReducer::start_code_cell 的关键依赖。start_code_cell 需要它证明“这段运行时代码确实来自某条模型对话记录”,然后才允许创建 CodeCell。

调用图:调用 1 个内部函数(model_visible_code_cell_item_ids);被 1 处调用(start_code_cell)。

TraceReducer::add_code_cell_output_item696–712 ↗
fn add_code_cell_output_item(&mut self, code_cell_id: &str, item_id: &str) -> Result<()>

作用:把一个模型可见输出项和 CodeCell 双向连起来。也就是说,CodeCell 记住自己有哪些输出,对话项也记住自己是由哪个 CodeCell 产生的。

数据流:输入是 code cell id 和输出 item id。它先找到 CodeCell,把 item id 加入 output_item_ids 且不重复;再找到 conversation item,把 ProducerRef::CodeCell 加入 produced_by 且不重复。任一对象找不到都会报错。

调用关系:它是输出关系写入的底层函数。TraceReducer::start_code_cell 在创建时连接已有输出,TraceReducer::attach_model_visible_code_cell_item 在后续看到输出时也调用它;内部用 push_unique 避免重复 item id。

调用图:调用 1 个内部函数(push_unique);被 2 处调用(attach_model_visible_code_cell_item, start_code_cell);外部调用 1 个(bail!)。

execution_status_for_code_cell715–724 ↗
fn execution_status_for_code_cell(status: &CodeCellRuntimeStatus) -> ExecutionStatus

作用:把 code cell 专用的运行时状态,翻译成系统通用的执行状态。这样外层查看运行结果时不用理解所有 code cell 细节。

数据流:输入是 CodeCellRuntimeStatus。Starting、Running、Yielded 会变成 Running;Completed 变成 Completed;Failed 变成 Failed;Terminated 变成 Cancelled。它只返回转换结果,不修改状态。

调用关系:它被 TraceReducer::end_code_cell 调用。end_code_cell 用它把运行时的细粒度状态写成 CodeCell.execution.status 里的通用状态。

调用图:被 1 处调用(end_code_cell)。

push_unique726–730 ↗
fn push_unique(items: &mut Vec<String>, item_id: &str)

作用:往字符串列表里追加一个值,但如果已经有了就不重复加。它用于维护各种“关联 id 列表”的干净程度。

数据流:输入是一个可修改的字符串列表和一个 item id。它先检查列表里是否已有相同字符串;没有就复制并追加,有就保持原样。

调用关系:它是这个文件里的小工具函数。TraceReducer::add_code_cell_output_item、TraceReducer::link_tool_call_to_code_cell 和 TraceReducer::link_wait_tool_call_from_request_payload 都用它避免重复关系。

调用图:被 3 处调用(add_code_cell_output_item, link_tool_call_to_code_cell, link_wait_tool_call_from_request_payload)。

runtime_code_cell_key732–734 ↗
fn runtime_code_cell_key(thread_id: &str, runtime_cell_id: &str) -> (String, String)

作用:把线程 id 和运行时 cell id 组合成一个查询 key。这样即使不同线程里出现相同 runtime cell id,也不会混在一起。

数据流:输入是 thread_id 和 runtime_cell_id。它把两者复制成一个二元组并返回,不读取也不修改其他状态。

调用关系:它是运行时 id 映射表的统一造钥匙函数。TraceReducer::record_runtime_code_cell_id 用它写入映射,TraceReducer::code_cell_id_for_runtime_cell_id_if_known 用它查询映射。

调用图:被 2 处调用(code_cell_id_for_runtime_cell_id_if_known, record_runtime_code_cell_id)。

rollout-trace/src/reducer/tool.rs源码 ↗
domain_logicmain loop / event reduction

可以把这个文件想成“工具调用登记处”。每当系统看到一个工具开始、结束,或者运行时又补充了更细的信息,它就把这些信息填进总账本 TraceReducer 里。它会检查同一个工具不能重复开始,工具必须属于一个真实存在的线程或 Codex 回合,还会把模型能看到的调用编号、对话条目、推理响应、终端操作、MCP 这类外部桥接编号互相连起来。这里的“工具”不只是普通函数,也可能是终端命令、写入标准输入、代理交互等。文件特别重要的一点是:很多信息到达顺序不固定。对话条目可能先来,工具开始事件可能后到;终端运行细节也可能和最终结果分开到。这个文件负责事后补链,保证最后看到的是一张完整关系网。

函数细节15
TraceReducer::start_tool_call48–169 ↗
fn start_tool_call(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        thread_id: Option<String>,
        codex_turn_id: Option<String>,
        started: ToolCallStar

作用:记录一个工具调用正式开始,并把它和线程、Codex 回合、模型可见的对话条目、终端操作等关联起来。别人会在读到“工具开始”原始事件时用它。

数据流:进去的是事件序号、时间、可能的线程或回合编号,以及 ToolCallStarted 这包工具开始信息。它先查重,确认模型可见调用编号没有被别的工具占用,再找出正确线程,检查线程和回合是否匹配;然后收集已经出现过的调用条目和输出条目,必要时从调用参数里创建终端操作,最后把一个新的 ToolCall 放进总账本。出来的结果是成功或报错,同时总账本多了一条正在运行的工具记录,并补上相关反向链接。

调用关系:它是工具生命周期的入口。开始时会请 TraceReducer::ensure_unique_model_visible_tool_call 查模型可见编号是否唯一,请 TraceReducer::tool_thread_id 找线程,请 TraceReducer::validate_tool_turn 校验归属;插入工具后会用 TraceReducer::add_tool_output_item 补输出条目的生产者关系,并用 TraceReducer::link_tool_to_inference_response 把工具挂到触发它的推理响应上。发现重复或上下文不对时会通过 bail! 直接中止。

调用图:调用 5 个内部函数(add_tool_output_item, ensure_unique_model_visible_tool_call, link_tool_to_inference_response, tool_thread_id, validate_tool_turn);外部调用 2 个(new, bail!)。

TraceReducer::assign_mcp_tool_call_correlation172–184 ↗
fn assign_mcp_tool_call_correlation(
        &mut self,
        tool_call_id: ToolCallId,
        mcp_call_id: McpCallId,
    ) -> Result<()>

作用:给已经存在的工具调用补上 MCP 调用编号。MCP 可以理解为外部工具桥接协议,这个编号用来把内部工具记录和桥接层看到的那次调用对上。

数据流:进去的是内部工具调用编号和 MCP 调用编号。它先在总账本里找到那条工具记录;如果找不到就报错,如果已经绑定过 MCP 编号也报错;否则把这个 MCP 编号写进去。出来的是成功或错误,总账本中的工具记录多了一个外部关联编号。

调用关系:它发生在通用工具记录已经创建之后,是补充关联信息的一步。它不创建新工具,只给现有工具加桥接层身份证;遇到未知工具或重复绑定时用 bail! 终止,避免同一条记录被错误地连到多个外部调用。

调用图:外部调用 1 个(bail!)。

TraceReducer::end_tool_call190–230 ↗
fn end_tool_call(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        tool_call_id: ToolCallId,
        status: ExecutionStatus,
        result_payload: Option<RawPayl

作用:记录一个工具调用结束,保存结束时间、状态和结果内容。它让系统知道这次工具是成功、失败,还是以别的状态收尾。

数据流:进去的是事件序号、时间、工具调用编号、结束状态,以及可选的结果原始载荷。它找到对应工具,填上结束时间、结束序号、状态和结果载荷编号;如果这个工具背后有终端操作,而且没有更底层的运行时输出来结束终端,它还会顺带结束终端操作;最后尝试把结果接到代理交互上。出来的是更新后的总账本或错误。

调用关系:它是普通工具生命周期的收尾步骤。它依赖之前 TraceReducer::start_tool_call 创建过工具记录;如果工具不存在会用 bail! 报错。对于终端类工具,它会决定是否把结束动作继续传给终端操作;对于代理交互,它也会把结果接过去,让上层能看到完整闭环。

调用图:外部调用 2 个(bail!, clone)。

TraceReducer::start_tool_runtime_observation236–293 ↗
fn start_tool_runtime_observation(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        tool_call_id: ToolCallId,
        runtime_payload: RawPayloadRef,
    ) -> Resul

作用:记录工具在“运行时”刚开始被观察到的额外信息。运行时信息指工具真正执行时暴露出的细节,比如终端进程、工作目录等,不一定在最初的调用参数里有。

数据流:进去的是事件序号、时间、工具编号和一份运行时原始载荷。它先找到工具,把这份载荷编号加入工具的运行时载荷列表,并避免重复;然后根据工具种类尝试创建更具体的终端操作或代理交互。如果已经有终端操作却又要为同一个终端类工具再创建一个,它会报错。出来的是更新后的工具记录、可能新建的终端或代理关系,或者错误。

调用关系:它接在 TraceReducer::start_tool_call 之后,用来给已登记的工具补运行时事实。它调用 push_unique 保存载荷编号不重复,并用 matches! 判断是否属于会冲突的终端类工具;遇到未知工具或重复终端操作会 bail!。如果新连上了终端操作,还会同步模型可见的观察关系。

调用图:调用 1 个内部函数(push_unique);外部调用 2 个(bail!, matches!)。

TraceReducer::end_tool_runtime_observation296–334 ↗
fn end_tool_runtime_observation(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        tool_call_id: ToolCallId,
        status: ExecutionStatus,
        runtime_payload

作用:记录工具运行时层面的结束观察。它处理的是底层执行环境说“我这边结束了”的信息。

数据流:进去的是事件序号、时间、工具编号、结束状态和运行时载荷。它找到工具,把载荷编号加入运行时载荷列表;如果工具关联了终端操作,就用这份运行时载荷结束终端操作;同时把结束信息交给代理交互相关逻辑。出来的是总账本里工具、终端、代理关系的结束状态被补齐,或返回错误。

调用关系:它通常和 TraceReducer::start_tool_runtime_observation 成对出现。它调用 push_unique 防止同一份运行时载荷被重复记录;如果工具编号不存在,就用 bail! 报错。它是底层运行时信息和上层工具记录之间的收尾桥梁。

调用图:调用 1 个内部函数(push_unique);外部调用 1 个(bail!)。

TraceReducer::attach_model_visible_tool_item341–370 ↗
fn attach_model_visible_tool_item(
        &mut self,
        item_id: &str,
        call_id: Option<&str>,
        kind: &ConversationItemKind,
    ) -> Result<()>

作用:把后来才出现的对话条目接到已有工具调用上。这里的“模型可见”是指模型或用户在对话记录里能看到的函数调用、工具调用输出等条目。

数据流:进去的是对话条目编号、可选的调用编号和条目类型。没有调用编号就什么也不做;如果是调用类条目,它找到唯一匹配的工具,把条目加入工具的调用条目列表,并尝试把工具连到推理响应;如果是输出类条目,它把输出条目加入工具,并给对话条目补上“由这个工具产生”的标记。出来的是补好链接的总账本。

调用关系:它由对话记录归约流程在发现新条目时回调使用,因为条目和工具开始事件的到达顺序不固定。它会先用 TraceReducer::single_tool_for_model_visible_call 找匹配工具,再按条目类型调用 TraceReducer::add_tool_call_item 或 TraceReducer::add_tool_output_item,必要时再调用 TraceReducer::link_tool_to_inference_response。

调用图:调用 4 个内部函数(add_tool_call_item, add_tool_output_item, link_tool_to_inference_response, single_tool_for_model_visible_call)。

TraceReducer::tool_thread_id372–390 ↗
fn tool_thread_id(
        &self,
        thread_id: Option<String>,
        codex_turn_id: Option<&str>,
    ) -> Result<String>

作用:为一个工具调用找出它属于哪个线程。线程可以理解为一条对话或执行脉络,工具必须挂在某条脉络下才有意义。

数据流:进去的是可选线程编号和可选 Codex 回合编号。它优先使用直接给出的线程编号;如果没有,就根据 Codex 回合去总账本里查这个回合所属线程;如果两者都没有,或回合不存在,就报错。出来的是确定的线程编号。

调用关系:它被 TraceReducer::start_tool_call 调用,是开始登记工具前的定位步骤。它不改总账本,只负责把上下文补清楚;无法定位时用 bail! 阻止继续登记,避免工具变成无主记录。

调用图:被 1 处调用(start_tool_call);外部调用 1 个(bail!)。

TraceReducer::validate_tool_turn392–409 ↗
fn validate_tool_turn(&self, thread_id: &str, codex_turn_id: Option<&str>) -> Result<()>

作用:检查工具声称的线程和 Codex 回合是否真的存在并且互相匹配。这样可以防止把工具错误地挂到别人的对话回合里。

数据流:进去的是线程编号和可选 Codex 回合编号。它先确认线程在总账本里存在;如果给了回合编号,再确认回合存在,并检查这个回合记录的线程是否等于传入线程。出来的是通过校验的成功结果,或者说明哪里不一致的错误。

调用关系:它被 TraceReducer::start_tool_call 调用,紧跟在线程定位之后。它像门卫一样做身份核验;发现未知线程、未知回合或线程回合不匹配时,通过 bail! 停止后续写入。

调用图:被 1 处调用(start_tool_call);外部调用 1 个(bail!)。

TraceReducer::ensure_unique_model_visible_tool_call411–425 ↗
fn ensure_unique_model_visible_tool_call(
        &self,
        model_visible_call_id: Option<&str>,
        tool_call_id: &str,
    ) -> Result<()>

作用:确保一个模型可见的调用编号不会对应到两个不同的工具调用。这个编号是把对话里的调用和后台工具记录对上的关键线索,不能乱。

数据流:进去的是可选的模型可见调用编号和当前工具编号。没有模型可见编号就直接通过;有的话,它查现在是否已有工具使用这个编号。如果已有工具而且不是当前工具,就报错;否则通过。出来的是成功或重复编号错误。

调用关系:它被 TraceReducer::start_tool_call 在创建工具前调用。它会借助 TraceReducer::single_tool_for_model_visible_call 查现有匹配项;如果发现同一个对话调用编号被多个工具争用,就用 bail! 阻止登记。

调用图:调用 1 个内部函数(single_tool_for_model_visible_call);被 1 处调用(start_tool_call);外部调用 1 个(bail!)。

TraceReducer::single_tool_for_model_visible_call427–442 ↗
fn single_tool_for_model_visible_call(
        &self,
        model_visible_call_id: &str,
    ) -> Result<Option<ToolCallId>>

作用:根据模型可见调用编号,找出唯一对应的工具调用。它既能返回“找到了哪一个”,也能发现“居然有多个”的脏数据。

数据流:进去的是模型可见调用编号。它遍历总账本里的工具调用,筛出这个编号相同的工具;没有匹配就返回空,有一个就返回工具编号,多于一个就报错。它不修改任何数据。

调用关系:它被 TraceReducer::ensure_unique_model_visible_tool_call 用来查重,也被 TraceReducer::attach_model_visible_tool_item 用来把后来的对话条目接到正确工具上。它是模型可见编号和内部工具编号之间的小型查询器。

调用图:被 2 处调用(attach_model_visible_tool_item, ensure_unique_model_visible_tool_call);外部调用 1 个(bail!)。

TraceReducer::model_visible_tool_item_ids444–460 ↗
fn model_visible_tool_item_ids(
        &self,
        thread_id: &str,
        call_id: &str,
        kinds: &[ConversationItemKind],
    ) -> Vec<String>

作用:找出某个线程里、某个模型可见调用编号对应的对话条目编号。它用来把已经出现过的调用条目或输出条目补到新建工具上。

数据流:进去的是线程编号、调用编号和允许的条目类型列表。它扫描所有对话条目,只保留线程相同、调用编号相同、类型也在目标列表里的条目,然后收集这些条目的编号。出来的是一个条目编号列表,不改总账本。

调用关系:它在 TraceReducer::start_tool_call 里被用来处理“对话条目先到、工具开始后到”的情况。工具创建时可以立刻带上这些已有条目,避免之后关系断开。

TraceReducer::add_tool_call_item462–468 ↗
fn add_tool_call_item(&mut self, tool_call_id: &str, item_id: &str) -> Result<()>

作用:把一个模型可见的“工具调用条目”加入某个工具调用的记录里。它让工具知道:对话里哪一条是在展示我这次调用。

数据流:进去的是工具调用编号和对话条目编号。它先找到工具记录;找不到就报错;找到后把条目编号加入工具的调用条目列表,并用去重方式避免重复加入。出来的是工具记录多了这个调用条目链接。

调用关系:它被 TraceReducer::attach_model_visible_tool_item 在后来观察到调用类对话条目时使用。它内部调用 push_unique 保证列表里不会有重复编号;如果工具突然不存在,会用 bail! 报错。

调用图:调用 1 个内部函数(push_unique);被 1 处调用(attach_model_visible_tool_item);外部调用 1 个(bail!)。

TraceReducer::add_tool_output_item470–486 ↗
fn add_tool_output_item(&mut self, tool_call_id: &str, item_id: &str) -> Result<()>

作用:把一个模型可见的“工具输出条目”加入工具记录,并反过来标记这个对话条目是由该工具产生的。这样从工具能找到输出,从输出也能追回工具。

数据流:进去的是工具调用编号和输出条目编号。它先找到工具,把输出条目编号去重加入列表;再找到对应对话条目,给它的 produced_by 列表加上 ProducerRef::Tool,也就是“生产者是这个工具”的标记。出来的是双向链接都补好的总账本。

调用关系:它被 TraceReducer::start_tool_call 用来接上已经存在的输出条目,也被 TraceReducer::attach_model_visible_tool_item 用来接上后来才出现的输出条目。它调用 push_unique 防重复;找不到工具或对话条目时用 bail! 报错。

调用图:调用 1 个内部函数(push_unique);被 2 处调用(attach_model_visible_tool_item, start_tool_call);外部调用 1 个(bail!)。

push_unique513–517 ↗
fn push_unique(items: &mut Vec<String>, item_id: &str)

作用:往字符串列表里加一个编号,但如果已经有了就不再加。它是一个很小的去重工具,防止同一条关系被记录两遍。

数据流:进去的是一个可修改的字符串列表和一个要加入的编号。它先检查列表里有没有完全相同的编号;没有才把编号复制进去,有就保持原样。出来的是可能多了一个元素的列表。

调用关系:它被 TraceReducer::add_tool_call_item、TraceReducer::add_tool_output_item、TraceReducer::start_tool_runtime_observation 和 TraceReducer::end_tool_runtime_observation 使用。它不懂工具业务,只负责“别重复塞同一个编号”这一件基础小事。

调用图:被 4 处调用(add_tool_call_item, add_tool_output_item, end_tool_runtime_observation, start_tool_runtime_observation)。

rollout-trace/src/reducer/tool/agents.rs源码 ↗
domain_logictrace reduction / cross-thread interaction linking

这段代码像一个“物流追踪员”。多智能体运行时会先记录发送方的工具调用,收件方线程里的那条消息可能稍后才出现,所以它不能马上画线,而是先把关系放进待办箱。等真正的收件消息进入对话后,再把这条线连到准确的消息上。它处理几类关系:创建子智能体、分配任务、发送消息、关闭智能体、子智能体把结果通知回父线程。它还会把原始载荷编号一起带上,方便以后追证据。比较重要的是:如果子智能体刚创建就失败,没有出现任务消息,它会退而求其次,把创建关系连到子线程本身,避免这条真实关系丢失。

函数细节28
spawn_edge_id66–68 ↗
fn spawn_edge_id(parent_thread_id: &str, child_thread_id: &str) -> String

作用:给“父线程创建子线程”这条关系生成一个稳定的编号。这样同一对父子线程不管被看到几次,都会落到同一条关系线上。

数据流:输入父线程编号和子线程编号 → 把它们拼成形如“edge:spawn:父:子”的字符串 → 输出这个关系编号,不改动任何数据。

调用关系:创建子智能体关系时会用它;TraceReducer::end_spawn_agent_interaction 和 TraceReducer::end_sub_agent_activity 都靠它保证同一条创建关系不会被重复当成不同关系。

调用图:被 2 处调用(end_spawn_agent_interaction, end_sub_agent_activity);外部调用 1 个(format!)。

TraceReducer::start_agent_interaction_from_runtime72–126 ↗
fn start_agent_interaction_from_runtime(
        &mut self,
        tool_call_id: &str,
        runtime_payload: &RawPayloadRef,
    ) -> Result<()>

作用:在工具刚开始运行时,先记录可能发生的智能体互动。比如开始派任务、发消息或关闭某个智能体。

数据流:输入工具调用编号和运行时载荷 → 查看这个工具调用是哪一类,读取载荷里的目标线程和消息内容 → 对派任务、发消息先排队等待落点;对关闭智能体直接尝试写入关闭关系;其他工具不处理。

调用关系:它是运行时“开始事件”的入口之一。它会把派任务和发消息交给 TraceReducer::queue_message_agent_interaction,把关闭交给 TraceReducer::upsert_close_agent_interaction,并用 serde_json 的 from_value 把原始 JSON 变成有字段名的数据。

调用图:调用 2 个内部函数(queue_message_agent_interaction, upsert_close_agent_interaction);外部调用 1 个(from_value)。

TraceReducer::end_agent_interaction_from_runtime129–184 ↗
fn end_agent_interaction_from_runtime(
        &mut self,
        wall_time_unix_ms: i64,
        tool_call_id: &str,
        runtime_payload: &RawPayloadRef,
    ) -> Result<()>

作用:在工具运行结束时,补全或结束一条智能体互动关系。它负责把“开始时还不知道的结果”补上,比如子线程编号、结束时间等。

数据流:输入当前时间、工具调用编号和结束载荷 → 读取工具类型和载荷内容 → 如果载荷是子智能体活动通知,就走专门分支;否则按创建、派任务、发消息、关闭分别补全关系 → 输出成功或错误,并更新轨迹里的关系或待办关系。

调用关系:它是运行时“结束事件”的入口之一。它根据情况把工作交给 TraceReducer::end_sub_agent_activity、TraceReducer::end_spawn_agent_interaction、TraceReducer::end_message_agent_interaction 或 TraceReducer::upsert_close_agent_interaction。

调用图:调用 4 个内部函数(end_message_agent_interaction, end_spawn_agent_interaction, end_sub_agent_activity, upsert_close_agent_interaction);外部调用 1 个(from_value)。

TraceReducer::end_sub_agent_activity186–243 ↗
fn end_sub_agent_activity(
        &mut self,
        wall_time_unix_ms: i64,
        tool_call_id: &str,
        tool_kind: &ToolCallKind,
        payload: &SubAgentActivityEvent,
    ) -> Result<()>

作用:处理一种特殊的结束事件:子智能体活动通知。它判断这条通知和当前工具类型是否对得上,避免把不相干的事件错误连线。

数据流:输入时间、工具调用编号、工具类型和活动载荷 → 取出目标子线程编号 → 按“创建已开始、任务已交互、消息已交互、关闭被打断”等组合生成或更新对应关系 → 如果组合不合理,就返回错误。

调用关系:它由 TraceReducer::end_agent_interaction_from_runtime 调用。它会用 spawn_edge_id 或 tool_edge_id 生成关系编号,再把消息类关系交给 TraceReducer::queue_sub_agent_activity_message_edge,关闭关系交给 TraceReducer::upsert_close_agent_interaction。

调用图:调用 4 个内部函数(queue_sub_agent_activity_message_edge, upsert_close_agent_interaction, spawn_edge_id, tool_edge_id);被 1 处调用(end_agent_interaction_from_runtime);外部调用 1 个(bail!)。

TraceReducer::queue_sub_agent_activity_message_edge245–275 ↗
fn queue_sub_agent_activity_message_edge(
        &mut self,
        wall_time_unix_ms: i64,
        tool_call_id: &str,
        edge_id: String,
        edge_kind: InteractionEdgeKind,
        target

作用:把子智能体活动通知转换成一条“发送方工具调用 → 接收方消息”的待连接关系。它负责凑齐作者、正文、时间和证据载荷。

数据流:输入时间、工具调用编号、关系编号、关系类型和目标线程 → 读取工具调用开始时间、发送方智能体路径、调用参数里的消息正文、相关原始载荷编号 → 生成一个待处理关系,尝试马上连到收件消息,连不上就先排队。

调用关系:它由 TraceReducer::end_sub_agent_activity 调用。它会向 TraceReducer::agent_message_content_from_invocation、TraceReducer::agent_path_for_thread、TraceReducer::agent_tool_payload_ids 要信息,最后交给 TraceReducer::queue_or_resolve_agent_interaction_edge 统一处理。

调用图:调用 4 个内部函数(agent_message_content_from_invocation, agent_path_for_thread, agent_tool_payload_ids, queue_or_resolve_agent_interaction_edge);被 1 处调用(end_sub_agent_activity)。

TraceReducer::agent_message_content_from_invocation277–307 ↗
fn agent_message_content_from_invocation(&self, tool_call_id: &str) -> Result<String>

作用:从工具的调用参数里取出真正发送给另一个智能体的消息正文。因为活动通知本身可能只说“发生了交互”,不一定直接带完整内容。

数据流:输入工具调用编号 → 找到这个工具调用的原始调用载荷 → 读取其中 payload.arguments 字符串 → 把这段 JSON 字符串解析成参数对象 → 输出 message 字段。

调用关系:它被 TraceReducer::queue_sub_agent_activity_message_edge 调用,用来补齐待连接关系的消息正文。它内部用 serde_json 的 from_str 解析嵌在字符串里的 JSON。

调用图:被 1 处调用(queue_sub_agent_activity_message_edge);外部调用 1 个(from_str)。

TraceReducer::attach_agent_interaction_tool_result310–346 ↗
fn attach_agent_interaction_tool_result(
        &mut self,
        tool_call_id: &str,
        result_payload: Option<&RawPayloadRef>,
    ) -> Result<()>

作用:把工具结果载荷补挂到已经存在或还在等待的智能体关系上。这样以后查看关系时,不只知道发生了什么,也能找到原始结果证据。

数据流:输入工具调用编号和可选结果载荷 → 如果没有结果载荷就什么也不做 → 先找已经写入的关系,找到就把结果载荷编号加入;找不到就找待处理关系并加入 → 保证同一个编号不会重复加入。

调用关系:它通常在工具结果被归档后使用。它通过 push_unique 避免重复记录,同一工具调用对应的关系无论已经落地还是仍在等待,都能拿到这份结果证据。

调用图:调用 1 个内部函数(push_unique)。

TraceReducer::end_spawn_agent_interaction348–376 ↗
fn end_spawn_agent_interaction(
        &mut self,
        wall_time_unix_ms: i64,
        tool_call_id: &str,
        payload: &CollabAgentSpawnEndEvent,
    ) -> Result<()>

作用:处理“创建子智能体”工具结束后的信息,特别是拿到新子线程编号后建立父子关系。

数据流:输入时间、工具调用编号和创建结束载荷 → 如果没有新线程编号就不做事 → 生成创建关系编号,读取发送方智能体路径和相关载荷编号 → 建立一条待连接关系,目标优先是子线程里出现的任务消息,必要时可退回到子线程本身。

调用关系:它由 TraceReducer::end_agent_interaction_from_runtime 调用。它使用 spawn_edge_id 生成父子关系编号,用 TraceReducer::agent_path_for_thread 和 TraceReducer::agent_tool_payload_ids 补资料,再交给 TraceReducer::queue_or_resolve_agent_interaction_edge。

调用图:调用 4 个内部函数(agent_path_for_thread, agent_tool_payload_ids, queue_or_resolve_agent_interaction_edge, spawn_edge_id);被 1 处调用(end_agent_interaction_from_runtime)。

TraceReducer::end_message_agent_interaction378–392 ↗
fn end_message_agent_interaction(
        &mut self,
        wall_time_unix_ms: i64,
        tool_call_id: &str,
        edge_kind: InteractionEdgeKind,
        payload: &CollabAgentInteractionEndEven

作用:处理派任务或发消息工具的结束事件,把结束时间和结束载荷里的收件信息补到消息关系里。

数据流:输入时间、工具调用编号、关系类型和结束载荷 → 从载荷取目标线程和消息正文 → 调用统一的排队/解析逻辑,并把结束时间一起传进去 → 更新或生成待连接关系。

调用关系:它由 TraceReducer::end_agent_interaction_from_runtime 调用,本身不做复杂判断,而是把实际工作交给 TraceReducer::queue_message_agent_interaction。

调用图:调用 1 个内部函数(queue_message_agent_interaction);被 1 处调用(end_agent_interaction_from_runtime)。

TraceReducer::queue_message_agent_interaction394–418 ↗
fn queue_message_agent_interaction(
        &mut self,
        tool_call_id: &str,
        kind: InteractionEdgeKind,
        target_thread_id: String,
        message_content: String,
        ended_a

作用:为“给另一个智能体发消息”这类工具创建一条待连接关系。它把工具调用和将来收件方对话里的那条消息对应起来。

数据流:输入工具调用编号、关系类型、目标线程、消息正文和可选结束时间 → 读取工具开始时间、发送方智能体路径、相关原始载荷编号 → 用工具调用编号生成关系编号 → 尝试连到目标线程中匹配的消息,连不上就先保存为待处理。

调用关系:它被 TraceReducer::start_agent_interaction_from_runtime 和 TraceReducer::end_message_agent_interaction 调用。它会使用 TraceReducer::agent_path_for_thread、TraceReducer::agent_tool_payload_ids、tool_edge_id,并把最终处理交给 TraceReducer::queue_or_resolve_agent_interaction_edge。

调用图:调用 4 个内部函数(agent_path_for_thread, agent_tool_payload_ids, queue_or_resolve_agent_interaction_edge, tool_edge_id);被 2 处调用(end_message_agent_interaction, start_agent_interaction_from_runtime)。

TraceReducer::agent_tool_payload_ids420–436 ↗
fn agent_tool_payload_ids(&self, tool_call_id: &str) -> Result<Vec<String>>

作用:收集某个智能体工具调用相关的所有原始载荷编号。这里的载荷可以理解为“原始收据”,包括调用、运行中事件和结果。

数据流:输入工具调用编号 → 找到这个工具调用 → 依次收集调用载荷、运行时载荷、结果载荷 → 去重后输出编号列表。

调用关系:它被多处用来给关系补证据:TraceReducer::end_spawn_agent_interaction、TraceReducer::queue_message_agent_interaction、TraceReducer::queue_sub_agent_activity_message_edge 和 TraceReducer::upsert_close_agent_interaction 都会调用它。

调用图:调用 1 个内部函数(push_unique);被 4 处调用(end_spawn_agent_interaction, queue_message_agent_interaction, queue_sub_agent_activity_message_edge, upsert_close_agent_interaction);外部调用 1 个(new)。

TraceReducer::upsert_close_agent_interaction438–472 ↗
fn upsert_close_agent_interaction(
        &mut self,
        tool_call_id: &str,
        target_thread_id: String,
        ended_at_unix_ms: Option<i64>,
    ) -> Result<()>

作用:写入或更新“关闭某个智能体线程”的关系。upsert 的意思是“有就更新,没有就插入”。

数据流:输入工具调用编号、目标线程编号和可选结束时间 → 如果目标线程根本不在轨迹里,就不创建假关系 → 否则读取工具开始时间和相关载荷编号 → 写入或合并一条从工具调用指向目标线程的关闭关系。

调用关系:它由 TraceReducer::start_agent_interaction_from_runtime、TraceReducer::end_agent_interaction_from_runtime 和 TraceReducer::end_sub_agent_activity 调用。它用 tool_edge_id 生成编号,用 TraceReducer::agent_tool_payload_ids 收集证据,最后交给 TraceReducer::upsert_interaction_edge 合并保存。

调用图:调用 3 个内部函数(agent_tool_payload_ids, upsert_interaction_edge, tool_edge_id);被 3 处调用(end_agent_interaction_from_runtime, end_sub_agent_activity, start_agent_interaction_from_runtime);外部调用 1 个(new)。

TraceReducer::queue_agent_result_interaction_edge475–513 ↗
fn queue_agent_result_interaction_edge(
        &mut self,
        observed: ObservedAgentResultEdge,
    ) -> Result<()>

作用:记录“子智能体完成后,把结果通知父线程”这条关系。它让读者能看出结果是从哪个子智能体流回父智能体的。

数据流:输入一个 ObservedAgentResultEdge 包,里面有时间、子线程、父线程、消息和可选载荷 → 先尽量把来源定位到子线程最后一条助手消息;如果没有,就定位到子线程本身 → 生成待连接关系,目标是父线程里的通知消息 → 能立即匹配就落地,不能就等待。

调用关系:它是子结果通知进入 reducer 时使用的入口。它调用 TraceReducer::agent_path_for_thread 找作者路径,调用 TraceReducer::latest_assistant_message_item_for_turn 找更精确的来源,最后交给 TraceReducer::queue_or_resolve_agent_interaction_edge。

调用图:调用 3 个内部函数(agent_path_for_thread, latest_assistant_message_item_for_turn, queue_or_resolve_agent_interaction_edge)。

TraceReducer::resolve_pending_agent_edges_for_item516–541 ↗
fn resolve_pending_agent_edges_for_item(
        &mut self,
        item_id: &str,
    ) -> Result<()>

作用:当一条新的对话消息被整理出来时,检查它是不是某条等待中的智能体关系的收件消息。

数据流:输入新消息编号 → 如果这条消息已经被某条关系当成目标,就跳过 → 尝试识别它是不是跨智能体消息,并取出线程、作者、正文 → 在待处理关系里找完全匹配的一条 → 找到后移出待办箱并写成正式关系。

调用关系:它通常在对话项被归约完成后调用。它使用 TraceReducer::is_interaction_edge_target_item 避免重复连接,用 TraceReducer::inter_agent_message_item 识别消息,最后交给 TraceReducer::upsert_agent_interaction_edge_for_item。

调用图:调用 3 个内部函数(inter_agent_message_item, is_interaction_edge_target_item, upsert_agent_interaction_edge_for_item)。

TraceReducer::queue_or_resolve_agent_interaction_edge543–590 ↗
fn queue_or_resolve_agent_interaction_edge(
        &mut self,
        pending: PendingAgentInteractionEdge,
    ) -> Result<()>

作用:这是待连接关系的总调度口:能马上连到消息就连,暂时连不上就排队。它还负责合并同一条关系的多次观察结果。

数据流:输入一条待处理关系 → 先在目标线程里找尚未被占用且作者、正文都匹配的消息 → 找到就写成正式关系 → 找不到就看待办箱里是否已有同编号关系;有则校验关键信息一致并合并时间和载荷;没有就加入待办箱。

调用关系:它被 TraceReducer::end_spawn_agent_interaction、TraceReducer::queue_agent_result_interaction_edge、TraceReducer::queue_message_agent_interaction 和 TraceReducer::queue_sub_agent_activity_message_edge 调用。它会调用 TraceReducer::find_unlinked_inter_agent_message_item、TraceReducer::upsert_agent_interaction_edge_for_item 和 extend_unique;如果同一编号出现矛盾信息,会报错。

调用图:调用 3 个内部函数(find_unlinked_inter_agent_message_item, upsert_agent_interaction_edge_for_item, extend_unique);被 4 处调用(end_spawn_agent_interaction, queue_agent_result_interaction_edge, queue_message_agent_interaction, queue_sub_agent_activity_message_edge);外部调用 1 个(bail!)。

TraceReducer::resolve_pending_spawn_edge_fallbacks593–628 ↗
fn resolve_pending_spawn_edge_fallbacks(&mut self) -> Result<()>

作用:在最后收尾时,处理那些一直没等到“子线程任务消息”的创建关系。只要子线程本身存在,就把创建关系连到线程,避免丢失事实。

数据流:取出全部待处理关系 → 只处理带有创建备用目标的关系 → 如果不是创建关系却带了创建备用目标,就报错 → 如果子线程存在,就写入一条从创建工具到子线程的关系;不存在则跳过 → 最后返回结果。

调用关系:它通常在归约后期或收尾阶段使用。它调用 TraceReducer::upsert_interaction_edge 保存 fallback 关系,并用 std::mem::take 先清空待办箱再逐条处理。

调用图:调用 1 个内部函数(upsert_interaction_edge);外部调用 3 个(new, bail!, take)。

TraceReducer::upsert_agent_interaction_edge_for_item630–647 ↗
fn upsert_agent_interaction_edge_for_item(
        &mut self,
        pending: PendingAgentInteractionEdge,
        target_item_id: String,
    ) -> Result<()>

作用:把一条待处理的智能体互动关系,正式落到某个具体的对话消息上。

数据流:输入待处理关系和目标消息编号 → 构造一条正式关系,目标锚点是这条对话消息,同时把这条消息编号放入 carried_item_ids → 调用统一保存逻辑写入或合并。

调用关系:它被 TraceReducer::queue_or_resolve_agent_interaction_edge 和 TraceReducer::resolve_pending_agent_edges_for_item 调用。它本身只负责把“待办格式”翻译成正式 InteractionEdge,然后交给 TraceReducer::upsert_interaction_edge。

调用图:调用 1 个内部函数(upsert_interaction_edge);被 2 处调用(queue_or_resolve_agent_interaction_edge, resolve_pending_agent_edges_for_item);外部调用 1 个(vec!)。

TraceReducer::upsert_interaction_edge649–677 ↗
fn upsert_interaction_edge(&mut self, edge: InteractionEdge) -> Result<()>

作用:把一条互动关系保存进最终轨迹,并处理重复出现的同一条关系。它是关系写入的最后一道关口。

数据流:输入一条关系 → 如果同编号关系已存在,就检查类型、来源、目标必须一致;一致则合并最早开始时间、最晚结束时间、携带的消息和原始载荷 → 如果不存在,就直接插入 → 若同编号但端点冲突,则报错。

调用关系:它被 TraceReducer::resolve_pending_spawn_edge_fallbacks、TraceReducer::upsert_agent_interaction_edge_for_item 和 TraceReducer::upsert_close_agent_interaction 调用。它使用 extend_unique 合并列表,保证同一证据不会重复出现。

调用图:调用 1 个内部函数(extend_unique);被 3 处调用(resolve_pending_spawn_edge_fallbacks, upsert_agent_interaction_edge_for_item, upsert_close_agent_interaction);外部调用 1 个(bail!)。

TraceReducer::find_unlinked_inter_agent_message_item679–699 ↗
fn find_unlinked_inter_agent_message_item(
        &self,
        thread_id: &str,
        message_author: &str,
        message_content: &str,
    ) -> Option<String>

作用:在某个线程里寻找一条还没被关系占用的跨智能体消息。它用来判断待处理关系是否已经可以落到具体消息上。

数据流:输入线程编号、期望作者和期望正文 → 查看该线程的对话消息列表 → 跳过已经作为关系目标的消息 → 找到作者和正文都匹配的跨智能体消息 → 输出它的消息编号;找不到就输出空。

调用关系:它被 TraceReducer::queue_or_resolve_agent_interaction_edge 调用,是“能不能立即解析待处理关系”的查找步骤。

调用图:被 1 处调用(queue_or_resolve_agent_interaction_edge)。

TraceReducer::inter_agent_message_item701–710 ↗
fn inter_agent_message_item(&self, item_id: &str) -> Option<(String, String, String)>

作用:判断一条对话消息是不是发给当前线程所属智能体的跨智能体消息,并提取关键字段。

数据流:输入消息编号 → 找到消息本身 → 调用 inter_agent_message_fields 提取作者、收件人和正文 → 再检查收件人是否等于该线程的智能体路径 → 如果匹配,输出线程编号、作者和正文;否则输出空。

调用关系:它被 TraceReducer::resolve_pending_agent_edges_for_item 调用,用于识别新出现的消息是否能接住某条等待中的关系。

调用图:调用 1 个内部函数(inter_agent_message_fields);被 1 处调用(resolve_pending_agent_edges_for_item)。

TraceReducer::agent_path_for_thread712–718 ↗
fn agent_path_for_thread(&self, thread_id: &str) -> Result<String>

作用:根据线程编号找到这个线程代表的智能体路径。智能体路径可以理解为“这个智能体在团队里的名字或地址”。

数据流:输入线程编号 → 在线程表里查找 → 找到就输出 agent_path → 找不到就返回带说明的错误。

调用关系:它被 TraceReducer::end_spawn_agent_interaction、TraceReducer::queue_agent_result_interaction_edge、TraceReducer::queue_message_agent_interaction 和 TraceReducer::queue_sub_agent_activity_message_edge 调用,用来填关系里的消息作者。

调用图:被 4 处调用(end_spawn_agent_interaction, queue_agent_result_interaction_edge, queue_message_agent_interaction, queue_sub_agent_activity_message_edge)。

TraceReducer::is_interaction_edge_target_item720–725 ↗
fn is_interaction_edge_target_item(&self, item_id: &str) -> bool

作用:检查某条对话消息是否已经是某条互动关系的目标。这样可以避免一条消息被两条关系重复认领。

数据流:输入消息编号 → 遍历已经保存的互动关系 → 如果发现某条关系的目标正是这条消息,就返回 true;否则返回 false。

调用关系:它被 TraceReducer::resolve_pending_agent_edges_for_item 调用,是解析新消息前的防重检查。

调用图:被 1 处调用(resolve_pending_agent_edges_for_item)。

TraceReducer::latest_assistant_message_item_for_turn727–744 ↗
fn latest_assistant_message_item_for_turn(
        &self,
        thread_id: &str,
        codex_turn_id: &str,
    ) -> Option<String>

作用:在某个子线程的一轮对话里,找最后出现的普通助手消息。它常被用作“子智能体结果”的来源点。

数据流:输入线程编号和回合编号 → 从所有对话消息中筛选同线程、同回合、角色是助手、类型是普通消息且不是 agent_message 的项目 → 按首次出现时间取最新一条 → 输出它的消息编号;没有则输出空。

调用关系:它被 TraceReducer::queue_agent_result_interaction_edge 调用。若找到,就让结果关系从具体助手消息出发;若找不到,调用方会退回到从子线程本身出发。

调用图:被 1 处调用(queue_agent_result_interaction_edge)。

extend_unique747–753 ↗
fn extend_unique(items: &mut Vec<String>, new_items: Vec<String>)

作用:把一批字符串加入列表,但不重复加入已有内容。这里主要用于合并消息编号或原始载荷编号。

数据流:输入一个可修改列表和一批新字符串 → 逐个检查新字符串是否已存在 → 不存在才追加 → 修改原列表,不返回新列表。

调用关系:它被 TraceReducer::queue_or_resolve_agent_interaction_edge 和 TraceReducer::upsert_interaction_edge 调用,用来合并多次观察到的证据。

调用图:被 2 处调用(queue_or_resolve_agent_interaction_edge, upsert_interaction_edge)。

tool_edge_id755–757 ↗
fn tool_edge_id(tool_call_id: &str) -> String

作用:给普通工具调用相关的互动关系生成稳定编号。这样同一个工具调用对应的关系可以被反复更新而不是重复创建。

数据流:输入工具调用编号 → 拼成形如“edge:tool:工具编号”的字符串 → 输出这个关系编号。

调用关系:它被 TraceReducer::end_sub_agent_activity、TraceReducer::queue_message_agent_interaction 和 TraceReducer::upsert_close_agent_interaction 调用,用于派任务、发消息、关闭等以工具调用为核心的关系。

调用图:被 3 处调用(end_sub_agent_activity, queue_message_agent_interaction, upsert_close_agent_interaction);外部调用 1 个(format!)。

tool_call_source_matches759–761 ↗
fn tool_call_source_matches(anchor: &TraceAnchor, tool_call_id: &str) -> bool

作用:判断一个关系来源是不是指定的工具调用。它是一个小检查器,用来把工具结果补到正确的关系上。

数据流:输入一个锚点和工具调用编号 → 如果锚点类型是 ToolCall 且编号相同,就返回 true → 否则返回 false;不改动任何数据。

调用关系:它服务于按工具调用查找关系的场景,尤其适合在补充结果载荷时确认“这条关系是不是来自这个工具”。

调用图:外部调用 1 个(matches!)。

push_unique763–767 ↗
fn push_unique(items: &mut Vec<String>, item: &str)

作用:把一个字符串加入列表,但如果已经有了就不加。它是单个元素版本的去重追加工具。

数据流:输入一个可修改列表和一个字符串 → 检查列表里是否已有相同字符串 → 没有就复制并追加 → 修改原列表,不返回值。

调用关系:它被 TraceReducer::agent_tool_payload_ids 和 TraceReducer::attach_agent_interaction_tool_result 调用,用来保证载荷编号不会重复记录。

调用图:被 2 处调用(agent_tool_payload_ids, attach_agent_interaction_tool_result)。

inter_agent_message_fields769–805 ↗
fn inter_agent_message_fields(item: &ConversationItem) -> Option<(String, String, String)>

作用:从一条对话消息里识别并提取跨智能体通信的作者、收件人和正文。它同时兼容新格式和旧格式的轨迹。

数据流:输入一条 ConversationItem → 先确认它是助手发出的消息 → 如果里面有结构化 agent_message,就从结构化字段和正文部分取作者、收件人、内容;如果没有,就尝试把文本解析成旧版 InterAgentCommunication → 成功则输出三项信息,失败则输出空。

调用关系:它被 TraceReducer::inter_agent_message_item 调用。它把复杂的消息格式判断封装起来,让上层只关心“这是不是一条可连线的跨智能体消息”。

调用图:被 1 处调用(inter_agent_message_item)。

rollout-trace/src/reducer/tool/terminal.rs源码 ↗
domain_logictrace reduction

原始 trace 里,终端行为只是散落在一堆工具开始、工具结束、运行时事件里的碎片。这个文件的作用,就是把这些碎片拼成一张人能读懂的“终端操作记录”。比如执行命令、往已有进程里写 stdin,都要变成 TerminalOperation;如果能认出它属于哪个终端进程,还会挂到 TerminalSession 上。它会从不同来源读 JSON 载荷:有些是协议层直接给的 exec 信息,信息比较完整;有些是普通工具派发记录,比如 write_stdin,就需要从参数里抠出 session_id。结束时,它会补上退出码、stdout、stderr、格式化输出等结果。它还会把模型实际看见的工具调用和输出条目同步到终端记录上,方便界面在“聊天记录”和“终端时间线”之间互相跳转。这里很重要的一点是:它不会凭空编造缺失信息;缺少关键载荷时宁可不生成终端行。

函数细节16
TraceReducer::start_terminal_operation_from_invocation36–72 ↗
fn start_terminal_operation_from_invocation(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        thread_id: &str,
        tool_call_id: &str,
        kind: &ToolCallKi

作用:从普通工具派发的“调用参数”里启动一条终端操作记录。它主要是为了 write_stdin 这种没有完整运行时开始事件的工具,否则这类输入操作会在终端时间线里消失。

数据流:进去的是事件序号、时间、线程、工具调用 ID、工具种类,以及可选的原始调用载荷。它先确认工具是不是 WriteStdin;不是就什么也不做。是的话,它读取载荷里的 JSON,解析出要写入哪个终端、写入什么内容等请求信息,然后交给插入函数创建 TerminalOperation。出来的是一个可选的终端操作 ID;如果缺载荷或不是目标工具,就返回空。

调用关系:它通常在 reducer 看到工具调用开始时被尝试调用。它自己不直接改很多细节,而是先用 parse_dispatch_terminal_request 把派发载荷翻译成统一格式,再把创建记录的活交给 TraceReducer::insert_terminal_operation。

调用图:调用 2 个内部函数(insert_terminal_operation, parse_dispatch_terminal_request);外部调用 1 个(matches!)。

TraceReducer::start_terminal_operation_from_runtime75–106 ↗
fn start_terminal_operation_from_runtime(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        thread_id: &str,
        tool_call_id: &str,
        kind: &ToolCallKind,

作用:从协议层的运行时开始事件里启动终端操作记录。协议层事件通常比普通工具参数更接近真实运行情况,所以它能拿到命令、工作目录、进程 ID 等更可靠的信息。

数据流:进去的是事件序号、时间、线程、工具调用 ID、工具种类和运行时载荷。它先把工具种类换算成终端操作种类;如果这个工具根本不是终端工具,就返回空。然后读取 JSON,按 ExecCommandBeginPayload 解析,再整理成统一的终端请求,最后插入 rollout 的终端操作表。出来的是可选的终端操作 ID。

调用关系:它在 reducer 处理 exec 这类协议运行时开始事件时使用。它先问 terminal_operation_kind 这个工具该不该算终端操作,再用 parse_protocol_terminal_request 翻译请求,最后交给 TraceReducer::insert_terminal_operation 统一落表。

调用图:调用 3 个内部函数(insert_terminal_operation, parse_protocol_terminal_request, terminal_operation_kind);外部调用 1 个(from_value)。

TraceReducer::insert_terminal_operation108–150 ↗
fn insert_terminal_operation(
        &mut self,
        start: TerminalOperationStart<'_>,
    ) -> Result<Option<TerminalOperationId>>

作用:真正创建一条终端操作记录,并放进最终的 rollout 结果里。可以把它理解成“给一次终端动作登记建档”。

数据流:进去的是已经整理好的开始信息:时间、序号、线程、工具调用 ID、操作种类、原始载荷和请求内容。它先生成一个新的 terminal_operation ID,然后把开始时间、运行状态、请求内容、原始载荷 ID 等写成 TerminalOperation 存起来。如果请求里带了终端 ID,还会确保对应的 TerminalSession 存在,并把这次操作挂到会话下。出来的是新建的操作 ID。

调用关系:它是两个启动入口的共同下游:TraceReducer::start_terminal_operation_from_invocation 和 TraceReducer::start_terminal_operation_from_runtime 都会把解析好的请求交给它。它内部会调用 TraceReducer::next_terminal_operation_id 生成编号,也可能调用 TraceReducer::ensure_terminal_session 建立或更新会话。

调用图:调用 2 个内部函数(ensure_terminal_session, next_terminal_operation_id);被 2 处调用(start_terminal_operation_from_invocation, start_terminal_operation_from_runtime);外部调用 2 个(new, vec!)。

TraceReducer::end_terminal_operation156–231 ↗
fn end_terminal_operation(
        &mut self,
        seq: RawEventSeq,
        wall_time_unix_ms: i64,
        thread_id: &str,
        operation_id: &str,
        status: ExecutionStatus,
        re

作用:给一条已经开始的终端操作补上结束信息。也就是把“正在运行”变成“已经结束”,并记录输出、错误、退出状态等结果。

数据流:进去的是结束事件的序号、时间、线程、操作 ID、执行状态,以及可选的响应载荷。它先找到对应的终端操作;找不到就报错。若有响应载荷,它读取 JSON,并按操作类型解析成终端结果。随后它更新结束时间、结束序号和状态,追加原始载荷 ID,保存 stdout、stderr、退出码等结果。如果结束事件才带来终端 ID,它也会补上;如果开始和结束给出的终端 ID 冲突,它会报错。最后如果知道终端 ID,就确保会话存在并把操作挂进去。

调用关系:它在工具调用结束时被使用,可以对终端工具完成收尾。它会复用 TraceReducer::ensure_terminal_session 来维护会话关系,也会用 push_unique 避免同一个载荷 ID 或操作 ID 被重复加入。

调用图:调用 1 个内部函数(ensure_terminal_session);外部调用 2 个(bail!, push_unique)。

TraceReducer::ensure_terminal_session233–274 ↗
fn ensure_terminal_session(
        &mut self,
        thread_id: &str,
        terminal_id: &str,
        operation_id: &str,
        started_at_unix_ms: i64,
        started_seq: RawEventSeq,
    )

作用:确保某个终端会话存在,并把当前终端操作加入这个会话。就像发现一张车票属于某辆车时,先确认车次档案有了,再把车票挂到车次下面。

数据流:进去的是线程 ID、终端 ID、操作 ID、这次操作的开始时间和开始序号。它先看 terminal_sessions 里有没有这个终端;没有就新建一个正在运行的 TerminalSession。然后检查这个会话是不是属于同一个线程;如果不是,说明记录串线了,会报错。最后把操作 ID 加进会话的 operation_ids,且避免重复。出来没有新值,但会改动 rollout 里的会话表。

调用关系:它被 TraceReducer::insert_terminal_operation 和 TraceReducer::end_terminal_operation 调用。前者在开始时就知道终端 ID 时使用它,后者在结束时才补齐终端 ID 时也会使用它。

调用图:被 2 处调用(end_terminal_operation, insert_terminal_operation);外部调用 3 个(new, bail!, push_unique)。

TraceReducer::sync_terminal_model_observation280–317 ↗
fn sync_terminal_model_observation(
        &mut self,
        tool_call_id: &str,
    ) -> Result<()>

作用:把“模型实际看见的工具调用和工具输出”同步到终端操作上。这样查看器既能看终端运行结果,也能知道哪些内容真的进入了模型上下文。

数据流:进去的是工具调用 ID。它先找到对应的 tool call,再看这个工具调用有没有关联的终端操作;没有就直接返回。然后复制模型可见的调用条目 ID 和输出条目 ID;如果两者都为空,也不做事。否则它找到对应终端操作,把这些 ID 写入 model_observations;已有同来源记录就更新,没有就新增。出来没有新值,但终端操作会多出或更新一条模型观察记录。

调用关系:它在工具调用和模型可见 transcript 条目已经对上之后使用。它不解析终端输出,而是把 tool_call 已经收集好的可见条目映射到 TerminalOperation 上;如果找不到应有对象,会用 bail 报错,说明 reducer 的内部状态不一致。

调用图:外部调用 1 个(bail!)。

TraceReducer::next_terminal_operation_id319–323 ↗
fn next_terminal_operation_id(&mut self) -> TerminalOperationId

作用:生成下一个终端操作 ID。它保证每条终端操作都有一个稳定、不会和前面重复的名字。

数据流:进去的是 reducer 自己保存的计数器。它取出当前序号,计数器加一,然后把序号格式化成类似 terminal_operation:0 的字符串。出来的是新的 TerminalOperationId,同时 reducer 内部计数器已经前进一格。

调用关系:它只被 TraceReducer::insert_terminal_operation 使用。每次真正创建终端操作前,都会先通过它拿到唯一编号。

调用图:被 1 处调用(insert_terminal_operation);外部调用 1 个(format!)。

terminal_operation_kind326–341 ↗
fn terminal_operation_kind(kind: &ToolCallKind) -> Option<TerminalOperationKind>

作用:判断一个工具调用种类是不是终端相关,并把它换成终端操作种类。它相当于一个小分类器。

数据流:进去的是 ToolCallKind。它只认可 ExecCommand 和 WriteStdin,分别返回对应的 TerminalOperationKind;其他工具,比如网页、图片、补丁、代理任务等,都返回空。出来的是可选的终端操作种类。

调用关系:它被 TraceReducer::start_terminal_operation_from_runtime 调用。运行时事件来了以后,先靠它判断这件事要不要进入终端时间线。

调用图:被 1 处调用(start_terminal_operation_from_runtime)。

parse_protocol_terminal_request363–388 ↗
fn parse_protocol_terminal_request(
    payload: ExecCommandBeginPayload,
    operation_kind: &TerminalOperationKind,
) -> ParsedTerminalRequest

作用:把协议层的终端开始载荷翻译成统一的终端请求。协议层载荷是原始说法,这个函数把它改成系统手册和界面更容易使用的说法。

数据流:进去的是 ExecCommandBeginPayload 和终端操作种类。它先取出可能存在的 process_id 作为终端 ID。若是 ExecCommand,它把命令数组拼成展示用字符串,同时保留原命令和工作目录;若是 WriteStdin,它取 interaction_input 作为要写入的 stdin,缺失就用空字符串。出来的是 ParsedTerminalRequest,里面有可选终端 ID 和统一请求内容。

调用关系:它被 TraceReducer::start_terminal_operation_from_runtime 调用。运行时开始载荷先被 JSON 反序列化成结构体,再由它整理成 insert_terminal_operation 能接受的格式。

调用图:被 1 处调用(start_terminal_operation_from_runtime)。

parse_dispatch_terminal_request390–421 ↗
fn parse_dispatch_terminal_request(value: JsonValue) -> Result<ParsedTerminalRequest>

作用:从普通工具派发载荷里解析 write_stdin 的请求。它专门补救那些没有协议层开始事件、但派发参数里有 session_id 的情况。

数据流:进去的是一段 JSON。它先按派发工具请求格式解析,确认工具名必须是 write_stdin,载荷类型必须是 function。然后取出 arguments 字符串,再解析成 DispatchedWriteStdinArgs。它从 session_id 里提取终端 ID,并拿到要写入的字符、yield_time_ms、max_output_tokens。出来的是 ParsedTerminalRequest;如果工具名、类型或 session_id 不对,就报错。

调用关系:它被 TraceReducer::start_terminal_operation_from_invocation 调用。它内部会用 terminal_id_from_json 把 JSON 里的 session_id 变成字符串形式的终端 ID。

调用图:调用 1 个内部函数(terminal_id_from_json);被 1 处调用(start_terminal_operation_from_invocation);外部调用 3 个(bail!, from_str, from_value)。

parse_terminal_response_payload423–446 ↗
fn parse_terminal_response_payload(
    value: JsonValue,
    operation_kind: &TerminalOperationKind,
    raw_payload_id: &str,
) -> Result<ParsedTerminalResponse>

作用:根据操作种类解析终端结束载荷。它解决的问题是:同样是结束响应,不同来源的 JSON 长得可能不一样。

数据流:进去的是响应 JSON、终端操作种类和原始载荷 ID。若是 ExecCommand,它按协议层结束格式解析,然后转成终端响应。若是 WriteStdin,它先尝试按协议层格式解析;如果失败,再按普通派发响应格式解析。出来的是 ParsedTerminalResponse,包含可选终端 ID 和统一的 TerminalResult。

调用关系:它是终端操作结束时的解析入口。它会把协议格式交给 parse_protocol_terminal_response,把派发格式交给 parse_dispatch_terminal_response;这样上层结束流程不用关心响应具体来自哪条通道。

调用图:调用 2 个内部函数(parse_dispatch_terminal_response, parse_protocol_terminal_response);外部调用 1 个(clone)。

parse_protocol_terminal_response448–460 ↗
fn parse_protocol_terminal_response(payload: ExecCommandEndPayload) -> ParsedTerminalResponse

作用:把协议层的 exec 结束载荷变成统一的终端结果。它保留进程 ID、退出码、标准输出、标准错误和格式化输出。

数据流:进去的是 ExecCommandEndPayload。它把 process_id 放到 terminal_id,把 exit_code、stdout、stderr、formatted_output 放进 TerminalResult,并把这里没有的信息,比如 original_token_count 和 chunk_id,留为空。出来的是 ParsedTerminalResponse。

调用关系:它被 parse_terminal_response_payload 调用。只要结束响应能按协议层 exec 格式读出来,就由它负责做最后的字段映射。

调用图:被 1 处调用(parse_terminal_response_payload)。

parse_dispatch_terminal_response462–499 ↗
fn parse_dispatch_terminal_response(value: JsonValue) -> Result<ParsedTerminalResponse>

作用:解析普通工具派发返回的响应,并投影成终端结果。它处理的是不像协议层 exec 那么标准的返回格式。

数据流:进去的是响应 JSON。它先按 DispatchedToolTraceResponsePayload 解析。若是 direct_response,就从 response_item 里尽量取 output 文本;若是 code_mode_response,就交给 parse_code_mode_exec_result 解析更像 exec 的结构化结果;若是 error,就把错误文字放进 stderr 和 formatted_output。出来的是 ParsedTerminalResponse,通常没有 terminal_id,但有统一的 TerminalResult。

调用关系:它被 parse_terminal_response_payload 在 write_stdin 的协议格式解析失败后调用。它还会在 code mode 场景下把进一步解析交给 parse_code_mode_exec_result。

调用图:调用 1 个内部函数(parse_code_mode_exec_result);被 1 处调用(parse_terminal_response_payload);外部调用 2 个(new, from_value)。

parse_code_mode_exec_result501–523 ↗
fn parse_code_mode_exec_result(value: JsonValue) -> TerminalResult

作用:解析 code mode 返回的执行结果。code mode 可以理解成“给代码侧看的工具返回值”,它不一定就是模型看到的文字,所以这里要单独整理成终端输出。

数据流:进去的是一段 JSON。它先尝试按 CodeModeExecResult 结构读取;成功时,提取 exit_code、output、original_token_count、chunk_id,并把 output 同时作为 stdout 和 formatted_output。失败时,它退一步把 JSON 当普通文本内容提取,实在不行就转成 JSON 字符串。出来的是 TerminalResult。

调用关系:它被 parse_dispatch_terminal_response 调用,专门处理 CodeModeResponse 分支。它会借助 json_text_content 做兜底的文本提取。

调用图:调用 1 个内部函数(json_text_content);被 1 处调用(parse_dispatch_terminal_response);外部调用 2 个(clone, new)。

json_text_content525–539 ↗
fn json_text_content(value: &JsonValue) -> Option<String>

作用:从 JSON 值里尽量取出人能读的文本。它是一个小工具,用来避免把复杂 JSON 直接粗暴显示给人看。

数据流:进去的是一个 JSON 值。如果它本来就是字符串,就直接返回字符串;如果是数组,就把数组元素里名为 text 的字段取出来,用换行拼接;如果是 null,就返回空;其他类型就转成字符串。出来的是可选文本。

调用关系:它被 parse_code_mode_exec_result 用作兜底文本提取工具。虽然函数很小,但能让异常或非标准返回也尽量显示出可读内容。

调用图:被 1 处调用(parse_code_mode_exec_result)。

terminal_id_from_json541–547 ↗
fn terminal_id_from_json(value: &JsonValue) -> Option<String>

作用:把 JSON 里的 session_id 转成终端 ID 字符串。因为 session_id 可能是字符串,也可能是数字,所以这里统一一下格式。

数据流:进去的是一个 JSON 值。如果是非空字符串,就原样返回;如果是数字,就转成字符串;其他情况,比如空字符串、对象、数组或 null,都认为不能作为终端 ID。出来的是可选字符串。

调用关系:它被 parse_dispatch_terminal_request 调用。write_stdin 的派发参数必须靠它从 session_id 中得到终端会话的连接钥匙。

调用图:被 1 处调用(parse_dispatch_terminal_request);外部调用 3 个(clone, is_empty, to_string)。