OpenAI Codex · 代码模式

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

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

课程目标读完能说清三件事。第一,工具清单冻在一次采样上,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