专题篇章 · 拆开 OpenAI Codex

模型看见的工具清单,是一次采样算出来的

同一段对话、同一份配置,下一轮采样却可能多出 MCP 名字、协作入口或 tool_search。变的是这一次允许广告哪些 handler。

本页解决的问题

先给结论

「模型看见的工具清单,是一次采样算出来的」要解决的关键问题是什么?

同一段对话、同一份配置,下一轮采样却可能多出 MCP 名字、协作入口或 tool_search。变的是这一次允许广告哪些 handler。

判断标准

跟着交接处走,不要只看 Demo。 系统是否可靠,往往取决于模型、工具、状态、权限和人的交接处。把每次交接都当成可以观察、测试和恢复的地方。

下一步

为一个自动化步骤写清输入、负责人、审批和恢复动作。

常见误区

一次运行成功了,却说不清发生了什么,也无法安全重放。

课程目标读完能说清三件事。第一,工具清单冻在一次采样上,MCP 中途掉线改不了已经发出去的表。第二,规划按身份裁剪来源,注册了不等于看得见。第三,搜索一开,贵的 MCP schema 会撤到延迟发现,只留 tool_search
先玩一遍 · 拨开关,看这一轮菜单怎么变
同一会话:条件一变,模型这一轮看见的表就重算
条件
左边是这一轮的输入。点播放按源码顺序走一遍,也可以自己拨。
发给模型的菜单0件可见0字描述
本轮已冻结,掉线改不了这份表
注册了但看不见0
逻辑轨迹 · 动画每一步对应源码里的哪一段
  1. 解析这一步的 MCP 绑定mcp.rs L310
  2. 采集 MCP 目录,交给规划函数turn.rs L1494
  3. 按身份决定是否走完整来源spec_plan.rs L889
  4. 登记 shell、资源、实用、协作spec_plan.rs L930
  5. 追加 MCP 并套上暴露策略spec_plan.rs L148
  6. 有 deferred 才挂 tool_searchspec_plan.rs L335
  7. 只把 is_direct 的 spec 发给模型spec_plan.rs L484
  8. 冻进 StepContext.tool_routerstep_context.rs L44
  9. build_prompt 读取可见表turn.rs L1320
点播放,看同一会话里拨几个开关,模型这一轮的菜单怎么增减。
菜单从哪来环境在、ShellTool 和 UnifiedExec 开着,先上 shell 两件套。MCP 连上再加资源入口和规范化后的外部名。
看得见和注册了搜索一开,贵的 MCP schema 撤到灰色区,初始表换成 tool_search。评审员跳过整组来源,非 Managed 时表是空的。
掉线改哪一轮采样中途断开,已经发出去的菜单不动。下一轮按空绑定重算,那些名字才会消失。
教学示意:开关组合与描述长度是课程化设定,规划顺序对齐 build_tool_router。逻辑轨迹右侧行号对应 openai/codex 仓库 commit 4f39251a01。
思路一 · 冻在一次采样上
它解决什么问题

你刚接上 MCP filesystem,模型这一轮还在跑。要是按整轮用户对话冻工具表,新连上的服务器得等下一条用户消息才进菜单。要是每调一次工具冻一次,同一次采样里并行的两次调用可能看见两份不同的表。

广告一份、执行另一份,或者执行到一半表变了,是工具表最常见的翻车。

思路是什么

Codex 把这一次采样的模型、审批、环境、MCP 绑定和已经算完的 tool_router 一起放进 StepContext。注释写明它是 request-scoped:采样请求之间可以变,同一次采样里不变。tool_router 是 this exact sampling request 的计划。

采样进行期间,模型看见的名字和能 dispatch 的 runtime 都读这份快照。MCP 服务器在这次采样中途掉线,改不了已经算完的表。下一次采样会再走 mcp_runtime_for_step,绑定可能复用,也可能换成空绑定。出处:codex-rs/core/src/session/step_context.rs 第 17 至 47 行;codex-rs/core/src/session/mcp.rs 第 348 至 357 行

MCP 绑定 这一 step 的目录 build_tool_router 按身份规划 StepContext 冻住 router Prompt.tools 发给模型 中途掉线改不了这份快照 下一轮采样再解析绑定,空绑定就不再挂 MCP 名字
教学化结构图:冻结发生在采样边界,广告和执行读同一份 router。

所以冻结单位是 step,不是 turn。一个 turn 里可以有多次采样,每次重建一份 StepContext出处:codex-rs/core/src/session/turn.rs 第 1318 至 1320 行

为什么长期成立

可见和可执行共用一份快照,下一轮再吸收新连接。换个语言重写,最小形态仍是一个函数:输入开关、MCP 目录、身份,输出可见表和可执行表,请求开始时算一次,算完就冻。

思路二 · 按身份裁来源
它解决什么问题

审批模型如果看见工作模型那整张 MCP 和协作工具表,它就能去 spawn_agent、读外部资源,审批本身被绕开。先注册全表再过滤,漏一条分支就会把不该给的名字送出去。

思路是什么

规划函数按身份决定走哪些来源。Guardian 评审员的标签必须恰好是 guardian。权限档案不是 Managed,函数直接返回,注册表是空的。Managed 且有环境时,最多三件:exec_commandwrite_stdin、可选 view_image。普通会话才走 shell、MCP 资源、实用工具、协作这四组,再追加 MCP、扩展和动态工具。出处:codex-rs/core/src/guardian/review.rs 第 212 至 220 行;codex-rs/core/src/tools/spec_plan.rs 第 144 至 164 行、第 889 至 934 行

读盘也没有一等入口。handlers 目录里没有 ReadFileHandler。模型要读文件,走 exec_command、MCP filesystem,或 view_image 内部的 exec-server API。

为什么长期成立

身份一变,整组来源都不走。默认关上门发生在评审员、环境缺失、特性关闭这几处,换语言也用得上。

思路三 · 注册了不等于看得见
它解决什么问题

MCP 工具的 schema 很贵。全挂上,初始请求的 token 会被描述文本吃掉。两个 server 都提供 read_file,不消歧就会撞名。

思路是什么

暴露态把注册表拆成几条可见面。Direct 进初始表。Deferred 只留给 tool_search。Hidden 只留给 dispatch。搜索开着时,MCP 工具默认 Deferred,不进初始可见表。出处:codex-rs/tools/src/tool_executor.rs 第 51 至 80 行;codex-rs/core/src/mcp_tool_exposure.rs 第 90 至 94 行

finalize_tool_router 只在两件事同时成立时才挂 tool_search:模型支持 search tool,并且注册表里还有至少一个 deferred 且带 search_info 的工具。没有 deferred,它不会进表。出处:codex-rs/core/src/tools/spec_plan.rs 第 335 至 370 行、第 578 至 580 行

已登记 能 dispatch Direct · 初始表 Deferred · 先搜再看见 Hidden · 只留给执行 Prompt.tools tool_search 的检索面 模型看不见,调用时还能命中
教学化对照:同一份注册表拆成直接可见、延迟发现、只可执行三条面。

两个 MCP server 的同名工具,在规范化阶段消歧:默认加 mcp__ 前缀,sanitize 后仍撞车再加 12 位哈希后缀。外部工具撞到已占用的核心名,直接跳过。仓库根 AGENTS.md 仍写着 mcp_connection_manager.rs,当前树里没有这个文件,消歧在 tools.rs出处:codex-rs/codex-mcp/src/tools.rs 第 1 至 5 行、第 113 至 151 行;AGENTS.md 第 35 行

注册了,还不等于这一轮发给了模型。
为什么长期成立

可见、可检索、可执行是三份集合。token 预算紧就推迟发现,核心名保留、外部让路。这些不依赖 Rust。

横向对比 · 同一道题的另一种答法

DeepSeek Harness:插件往装配器里投 schema

DSH 没有集中规划函数。每个工具包在 assemble 时通过 systemPrompt.tools() 投递 schema,装配器再用部署配置的 toolOrder 排座位。未点名的工具插在保留标记 <unlisted-tools> 处,按名字字典序。漏写这个标记,装配期直接失败。

装上 @deepseek-ai/dsh-tool-fs,模型就看见一等工具 read。描述要求用它读文本。Codex 把读盘留给 shell、MCP 和内部 API,规划函数里没有 read_file 这个入口。

两侧均已核对源码 · 2026-08-22 · DSH · 工具顺序中心列表

Claude Code:静态基表加 MCP,内置优先

getAllBaseTools 是一张写死的数组,FileReadTool 是一等成员。请求时 assembleToolPool 先过滤 deny 规则,再把内置和 MCP 各自按名字排序后拼接。内置在前,撞名时内置赢。注释写明这样排是为了 prompt cache:MCP 插到内置中间,后面的 cache key 会全失效。

Claude Code 的基表打开一个文件就能数完。Codex 要数清单必须走完 add_core_tool_sources,同一套规划能按身份和预算裁剪。

两侧均已核对源码 · 2026-08-22
课堂练习
01

这一轮菜单上还剩什么

先开 MCP 和搜索,确认 mcp__filesystem__read_file 在灰色区、tool_search 在菜单上。然后推演三步:搜索仍开着,但 MCP 全部变成 Hidden;采样中途掉线;再走下一轮采样。每一拍写下可见表、灰色区和 tool_search 在不在。

进阶一问:把身份改成 Guardian 评审员,权限先 Managed 再改成其他,菜单分别是什么,为什么不是少挂几件工具。

Takeaway:规划函数决定广告什么,注册表决定能 dispatch 什么,暴露态决定直接、延迟还是隐藏,StepContext 把它们冻在采样边界。模型仍可用 exec_command 读文件,即使表上没有 read_file

「先玩一遍 · 拨开关,看这一轮菜单怎么变」的能力藏在每次交接里

「你刚接上 MCP filesystem,模型这一轮还在跑。要是按整轮用户对话冻工具表,新连上的服务器得等下一条用户消息才进菜单。要是每调一次工具冻一次,同一次采样里并行的两次调用可能看见两份不同的表」说明,Agent 的表现不只由模型决定。模型、上下文、工具、状态、权限和人之间的每次交接,都会改变任务能否继续以及出了问题能否恢复。

先写清状态,再增加能力

从「Codex 把这一次采样的模型、审批、环境、MCP 绑定和已经算完的 tool_router 一起放进 StepContext 。注释写明它是 request-scoped:采样请求之间可以变,同一次采样里不变。」出发,可以把流程拆成起始状态、下一步动作、工具返回、状态更新和停止条件。这样调试时找的是第一处丢失信息或权限的位置,而不是笼统地说“模型变笨了”。

  • 解析这一步的 MCP 绑定 mcp.rs L310
  • 采集 MCP 目录,交给规划函数 turn.rs L1494
  • 按身份决定是否走完整来源 spec_plan.rs L889

成功路径不能代表系统可靠

用「进阶一问:把身份改成 Guardian 评审员,权限先 Managed 再改成其他,菜单分别是什么,为什么不是少挂几件工具」重放一次成功和一次失败,记录每一轮真正传入的上下文、工具结果和负责人;只要第二个人能复述这条链,系统才有可维护性。

从「先玩一遍 · 拨开关,看这一轮菜单怎么变」走到「思路一 · 冻在一次采样上」

「先玩一遍 · 拨开关,看这一轮菜单怎么变」先把问题落在「同一会话:条件一变,模型这一轮看见的表就重算 播放 单步 重置 条件 MCP 已连接 搜索工具 第二台 MCP Guardian 评审员 权限 Managed 左边是这一轮的输入。点播放按源码顺序走一遍,也可以自己拨。 采样中途掉线 下一轮采样 发给模型的菜单 0 件可见 0 字描述 本轮已冻结,掉线改不了这份表 注册了但看不见 0 件 逻辑轨迹 · 动画每一步对应源码里的哪一段 解析这一步的 MCP 绑定 mcp…」上;到了「思路一 · 冻在一次采样上」,讨论继续推进到「你刚接上 MCP filesystem,模型这一轮还在跑。要是按整轮用户对话冻工具表,新连上的服务器得等下一条用户消息才进菜单。要是每调一次工具冻一次,同一次采样里并行的两次调用可能看见两份不同的表」。两段连起来,重点就不只是记住一个结论,而是看清它成立所依赖的条件。

把这条判断带到下一个场景

分析 Agent 时,沿着状态、动作、工具结果和下一步的顺序走一遍;每次交接都要能说明信息从哪里来、由谁确认、失败时停在哪里。

  • 「先玩一遍 · 拨开关,看这一轮菜单怎么变」:同一会话:条件一变,模型这一轮看见的表就重算 播放 单步 重置 条件 MCP 已连接 搜索工具 第二台 MCP Guardian 评审员 权限 Managed 左边是这一轮的输入。点播放按源码顺序走一遍,也可以自己拨。 采样中途掉线 下一轮采样 发给模型的菜单 0 件可见 0 字描述 本轮已冻结,掉线改不了这份表 注册了但看不见 0 件 逻辑轨迹 · 动画每一步对应源码里的哪一段 解析这一步的 MCP 绑定 mcp…
  • 「思路一 · 冻在一次采样上」:你刚接上 MCP filesystem,模型这一轮还在跑。要是按整轮用户对话冻工具表,新连上的服务器得等下一条用户消息才进菜单。要是每调一次工具冻一次,同一次采样里并行的两次调用可能看见两份不同的表
  • 「最后的要点」:追加 MCP 并套上暴露策略 spec_plan.rs L148

最后的「最后的要点」把讨论落到「追加 MCP 并套上暴露策略 spec_plan.rs L148」。回看这条线索时,最值得保留的是:当输入、规模或风险改变,哪些判断需要重新做一遍。

标记为已学完 阅读进度会自动记录
← 上一篇下一篇 →

继续阅读

同一条线上的下一篇。

文章讨论

读到这里,留下一个判断。

把刚想明白的地方、还没想通的问题,留给下一位一起学习的人。

正在讨论 模型看见的工具清单,是一次采样算出来的 拆开 OpenAI Codex
3条讨论文章讨论 · 与共学社区同步
在共学社区查看
AM
Asha Morgan内容编辑
观点实践记录

我把这篇文章里的一个判断改写成了今天可以验证的小实验。比记住结论更有用的是,知道下一步要观察什么。

文章讨论7 有帮助
LH
Lin Harper独立开发者
观点观点

读完以后我先回头找它成立的条件,而不是直接把方法搬进项目。这个顺序让后面的取舍清楚很多。

文章讨论5 有帮助
KM
Kiki Moore产品运营
问题问题

如果把这个判断放到真实工作里,最先需要补的约束是什么?我想知道从阅读到第一次实践之间,哪一步最值得先做。

文章讨论4 有帮助