雨天小六

读懂 Codex(5.3):ModelClient 与 ModelClientSession 的生命周期差异

· 更新于 2026-08-02 · 专栏:读懂 Codex

#Codex#Agent Runtime#Responses API#流式协议#软件架构

连接可以跨 Turn 复用,粘性路由令牌却绝不能跨 Turn 复用。Codex 用 Session 级 ModelClient、Turn 级 ModelClientSession 和可转移 WebsocketSession 把这两个看似冲突的需求分开。

具体问题与启用条件

本节回答状态放在哪里以及何时创建、转移和清空。具体增量请求条件在 5.10,Turn State Header 在 5.5。

条件来源决定字段或状态对本机制的影响
Session 创建有效 Provider、认证、Thread ID构造长期 ModelClient
每个 Turnnew_session()创建空 OnceLock turn_state
传输复用缓存 WebsocketSession 且连接健康取出连接和请求基线

协议、类型与状态所有权

状态或协议所有者生命周期关键不变量
Provider/auth/fallback flagModelClientState整个 Codex SessionHTTP 回退对后续 Turn 粘滞
turn_stateModelClientSession恰好一个 Turn一旦设置不变且不跨 Turn
连接、last request/responseWebsocketSession可跨 Turn 转移连接关闭时基线失效
Prompt cache key overrideModelClientSession与 Response ID 分离

机制调用链如下:

Session 创建 ModelClient
→ Turn 调用 new_session
→ 从共享缓存 take WebsocketSession
→ Turn 内多次 stream/prewarm
→ Drop ModelClientSession
→ 将传输状态放回 ModelClient 缓存
→ 下一 Turn 获得新 turn_state
ModelClient、Turn Session 和 WebsocketSession 的生命周期
图 5.3-1:连接状态可转移,两个 Turn 的 turn_state 始终各自新建。

机制怎样工作

new_session() 本身不联网。它克隆长期客户端、原子地取走缓存中的 WebsocketSession,并新建空 OnceLock<String>。第一个请求才懒建立连接。Turn 内一旦响应头或 metadata 给出 x-codex-turn-stateOnceLock 只接受第一次值,后续请求原样回送。

Turn 结束时 Drop 把 WebsocketSession 放回共享缓存,但不会把 turn_state 一起放回。这样物理连接、上次完整请求和完成响应可被下一 Turn 评估是否复用,同时粘性路由语义重新开始。若回退 HTTP,长期 disable_websockets 原子标志被置位,当前 WebsocketSession 清空,后续 Turn 也不再试同一失败路径。

Python 风格伪代码

这段伪代码保留生产实现中会改变结果的状态、分支和异步边界;认证 SDK、遥测字段和 Rust 所有权样板被折叠为明确的领域对象。

class ModelClient:
    def __init__(self, provider: Provider, auth: Auth):
        self.provider = provider
        self.auth = auth
        self.http_fallback = False
        self._cached_ws = WebsocketState.empty()
        self._lock = Lock()

    def new_turn_session(self) -> "ModelTurnSession":
        with self._lock:
            ws, self._cached_ws = self._cached_ws, WebsocketState.empty()
        return ModelTurnSession(self, ws, turn_state=None)

class ModelTurnSession:
    async def close(self) -> None:
        # turn_state 故意不归还
        with self.client._lock:
            self.client._cached_ws = self.ws_state

失败、取消与恢复

跨 Turn 连接复用时 turn_state 被重置
图 5.3-2:时序显示旧 Turn 的路由令牌不会随连接进入新 Turn。
故障或边界已发生的状态对上层的结果能否直接重试恢复动作
跨 Turn 复用 ModelClientSession旧 turn_state 仍存在路由合同被破坏每 Turn 新建 session
连接已关闭last request 可能仍在重新连接同时清空增量基线
WebSocket 回退Session flag 已置位改走 HTTP按 HTTP 规则丢弃 WS 状态
Drop 时缓存锁中毒传输状态待归还恢复 mutex 内值不适用避免丢失长期客户端

设计取舍与验证

把可复用状态从 Turn 对象中拆出来,比每次重连复杂,也要求 Drop 路径正确。收益是生命周期成为类型边界:连接复用不再意味着所有语义状态都复用。当前只缓存一个 WebsocketSession,避免并发 Turn 争用同一串行连接,但也限制并行连接复用。

可验证契约证据方式预期结果
同一物理连接跨 Turn 复用请求捕获统计握手握手一次、Turn 请求多次
turn_state 不跨 Turn连续 Turn Header 断言第二 Turn 首请求无旧值
回退对后续 Turn 生效WebSocket fallback 集成测试后续请求直接 HTTP

Mini Codex 对照

Mini Codex 应实现显式 close() 或异步上下文管理器,不依赖 Python 析构时机。只缓存健康连接;Turn token 存在 Turn session 中。可以先不做跨 Turn last-request 增量,但生命周期分离不能省。

本节边界

本节只确定状态归属。Responses 与 Lite 如何改变请求形状,由 5.4 展开。

评论


← 返回文章列表