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

展开阅读路线与实验入口
读这一部之前,需要知道什么
不用先读完 Rust workspace,也不用背 Codex 的 crate 列表。能看懂 enum、struct、trait 和 async 调用,知道 TOML 会被合并、HTTP 请求会带 header,已经足够开始。遇到陌生类型时,先找它的 consumer,再判断谁持有状态;这比先补一整套 Rust 语法更有效。
真正需要提前接受的是本书的证据规则:第一版只讨论 openai/codex 的 rust-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。图只表示后一个问题需要前一个问题交出的概念边界,不能拿来声称每个进程都会按五个节点逐一执行。
这一部有几组重名词,最好现在就拆开:
| 词 | 本部中的不同对象 | 不能互相推出什么 |
|---|---|---|
task | core 的 RegularTask / RunningTask 持有 turn 执行;Agent Identity registration 返回的 task_id 是认证记录和 assertion 使用的服务端标识 | task_id 不证明 core 创建了 RegularTask,core task 完成也不等于认证记录里的 id 随之失效 |
source | SessionSource 与独立的 ThreadSource 是 thread metadata;ConfigLayerSource 是 ordinary layer provenance;RequirementSource 是约束 provenance;auth input 与 model catalog 还有各自来源 | 同名的 source 不共享 precedence、生命周期或安全含义 |
provider | request-time auth provider 负责附加凭据;ModelProviderInfo 保存可序列化连接配置;runtime ModelProvider 提供认证入口、API provider、目录 manager 与能力上限 | Bearer / Agent Assertion 的选择不等于选定模型目录,ModelProviderInfo 也不能替 ModelInfo 描述能力 |
AgentIdentityAuthRecord.task_id 是 Option<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_DIR 或 EXIT 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、runtimeModelProvider、ModelsManager和ModelInfo,不从 slug 猜 endpoint 或能力; - 明确当前证据只证明了类型、分支、测试还是运行行为,并知道下一步应在哪个 crate 继续追。
这时还不能声称自己已经解释完 Agent loop。第一部交出的只是进入 loop 前的一组 resolved contracts;第 7 章才开始追 turn ownership。
源码工作台
核心目录
| 目录 | 本部使用它回答什么 |
|---|---|
codex-rs/protocol | Submission、Op、Event、SessionSource 等跨边界类型 |
codex-rs/core | Session、task ownership、最终 Config、turn context 和 provider/model 汇合点 |
codex-rs/cli | MultitoolCli、Subcommand 与顶层 dispatch |
codex-rs/tui、codex-rs/exec | 两种客户端怎样使用 app-server client model,以及各自怎样观察终态 |
codex-rs/app-server | JSON-RPC runtime、ThreadManager 与 core event 投影入口 |
codex-rs/mcp-server | 独立 MCP server route 怎样直接持有 ThreadManager |
codex-rs/config | ordinary layers、Requirements stack、constraint 与 provenance |
codex-rs/features | Feature 与 Stage 的 flag lifecycle metadata |
codex-rs/login | CodexAuth、AuthDotJson、storage backend、Agent Identity bootstrap |
codex-rs/model-provider-info | provider 的 URL、wire、auth 配置、重试与 WebSocket 边界 |
codex-rs/model-provider | runtime ModelProvider、auth provider 与 capability upper bound |
codex-rs/models-manager | bundled / remote / cache catalog、ModelInfo lookup 与 fallback |
先抓住这些类型
| 边界 | 类型 / 函数 | 读源码时先问的问题 |
|---|---|---|
| 输入与执行 | Submission、Op、Session、RegularTask、run_turn | 谁消费输入,谁持有 active task,哪条分支只是在 steering? |
| 入口与来源 | MultitoolCli、Subcommand、ThreadManager、CodexThread、SessionSource、ThreadSource | route、client、wire、metadata 和终态分别由谁定义? |
| 配置与约束 | LoaderOverrides、ConfigLayerSource、ConfigOverrides、ConfigRequirements、Constrained | 值从哪里来,约束从哪里来,冲突是 fallback 还是错误? |
| 身份 | CodexAuth、AuthDotJson、AgentIdentityAuthRecord、AgentIdentityKey、AgentAssertion | 哪个对象可持久化,哪个只进入请求头? |
| provider/模型 | ModelProviderInfo、ModelProvider、ModelsManager、ModelInfo、TurnContext | 连接配置、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 成熟度
Feature 的 Stage 只描述 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,最后才在 Secrets 与 Direct 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 socket | Unix socket 路径不代表 Windows 有同一种本地 transport |
| managed Requirements | macOS managed preference loader 由 target_os = "macos" 编译条件控制 | system/cloud/legacy 来源仍需按当前部署逐项确认 |
| File auth storage | OpenOptions.mode(0o600) 只在 Unix 编译分支出现;Keyring/Auto 还有各自 backend 行为 | 不能从“没有 auth.json”推出未认证,也不能承诺所有 OS 权限相同 |
| provider/model 网络行为 | unit test 可以验证 catalog 和 fallback 逻辑 | 真实 endpoint、账号授权、缓存归属和服务端模型接受结果不在其中 |
这份工作台会随六部导读持续扩展,但不会替章节正文重复结论。现在从第 1 章开始:先学会判断一条箭头究竟是目录依赖、值传递,还是运行时控制权。进入《不要从目录开始读 Codex》
不要从目录开始读 Codex
沿 Submission、Session、turn loop、rollout 与 app-server 投影建立 Codex 源码地图,先分清模块责任,再进入各章的运行时细节。
打开 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 上,将源码固定到 commit 5d1fbf26c43abc65a203928b2e31561cb039e06d 后运行的完整命令为:
git describe --tags --exact-match && git rev-parse HEAD预期 tag 精确匹配 rust-v0.144.6,且 HEAD 等于该 commit;实际终端依次返回 rust-v0.144.6 与
5d1fbf26c43abc65a203928b2e31561cb039e06d。这张图只固定本册核对源码时使用的版本,不能证明工作树干净、后续命令仍运行在同一
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.rs,submission_loop 和 Op::UserInput 分支会落在 handlers.rs,RegularTask::run 会调用 run_turn,record_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。
Submission、TurnContext、Event、ThreadId 是不同类型。前三者各有自己的关联字段,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 | 定义 Submission、Op、Event、EventMsg 等跨队列类型 | 类型定义不执行 Agent loop |
core/session | 持有 session state、active turn、input queue,消费 submission | 收到 UserInput 不代表必建新 task |
core/tasks | 持有 task 生命周期,RegularTask 交给 run_turn | TurnStarted 不代表已经返回 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_turn,record_conversation_items 在 turn 内汇合 history / persistence / raw response items,app-server 在下游消费事件并维护投影。
还有一个问题没有回答:真正的进程入口在哪里,CLI、TUI、app-server 又分别怎样把控制权交到这张地图上?下一章《运行时入口:CLI、TUI 与 app-server 怎样连到 core》从这里继续。