先想一个具体场景:你让 Codex 写一篇文章,并点名使用 $article 和
$image;同时本地装着 Browser、GitHub、Documents 这些 plugin;
这轮可能还会出现几十个 MCP 工具,甚至需要开一个 subagent 专门审稿。
如果把这些都理解成“给模型多塞一些东西”,源码很快会读乱。
Codex 的处理方式更像一个分拣台。有的能力进入 prompt,有的能力进入工具列表, 有的能力只提供安装或发现入口,有的能力直接变成一条新的线程。 它们最终都服务同一轮工作,但进入 runtime 的形状完全不同。
这篇可以抓住一句话:扩展会以不同形状投到这一次 turn 里,继续受 turn runtime 管理。
skills 进入上下文,plugins 提供能力根目录和包边界,
MCP 进入工具暴露计划,subagents 进入线程控制面。
证据边界。
本文只描述 openai/codex 公开源码里可验证的 skill 发现与注入、plugin manifest 与
load outcome、MCP tool exposure、tool_search、dynamic tools、multi-agent tool surface
和 AgentControl 行为。文中提到 app、plugin、subagent 时,指这些公开代码里的运行时结构。
第七篇按六个问题拆:
- 为什么要先区分“上下文能力”“工具能力”和“线程能力”?
TurnContext里哪些字段承接扩展输入?- skill 从“可用目录”到“完整 SKILL.md 注入”经历了什么?
- plugin 作为能力包,怎样同时贡献 skills、MCP、apps 和 hooks?
- MCP 工具什么时候直接给模型,什么时候交给
tool_search? - subagent 为什么是线程控制面,而不是普通工具输出?
一、先把四种能力形状分开
读扩展代码最容易卡住的地方,是几个词都会被泛泛地叫成“能力”。但在 Codex 里, 它们落到模型面前的形状不一样,也由不同 owner 管。
| 能力形状 | 用户看到的入口 | 进入 runtime 的方式 | 源码里要跟的 owner |
|---|---|---|---|
| 工作方法 | $article、$image、显式 skill 选择。 |
先展示 skill catalog;被点名后读取完整 SKILL.md,作为 contextual user fragment 注入。 |
SkillsManager、build_skill_injections。 |
| 能力包 | Browser、GitHub、Documents 这类 plugin。 | manifest 声明 skills、MCP servers、apps、hooks;加载后产出 effective roots 和 capability summaries。 | PluginManifest、PluginLoadOutcome。 |
| 可调用工具 | MCP server、app connector、dynamic tool。 | 转换成 model-visible tool spec;数量或配置触发时变成 deferred tools,由 tool_search 再发现。 |
build_mcp_tool_exposure、ToolSearchHandler、ToolRouter。 |
| 并行工作单元 | spawn_agent、send_message、wait_agent。 |
创建或恢复子线程,复制本轮运行配置和权限边界,通过 inter-agent communication 回到父线程。 | AgentControl、multi-agent handlers、SessionSource::SubAgent。 |
这张表先放在脑子里,后面看源码就不会混:skill 解决“这件事该怎么做”,plugin 解决“这些能力属于哪个包”, MCP 解决“模型能调用什么”,subagent 解决“是否需要另一条上下文和执行轨道”。
1.1 用同一条请求串起四种入口
可以拿一条真实工作流做代表性单元:用户说“用 $article 写一篇 Codex memory,
必要时用 Browser 查页面,再开一个 subagent 审稿”。这句话不会被 Codex 合并成一个万能能力;
turn 开始后,它会被拆成几类有不同归属的材料。
| 阶段 | 这时的单元 | 谁能看或改 | 后续变化 |
|---|---|---|---|
| 原始输入 | 用户文本和显式 mention。 | 输入解析层识别 $article、Browser、subagent 需求。 |
只决定本轮要准备哪些入口,不直接产生工具结果。 |
| 能力快照 | TurnContext 里的 turn_skills、extension_data、dynamic_tools。 |
runtime 在 turn 边界内维护;后续注入和工具规划读取它。 | 把“当前有哪些能力”固定成本轮事实。 |
| 上下文注入 | $article 展开的 SKILL.md。 |
模型可见,作为工作方法约束。 | 影响写作方式,但它本身不是可调用函数。 |
| 工具暴露 | Browser 或其他 MCP/app tool spec。 | 模型只能看到 direct tools 或 tool_search 找回的 deferred tools。 |
真正调用时仍由 tool router 和权限层接管。 |
| 子线程 | spawn_agent 创建的审稿任务。 |
父线程发起,子线程有自己的上下文和事件流。 | 结果通过 inter-agent communication 回到父线程。 |
这条小链路比术语表更重要。一次用户请求里可以同时出现 skill、plugin、MCP 和 subagent, 但它们不会在同一层生效。先分清“进入上下文”“暴露工具”“启动另一条线程”,后面的源码就能顺着 owner 读。
二、TurnContext 先把入口留出来
第二篇讲 TurnContext 时,我们重点看上下文、权限和 token budget。第七篇要注意另一组字段:
dynamic_tools、extension_data、turn_skills、
multi_agent_version、parent_thread_id 和 session_source。
它们让一次 turn 有地方承接外部能力。
make_turn_context 会把当前 turn 的配置、模型信息、权限 profile、动态工具、
extension data 和 skills outcome 放进同一个结构里。这里有两个细节很关键。
第一,HostLoadedSkills 被插入 extension_data,让 host extension
也能看到已加载的 skill 集合。第二,dynamic_tools 来自 session configuration,
后面工具规划阶段会把它们追加到工具 surface。
所以扩展入口并不是散落在各处临时读取。Codex 会在 turn 开始时先形成一个能力快照: 当前配置允许什么、已加载哪些 skill、有哪些 extension data、是否启用多 agent、 本轮权限边界是什么。后续注入和工具规划都围绕这份快照展开。
三、skills:先给目录,再按需展开全文
skill 的第一层很轻:Codex 会把可用 skill 的列表和使用规则放进 developer 上下文。
AvailableSkillsInstructions 只渲染 catalog 级别的信息,告诉模型有哪些 skill、
什么时候应该使用、怎么读取附加说明。
真正重的部分发生在用户点名之后。build_skills_and_plugins 从本轮 user input
里收集显式 skill mention,然后调用 build_skill_injections。这个函数会按 skill metadata
找到对应的文件系统,读取完整 SKILL.md,再把内容包装成
SkillInstructions。换句话说,完整技能说明是本轮被需要时才进入 prompt。
| 阶段 | 放进模型上下文的内容 | 为什么这样做 |
|---|---|---|
| 可用列表 | skill name、description、path、触发规则。 | 让模型知道“可以选什么”,但不把所有长说明一次塞进上下文。 |
| 显式点名 | 完整 SKILL.md 内容。 |
用户或结构化输入已经确认需要它,这时才值得付出上下文成本。 |
| 注入形状 | ContextualUserFragment 里的 SkillInstructions。 |
skill 是工作方法和约束,进入的是上下文层,不是工具调用层。 |
这也解释了为什么一个 skill 写得好不好,会直接影响文章或图的质量。runtime 只负责把它放到正确位置, 至于模型读到后如何执行,取决于 skill 里面是否把工作流、边界、验收方式写清楚。
四、plugins:把一组能力打成包
plugin 解决的是另一个问题:一组能力往往不是单独一个 SKILL.md。例如 Browser plugin
可能同时带 skill、脚本、MCP server、app connector;GitHub plugin 可能同时提供 PR triage、
CI 排查和发布流程。源码里
PluginManifest.paths
把这些入口分开声明:
skills、mcp_servers、apps、hooks。
pub struct PluginManifestPaths<Resource> {
pub skills: Option<Resource>,
pub mcp_servers: Option<Resource>,
pub apps: Option<Resource>,
pub hooks: Option<PluginManifestHooks<Resource>>,
}
这几行很适合作为插件心智模型:plugin manifest 只是把一组资源挂到同一个包名下面。 它没有把 skill、MCP、app 和 hook 合成一种新能力;相反,它保留了四条入口的差异, 后续加载器再分别把它们送到上下文、工具、app connector 或生命周期边界。
加载后,PluginLoadOutcome 会给出几类 effective 结果:
skill roots、plugin skill roots、MCP servers、apps、hook sources 和 capability summaries。
capability summary 只保留对模型有用的短描述;真正能被调用的能力,仍然要走它各自的通道。
这里有个很容易忽略的边界:plugin 自己不是一个万能工具。源码给模型的 plugin 使用说明里明确强调, plugin 是 skills、MCP servers 和 apps 的本地 bundle。要解决任务,仍然使用它贡献出来的 skill、MCP 工具或 app tool。
build_skills_and_plugins 会先从当前配置加载 plugins,再解析显式 plugin mention。
如果用户点名某个 plugin,Codex 会拿到当前可用的 MCP/app inventory,用
build_plugin_injections 生成本轮 guidance。这样模型知道“这个包现在有哪些可用能力”,
但调用时还是回到具体工具或 skill。
五、MCP 与 tool_search:工具太多时先发现,再展开
第四篇已经讲过普通工具调用路径。第七篇要补上 MCP 工具进入模型前的一层选择:
build_mcp_tool_exposure 会先筛出 model-visible 的 MCP tools,
再根据配置和数量决定直接暴露,还是作为 deferred tools 交给搜索工具。
代码里有一个很具体的阈值:DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD 是 100。
如果启用了 search tool,并且配置要求始终 defer,或者待暴露工具数量达到阈值,
Codex 就不把这一大批工具全塞到 direct tool list;它会提供 tool_search,
让模型按 query 找到本轮真正需要的工具规格。
| 工具来源 | 进入模型前的处理 | 运行时保护 |
|---|---|---|
| 普通 MCP tool | 筛 model-visible,数量合适时直接进入 tool spec。 | 调用时仍走 McpHandler 和 MCP approval metadata。 |
| 大量 MCP tool | 作为 deferred tool 建索引,先通过 tool_search 返回匹配工具。 |
减少上下文噪音,调用路径没有绕开工具审批。 |
| app connector tool | 需要 connector 在本轮允许并启用,才会进入可见集合。 | tool metadata 里保留 connector 和 plugin 相关信息。 |
| dynamic tool | 从 TurnContext.dynamic_tools 追加到工具规划。 |
仍然变成标准 handler,由 ToolRouter 分发。 |
这一层的价值很实际:工具多起来以后,问题不只是“能不能调用”,还有“模型面前应该先出现多少”。 direct exposure 适合少量稳定工具;deferred exposure 适合大工具库;tool_search 像一个按需打开的目录。
六、subagents:需要另一条线程时,才开并行轨道
多 agent 的入口看上去也像工具:spawn_agent、send_message、
followup_task、wait_agent、list_agents 都是在工具规划里暴露的。
但它们执行后的结果不止是一段工具返回值;运行时会创建一条由 AgentControl 管理的子线程。
handle_spawn_agent 会解析 task_name、agent_type、
model、reasoning_effort、fork_turns 等参数。
之后它会从父 turn 构建子 agent 配置:复制必要的 runtime 字段,应用模型或 role 覆盖,
再把本轮权限 profile、sandbox、cwd、environment selection 等运行时边界带过去。
真正生成子线程时,AgentControl.spawn_agent_with_metadata 会使用
SessionSource::SubAgent 和 ThreadSpawn metadata。
如果选择 fork history,它会先读取父线程的历史,再按 fork_turns 决定继承全部、
最近 N 轮,或者不继承历史。子 agent 结束后,completion watcher 会把结果作为
InterAgentCommunication 或用户消息送回父线程。
subagent 的重点在于多一条受控线程。它有自己的 context、status、 history 和 completion notification;但它的权限与环境来自父 turn 的运行时配置, 不会变成脱离主线程监管的后台过程。
七、把四条入口接回同一条主线
到这里可以把第七篇合回前六篇的路线图:用户输入进入 turn;TurnContext 固定本轮快照;
skill 和 plugin guidance 先形成上下文注入;MCP、dynamic tools 和 collaboration tools
进入工具规划;工具执行、agent 活动和普通模型输出再继续变成事件,交给客户端投影。
| 你在使用时看到 | 源码里可以这样定位 | 接到前文哪一层 |
|---|---|---|
| “用 $article 写一篇” | 先看 collect_explicit_skill_mentions,再看 SkillInstructions 注入。 |
第二篇的上下文管理。 |
| “启用 Browser / GitHub plugin” | 先看 manifest 和 load outcome,再看它贡献的 skill roots、MCP、apps。 | 本篇的 plugin 包边界,第四篇的工具系统。 |
| “工具很多,先搜索工具” | 看 build_mcp_tool_exposure 和 ToolSearchHandler。 |
第四篇的 tool spec 与 tool router。 |
| “开一个 subagent 审稿” | 看 multi-agent handlers、AgentControl、SessionSource::SubAgent。 |
第三篇的事件流,第六篇的客户端投影。 |
这样看,Codex 的扩展机制并不是给 runtime 外面套一层万能插件系统。它更像给一次 turn 增加几种受控入口:能写进 prompt 的写进 prompt,能变成工具的变成工具, 需要独立上下文的开成子线程,所有结果继续回到事件和记录。
八、读完第七篇,回头看一次完整请求
如果把前七篇连起来,一次普通请求已经可以这样读:入口把用户意图变成 typed operation; session 建立 turn;context manager 整理模型能看到的材料;tool spec 和 tool router 决定模型能调用什么;permission 和 sandbox 管住副作用;event stream 把运行事实投给客户端; extensions 和 multi-agent 则回答“额外能力怎样加入这条主线”。
第八篇继续顺着 hooks 和生命周期走:用户 prompt 提交前后、工具调用前后、
turn 停止时,Codex 还允许哪些代码在边界处补充上下文或拦截副作用。到那一层,
plugin 里的 hooks 就会从 manifest 字段变成真正的执行路径。
参考源码
- openai/codex 固定源码快照
- TurnContext 字段:dynamic tools、extension data、turn skills、multi-agent runtime
- make_turn_context 构建本轮能力快照
- build_skills_and_plugins 组装 skill、plugin 和 extension 注入
- AvailableSkillsInstructions
- session prompt 中加入 available skills
- SkillsManager 根据配置计算 skill roots
- build_skill_injections 读取完整 SKILL.md
- collect_explicit_skill_mentions
- SkillInstructions contextual fragment
- PluginManifest paths:skills、MCP、apps、hooks
- LoadedPlugin 与 PluginLoadOutcome
- effective skill roots、MCP servers、apps、hooks 和 capability summaries
- AvailablePluginsInstructions
- build_plugin_injections
- app-server 注册 extension registry
- build_mcp_tool_exposure 与 deferred threshold
- ToolSearchHandler
- 工具规划:MCP resources、plugin install、collaboration tools、MCP handlers、dynamic tools
- MCP tool approval metadata 与 request metadata
- handle_spawn_agent 构建子 agent
- SpawnAgentArgs 与 fork_turns
- build_agent_spawn_config 与 runtime overrides
- AgentControl 控制面与 inter-agent communication
- subagent completion watcher 与 ThreadSpawn metadata