青雲的博客

第一部:先建立系统地图

这一部先固定版本与控制权读法,再沿入口、配置、Requirements、身份和模型边界定义 Agent runtime 的运行前提;Agent loop 的内部机制从下一部开始。

入口选择器、配置层、Requirements、身份密钥与模型能力仪表组成的运行前系统地图。
展开阅读路线与实验入口

读这一部之前,需要知道什么

不用先读完 Rust workspace,也不用背 Codex 的 crate 列表。能看懂 enum、struct、trait 和 async 调用,知道 TOML 会被合并、HTTP 请求会带 header,已经足够开始。遇到陌生类型时,先找它的 consumer,再判断谁持有状态;这比先补一整套 Rust 语法更有效。

真正需要提前接受的是本书的证据规则:第一版只讨论 openai/codexrust-v0.144.6,commit 固定为 5d1fbf26c43abc65a203928b2e31561cb039e06d。目录名、字段名和测试名都只是线索。只有固定版本里的调用、channel、状态字段、分支和可复现实验,才能支撑当前行为结论。

第 1 章先建立这套读法。它不和后五章争一个“架构层级”,而是给整部书提供方法前置:固定版本,区分 build relation、value correlation、lifecycle event 与 runtime ownership,再沿控制权交接画图。

这一部负责讲清什么

第一部停在 Agent loop 的入口之前。六章依次回答:源码地图怎样建立;同一个 codex 会分派到哪些 runtime;普通配置怎样得到 candidate;Requirements 怎样约束它;账号凭据与 Agent Identity 怎样进入请求;provider 与 ModelInfo 怎样共同成为 turn 的运行前提。

flowchart TB
  accTitle: 第一部系统地图
  accDescr: 从运行入口经过配置、托管约束、认证到模型能力的控制链
  METHOD["第 1 章\n固定版本 / 追控制权"]
  ENTRY["第 2 章\nentrypoints"]
  CONFIG["第 3 章\nconfig candidate"]
  REQUIREMENTS["第 4 章\nmanaged requirements"]
  AUTH["第 5 章\nauth / Agent Identity"]
  MODEL["第 6 章\nprovider / ModelInfo"]
  METHOD --> ENTRY
  ENTRY --> CONFIG
  CONFIG --> REQUIREMENTS
  REQUIREMENTS --> AUTH
  AUTH --> MODEL

这是一条阅读顺序和合同交接链,不是所有入口共享的运行时时序。第 2 章将说明 TUI、exec、app-server、mcp-server 在 client model、wire protocol 和终态上分叉;第 6 章的 runtime provider 还会回到第 5 章取得 provider-scoped auth。图只表示后一个问题需要前一个问题交出的概念边界,不能拿来声称每个进程都会按五个节点逐一执行。

这一部有几组重名词,最好现在就拆开:

本部中的不同对象不能互相推出什么
taskcore 的 RegularTask / RunningTask 持有 turn 执行;Agent Identity registration 返回的 task_id 是认证记录和 assertion 使用的服务端标识task_id 不证明 core 创建了 RegularTask,core task 完成也不等于认证记录里的 id 随之失效
sourceSessionSource 与独立的 ThreadSource 是 thread metadata;ConfigLayerSource 是 ordinary layer provenance;RequirementSource 是约束 provenance;auth input 与 model catalog 还有各自来源同名的 source 不共享 precedence、生命周期或安全含义
providerrequest-time auth provider 负责附加凭据;ModelProviderInfo 保存可序列化连接配置;runtime ModelProvider 提供认证入口、API provider、目录 manager 与能力上限Bearer / Agent Assertion 的选择不等于选定模型目录,ModelProviderInfo 也不能替 ModelInfo 描述能力

AgentIdentityAuthRecord.task_idOption<String>,固定源码允许 record 保留一个已有值;本部不把它写成“每次运行必然重新生成”或“一次性 id”。它只与认证注册和 assertion 合同有关。core RunningTask 则保存 Tokio handle、task kind、cancellation token 和 TurnContext,二者连类型都不在同一个 crate。

命令实验还默认支持 pipefail 的 bash 或 zsh、Git 和 Cargo;Windows 需要做 shell/path 转换以及命令调用适配,不能把这些类 Unix 示例当成 Windows 的原样操作步骤。

六章共用哪套实验

六章共用同一个 source identity,但不直接在权威 checkout 里编译。固定 checkout 只负责读取 Git 对象;测试、lockfile 校准、target 和日志都放进一次性的 disposable archive。这样既不会把本机工作区的改动混进源码证据,也不会把 Cargo 的机械 lockfile 更新误写成运行时结论。

每一部都要单独新开一个支持 pipefail 的 bash 或 zsh;部内的章节再共享这一个会话。先在本部 shell 执行下面的准备脚本。后面章节的测试命令都要求已经存在 $ARCHIVE_CODEX_RS;如果直接打开某一章阅读,先回到这里执行一次。不要把本部的 ARCHIVE_DIREXIT trap 带到下一部。

set -euo pipefail

export SOURCE_ROOT="${CODEX_SOURCE_DIR:-/tmp/codex-handbook-final-rust-v0.144.6}"
export repo="$SOURCE_ROOT"
export COMMIT=5d1fbf26c43abc65a203928b2e31561cb039e06d
export ARCHIVE_DIR="$(mktemp -d "${TMPDIR:-/tmp}/codex-part1.XXXXXX")"
export CARGO_TARGET_DIR="$ARCHIVE_DIR/target"
trap 'rm -rf "$ARCHIVE_DIR"' EXIT

test "$(git -C "$SOURCE_ROOT" rev-parse HEAD)" = "$COMMIT"
test "$(git -C "$SOURCE_ROOT" describe --tags --exact-match HEAD)" = rust-v0.144.6
git -C "$SOURCE_ROOT" archive "$COMMIT" | tar -x -C "$ARCHIVE_DIR"

cd "$ARCHIVE_DIR/codex-rs"
cargo update --workspace --offline
cargo metadata --locked --format-version 1 --no-deps >/dev/null
export ARCHIVE_CODEX_RS="$PWD"

cargo update --workspace --offline 只在 archive 内校准这个 tag 的 local workspace package 版本;完整的 132-package diff 审计放在第三部导读。若本机没有离线 registry/cache,准备阶段会失败,不能把它记成某章测试失败。静态 git grep 仍从 $SOURCE_ROOT 读取;可执行命令一律从 $ARCHIVE_CODEX_RS 读取。

共同产物不是一份漂亮的“全链路日志”,而是六类边界证据:控制权定位、可见入口、配置覆盖结果、约束 fallback、认证写入、模型 metadata fallback。每类证据都保留反例,防止下一章把上一章的结论外推过头。

哪些机制暂时不讲

这里故意不展开 run_turn 内部。Submission 怎样变成 task、instructions 与 history 怎样进入 Prompt、Responses 请求怎样流式返回、为什么存在两层循环,都从第二部开始。

工具执行、patch、shell、审批、Permission Profile、沙箱和长进程属于第三部;网络、Skills、MCP(Model Context Protocol,模型上下文协议)、Dynamic Tools、Code Mode 与 Plugins 属于第四部;history、rollout、SQLite、compaction、resume、Memory、Goal 与 app-server 投影属于第五部;TUI、subagent、多 Agent、Realtime、Hooks 和证据闭环留到第六部。

这不是把横切机制假装成互不相干的层。第一部已经遇到 app-server、MCP、Permission Profile、subagent metadata 和 context window,只在当前问题需要的边界上点到为止,后面再沿各自 owner 展开。

读完这一部,你应该能做什么

读完后,面对一条“Codex 为什么这样启动或这样请求”的问题,应该能先选对观察面:

  • 从 CLI route 追到实际 runtime,而不是把同名 binary 当成同一系统;
  • 分开 ordinary candidate、typed override、Requirements constraint 与最终值;
  • 分开账号 credential、Agent Identity record、task assertion、session metadata 与 routing header;
  • 同时检查 ModelProviderInfo、runtime ModelProviderModelsManagerModelInfo,不从 slug 猜 endpoint 或能力;
  • 明确当前证据只证明了类型、分支、测试还是运行行为,并知道下一步应在哪个 crate 继续追。

这时还不能声称自己已经解释完 Agent loop。第一部交出的只是进入 loop 前的一组 resolved contracts;第 7 章才开始追 turn ownership。

源码工作台

核心目录

目录本部使用它回答什么
codex-rs/protocolSubmissionOpEventSessionSource 等跨边界类型
codex-rs/coreSession、task ownership、最终 Config、turn context 和 provider/model 汇合点
codex-rs/cliMultitoolCliSubcommand 与顶层 dispatch
codex-rs/tuicodex-rs/exec两种客户端怎样使用 app-server client model,以及各自怎样观察终态
codex-rs/app-serverJSON-RPC runtime、ThreadManager 与 core event 投影入口
codex-rs/mcp-server独立 MCP server route 怎样直接持有 ThreadManager
codex-rs/configordinary layers、Requirements stack、constraint 与 provenance
codex-rs/featuresFeatureStage 的 flag lifecycle metadata
codex-rs/loginCodexAuthAuthDotJson、storage backend、Agent Identity bootstrap
codex-rs/model-provider-infoprovider 的 URL、wire、auth 配置、重试与 WebSocket 边界
codex-rs/model-providerruntime ModelProvider、auth provider 与 capability upper bound
codex-rs/models-managerbundled / remote / cache catalog、ModelInfo lookup 与 fallback

先抓住这些类型

边界类型 / 函数读源码时先问的问题
输入与执行SubmissionOpSessionRegularTaskrun_turn谁消费输入,谁持有 active task,哪条分支只是在 steering?
入口与来源MultitoolCliSubcommandThreadManagerCodexThreadSessionSourceThreadSourceroute、client、wire、metadata 和终态分别由谁定义?
配置与约束LoaderOverridesConfigLayerSourceConfigOverridesConfigRequirementsConstrained值从哪里来,约束从哪里来,冲突是 fallback 还是错误?
身份CodexAuthAuthDotJsonAgentIdentityAuthRecordAgentIdentityKeyAgentAssertion哪个对象可持久化,哪个只进入请求头?
provider/模型ModelProviderInfoModelProviderModelsManagerModelInfoTurnContext连接配置、runtime 行为、目录 metadata 与本轮状态如何汇合?

实验准备与六个验证锚点

准备阶段只做三件事:校验固定 commit,确认 Rust toolchain 能读取 workspace,在运行测试前记录当前 OS 与 target。不要把本机的缓存命中、构建耗时、真实账号状态或网络返回混进这些确定性锚点。

第 1 章是一条 git grep 控制链。下面保留开头和关键交接;完整八步路径以第 1 章为准:

git -C "$repo" grep -n 'pub struct Submission' rust-v0.144.6 -- \
  codex-rs/protocol/src/protocol.rs
git -C "$repo" grep -n 'submission_loop' rust-v0.144.6 -- \
  codex-rs/core/src/session
git -C "$repo" grep -n 'RegularTask' rust-v0.144.6 -- \
  codex-rs/core/src/session/handlers.rs codex-rs/core/src/tasks/regular.rs
git -C "$repo" grep -n 'run_turn(' rust-v0.144.6 -- \
  codex-rs/core/src/tasks/regular.rs codex-rs/core/src/session/turn.rs
git -C "$repo" grep -n 'record_conversation_items' rust-v0.144.6 -- \
  codex-rs/core/src/session

后五章的锚点从 disposable archive 的 codex-rs 目录运行:

: "${ARCHIVE_CODEX_RS:?先执行本部导读的 archive 准备脚本}"
cd "$ARCHIVE_CODEX_RS"

cargo run --locked -q -p codex-cli --bin codex -- --help
just test --locked -p codex-core selected_user_config_file_layers_over_base_user_config
just test --locked -p codex-core permission_profile_override_falls_back_when_disallowed_by_requirements
just test --locked -p codex-login login_with_access_token_writes_agent_identity_jwt
just test --locked -p codex-models-manager get_model_info_tracks_fallback_usage

验收时确认四个命名测试确实由 nextest 运行并出现 Summary: 1 test run, 1 passed(或等价的 1 tests run: 1 passed),不能接受零匹配或 fixture skip。cargo run --help 只记录可见 Commands list;git grep 只记录定义与调用点,不伪造动态 trace。

Feature 成熟度

FeatureStage 只描述 feature flag 的生命周期,不是同名 runtime 子系统的成熟度判决。固定版本中有些标为 Removed 的 flag,其同名或相关 runtime 路径仍然存在;因此不能从 Stage 反推“Remote Control、WebSocket transport 或远端模型刷新已经没有代码”。

本部可以直接验证的例子是 Feature::SecretAuthStorage:它的 Stage 是 Stable,默认值却是 cfg!(windows)Config::auth_keyring_backend_kind() 还会读取经过 Requirements pin 的 feature state,最后才在 SecretsDirect backend 之间选择。Stable 不等于当前平台默认启用,更不等于 Requirements 无法约束。

Agent Identity 对应的 Feature::UseAgentIdentity 在固定版本是 UnderDevelopment、默认关闭。另一方面,第 4 章提到的 network_approval 若指 ConfigRequirements 中一个同名新字段,它在本 commit 的结构里不存在,只能作为局部改造设想;不能把仓库里已经存在的 network approval flow 与这个拟议字段混成当前配置合同。

平台限制

机制固定版本能证明的条件仍需单独验证的边界
CLI 可见命令app 入口受 macOS / Windows cfg 约束,hidden command 不进入普通 help不能把一台机器的 --help 输出当成跨平台永久清单
remote app-server client统一使用 WebSocket frames,底层可以是 TCP URL 或本地 Unix socketUnix socket 路径不代表 Windows 有同一种本地 transport
managed RequirementsmacOS managed preference loader 由 target_os = "macos" 编译条件控制system/cloud/legacy 来源仍需按当前部署逐项确认
File auth storageOpenOptions.mode(0o600) 只在 Unix 编译分支出现;Keyring/Auto 还有各自 backend 行为不能从“没有 auth.json”推出未认证,也不能承诺所有 OS 权限相同
provider/model 网络行为unit test 可以验证 catalog 和 fallback 逻辑真实 endpoint、账号授权、缓存归属和服务端模型接受结果不在其中

这份工作台会随六部导读持续扩展,但不会替章节正文重复结论。现在从第 1 章开始:先学会判断一条箭头究竟是目录依赖、值传递,还是运行时控制权。进入《不要从目录开始读 Codex》

拆开 Codex 第一部:先建立系统地图 第 01 章

不要从目录开始读 Codex

沿 Submission、Session、turn loop、rollout 与 app-server 投影建立 Codex 源码地图,先分清模块责任,再进入各章的运行时细节。

源码版本
rust-v0.144.6
验证日期
Commit
5d1fbf26c43abc65a203928b2e31561cb039e06d

打开 openai/codex 的 Rust workspace,目录看起来很愿意帮忙:protocol 放协议,core 放核心,app-server 接客户端,tui 管终端界面。顺着这个印象往下读,很快就会遇到一个麻烦:这些目录名只告诉你代码放在哪里,没有告诉你运行时谁持有状态,谁决定继续,谁只是转发。

我以前读这类大型仓库,也会先给 crate 贴上“入口”、“核心”、“存储”这些标签。它对找文件有用,但一到多层异步系统,这张图就容易失真。Cargo.toml 里的 workspace membership 只说某个 package 参与同一次构建;codex-app-server 依赖 codex-core 只说前者可以引用后者的公开能力。它们都没有直接证明一条用户输入会怎样经过运行时。

本章换一种读法:先固定版本,再追控制权。只有当源码出现实际的函数调用、channel 收发、ownership 字段或事件消费时,我们才把箭头画上去。

先把证据固定在一个版本

本书只分析 openai/codex 开源仓库,第一版锁定 rust-v0.144.6,对应 commit 是:

5d1fbf26c43abc65a203928b2e31561cb039e06d

后面所有 SourceEvidence 都指向这个 commit。不锁版本的问题不只是行号会移动。Codex 的协议、task 抽象、工具和 app-server 转译都在变;如果从不同 commit 拼类型名,最后可能得到一条“每个符号都存在,但从没有整体运行过”的伪调用链。

先准备一份读者自己持有的 checkout。下面的 repo 默认指向系统临时目录里的固定子目录;如果你已经有一份固定 checkout,可以先设置 CODEX_SOURCE_DIR

repo="${CODEX_SOURCE_DIR:-/tmp/codex-handbook-final-rust-v0.144.6}"

if [ ! -e "$repo" ]; then
  git clone --depth 1 --branch rust-v0.144.6 \
    https://github.com/openai/codex.git "$repo"
fi

test "$(git -C "$repo" rev-parse HEAD)" = \
  5d1fbf26c43abc65a203928b2e31561cb039e06d
git -C "$repo" describe --tags --exact-match HEAD

clone 会访问网络并写入目标目录,后两条校验只读 Git 对象。预期 test 静默通过,describe 输出 rust-v0.144.6。如果目标已存在但 commit 不匹配,不要直接覆盖;把 repo 换到一个空目录重新 clone。

macOS 固定 Codex 源码 commit 后校验 tag 与 HEAD

在 macOS 上,将源码固定到 commit 5d1fbf26c43abc65a203928b2e31561cb039e06d 后运行的完整命令为:

git describe --tags --exact-match && git rev-parse HEAD

预期 tag 精确匹配 rust-v0.144.6,且 HEAD 等于该 commit;实际终端依次返回 rust-v0.144.65d1fbf26c43abc65a203928b2e31561cb039e06d。这张图只固定本册核对源码时使用的版本,不能证明工作树干净、后续命令仍运行在同一 checkout,也不能证明某个二进制确实由该 commit 构建。

读者实验:只用 git grep 串起控制链

这组命令不运行 Codex,也不伪造 trace 输出。它在固定 tag 上定位静态控制权交接点:先找信封与操作词汇,再找 submission consumer 和 UserInput 分支,然后追到 RegularTask / run_turn、conversation 记录,最后看 app-server 如何消费 core event。

repo="${CODEX_SOURCE_DIR:-/tmp/codex-handbook-final-rust-v0.144.6}"

git -C "$repo" grep -n 'pub struct Submission' rust-v0.144.6 -- \
  codex-rs/protocol/src/protocol.rs
git -C "$repo" grep -n 'pub enum Op' rust-v0.144.6 -- \
  codex-rs/protocol/src/protocol.rs
git -C "$repo" grep -n 'submission_loop' rust-v0.144.6 -- \
  codex-rs/core/src/session
git -C "$repo" grep -n 'Op::UserInput' rust-v0.144.6 -- \
  codex-rs/core/src/session/handlers.rs
git -C "$repo" grep -n 'RegularTask' rust-v0.144.6 -- \
  codex-rs/core/src/session/handlers.rs codex-rs/core/src/tasks/regular.rs
git -C "$repo" grep -n 'run_turn(' rust-v0.144.6 -- \
  codex-rs/core/src/tasks/regular.rs codex-rs/core/src/session/turn.rs
git -C "$repo" grep -n 'record_conversation_items' rust-v0.144.6 -- \
  codex-rs/core/src/session
git -C "$repo" grep -n 'next_event()' rust-v0.144.6 -- \
  codex-rs/app-server/src/request_processors/thread_lifecycle.rs

预期观察是一组“定义 + 调用点”,不是动态时序:Submission 定义会落在 protocol.rssubmission_loopOp::UserInput 分支会落在 handlers.rsRegularTask::run 会调用 run_turnrecord_conversation_items 会有多个调用点,app-server listener 则在 thread_lifecycle.rs 等待 next_event()。命中数会随版本变化,所以实验不声称固定输出行数。

先把地图上的对象分开

Submission 是一个包含 { id, op, ... } 的信封,Op 是信封承载的操作词汇。Op::UserInput 只是其中一种 payload;同一个 enum 里还有中断、Realtime 控制和 thread settings 等操作。它们位于协议边界,不能因为名字里有 input,就把它们直接当成 turn 或 Agent loop。

SubmissionTurnContextEventThreadId 是不同类型。前三者各有自己的关联字段,ThreadId 则是基于 UUID 的长期 thread 身份。值相同只说明某条路径传递了 correlation id,不说明四个对象共享生命周期。

地图阶段只需要记住这条分叉:只有 NoActiveTurn 才让 Submission.id -> TurnContext.sub_id -> Event.id 成为新 task 路径中的关联链;steering submission 进入已有任务后,后续 lifecycle event 沿用 active task 原有的 turn_context.sub_id。这里先确认分支存在,不继续展开 pending queue、取消与完成清理;这些正是第 7 章的主线。

从门面到投影,先记住七个导航点

Codex 在注释里被称为 high-level interface,它是 submission / event 双向队列门面:tx_sub 接受操作,rx_event 向外提供事件。它持有 Arc<Session>,也负责启动后台 submission loop,但不亲自执行模型回路。

Session 是运行状态的 owner:它保存长期 state、active turn、input queue 以及工具和模型相关 services。继续向下定位,主干是 start_task -> RegularTask::run -> run_turn。这串名字在本章只用于确认代码应往哪里读;RunningTask 怎样写入、steer 怎样排队、cancel 怎样传播,都留给第 7 章逐项证明。

再往下有三个不同去向。record_conversation_items 把会话项写入 live history,并请求 rollout 持久化;core event 由下游 listener 消费;app-server 的 ThreadState 只维护客户端需要的投影。它们彼此相关,却不是同一份状态的三个名字。

flowchart TD
    accTitle: Codex 源码导航图
    accDescr: 这张图只画控制权交接:Submission 经队列进入 Session;Session 选择已有任务或新任务,再进入 turn loop。会话项写入 live history 与 rollout,core event 由 app-server listener 消费并形成客户端投影。
    CLIENT["CLI / TUI / app-server client"] --> CODEX["Codex\nsubmission / event facade"]
    CODEX --> PROTOCOL["Submission { id, op }"]
    PROTOCOL --> SESSION["Session\nstate / active turn / input queue"]
    SESSION --> DECISION{"steer decision"}
    DECISION -->|"existing active task"| EXISTING["active turn / pending input"]
    DECISION -->|"NoActiveTurn"| TASK["new RegularTask"]
    EXISTING --> TURN["RegularTask / run_turn"]
    TASK --> TURN
    TURN --> ITEMS["record_conversation_items"]
    ITEMS --> HISTORY["live History"]
    ITEMS --> ROLLOUT["rollout persistence"]
    TASK --> EVENTS["core Event queue"]
    EVENTS --> LISTENER["app-server thread listener"]
    LISTENER --> PROJECTION["ThreadState / typed notification"]

app-server thread listener 等待 conversation.next_event(),先更新 thread-local 投影,再处理协议通知。RawResponseItem 在未启用 raw event 时会被过滤。这个消费端能回答客户端最后看到了什么,不能替 core Session 回答当前 task 由谁持有。

这张图到此刻意停止。history、rollout 与 SQLite 的写入和恢复由第 25 章解释;app-server 的 JSON-RPC 投影由第 30 章解释。本章只负责让后续问题落到正确 owner,而不是提前替这些章节讲完内部机制。

回到 crate 地图,但不再把目录当调用链

现在可以回头看目录。下表标的是本章已经追到的责任,不是宣称模块之间没有其他交叉。

区域本章确认的责任不能由此推断
protocol定义 SubmissionOpEventEventMsg 等跨队列类型类型定义不执行 Agent loop
core/session持有 session state、active turn、input queue,消费 submission收到 UserInput 不代表必建新 task
core/tasks持有 task 生命周期,RegularTask 交给 run_turnTurnStarted 不代表已经返回 token
core/session/turn.rs组织 sampling、function-call follow-up、输入记录它不直接代替每个工具 runtime
rollout 与 thread store保留可恢复记录和相关投影它们不是“每次模型请求的 prompt”同义词
app-server接协议请求,消费 core event,维护客户端投影并翻译通知它不拥有 core turn loop
tui收集输入、消费客户端通知并渲染交互看见界面更新不能证明 rollout 已持久化

对后面的阅读,这张表有一个很实用的用法:每当看见一个类型或事件,先问它只是描述了数据,还是真的持有了下一步控制权。前者需要继续找 consumer,后者才值得在调用图上画箭头。

失败边界:这张地图不能替你证明什么

第一,workspace membership / dependency edge 只能证明 build membership 或可引用关系,不能证明 runtime control flow。如果要画 app-server -> core 的运行时箭头,还得找到实际的调用或 channel 交接。

第二,同值 id 只能证明 correlation / value transfer。在 NoActiveTurn 新建 task 的路径里,新 submission id 会进入新 TurnContext.sub_id,该 task 后续事件再从这份 context 取得 Event.id;steering 则把输入加入已有 active turn,后续 task event 沿用原 active turn id。这两条路径都不能让 submission、turn、event 和 thread 变成同一个对象。

第三,Op::UserInput 命中了 submission handler,不能证明新 RegularTask 已创建。还要看 steer_input 的结果;Ok 续接活跃 turn,NoActiveTurn 才进创建分支。

第四,TurnStarted 不能证明模型已经返回 token。RegularTask::run 在调用 run_turn 之前就发出了这个事件,它是生命周期起点,不是 sampling 成功证据。

第五,app-server 的 ThreadState 收到 TurnComplete 只能说明它消费到了 core 终态事件。它不能反向证明某一个 rollout writer 的 durability 级别,也不能说明下一次模型请求将怎样构造 history。

这些负向结论比“哪个 crate 最重要”更值得保留。它们直接决定排障顺序:先确认你看到的是 build relation、value correlation、lifecycle event,还是真正的 runtime ownership,然后再往下追。

把入口问题留给下一章

到这里,我们已经有一张足以导航的地图:Codex 是提交/事件门面,Session 持有 active turn 和运行状态,submission_loop 分派操作,RegularTask::run 交给 run_turnrecord_conversation_items 在 turn 内汇合 history / persistence / raw response items,app-server 在下游消费事件并维护投影。

还有一个问题没有回答:真正的进程入口在哪里,CLI、TUI、app-server 又分别怎样把控制权交到这张地图上?下一章《运行时入口:CLI、TUI 与 app-server 怎样连到 core》从这里继续。