模型看见的工具清单,是一次采样算出来的
同一段对话、同一份配置,下一轮采样却可能多出 MCP 名字、协作入口或 tool_search。变的是这一次允许广告哪些 handler。
本页解决的问题
先给结论「模型看见的工具清单,是一次采样算出来的」要解决的关键问题是什么?
同一段对话、同一份配置,下一轮采样却可能多出 MCP 名字、协作入口或 tool_search。变的是这一次允许广告哪些 handler。
跟着交接处走,不要只看 Demo。 系统是否可靠,往往取决于模型、工具、状态、权限和人的交接处。把每次交接都当成可以观察、测试和恢复的地方。
为一个自动化步骤写清输入、负责人、审批和恢复动作。
一次运行成功了,却说不清发生了什么,也无法安全重放。
tool_search。
- 解析这一步的 MCP 绑定mcp.rs L310
- 采集 MCP 目录,交给规划函数turn.rs L1494
- 按身份决定是否走完整来源spec_plan.rs L889
- 登记 shell、资源、实用、协作spec_plan.rs L930
- 追加 MCP 并套上暴露策略spec_plan.rs L148
- 有 deferred 才挂 tool_searchspec_plan.rs L335
- 只把 is_direct 的 spec 发给模型spec_plan.rs L484
- 冻进 StepContext.tool_routerstep_context.rs L44
- build_prompt 读取可见表turn.rs L1320
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 行
所以冻结单位是 step,不是 turn。一个 turn 里可以有多次采样,每次重建一份 StepContext。出处:codex-rs/core/src/session/turn.rs 第 1318 至 1320 行
可见和可执行共用一份快照,下一轮再吸收新连接。换个语言重写,最小形态仍是一个函数:输入开关、MCP 目录、身份,输出可见表和可执行表,请求开始时算一次,算完就冻。
审批模型如果看见工作模型那整张 MCP 和协作工具表,它就能去 spawn_agent、读外部资源,审批本身被绕开。先注册全表再过滤,漏一条分支就会把不该给的名字送出去。
规划函数按身份决定走哪些来源。Guardian 评审员的标签必须恰好是 guardian。权限档案不是 Managed,函数直接返回,注册表是空的。Managed 且有环境时,最多三件:exec_command、write_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 行
两个 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 这个入口。
Claude Code:静态基表加 MCP,内置优先
getAllBaseTools 是一张写死的数组,FileReadTool 是一等成员。请求时 assembleToolPool 先过滤 deny 规则,再把内置和 MCP 各自按名字排序后拼接。内置在前,撞名时内置赢。注释写明这样排是为了 prompt cache:MCP 插到内置中间,后面的 cache key 会全失效。
Claude Code 的基表打开一个文件就能数完。Codex 要数清单必须走完 add_core_tool_sources,同一套规划能按身份和预算裁剪。
这一轮菜单上还剩什么
先开 MCP 和搜索,确认 mcp__filesystem__read_file 在灰色区、tool_search 在菜单上。然后推演三步:搜索仍开着,但 MCP 全部变成 Hidden;采样中途掉线;再走下一轮采样。每一拍写下可见表、灰色区和 tool_search 在不在。
进阶一问:把身份改成 Guardian 评审员,权限先 Managed 再改成其他,菜单分别是什么,为什么不是少挂几件工具。
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」。回看这条线索时,最值得保留的是:当输入、规模或风险改变,哪些判断需要重新做一遍。
我把这篇文章里的一个判断改写成了今天可以验证的小实验。比记住结论更有用的是,知道下一步要观察什么。
读完以后我先回头找它成立的条件,而不是直接把方法搬进项目。这个顺序让后面的取舍清楚很多。
如果把这个判断放到真实工作里,最先需要补的约束是什么?我想知道从阅读到第一次实践之间,哪一步最值得先做。
还没有这篇文章的讨论。