雨天小六

读懂 Codex(5.19):active item、完整 ResponseItem 与历史提交边界

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

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

active_item 是 UI 生命周期的暂态指针,不是正在拼装的权威历史项。权威内容来自 OutputItemDone;它会在工具开始执行前立即写入 History 与 Rollout,即使 Turn 随后取消,调用事实也不会消失。

具体问题与启用条件

本节统一说明活动项清理、完整项记录、工具 future 和 Completed 的边界,回答断流与取消时哪些事实已经提交。

条件来源决定字段或状态对本机制的影响
Item Added 可投影Message/Reasoning/WebSearch 等建立 active TurnItem
Item Done完整 ResponseItem 可反序列化进入记录和路由
工具调用ToolRouter 产生 Local ToolCall记录后再创建异步 future

协议、类型与状态所有权

状态或协议所有者生命周期关键不变量
active_item采样循环栈Added→Done用于 item_id 与 Started/Delta 对齐
完整 ResponseItemContextManager/Rollout对话历史Done 后立即记录
in_flight FuturesOrdered采样循环到响应结束后 drain工具结果保持调用顺序
LastResponse.items_added传输映射层Completed 后 previous 基线与 History 是不同用途的副本

机制调用链如下:

Item Added
→ active_item + optional ItemStarted
→ Delta 仅引用 active ID
→ Item Done
→ finish parsers/consumer + active_item.take
→ Tool call: 先 record item,再创建 tool future
→ Non-tool: finalize/ItemCompleted,再 record item
→ Completed
→ drain in-flight results
→ 工具 output 进入 History,下一采样
active_item 作为 UI 暂态指针的状态机
图 5.19-1:活动项在 Done 时清空,异常半项被放弃而不是写入历史。

机制怎样工作

Added 阶段只保存可投影的 TurnItem;Function/Custom ToolCall 往往没有普通 TurnItem,但仍可建立参数 consumer。Done 时若服务端漏了 Item ID,Core会尝试从先前 active item 补齐,随后立即清空 active 状态。

本地 ToolCall 的完整 Item 先由 record_completed_response_item 写入 History/Rollout,再创建带 child cancellation token 的执行 future,设置 needs_follow_up=true。这条顺序很关键:工具即使取消或进程崩溃,History 里仍有与后续 aborted/error output 可配对的 call。非工具项先生成最终 TurnItem 与客户端 Completed,再记录原始 ResponseItem;最终事实还可能触发 memory citation 或 mailbox 语义。响应 Completed 后才 drain FuturesOrdered,记录工具结果。

Python 风格伪代码

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

async def handle_done(item: ResponseItem, state: StreamState):
    await state.finish_preview_and_text(state.active_item)
    previous_ui_item, state.active_item = state.active_item, None

    call = tool_router.try_build_call(item)
    if call is not None:
        await history.record(item)  # 副作用开始前的事实屏障
        future = asyncio.create_task(
            tool_runtime.execute(call, state.cancel.child())
        )
        state.in_flight.append(future)
        state.needs_follow_up = True
        return

    finalized = await finalize_turn_item(item)
    if finalized is not None:
        if previous_ui_item is None:
            await emit_started(finalized)
        await emit_completed(finalized)
    await history.record(item)

async def finish_response(state: StreamState):
    results = [await future for future in state.in_flight]  # ordered drain
    for result in results:
        await history.record(result.to_response_item())

失败、取消与恢复

完整工具 Item 的提交屏障与执行顺序
图 5.19-2:记录 call 在工具副作用之前,保证失败和取消仍可形成可恢复历史。
故障或边界已发生的状态对上层的结果能否直接重试恢复动作
Added 后断流、无 Doneactive/UI 暂态存在不记录半项flush 展示状态并重试
Done 工具项后取消Call 已记录,future 可能取消TurnAborted/aborted output下一 Turn 可继续保持 call/output 配对
ToolRouter RespondToModelCall Item 已记录追加错误 output 并 follow-up由模型修正下一采样
Fatal 路由错误完整项可能已到达终止采样/Turn上报 Fatal

设计取舍与验证

将 History 提交点放在 Item Done,而非 Response Completed,使同一响应中已完成的工具调用在后续断流时仍可恢复;代价是一次失败响应可能留下若干已完成 Item。重试从最新 History 重建正是为此设计。active_item 只服务 UI,避免它与持久事实争夺权威。

可验证契约证据方式预期结果
Call 在工具执行前进入 History取消工具后检查 Rollout仍存在完整 call
Added/Delta 不产生 History 项中断半流原始历史无半截 Message
多个工具结果按调用顺序 drain并行完成顺序反转测试History 顺序仍稳定

Mini Codex 对照

Mini Codex 应定义 commit_completed_item() 作为唯一 History 入口。工具 future 必须在 call 提交之后创建;UI active state 放在采样局部对象,绝不能直接引用可变 History 项。

本节边界

本节关闭了内容事实边界。5.20 转向安全元数据,说明 Safety Buffering 在客户端究竟做什么、又不做什么。

评论


← 返回文章列表