AI图鉴

AI图鉴首页 › AI 资讯 › Codex 中智能体的执行机制分析和对比

Codex 中智能体的执行机制分析和对比

来源:掘金 · 发布时间:10-25 · 由 AI图鉴每小时自动聚合

讲解Codex 执行智能体的机制,首先讲解了基础的ReAct的执行机制,然后讲解Codex V1版本、V2版本的实现机制以及与基础的ReAct的查询、V1与V2的差异等,并分别介绍了其采用的机制。

读者对象:了解大模型的调用和工具执行、ReAct等知识,但想近一步学习了解多智能体等更复杂实现机制的技术人员。

备注说明: 所分析的是Codex CLI 命令行执行方式下智能体代码的执行机制,Codex App应用的代码是没有开源的,但底层执行逻辑是一致的。代码位置 :github.com/openai/code… · Rust 工作区 codex-rs · commit 8c07694(2026-10-18 克隆) Codex 代码中智能体执行机制同时提供了两个版本,通过如下的参数进行控制,后续称为 V1、V2。 通过研究同一场景的实现机制的对比和版本的演进,能对自己的产品研发具有借鉴意义。

针对Codex中多智能体的使用/触发,可以看我写的前一篇文章 : 《从使用者角度看看Codex如何使用多智能体》

1. 基线:普通 ReAct 是怎么执行的

1.1 它要解决什么问题

给模型一个需要多步才能完成的任务——例如"把配置文件里的超时改成 30 秒"——模型不可能一步到位:它要先想清楚改哪个文件、再真的去改、看到结果后确认改成功了,才能回复用户。

ReAct 就是把这类过程标准化成一个闭环:想一步、做一步、看一步结果,再想下一步,直到模型认为可以给出最终答案。它是今天绝大多数智能体(Codex 也在其列)的底座。不理解这个闭环,就看不懂 V1/V2 到底"多"出了什么。

1.2 机制:一个闭环,四个动作

- 模型读上下文。 上下文是一个消息列表(messages),里面放着任务描述,以及此前每一轮的执行结果。

- 模型做一次推理。 输出两种可能之一:一个结构化的工具调用请求(用哪个工具、参数是什么),或者一条最终答案。

- 运行时(Runtime)执行工具。 若是工具调用,由外层运行时真正执行(读写文件、执行命令、查询数据),把返回结果作为一条新消息追加进上下文。

- 回到第 1 步。 模型基于更新后的上下文继续推理;若第 2 步输出的是最终答案,循环结束。

由此得到三个关键性质:

- 单线程:动作顺序执行,不存在并行的执行体;

- 单上下文:全程共享同一份记忆,无隔离、无分叉;

- 同步内联:工具结果直接回填同一上下文,调用方原地等待。

另有一个工程细节不容忽略:循环必须设最大步数保护。否则模型可能反复调用同一个工具,永远停不下来。

1.3 代码样例

下面是一份最小实现(教学示意,省略了错误处理与重试;call_model 为伪函数,真实项目里它是一次大模型 API 调用,返回结构化结果):

from typing import Any

# ---------- 工具层:由外层运行时真正执行 ----------

def edit_file(path: str, old: str, new: str) -> str:

"""把文件中一处 old 替换为 new。"""

return f"已替换 1 处:{path}"

TOOLS = {"edit_file": edit_file}

# ---------- ReAct 主循环 ----------

def react_loop(task: str, max_steps: int = 10) -> str:

# 上下文:一个消息列表,所有回合共享同一份

messages: list[dict[str, Any]] = [{"role": "user", "content": task}]

for _ in range(max_steps):

# 模型只负责“决定”:读上下文,输出思考 + 工具调用请求或最终答案

reply = call_model(messages, tools=TOOLS)

if reply.tool_calls:

# 把“思考 + 工具调用请求”写进上下文

messages.append(reply.to_message())

for call in reply.tool_calls:

# 真正执行工具的是运行时,不是模型

result = TOOLS[call.name](**call.args)

# 观察结果作为新消息追加,驱动下一轮推理

messages.append({

"role": "tool",

"tool_call_id": call.id,

"content": result,

})

continue # 回到循环顶部

# 没有工具调用:模型给出最终答案,循环结束

return reply.content

return "已达到最大步数,任务未完成"

请注意其中两行的分工:call_model 只产出"用哪个工具、参数是什么"的请求;TOOLScall.name 才真正执行工具。模型负责决定,运行时负责执行——这条分界线在 V1/V2 里依然成立,只是组织的形态变了。

1.4 一张图看懂普通的ReAct机制

普通 ReAct 的执行闭环:模型读上下文后二选一——输出工具调用请求(由运行时执行,观察结果写回上下文,进入下一轮),或直接给出最终答案(循环结束)。左侧是随回合增长的消息列表,底部是单线程、单上下文、同步内联三个关键性质。

1.5 再进一步:普通 subagent 长什么样

许多框架在 ReAct 之上加了"子智能体":父在某一轮决定"这块活交给一个专职的子去干",于是同步调用一个子——父原地等待,子自己跑完一整个 ReAct 循环,把最终答案作为这次调用的返回值交还父;父把答案内联回填进自己的上下文,继续下一轮。子用完即弃:没有名字、没有历史、不留记录。

# 普通 subagent:同步内联,跑完即弃

def run_subagent(task: str) -> str:

sub_messages = [{"role": "user", "content": task}] # 子只拿到任务描述

return react_loop_with(sub_messages) # 子自己跑完整个循环

# 父智能体中:调用即阻塞,拿到最终答案才继续

answer = run_subagent("把 config.py 的超时改成 30 秒")

messages.append({"role": "tool", "content": answer}) # 结果内联回填父上下文

请记住这个"同步内联"的形态。Codex V1 的 spawn_agent 看起来在做同样的事,底层却是另一套路——下一章开始对比。

2. 三种执行模式对比

2.1 三者定位

- 普通 ReAct:一个智能体、一条循环、单线程单上下文,全程同步。

- Codex V1:父派生独立线程的子智能体,父子各自跑循环,子完成后异步通知父。

- Codex V2:核心与 V1 相同,额外支持子智能体的驻留、换出与重载,并挂了一个树级共享信箱。

V1 与 V2 的主要差异在驻留层、完成信号和通信设施,不在进程边界。

2.2 共同底座

三者的内核都是 ReAct:都是"模型决定下一步、运行时执行、结果喂回模型"这个循环;真正的执行者都是外层运行时,模型只输出结构化的工具调用请求。区别只在于这个循环有几个、怎么组织。

2.3 对比总览

三种执行模式对比。该图表达普通 ReAct 的"单循环、同步内联",V1 的"异步派生 + 独立线程 + 单向状态通知",V2 的"异步派生 + 驻留换出 + 信箱寻址"三者在执行结构上的差异。

为便于窄屏阅读,拆成两段:表 A 看 V1 相对普通 ReAct 改了什么,表 B 看 V2 相对 V1 又补了什么。

表 A:普通 ReAct ↔ Codex V1(跨越点:并行 + 异步)

表 B:Codex V1 ↔ Codex V2(跨越点:驻留 + 信箱)

一个最易混淆的点:普通 ReAct 也常支持"一个回合里并行调多个工具",就是一次大模型调用可以返回多个要执行的工具信息,应用程序运行时可以并行执行这些工具,但这和 Codex 的并行不是一回事——前者是同一条循环、同一个上下文里的并行步骤,结果仍全部回填同一处;后者是多个独立执行体、多个独立上下文的并行,各自有各自的循环和记忆,靠异步消息或通知汇合。区分点就一句:并行的是"步骤"还是"执行体"。

2.4 两次跃迁的直白解释

把三种模式排成一条线,从普通 ReAct 到 V2 是两次递进。

第一次跃迁(普通 ReAct → V1):从"一个循环"到"多个并行循环",从"同步"到"异步"。 普通 ReAct 是"一个人从头干到尾",所有事在一条上下文、一条线程里顺序完成。V1 允许父在干活的同时派出好几个子Agent并行干别的,父子各有各的上下文和线程。代价是控制流变了:父派完不傻等,而是先返回一个 id 继续干自己的活,子Agent干完了再回头异步通知父。V1 换来并行能力,付出的代价是结果不再内联回填、要异步收。

第二次跃迁(V1 → V2):从"静态常驻、单向协作"到"可换出重载、可寻址、可唤醒"。 V1 的子Agent跑完后线程并不销毁,但也没有"请出内存"一说——不显式关闭就一直占着线程,并发一多容易达到系统的线程控制上限。父若想再让它干活,得自己主动调 send_input 给它开一个新回合(被关闭过的先 resume_agent 拉起来);拿结果则靠 wait 或读子线程。总之协作全靠父单向发起:完成通知只带"状态"不带内容,兄弟之间无法直接说话。V2 补了三块:换出与重载(空闲的子Agent释放,需要时唤醒续跑,资源不再静态占用)、信箱(任意智能体可按标识互相发消息,支持"先排队、对方上线再投递"和"发信即唤醒对方开工"两种模式)、更干净的上下文继承(默认子只拿指令快照,不再把父整段历史复制过去)。

3. V1 相对"普通 subagent"的差异

第 1.5 节的同步 subagent 是最容易和 V1 混淆的对象。相同点是:都由父模型在一次决策里触发、子都跑自己的推理循环、都能再往下派生子。但底层是两回事。

用代码对照更直观:

# 普通 subagent:父阻塞,结果内联回填

answer = run_subagent(task) # 等子跑完才继续

messages.append({"role": "tool", "content": answer})

# Codex V1:异步派生,父不阻塞

agent_id = spawn_agent(task) # 立即返回子的 ID

# 父继续做别的事;子在独立线程里并行跑自己的 ReAct 循环

# 子跑完后,一条“仅状态”的通知被写进父的历史

一句话:普通 subagent 是"函数调用",V1 是"派生线程 + 异步消息"。 前者不并行(父在等),后者并行,代价是结果不再内联回填、要异步收。

4. V1 的异步完成通知:结果到底怎么回到父

这是 V1 最容易被误解的一处。父智能体并不是靠一次"同步返回值"拿到子智能体的结果的。

V1 异步完成通知流程。该图表达从父调用 spawn_agent、经独立监工等到子终态、再把"状态"写回父历史、最后由父自行取结果的八步过程;左侧三个标注点出"异步"具体落在哪里,右侧点出"通知不含实际输出"这一关键边界。

完整过程分八步:

- 父调用 spawn_agent。 运行时创建子线程后,spawn_agent 工具立刻返回子的 agent_id,父这一回合的工具调用就此结束,父不阻塞、继续做别的事。

- 运行时挂上监工。 派生时若不走 V2 驻留通道,运行时会起一个独立的监工后台任务,并把父子线程标识、父回合标识、子的引用名交给它。监工和父的回合解耦,只负责盯子的状态。

- 子并行运行。 子智能体在自己的独立线程里跑自己的 ReAct 循环,父子并行推进。

- 子到达终态。 监工持续观察子的状态,直到它进入终态(已完成、出错、关闭等都算;仍在运行或初始化中的不算)。

- 监工过滤误报。 如果终态是关闭、且整棵树正在关停,监工直接返回、不通知——这是"整树生命周期交接",不是子的执行结果,不能误报。

- 组装完成通知。 监工生成一条完成通知,内容只有两项:子的引用名和终态状态。

- 写入父历史。 监工把这条通知追加进父的对话历史——但只记录、不驱动新一轮推理。它渲染成一段带标记的结构化文本,角色相当于 user,让父在之后的某一轮里能读到。

- 父自取结果。 因为通知里没有子的实际输出,父要在后续某一轮读到这条通知后,再由模型决定下一步——通常是调等待工具阻塞等子并取其最终消息,或直接读子的线程历史。

"异步"具体落在三处:① spawn_agent 立即返回、父不阻塞;② 监工是独立后台任务、和父子并行;③ 通知只写进历史、不抢占父当前回合。三处都不占用父的执行流。

一个必须讲准的边界:由于通知是"只写历史、不驱动新回合",它并不会自动把父叫醒去开新一轮大模型调用;父何时处理这条通知,由上层(终端界面、应用服务后端、调用方)按各自策略决定。这正是 V2 要改进的地方——V2 的完成消息带实际内容、可寻址、还能触发新回合(见第 6 章)。

5. V1 的短板与 V2 的改进

把 V1 放到真实的大规模、长期协作场景里,会暴露三个软肋;V2 的增强基本是一一对症。

短板一:子智能体只能常驻,不能换出。 V1 每个子派生后就是一条常驻线程,没有"先请出内存、腾地方"的机制。并发一多容易超过线程配额上限,要么新派生的子被拒,要么内存和线程被占满。V2 引入驻留层 + LRU 换出:空闲子智能体按策略换出(保留状态,不是杀死),需要时由发信或派活重载唤醒,续上之前的进度。于是一棵很大的智能体树也能在固定槽位里运转。

短板二:上下文继承太粗。 V1 的继承开关配置只有"全给或不给"上下文:选全给就把父的整段历史复制给子——既浪费上下文窗口,也把父的全部中间过程泄露给子。V2 把它做成可调的 fork_turns(不继承 / 全继承 / 继承最近 N 条),默认只给一份指令快照,上下文干净、省窗口空间占用、不泄露上下文;需要时才全继承或继承最近 N 条。

短板三:协作单向,且没有信箱。 V1 的完成通知只带"谁 + 状态",没有内容、不可寻址、父子不可双向通信;父事后想再叫子Agent干活,只能自己主动调 send_input(被关闭过的先 resume_agent),兄弟之间想互通消息则完全没有渠道——总之没有标准信箱。V2 在树级挂了共享信箱:任意智能体可按目标寻址发消息,支持"先排队、对方上线再投递"和"发信即唤醒对方开工"两种模式;完成消息也从"空通知"升级成带实际内容的消息。

6. 两代实现差异总表

演进的主线:是"第一代进程内多线程 → 加上资源弹性与真正的多智能体通信"——把常驻变成可换出/重载,把单向一次性的状态通知变成可寻址、带内容、可唤醒的信箱,把粗粒度上下文继承变精细,把线程标识寻址变成规范路径寻址。这也是许多智能体框架从"能跑"走向"可治理"时都要补的一课。

7. 小结

一条主线收束全篇:

- 普通 ReAct 是基线:单循环、单线程、单上下文、同步内联——模型决定,运行时执行。

- Codex V1 完成第一次跃迁:把"派活"从同步函数调用变成异步派生——子有独立线程、独立上下文、持久身份和可恢复的历史;代价是结果异步回流、只带状态、不带内容。

- Codex V2 完成第二次跃迁:在异步派生之上补资源弹性(驻留/换出/重载)与真正的多智能体通信(树级信箱、可寻址、可唤醒、可带内容),并把上下文继承从"全给或不给"细化到按需裁剪。

分水岭始终是那一句:并行的是"步骤"(普通 ReAct)还是"执行体"(V1 / V2)。

本文由 AI图鉴自动抓取并翻译,版权归原作者所有;如需查看原文请点下方链接。

相关 AI 工具

相关免费羊毛

同源资讯

阅读原文(juejin.cn)

标题、摘要与正文均来自该信源的公开页面,版权归原作者所有;本站自动聚合与翻译,仅作出处跳转。

← 返回 AI 资讯列表AI工具图鉴首页

返回 AI图鉴首页 · 提交工具 · 商务合作 · 关于本站 · 591 款 AI 工具分类导航 · 每日 AI 热门项目与动态