ARTICLE · 1200959
Codex 源码图解:一条命令在 Agent 里走的 12 步
在 Codex 里输入一句:“帮我运行项目测试”, 按下回车后,Codex 会读取项目、调用工具、运行命令,再根据执行结果告诉测试是否通过。
这中间到底发生了什么?
以下沿着一次 shell 工具调用,把过程拆成 12 步:每一步都配一张流程图。
本文依据Codex源码 2026-10-01 版本整理。示例沿着“工具执行成功、无需额外审批”的路径展开。
第 1 步:输入抵达

用户可以从交互式终端 TUI、非交互式 exec,或者 SDK 提交请求。入口负责接收输入、处理交互,再把任务交给执行内核。
这里值得借鉴的是前端与执行逻辑的分工。CLI 负责命令分发,TUI 负责终端交互,exec 适合脚本和自动化;它们复用核心 Agent 能力。TypeScript SDK 则通过启动 exec 子进程、读取 JSONL 事件流来接入。
因此,增加一种界面不必重新实现整套 Agent 循环。
第 2 步:开始一个 Turn

Session 收到输入后,开始这一次执行,就是一个 Turn。本次使用的模型、环境、权限等信息由 TurnContext 承载,取消状态也有对应的运行时管理。
理解 Codex,需要先分清两个概念:Session 保存长期会话状态,Turn 表示这一次任务执行。 一个 Turn 可以包含多次模型调用和工具调用。
输入、中断、审批结果等操作,通过提交队列 SQ 进入运行时,由 submission_loop 按顺序分发;执行过程再通过事件队列 EQ 向客户端报告。这样,用户交互与后台执行可以异步配合。
第 3 步:构造本次上下文

交给模型的输入,除了用户刚刚说的那句话,还包括历史消息、项目指令、环境、权限、Skills,以及可用工具的描述。
例如,AGENTS.md 可以告诉 Codex 项目采用什么测试命令、有哪些代码约定。工具 schema 则告诉模型:当前有哪些工具,以及应该怎样传参。
这些信息共同构成模型判断下一步动作的依据。上下文接近容量阈值时,运行时还可以通过压缩历史,为后续工作腾出空间。
第 4 步:调用模型

上下文准备好后,Model Client 发起一次采样请求,接收模型的流式响应。
这里有两层生命周期:ModelClient 跟随 Session,保存认证、模型提供方等相对稳定的状态;ModelClientSession 按 Turn 创建,服务于这一轮里的多次模型请求。
同一个 Turn 中,可以复用按需建立的 WebSocket 连接,减少重复建连的开销。模型输出随后交给运行时处理,其中可能包含普通文字,也可能包含工具调用。
第 5 步:发现工具调用

Agent Loop 持续解析模型的流式输出。当一个工具调用条目完成时,运行时会记录调用,并把执行任务放入 in_flight,同时标记需要后续采样。
这里有个容易忽略的细节:模型还在继续输出时,工具可能已经开始执行。
handle_output_item_done 按完成的输出条目推进处理,无需等整段响应全部结束。因此,模型生成和工具执行在时间上可以重叠。
而 needs_follow_up 这个标记,则把“工具跑完之后还要继续问模型”的意图保留下来。
第 6 步:路由到工具

发现工具调用后,Tool Router 根据工具名称和参数,找到对应的 Handler 或 Runtime。
当前有哪些工具可用,会受到模型、权限和环境等条件影响。内建工具、MCP 工具、动态工具和扩展工具汇入注册表,再形成模型可见的工具描述及执行映射。
并发控制也在这一层附近展开:支持并行的工具可以取得读锁同时运行,不支持并行的工具取得写锁,排队执行。
这让工具可以扩展,也能明确约束哪些工作适合一起跑。
第 7 步:检查执行策略

对 shell 这类受管工具,找到执行器之后,还要判断权限、是否需要审批,以及使用什么沙箱。
ToolOrchestrator 集中协调这条执行路径:审批、选择沙箱、尝试执行,以及必要时的重试。
需要注意,重试仍然受策略约束。一次操作被沙箱拒绝,并不意味着运行时可以自行放开限制;只有满足相应条件,才会采用其他执行策略。
审批和沙箱也承担不同职责:审批解决“这次动作是否获得允许”,沙箱约束“执行时能够访问什么”。
第 8 步:执行并收集结果

本例无需额外审批,工具开始在选定环境中执行。命令的输出随后会被整理成运行时可以记录、回传的工具结果。
Unified Exec 主要管理进程、PTY、进程复用和有上限的输出缓冲。审批与沙箱策略交给前面的协调层处理,各层职责比较清楚。
另外,工具的执行位置并不相同。本地 shell 和补丁操作受到相应本地执行边界约束;MCP 工具则交给对应的 MCP 服务执行,不能简单套用同一套沙箱理解。
第 9 步:回填工具输出

命令运行完成后,工具结果被写入会话历史,进入后续模型上下文。
如果有多个工具并行执行,完成顺序可能不同。例如,模型先调用 A,再调用 B,但 B 先返回。
Codex 通过 FuturesOrdered 和 drain_in_flight 等逻辑,按照调用顺序收集、回填结果。这样,工具执行可以并行,模型看到的历史仍保持稳定顺序。
这对排查问题很有价值:回看一次执行时,能够沿着调用与结果之间的对应关系继续追踪。
第 10 步:再次采样

工具结果回填后,Model Client 再次调用模型。这一次,模型可以根据测试输出、错误日志或其他工具结果,决定下一步。
如果测试失败,模型可能继续读取相关文件、修改代码,再运行测试;如果已有足够信息,就可以组织最终回答。
一个 Turn 是一个执行循环,可以包含多次采样。 工具调用、执行、回填、再次采样,会根据任务需要反复发生。上下文接近阈值时,也可能先压缩,再继续工作。
第 11 步:解析最终消息

这次采样中,模型没有再请求工具,Agent Loop 读出答复,并判断当前 Turn 是否可以结束。
结束判断需要结合多种状态:是否还有待执行的工具工作,模型是否要求继续,是否有排队输入,以及 Stop hooks 是否要求继续处理。
因此,“模型输出了一段文字”与“整个任务已经完成”之间,还需要运行时做一次收束判断。
做 Agent 时,这部分逻辑值得单独设计。否则容易出现回答已经返回,后台工作却还没有真正处理完的情况。
第 12 步:完成当前 Turn

满足结束条件后,运行时发布完成事件,客户端展示最终结果,Session 等待下一条输入。
完成事件承载最终消息,以及起止时间、耗时、首个 token 延迟、错误详情等可观测性信息。前端展示进度、定位失败和分析执行效率,都需要这些数据支持。
这里再把生命周期连起来:结束一个 Turn,不等于关闭 Session。 会话历史仍然保留,后续还可以继续对话,或者用于恢复、分叉和调试。
总结:自己做 Agent,我认为值得借鉴的四点工程设计:
第一,把操作与事件协议先定下来。 用户输入、中断和审批怎样进入运行时,执行进度和结果怎样回到客户端,这些边界清楚了,后面增加界面和接入方式才容易。
第二,把一次任务建模成循环。 模型调用、工具执行、结果回填之间需要有明确的继续条件和结束条件,才能支撑多步任务。
第三,集中管理受管工具的执行策略。 审批、沙箱和重试放在统一路径中协调,可以减少各个工具重复实现判断的情况。
第四,允许并行执行,同时保持历史有序。 并发提升处理效率,稳定的回填顺序则帮助恢复、重放和调试。
PS. 这 12 步展示的是一条成功执行的简化路径。实际使用中,还会遇到审批暂停、工具失败、用户中断和上下文压缩。理解了主路径,再去看这些分支,代码就更容易读下去了。















































































