Agent Loop 是 Codex CLI 中的核心交互逻辑,负责协调用户、模型以及工具之间的交互,Codex 将其称为「harness」。
Agent Loop
每个 AI Agent 的核心都有一个被称为 Agent Loop 的机制。Agent Loop 的简化示意图如下所示:
首先,Agent 从用户那里获取输入,将其纳入为模型准备的文本指令集合中,这被称为提示词(prompt)。
下一步是向模型发送指令并请求其生成响应来查询它,这个过程称为推理(inference)。在推理过程中,文本提示首先被转化为一系列输入令牌(input tokens)——即用于索引模型词汇表的整数。这些令牌随后被用于对模型进行采样,从而生成新的输出令牌(output tokens)。
输出令牌(output tokens)被转换回文本,成为模型的响应(response)。由于令牌是逐步生成的,这种转换可以在模型运行时同步进行,这也是许多基于 LLM 的应用程序能显示流式输出的原因。实际上,推理过程通常被封装在一个基于文本操作的 API 之后,从而抽象了令牌化的具体细节。
推理步骤的结果有两种情况:
- 生成对用户原始输入的最终回复
- 请求执行一次工具调用(例如,“运行
ls并报告输出结果”)
在情况 2 中,Agent 会执行该工具调用,并将输出结果追加到原始提示中。此输出用于生成一个新的输入,进而重新查询模型;Agent 随后可考虑这一新信息并再次尝试。
这个过程会重复进行,直到模型停止生成工具调用,转而生成一条面向用户的消息(在 OpenAI 的模型中称之为“助手消息”(assistant message))。
在许多情况下,这条消息会直接回应用户的原始请求,但也可能是向用户提出的后续问题。
由于 Agent 能够执行修改本地环境的工具调用,其“输出”并不限于助手消息。在许多情况下,Agent 的主要输出是在机器上编写或编辑的代码。
尽管如此,每个轮次总是以一条助手消息(例如“我已添加了您请求的 architecture.md ”)结束,这标志着 Agent Loop 的终止状态。从 Agent 的角度来看,其工作已完成,控制权将返回给用户。
从用户输入到 Agent 响应的过程,如示意图所示,称为对话的一个轮次(在 Codex 中称为一个线程)。
这个对话轮次可能涉及模型推断与工具调用之间的多次迭代。每当您向现有对话发送新消息时,对话历史会作为新轮次提示的一部分被包含,其中包括先前轮次的消息和工具调用。

这意味着随着对话的增多,用于模型采样的提示长度也会增加。这一长度至关重要,因为每个模型都有一个上下文窗口(context window),即一次推理调用所能使用的最大令牌数。需要注意的是,这个窗口既包括输入令牌也包括输出令牌。可以想象为,一个 Agent 可能决定在一轮对话中调用数百个工具,从而耗尽上下文窗口。因此,管理上下文窗口是 Agent 的众多职责之一。
接下来,开始深入了解 Codex 是如何运行 Agent Loop 的。
模型推理
Codex CLI 通过向 Responses API 发送 HTTP 请求来运行模型推理(Model inference),它能与任何实现 Responses API 的端点协同工作:
- 使用 ChatGPT 登录
- 使用 API 密钥
- 本地 oss 模型
- 云端提供商(如 Azure)
构建初始化提示
作为终端用户,在使用 Responses API 时无需一字不差地指定用于采样模型的提示词。相反,可以将各种输入类型指定为查询的一部分,而 Responses API 服务器则负责决定如何将这些信息组织成模型设计所适用的提示。
可以视提示词为一个“项目列表”,本节将解释查询如何被转化为这个列表。
在初始化提示中,列表中的每一项都与一个角色(role)相关联。角色指明了关联内容的重要程度,其值按优先级递减顺序如下:系统、开发者、用户、助手。
Responses API 接收一个包含众多参数的 JSON 载荷。重点关注以下三个参数:
instructions: 插入模型上下文中的系统(或开发者)消息tools:模型生成响应时可调用的一系列工具列表input:模型接收的文本、图像或文件输入列表
instructions
在 Codex 中, instructions 字段若指定则从 ~/.codex/config.toml 中的 model_instructions_file 读取;否则就使用与模型关联的 base_instructions 。base_instructions 存放于 Codex 代码库中,并已打包进了 CLI(如 gpt-5.2-codex_prompt.md )中。
tools
tools 字段是符合 Responses API 定义架构的工具定义列表。对于 Codex 而言,这包括由 Codex CLI 提供的工具、由 Responses API 提供并供 Codex 使用的工具,以及通过 MCP 服务器由用户提供的工具:
|
|
input
最后的 input 字段是一个项目列表。在添加用户消息之前,会先将以下项目插入到 input 中:
-
一条包含
role=developer的消息,描述仅适用于 Codex 提供的在tools部分定义的shell工具的沙箱环境。也就是说,其他工具(例如来自 MCP 服务器提供的工具)不由 Codex 进行沙箱处理,需要自行实施防护措施。该信息是基于一个模板构建的,其中的关键内容片段来自捆绑到 Codex CLI 中的 Markdown 片段,例如
workspace_write.md 和on_request.md:<permissions instructions> - description of the sandbox explaining file permissions and network access - instructions for when to ask the user for permissions to run a shell command - list of folders writable by Codex, if any </permissions instructions> -
(可选)一条包含
role=developer的消息,其内容是从用户config.toml文件中读取的developer_instructions值。 -
(可选)一条包含
role=user的消息,其内容为“用户指令”,这些指令并非源自单一文件,而是整合自多个来源。通常,更具体的指令会出现在后面:AGENTS.override.md、AGENTS.md和$CODEX_HOME中的内容- 在默认限制(32 KiB)下,从项目的
cwd根目录(如果存在)开始,逐级向上查找至cwd所在目录:添加其中任何AGENTS.override.md、AGENTS.md的内容,或由project_doc_fallback_filenames in config.toml指定的任何文件名 - 如果配置有技能(Skill):
- 一份关于技能的简短说明
- 每个技能的元数据
- 关于如何使用技能的部分
-
一条包含
role=user的消息,用于描述当前运行 Agent 的本地环境。它指定了当前工作目录和用户使用的 shell:<environment_context> <cwd>/Users/mbolin/code/codex5</cwd> <shell>zsh</shell> </environment_context>
在 Codex 完成上述所有计算以初始化 input 后,会附加用户消息以启动对话。
之前的例子侧重于每条消息的内容,但请注意, input 的每个元素都是一个 JSON 对象,包含 type 、 role 和 content ,如下所示:
|
|
一旦 Codex 构建好要发送给 Responses API 的完整 JSON 载荷,它就会根据 Responses API 端点在 ~/.codex/config.toml 中的配置情况,使用对应的 Authorization 头部信息发起 HTTP POST 请求(如果指定了额外的 HTTP 头部和查询参数,也会一并添加)。
当 OpenAI Responses API 服务器收到请求时,它会使用 JSON 数据来推导出模型的提示,如下所示(请注意,自定义实现的 Responses API 可能会做出不同的选择):

可以看到,提示中前三个项目的顺序是由服务器决定的,而非客户端。但是在这三个项目中,只有系统消息的内容由服务器控制,因为 tools 和 instructions 是由客户端决定的。接着是来自 JSON 的有效载荷 input ,最终完成提示。
现在已经有了提示,准备对模型进行采样。
第一轮
这个向 Responses API 发出的 HTTP 请求启动了 Codex 中的第一轮“对话”。服务器会以 Server-Sent Events(SSE)流作为回复。
每个事件的 data 都是一个 JSON 负载,包含一个 "type" ,以 "response" 开始,类似于这样:
data: {"type":"response.reasoning_summary_text.delta","delta":"ah ", ...}
data: {"type":"response.reasoning_summary_text.delta","delta":"ha!", ...}
data: {"type":"response.reasoning_summary_text.done", "item_id":...}
data: {"type":"response.output_item.added", "item":{...}}
data: {"type":"response.output_text.delta", "delta":"forty-", ...}
data: {"type":"response.output_text.delta", "delta":"two!", ...}
data: {"type":"response.completed","response":{...}}
Codex 接收事件流并将其作为内部事件对象重新发布,供客户端使用。
像 response.output_text.delta 这样的事件用于支持 UI 中的流式传输。
而像 response.output_item.added 这样的其他事件则被转换为对象并追加到 input 中,供后续的 Responses API 调用使用。
假设对 Responses API 的首次请求包含两个 response.output_item.done 事件:一个带有 type=reasoning ,另一个带有 type=function_call 。当使用工具调用的响应再次查询模型时,这些事件必须体现在 JSON 的 input 字段中:
|
|
用于采样模型作为后续查询一部分的提示词最终形式如下:

特别是要注意,旧提示词是新提示词的精确前缀。这是有意为之的,能充分利用提示缓存,从而大幅提高后续的请求效率。
回顾最初绘制的 Agent Loop 图,可以看到推理与工具调用之间可能存在多次迭代过程。提示信息会持续累积,直到最终接收到助手消息——这标志着当前回合的结束。
data: {"type":"response.output_text.done","text": "I added a diagram to explain...", ...}
data: {"type":"response.completed","response":{...}}
Codex CLI 会向用户展示助手消息并聚焦到输入框,向用户示意轮到他们"继续对话"。如果用户作出回应,前一轮的助手消息和用户的新消息都必须追加到 Responses API 请求的 input 中,以开启新一轮对话:
|
|
再次强调,由于是在延续对话,发送给 Responses API 的 input 长度会持续增加:

接下来探讨一下这个不断增长的提示对性能意味着什么。
性能考量
你可能会问:"等等,在整个对话过程中发送给 Responses API 的 JSON 数据量,难道不是按照 Agent Loop 的平方来增长的吗?"
完全没错。虽然 Responses API 确实支持可选的 previous_response_id 参数来缓解这个问题,但 Codex 目前并未使用它,主要是为了保持请求完全无状态,并支持零数据保留(ZDR)配置。
避免使用
previous_response_id简化了 Responses API 提供方的工作,因为它确保每个请求都是无状态的。这也使得支持选择零数据保留(ZDR)的客户变得简单,因为存储支持previous_response_id所需的数据与 ZDR 原则相悖。请注意,ZDR 客户不会牺牲从前几轮中获得专有推理信息的能力,因为相关的
encrypted_content可以在服务器端解密。OpenAI 会保留 ZDR 客户的解密密钥,但不会保留数据。
一般而言,模型采样成本远高于网络传输成本,因此采样环节是效率优化的重点目标。这正是提示缓存如此重要的原因——它能复用先前推理调用中的计算结果。当缓存命中时,模型采样复杂度会从二次方降至线性。
关于提示缓存的文档对此有更详细的说明:
缓存命中仅当提示内容出现完全匹配的前缀时才可能实现。为充分发挥缓存优势,请将静态内容(如指令说明和示例)置于提示开头,而将动态内容(如用户特定信息)放在末尾。这一原则同样适用于图像和工具调用——两次请求间必须保持完全相同的内容构成。
哪些类型的操作可能导致 Codex 中发生“缓存未命中的情况”?
- 在对话中途改变模型可用的
tools - 更改作为 Responses API 请求目标的
model - 更改沙盒配置、审批模式或当前工作目录
Codex 团队在引入可能影响提示缓存的新功能时必须格外谨慎。例如,最初对 MCP 工具的支持引入了一个错误,即未能以一致的顺序枚举工具,从而导致缓存未命中。请注意,MCP 工具可能尤其棘手,因为 MCP 服务器可以通过 notifications/tools/list_changed 动态更改它们提供的工具列表。在长对话中途遵循此通知可能导致昂贵的缓存未命中。
在可能的情况下,通过向 input 追加一条新消息来反映配置变化,而不是修改之前的消息:
- 如果沙盒配置或审批模式发生变化,就插入一条格式与原
<permissions instructions>项相同的role=developer新消息 - 如果当前工作目录发生变化,就插入一条格式与原
<environment_context>相同的role=user新消息
Codex 为了确保性能缓存命中而不遗余力。但还有一个必须管理的关键资源:上下文窗口。
Codex 避免耗尽上下文窗口的一般策略是,一旦令牌数量超过某个阈值,就压缩对话内容。具体来说,Codex 将 input 替换为一个新的、更小的项目列表,这个列表代表了对话内容,使得 Agent 能够继续对话,同时了解迄今为止发生的情况。
早期的压缩实现需要用户手动调用 /compact 命令,该命令会利用现有对话加上用于摘要的自定义指令来查询 Responses API。Codex 使用生成的结果助手消息(包含摘要)作为后续对话回合的新 input 。
自那时起,Responses API 已演变为支持一个特殊的 /responses/compact 端点,该端点能更高效地执行压缩。它会返回一个列表,可用于替代先前的 input 以继续对话,同时释放上下文窗口的空间。此列表包含一个特殊的 type=compaction 物品,附带一个不透明的 encrypted_content 物品,用以保留模型对原始对话的潜在理解。现在,当 auto_compact_limit 超过限制时,Codex 会自动使用该端点来压缩对话。