雨天小六

读懂 Codex(7.2):Built-in Role、Agent TOML 与模型覆盖

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

#Codex#Agent Runtime#Multi-Agent#Extension#软件架构

Role 是高优先级配置层而不是整份配置替换:内建 default/awaiter/explorer 与用户 Agent TOML 走同一解析语义,Role 只覆盖自己声明的字段。

本节只研究“角色发现、合并和应用”。输入是 角色名、分层 agents 配置、独立 TOML、调用方模型参数,状态由 AgentRoleResolver 与配置层系统 持有,成功输出为 用于子 Thread 的有效 Config。相邻章节中看起来相似的对象如果由不同组件拥有,就不能用一个布尔状态替代。

先确定边界和状态所有者

问题本节答案
谁发起角色名、分层 agents 配置、独立 TOML、调用方模型参数
谁拥有可变状态AgentRoleResolver 与配置层系统
成功产物用于子 Thread 的有效 Config
不在本节内角色发现、合并和应用之外的上游产品策略和下游业务实现

这张表的用途不是复述名词,而是约束实现顺序:校验必须发生在副作用之前;已经提交的状态只能由原所有者撤销;只读投影不能反过来成为控制权威。

端到端调用链

  1. 收集内建/分层角色
  2. 合并元数据
  3. 校验描述与指令
  4. 叠加高优先级 ConfigLayer
  5. 保留未覆盖调用参数
Built-in Role、Agent TOML 与模型覆盖的端到端机制流程,展示收集内建/分层角色、合并元数据、校验描述与指令、叠加高优先级 ConfigLayer、保留未覆盖调用参数
图 7.2-1:收集内建/分层角色 → 合并元数据 → 校验描述与指令 → 叠加高优先级 ConfigLayer → 保留未覆盖调用参数。图中每条箭头都表示下一层只接收上一层已经确认的状态。

这条链里至少有三种时间尺度:配置或身份在 Thread 创建时冻结,Turn 内状态由 Session 串行协调,Step 级目录与能力在每次模型采样前重新捕获。若把后一个时点的新状态拿去解释前一个时点已经发出的调用,就会产生跨代错误。

源码机制拆解

内建角色也只是配置

awaiter.tomlexplorer.toml 提供描述和 Developer Instructions;default 表示不额外施加专用角色。它们不会绕过普通配置构建。

这项约束直接决定了边界两侧的数据形状。实现时应先证明前置状态,再写入由 AgentRoleResolver 与配置层系统 持有的对象;不能用日志、UI 状态或模型描述代替真实状态更新。

高层缺失字段可以继承低层元数据

配置中的 agents.roles 与独立 agents/*.toml 被分层发现。高优先级定义若只改指令,可以继承低层的名称或描述;合并后仍缺必需描述则丢弃并给出警告。

这项约束直接决定了边界两侧的数据形状。实现时应先证明前置状态,再写入由 AgentRoleResolver 与配置层系统 持有的对象;不能用日志、UI 状态或模型描述代替真实状态更新。

模型相关字段逐项覆盖

应用 Role 时先保留调用者已经选择的 model、provider、service tier 和 reasoning;只有 Role 明确提供相应键才替换。V2 中 Role 未给 Developer Instructions 时也保留调用者指令。

这项约束直接决定了边界两侧的数据形状。实现时应先证明前置状态,再写入由 AgentRoleResolver 与配置层系统 持有的对象;不能用日志、UI 状态或模型描述代替真实状态更新。

坏角色是局部配置错误

一个 TOML 解析失败不会使所有角色失效;加载器记录警告并跳过该项,Spawn 对未知角色再返回明确错误。

这项约束直接决定了边界两侧的数据形状。实现时应先证明前置状态,再写入由 AgentRoleResolver 与配置层系统 持有的对象;不能用日志、UI 状态或模型描述代替真实状态更新。

Python 风格伪代码

下面的伪代码提炼状态机与错误顺序,不逐行翻译 Rust,也不假装 Python 对象具有 Rust 的所有权保证:

def apply_role(base: Config, role: RoleDefinition) -> Config:
    layer = ConfigLayer.high_precedence(role.config_toml)
    merged = merge_config(base, layer)

    if role.model is None:
        merged.model = base.model
        merged.provider = base.provider
    if role.reasoning is None:
        merged.reasoning = base.reasoning
    if role.developer_instructions is None:
        merged.developer_instructions = base.developer_instructions
    return validate_config(merged)

阅读这段伪代码时要检查三件事:第一,输入是否在副作用前完成规范化;第二,异步等待是否仍携带原 Thread/Turn/Step 身份;第三,失败后究竟释放了什么、又保留了什么。只写快乐路径会把本节最重要的一致性条件删掉。

失败、取消与恢复

Built-in Role、Agent TOML 与模型覆盖的三类失败分支、可观察结果和恢复责任
图 7.2-2:失败不会抹掉已经提交的状态;每条分支都由拥有该状态的组件执行恢复或补偿。
故障点已发生的状态可观察结果恢复责任
角色 TOML 语法错误其他角色已加载坏角色被丢弃并警告修正文件后重新加载配置
合并后缺 description名称可能存在角色不可用于 Spawn补齐元数据
Role 指定未知模型尚未创建子 Thread配置解析/模型解析失败不提交 SpawnReservation

取消不自动等于回滚,列表不自动等于权威存储,模型看见的描述也不自动等于已经获准执行。对于已经建立的 Thread、写入的 Edge、入队的 Mailbox、安装的插件或外部工具副作用,必须由相应所有者执行显式关闭、补偿或保留。

并发与一致性不变量

  • 同一身份不能在并发路径中被重复预留或重复注册;若允许幂等重试,幂等键必须是稳定路径、ThreadId、request id 或 catalog revision,而不是展示名称。
  • 一次模型采样看见的 Prompt、工具目录和执行入口必须来自同一个 Step 快照;刷新只影响后续 Step。
  • Watch/Activity/事件是通知机制,不是状态本身。被唤醒后必须重新读取 Registry、Session、Mailbox、Store 或 Binding 的权威值。
  • 失败路径不得“为了干净”删除仍可恢复的历史;同样也不得把只剩历史的对象伪报成当前仍驻留运行。

设计思路与代价

本节设计保护的核心不变量是:Role 是高优先级配置层而不是整份配置替换:内建 default/awaiter/explorer 与用户 Agent TOML 走同一解析语义,Role 只覆盖自己声明的字段。代价是同一个功能会跨越地址、配置、Session、队列、存储或扩展适配层,测试也必须覆盖正常、并发和中途失败。更短的单体实现虽然容易演示,却无法区分“存在、已加载、正在运行、可恢复、已关闭、模型可见”这些彼此独立的事实。

设计动机部分是根据生产类型、调用顺序和测试行为归纳;源码可直接证明的是字段、分支、状态所有者和失败结果。文章不会把推断写成服务端或产品层的未公开事实。

源码与测试证据

位置能证明什么
codex-rs/core/src/agent/role.rsRole 解析、内建角色与高优先级应用
codex-rs/core/src/config/agent_roles.rs分层角色与独立 TOML 发现/合并
codex-rs/core/src/agent/builtins/awaiter.toml内建 awaiter 定义
codex-rs/core/src/agent/builtins/explorer.toml内建 explorer 定义
测试/可执行检查覆盖重点预期
codex-rs/core/src/agent/role_tests.rs正常、边界与状态转换应与本节不变量一致
codex-rs/core/src/config/agent_roles.rs失败、并发或兼容行为应与本节不变量一致

当前环境没有 Cargo,本轮不伪报 Rust 测试执行;验证由锁定提交下的源码—测试静态对读、源文件存在性检查、Mermaid 渲染、Astro 生产构建和公开页面检查组成。

Mini Codex 复刻建议

Mini Codex 应先复刻本节的协议不变量:明确状态所有者、稳定身份、可回滚预留、同 Step 快照、超时/取消和单一终态。平台沙箱、远程 Executor、OAuth、Backend App Route 或第三方 MCP Server 的安全性质不能由一个本地 Python mock 证明,适配器只能验证调用顺序和故障处理。

本节结论

Role 是高优先级配置层而不是整份配置替换:内建 default/awaiter/explorer 与用户 Agent TOML 走同一解析语义,Role 只覆盖自己声明的字段。 掌握这一点后,再看相邻模块时就能判断它是在改变身份、运行状态、模型可见上下文、外部能力,还是仅提供观察快照。

阅读导航

上一节:Agent 身份怎样由 Role、Thread、Prompt、Tool 和 Status 组合 · 下一节:AgentRegistry 的注册、活动索引与 SpawnReservation

源码依据

本文基于锁定提交 fe01054a28fa4bd04716d9ceadb410f2443a50ce 的生产代码和相邻测试静态核对。关键入口如下:

评论


← 返回文章列表