雨天小六

读懂 Codex(10.11):可迁移到其他 Agent Runtime 的设计

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

#Codex#Agent Runtime#软件架构#系统设计

具体问题与边界

研究 Codex 的目的不是照搬 Rust crate,而是找出当模型、工具和外部世界形成闭环后仍然成立的合同。 这些合同应能用不同语言、Provider 和存储实现,同时在并发与故障下给出确定答案。本节把前十节压缩 为一组迁移顺序,并标出什么条件下可以简化。

可迁移合同

合同最小表达保护的不变量可简化条件
输入/输出窄腰typed command + event控制与观察分离完全同步、无 Interrupt 时直接调用
状态所有权单一 owner + ID scope并发修改有唯一解释者单线程局部状态
生命周期快照request/step snapshot旧请求自洽、新请求见新世界无动态工具/环境时单层快照
Spec/Runtime 同源one ToolPlan模型合同与执行绑定一致固定工具集可启动时构建一次
Call/Result 配对stable call_id历史可继续采样/恢复仍不可删除,即使工具无副作用
副作用屏障validate→authorize→execute→commit失败残留可判断纯计算工具可省审批
有界资源queue/timeout/capacity内存和并发不无限增长仍需明确上限,不应用“足够小”代替
规范日志与派生投影append log + rebuildable index恢复方向唯一无持久需求时可只保留内存
显式取消/终态cancellation token + terminal once不出现双终态/悬挂资源同步短调用仍应定义超时
可注入 Adapterprotocol boundary离线故障测试可复现生产实现可以不同,合同不变

建立最小 Runtime 的顺序

从领域协议、状态所有权、快照、工具合同到恢复和故障测试的 Agent Runtime 建设顺序
图 10.11-1:迁移应按依赖顺序建立合同;先决定谁拥有状态,再增加工具、并发和持久化。
  1. 定义完整 Message/ToolCall/ToolResult 与控制 Operation,明确哪些对象可持久化。
  2. 建立单一 Session owner、有界输入/事件 Queue、CancellationToken 和 terminal-once。
  3. 把 Prompt 编译为纯函数输入,建立 request/step snapshot,不从 Handler 读取可变全局状态。
  4. 用同一 ToolPlan 生成模型 specs 和执行 bindings;未知/非法调用也返回配对结果。
  5. 将副作用路径拆为验证、策略、审批、执行、结果提交,并为每个提交点定义残留状态。
  6. 只有需要跨进程恢复时加入 append-only 规范日志,再按查询需求增加派生索引。
  7. 最后才增加动态 MCP、多 Agent、Hook/Plugin,并为每项能力设置容量、版本和失败边界。

这个顺序避免“先做二十个工具,再发现没有统一取消和恢复协议”。工具数量增加的是表面能力,合同 决定 Runtime 能否在第二次采样、进程崩溃或目录刷新后继续正确工作。

Python 风格伪代码

class AgentRuntime(Protocol):
    async def submit(self, operation: Operation) -> SubmissionId: ...
    def events(self) -> AsyncIterator[AgentEvent]: ...

@dataclass(frozen=True)
class RuntimeContracts:
    model: ModelClient
    prompt_compiler: PromptCompiler
    tool_planner: ToolPlanner
    execution: ExecutionEnvironment
    approval: ApprovalPolicy
    rollout: RolloutStore

async def run_step(turn: TurnSnapshot, contracts: RuntimeContracts) -> StepOutcome:
    step = await contracts.tool_planner.capture(turn)
    prompt = contracts.prompt_compiler.compile(turn, step)
    response = await contracts.model.stream(prompt, step.tool_plan.specs, turn.cancellation)
    items = await accumulate_complete_items(response)
    results = await execute_and_pair(items.tool_calls, step, turn, contracts)
    await contracts.rollout.append_and_flush((*items.complete, *results))
    return decide_next_step_or_terminal(items, results)

每个 Protocol 都对应一个可替换失败源。接口数量不是目标;只有能在测试中注入不同环境并保持相同 行为合同的边界才值得保留。

迁移失败模式

机械复制 Codex 类型或过度简化 Agent Loop 时的迁移失败图
图 10.11-2:照搬产品类型会引入无用复杂度,只复制 happy path 又会丢失决定正确性的故障合同。
迁移错误症状修正
按源码目录复制模块依赖方向与目标产品不匹配按所有权/生命周期重画边界
保留 Rust 名称但丢 await/取消看似相似,故障语义不同迁移状态机而非语法
只有 function-call demo第二次采样/恢复不合法建完整 Item、配对和 History
每层都抽象成万能 Adapter提交点被隐藏Adapter 合同写清资源和残留
一开始复制 SQLite/Graph/MCP 全家桶维护成本先于需求按功能触发条件逐层加入
只测输出文本资源泄漏/双终态未被发现断言事件序列和残留状态

设计判断

可迁移设计的共同点是“限制歧义”:谁修改、什么时候可见、失败后剩什么、恢复从哪里开始。技术选型 可以换成 TypeScript、Go 或 actor system;只要这些问题仍有明确答案,架构合同就保留下来。

迁移时每增加一层都应提供删除条件。如果目标 Runtime 没有多 Surface、热更新、跨进程恢复或不可信 命令,就应该主动省略对应产品复杂度,并把不支持写成边界。

证据与复现实验

本节是 1.1—10.10 的交叉归纳,证据矩阵见第十章总索引。Mini Codex 是一次 Python 迁移实验:

cd examples/mini-codex
uv run pytest -q
uv run mypy src

测试验证合同可跨语言表达,但不说明 Python 实现适合 Codex 的生产规模。

本节边界

可迁移合同已经列明;最后一节反向列出不该因为 Codex 存在就机械复制的产品规模复杂度。详细源码映射由研究仓库中的配套索引维护。

阅读导航

上一节:10.10 · 下一节:10.12

评论


← 返回文章列表