1)所谓 Agent Loop:其实就是“反复提问、反复操作、反复补充”的流水线
Codex 这套 loop 的节奏非常规律: 1. **接收用户输入**:将用户的话语放入即将发给模型的 prompt(注意:真实 prompt 并非一段简单的字符串,而是一个包含“多条消息/多种 item”的列表)。 2. **模型推理(inference)**:将 prompt 发送给模型,由模型生成输出。 3. **分支判断**:模型的输出要么是 - 最终回复(assistant message):本轮对话结束; - 工具调用(tool call):例如让 agent 执行 `ls`、读取文件、运行测试等操作。 4. **执行工具并追加结果**:agent 执行工具,然后将工具的输出追加到 prompt 中,再请求模型进行下一轮推理。 5. **循环直至停止工具调用**:最终必须以 assistant message 收尾(哪怕主要产出是“本地代码变更”)。 在一轮(turn)中可能包含多次“推理↔工具”的迭代;而多轮(multi-turn)则会带上所有历史对话,导致 prompt 不断膨胀。 这也解释了 Agent 工程化中最常见的两大难点: - **性能问题**:请求体越来越大,推理成本越来越高,缓存经常失效。 - **上下文窗口(context window)不足**:尤其在单轮中工具调用次数较多时更加明显。2)Codex 如何“组装 prompt”:并非你以为的一段文本,而是一组分角色的 item
Codex CLI 使用的是 Responses API,而并非让用户直接手动编写 prompt。用户提交的 JSON 中最重要的三个部分是: - `instructions`:系统/开发者指令(Codex 既支持用户自定义配置,也有模型内置的 base instructions) - `tools`:可调用的工具定义列表(Codex 内置 shell、plan 等功能,也可以接入 MCP 工具,甚至使用 web_search) - `input`:由多个 item 组成的数组(消息、文件、图片、推理结果、工具调用/输出等都包含在内) Codex 会先在 `input` 中插入一系列“铺垫项”,然后再追加用户的真实提问。典型的插入顺序包括: - developer 消息:权限/沙箱说明(仅约束 Codex 自带的 shell 工具) - developer 消息:用户自定义 developer_instructions(可选) - user 消息:用户指令聚合(可选)(例如 AGENTS.md/AGENTS.override.md、skills 等) - user 消息:环境上下文(cwd、shell 等) 示例(权限/沙箱说明): ```(此处应插入第一张图片:展示 prompt 组装过程的快照图)
3)流式推理 + 工具回填:SSE 事件才是“真实对话记录”
Codex 发起一次推理时,Responses API 会通过 SSE(Server-Sent Events)流式返回事件;这些事件不仅用于 UI 实时展示,还会被 Codex 转换为内部对象,并追加到 `input` 中,供下一轮推理继续使用。 示例(SSE 事件流片段): ``` 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":{...}} ``` 如果模型输出了 `function_call`,Codex 执行工具后会将推理摘要、函数调用、函数输出一同追加到下一次请求的 `input` 中,示例如下: ``` [ /* ... original items ... */ {"type": "reasoning", "summary": [{"type": "summary_text", "text": "**Adding an architecture diagram for README.md**\n\nI need to..."}], "encrypted_content": "gAAAAABpaDWNMxMeLw..."}, {"type": "function_call", "name": "shell", "arguments": "{\"command\":\"cat README.md\",\"workdir\":\"/Users/mbolin/code/codex5\"}", "call_id": "call_8675309..."}, {"type": "function_call_output", "call_id": "call_8675309...", "output": "npm i -g @openai/codex..."}
]
```
对应的第二张快照图:
(此处应插入第二张图片:展示工具调用和回填过程的快照图)
当模型最终输出 assistant message(不再请求工具)时,这一轮才算结束: ``` data: {"type":"response.output_text.done","text":"I added a diagram to explain...", ...} data: {"type":"response.completed","response":{...}} ``` 用户再发送一条消息,就进入下一轮:需要将上一次的 assistant message 和本次的 user message 一起追加进去: ``` [ /* ... all items from the last request ... */ {"type": "message", "role": "assistant", "content": [{ "type": "output_text", "text": "I added a diagram to explain the client/server architecture." }]}, {"type": "message", "role": "user", "content": [{ "type": "input_text", "text": "That's not bad, but the diagram is missing the bike shed." }]} ] ``` 第三张快照图更能说明问题:这个结构会一直增长……(此处应插入第三张图片:展示多回合上下文不断增长的快照图)
