Codex 源码解析 · 第 8 讲
Codex 源码解析系列 第 8 讲:Session 管理与 TurnContext 基于 OpenAI Codex 源码 · 2026-10-02 💡 本讲一句话: Codex 的会话运行时要同时满足两个矛盾需求——配置随时可改(用户中途换模型、调审批策略),而正在跑的 turn 必须看到一份"冻结的世界观"。这一讲拆它的解法:Session/TurnContext/StepContext 三层快照各管一段生命周期,所有操作走单线程 submission_loop 串行分发,设置提交严格"锁内换值、锁外做副作用"。读完你会明白一个 agent 会话的骨架是怎么搭起来的。 一、Session:端点与服务的容器,不是数据仓库 进入核心运行时(第三阶段第一讲)。前几讲的登录、身份、模型 provider 都是"会话启动前的准备",而 Session 是这些准备的汇聚点:一个对话线程(thread)的完整执行体。源码里它定义在 core/src/session/session.rs ,结构本身只有 40 来行——但每个字段都对应一个设计决策: 📄 codex-rs/core/src/session/session.rs (第 46-83 行,节选) pub(crate) struct Session { // 会话本体:一个 thread 的运行时容器,全局唯一、不可克隆 pub(crate) thread_id: ThreadId, // "thread"=对话线程;session 是它的执行体,ID 贯穿事件与日志 pub(super) tx_event: Sender<Event>, // 事件出口:所有 UI 可见事件都从这条 channel 流出 pub(super) agent_status: watch::Sender<AgentStatus>, // 状态广播(PendingInit/Running…):订阅者随时取最新值 pub(super) state: Mutex<SessionState>, // 可变会话状态(历史、token 账本)——唯一需要锁的"大脑" pub(super) thread_settings_persistence: Semaphore, // 串行化设置提交与持久化,存储 I/O 不阻塞运行时读 pub(super) features: ManagedFeatures, // 特性开关:注释明确"会话生命周期内不变" pub(crate) active_turn: Mutex<Option<ActiveTurn>>, // 当前正在跑的 turn(至多一个)——单任务约束的落点 pub(crate) input_queue: InputQueue, // 排队中的用户输入:turn 运行中可插入(steering) pub(crate) services: SessionServices, // 共享服务集合:exec policy、MCP、网络代理等依赖 } 为什么这样设计: 注意这个结构里 几乎没有业务数据 ——没有历史消息、没有 token 计数。原因是刻意的:Session 持有的是"端点"(channel sender/receiver)和"服务句柄",它们创建后不再变化;所有会变的账本被拆进 SessionState ,用一把 Mutex 圈起来。这样锁的临界区只覆盖真正可变的数据,事件发送、状态广播这些高频路径完全不碰锁。另外 thread_settings_persistence 用 Semaphore(而不是 Mutex)单独串行化"设置提交 + 持久化事件"这条链——源码注释写得很直白:让存储 I/O 不阻塞运行时状态访问。两个 Semaphore 、一把 Mutex ,三种同步原语各管一条互不干扰的序。 Session 只是最外层。往下还有两层快照,三者合起来才是完整的"会话世界观": 层级 生命周期 一句话职责 Session 整个会话 端点与共享服务的容器,单任务约束的持有者 TurnContext 单个 turn 冻结的世界观快照:配置、环境、权限、遥测 StepContext 单次模型请求 该次请求的设置版本 + MCP/工具路由绑定 二、spawn:一次启动要"…
阅读全文:Codex 源码解析 第 8 讲