将模型转化为可靠的软件代理,关键在于其代理循环(Agent Loop)的设计。Codex CLI 作为 OpenAI 推出的跨平台本地软件代理,其核心职责是协调用户、模型与工具之间的交互。这个被称为 "harness" 的编排层,正是确保 Codex 能够可靠执行真实软件变更的基础设施。本文将从工程实践角度,解析 Codex 代理循环的内部工作机制,重点关注上下文管理与性能优化的关键参数。
代理循环的核心架构
从宏观视角看,代理循环遵循一个简洁而重复的模式:首先,代理接收用户输入并将其整合到发送给模型的提示词中;随后,通过推理(Inference)阶段调用模型生成响应;模型此时有两种可能的输出 —— 直接回复用户,或请求执行工具调用。若为后者,代理执行工具调用并将结果追加到提示中,形成新的输入再次查询模型。此过程持续进行,直至模型输出助手消息(Assistant Message),标志着一轮对话的结束。
这一循环机制的本质,是将线性的人机交互转化为可迭代的自主执行流程。值得注意的是,由于代理执行的工具调用可能修改本地环境,其 "输出" 并不局限于助手消息 —— 在实际场景中,代理编写的代码或进行的文件编辑往往才是主要产出。然而,每一轮对话必定以助手消息作为终止信号,控制权由此返回给用户。从工程实现角度,Codex 将这一循环封装为状态机,追踪当前所处阶段并管理状态转换。
提示构建与角色优先级
Codex 使用 OpenAI 的 Responses API 执行模型推理。在构建初始提示时,系统将不同类型的输入组织为具有层级优先级的消息序列,角色权重按以下顺序递减:system > developer > user > assistant。system 消息由服务器控制,包含模型的基础行为指导;developer 消息承载开发者配置的系统级指令;user 消息包含最终用户的实际输入。
具体到 Codex 的实现,初始提示的构建涉及多个数据源的聚合。系统首先插入一条 role=developer 的消息,描述沙箱环境配置(仅适用于 Codex 提供的 shell 工具,其他工具如来自 MCP 服务器的工具不受 Codex 沙箱约束)。随后,根据用户配置,可能追加额外的 developer 消息包含 developer_instructions 配置项。接下来,系统从多个来源聚合 user instructions:首先是 $CODEX_HOME 目录下的 AGENTS.override.md 和 AGENTS.md 文件;然后从 Git 项目根目录向当前工作目录遍历,依次检查各层级的配置文件;最后,若用户配置了 skills(技能),相关内容也会被整合。
在将提示发送给 Responses API 时,Codex 构造包含 instructions(system/developer 消息)、tools(工具定义列表)、input(用户输入与对话历史)的 JSON payload。服务器端接收到请求后,将这些字段映射为模型可消费的提示结构。值得注意的是,提示中前三项的顺序由服务器决定,而非客户端控制,但 system 消息内容由服务器生成,而 tools 和 instructions 则由客户端指定。
状态管理与上下文窗口挑战
随着对话轮次的推进,input 字段持续膨胀。每次用户发送新消息时,对话历史(包括过往的消息与工具调用)都会被包含在新一轮的提示中。这意味着对话越长,提示越长,而每个模型都有其上下文窗口限制 —— 单次推理调用可使用的最大 token 数量(输入与输出 token 均计入此限额)。在极端情况下,代理可能在单轮对话中发起数百次工具调用,迅速耗尽上下文窗口。因此,上下文窗口管理成为代理系统的核心工程挑战之一。
一个关键的设计权衡在于 Responses API 支持的 previous_response_id 参数。理论上,此参数允许服务器引用先前对话状态,避免每次请求重复发送完整历史,从而将网络传输与服务器端处理优化为常量开销。然而,Codex 当前选择不使用此参数,主要原因有二:其一是保持请求完全无状态(stateless),简化服务端实现;其二是支持零数据保留(Zero Data Retention, ZDR)配置 ——ZDR 用户的数据不会在服务器端持久化,而使用 previous_response_id 需要服务器存储历史状态,这与 ZDR 的设计理念相悖。
为在无状态约束下维持性能,Codex 依赖提示缓存(Prompt Caching)机制。当后续请求的提示与先前请求存在完全相同的前缀时,服务器可复用先前推理的中间计算结果,使模型采样从二次方复杂度降为线性复杂度。提示缓存在 Codex 的性能优化中占据核心地位,因为模型采样成本远高于网络传输成本。
缓存策略与失效边界
提示缓存生效的前提是精确的前缀匹配。为最大化缓存命中率,Codex 在工程实践中遵循严格的策略:静态内容(如系统指令、工具定义、示例)置于提示开头,动态内容(如用户特定信息)置于末尾。导致缓存失效的操作包括:在对话中途更改可用工具列表、切换目标模型(模型特定指令会改变提示结构)、修改沙箱配置、审批模式或当前工作目录。
一个典型案例是 MCP(Model Context Protocol)工具集成的初期实现。由于工具枚举顺序不一致,Codex 曾触发大量意外缓存失效。在 MCP 协议中,服务器甚至可能通过 notifications/tools/list_changed 通知动态变更工具列表 —— 在长对话中响应此通知会导致昂贵的缓存失效。修复方案是确保工具定义列表的稳定序列化顺序,并在配置变更时采用追加而非修改策略:若沙箱配置或审批模式变更,系统插入新的 role=developer 消息而非修改原有消息;若工作目录变更,则插入新的 role=user 消息。通过这种增量式变更,保留原有前缀以维持缓存连续性。
对话压缩机制
即便优化缓存策略,上下文窗口的物理限制仍无法回避。当对话 token 数量超过阈值时,Codex 会触发压缩(Compaction)机制:用代表性内容替换原有对话历史,在释放上下文空间的同时保留代理对对话进展的理解。
早期实现中,用户需手动调用 /compact 命令,系统调用 Responses API 并携带专门的摘要指令来压缩对话,将结果作为新的 input。此后,Responses API 引入了专用的 /responses/compact 端点,可更高效地执行压缩并返回包含加密内容(encrypted_content)的 compaction item,保留模型对原始对话的潜在理解。Codex 现已实现自动压缩:当 token 数量超过 auto_compact_limit 配置阈值时,系统自动调用压缩端点。压缩后的对话状态可继续参与后续推理,无需用户手动干预。
从工程视角审视,Codex 代理循环的设计体现了几个核心权衡:无状态请求与状态复用之间的取舍、缓存命中率与功能灵活性之间的平衡、手动控制与自动化管理之间的边界。这些决策并非孤立的技术选择,而是服务于可靠性、性能与安全性的系统性工程目标。
资料来源:本文核心架构细节参考 OpenAI 官方技术博客《Unrolling the Codex agent loop》(2026 年 1 月 23 日)。