Salta al contingut principal
OpenAI

23 de gener del 2026

Enginyeria

Unrolling the Codex agent loop

By Michael Bolin, Member of the Technical Staff

S'està carregant…

Codex CLI(s'obre en una finestra nova) is our cross-platform local software agent, designed to produce high-quality, reliable software changes while operating safely and efficiently on your machine. We’ve learned a tremendous amount about how to build a world-class software agent since we first launched the CLI in April. To unpack those insights, this is the first post in an ongoing series where we’ll explore various aspects of how Codex works, as well as hard-earned lessons. (For an even more granular view on how the Codex CLI is built, check out our open source repository at https://github.com/openai/codex(s'obre en una finestra nova). Many of the finer details of our design decisions are memorialized in GitHub issues and pull requests if you’d like to learn more.)

To kick off, we’ll focus on the agent loop, which is the core logic in Codex CLI that is responsible for orchestrating the interaction between the user, the model, and the tools the model invokes to perform meaningful software work. We hope this post gives you a good view into the role our agent (or “harness”) plays in making use of an LLM.

Before we dive in, a quick note on terminology: at OpenAI, “Codex” encompasses a suite of software agent offerings, including Codex CLI, Codex Cloud, and the Codex VS Code extension. This post focuses on the Codex harness, which provides the core agent loop and execution logic that underlies all Codex experiences and is surfaced through the Codex CLI. For ease here, we’ll use the terms “Codex” and “Codex CLI” interchangeably.

The agent loop

At the heart of every AI agent is something called “the agent loop.” A simplified illustration of the agent loop looks like this:

Diagrama titulat «Agent loop» que il·lustra com un sistema d'IA processa una sol·licitud d'usuari, crida eines, observa resultats, actualitza el seu pla i retorna sortides. Les fletxes connecten passos com ara l'entrada de l'usuari, el raonament del model, les accions de les eines i la resposta final.

To start, the agent takes input from the user to include in the set of textual instructions it prepares for the model known as a prompt.

The next step is to query the model by sending it our instructions and asking it to generate a response, a process known as inference. During inference, the textual prompt is first translated into a sequence of input tokens(s'obre en una finestra nova)—integers that index into the model’s vocabulary. These tokens are then used to sample the model, producing a new sequence of output tokens.

The output tokens are translated back into text, which becomes the model’s response. Because tokens are produced incrementally, this translation can happen as the model runs, which is why many LLM-based applications display streaming output. In practice, inference is usually encapsulated behind an API that operates on text, abstracting away the details of tokenization.

As the result of the inference step, the model either (1) produces a final response to the user’s original input, or (2) requests a tool call that the agent is expected to perform (e.g., “run ls and report the output”). In the case of (2), the agent executes the tool call and appends its output to the original prompt. This output is used to generate a new input that’s used to re-query the model; the agent can then take this new information into account and try again.

This process repeats until the model stops emitting tool calls and instead produces a message for the user (referred to as an assistant message in OpenAI models). In many cases, this message directly answers the user’s original request, but it may also be a follow-up question for the user.

Because the agent can execute tool calls that modify the local environment, its “output” is not limited to the assistant message. In many cases, the primary output of a software agent is the code it writes or edits on your machine. Nevertheless, each turn always ends with an assistant message—such as “I added the architecture.md you asked for”—which signals a termination state in the agent loop. From the agent’s perspective, its work is complete and control returns to the user.

The journey from user input to agent response shown in the diagram is referred to as one turn of a conversation (a thread in Codex). Though this conversation turn can include many iterations between the model inference and tool calls. Every time you send a new message to an existing conversation, the conversation history is included as part of the prompt for the new turn, which includes the messages and tool calls from previous turns:

Diagrama titulat «Multi-turn agent loop» que mostra com un agent d'IA pren iterativament l'entrada de l'usuari, genera accions, consulta eines, actualitza l'estat i retorna resultats. Inclou passos etiquetats, fletxes i sortides d'eines d'exemple que il·lustren el cicle de raonament de l'agent.

This means that as the conversation grows, so does the length of the prompt used to sample the model. This length matters because every model has a context window, which is the maximum number of tokens it can use for one inference call. Note this window includes both input and output tokens. As you might imagine, an agent could decide to make hundreds of tool calls in a single turn, potentially exhausting the context window. For this reason, context window management is one of the agent’s many responsibilities. Now, let’s dive in to see how Codex runs the agent loop.

Model inference

The Codex CLI sends HTTP requests to the Responses API(s'obre en una finestra nova) to run model inference. We’ll examine how information flows through Codex, which uses the Responses API to drive the agent loop.

The Responses API endpoint that the Codex CLI uses is configurable(s'obre en una finestra nova), so it can be used with any endpoint that implements the Responses API(s'obre en una finestra nova):

Let’s explore how Codex creates the prompt for the first inference call in a conversation.

Building the initial prompt

As an end user, you don’t specify the prompt used to sample the model verbatim when you query the Responses API. Instead, you specify various input types as part of your query, and the Responses API server decides how to structure this information into a prompt that the model is designed to consume. You can think of the prompt as a “list of items”; this section will explain how your query gets transformed into that list.

In the initial prompt, every item in the list is associated with a role. The role indicates how much weight the associated content should have and is one of the following values (in decreasing order of priority): system, developer, user, assistant.

The Responses API(s'obre en una finestra nova) takes a JSON payload with many parameters. We’ll focus on these three:

In Codex, the instructions field is read from the model_instructions_file(s'obre en una finestra nova) in ~/.codex/config.toml, if specified; otherwise, the base_instructions associated with a model(s'obre en una finestra nova) are used. Model-specific instructions live in the Codex repo and are bundled into the CLI (e.g., gpt-5.2-codex_prompt.md(s'obre en una finestra nova)).

The tools field is a list of tool definitions that conform to a schema defined by the Responses API. For Codex, this includes tools that are provided by the Codex CLI, tools that are provided by the Responses API that should be made available to Codex, as well as tools provided by the user, usually via MCP servers:

JavaScript

1
[
2
// Codex's default shell tool for spawning new processes locally.
3
{
4
"type": "function",
5
"name": "shell",
6
"description": "Runs a shell command and returns its output...",
7
"strict": false,
8
"parameters": {
9
"type": "object",
10
"properties": {
11
"command": {"type": "array", "description": "The command to execute", ...},
12
"workdir": {"description": "The working directory...", ...},
13
"timeout_ms": {"description": "The timeout for the command...", ...},
14
...
15
},
16
"required": ["command"],
17
}
18
}
19

20
// Codex's built-in plan tool.
21
{
22
"type": "function",
23
"name": "update_plan",
24
"description": "Updates the task plan...",
25
"strict": false,
26
"parameters": {
27
"type": "object",
28
"properties": {"plan":..., "explanation":...},
29
"required": ["plan"]
30
}
31
},
32

33
// Web search tool provided by the Responses API.
34
{
35
"type": "web_search",
36
"external_web_access": false
37
},
38

39
// MCP server for getting weather as configured in the
40
// user's ~/.codex/config.toml.
41
{
42
"type": "function",
43
"name": "mcp__weather__get-forecast",
44
"description": "Get weather alerts for a US state",
45
"strict": false,
46
"parameters": {
47
"type": "object",
48
"properties": {"latitude": {...}, "longitude": {...}},
49
"required": ["latitude", "longitude"]
50
}
51
}
52
]

Finally, the input field of the JSON payload is a list of items. Codex inserts the following items(s'obre en una finestra nova) into the input before adding the user message:

1. A message with role=developer that describes the sandbox that applies only to the Codex-provided shell tool defined in the tools section. That is, other tools, such as those provided from MCP servers, are not sandboxed by Codex and are responsible for enforcing their own guardrails.

The message is built from a template where the key pieces of content come from snippets of Markdown bundled into the Codex CLI, such as workspace_write.md(s'obre en una finestra nova) and on_request.md(s'obre en una finestra nova):

Text sense format

1
<permissions instructions>
2
- description of the sandbox explaining file permissions and network access
3
- instructions for when to ask the user for permissions to run a shell command
4
- list of folders writable by Codex, if any
5
</permissions instructions>

2. (Optional) A message with role=developer whose contents are the developer_instructions value read from the user’s config.toml file.

3. (Optional) A message with role=user whose contents are the “user instructions,” which are not sourced from a single file but are aggregated across multiple sources(s'obre en una finestra nova). In general, more specific instructions appear later:

4. A message with role=user that describes the local environment in which the agent is currently operating. This specifies the current working directory and the user’s shell(s'obre en una finestra nova):

Text sense format

1
<environment_context>
2
<cwd>/Users/mbolin/code/codex5</cwd>
3
<shell>zsh</shell>
4
</environment_context>

Un cop Codex ha fet tot el càlcul anterior per inicialitzar input, afegeix el missatge de l'usuari per iniciar la conversa.

Els exemples anteriors se centraven en el contingut de cada missatge, però tingueu en compte que cada element d'input és un objecte JSON amb type, role(s'obre en una finestra nova) i content de la manera següent:

JSON

1
{
2
"type": "message",
3
"role": "user",
4
"content": [
5
{
6
"type": "input_text",
7
"text": "Add an architecture diagram to the README.md"
8
}
9
]
10
}

Un cop Codex construeix la càrrega JSON completa per enviar a l'API Responses, fa la sol·licitud HTTP POST amb una capçalera Authorization segons com estigui configurat el punt final de l'API Responses a ~/.codex/config.toml (s'afegeixen capçaleres HTTP i paràmetres de consulta addicionals si s'especifica).

Quan un servidor OpenAI Responses API rep la sol·licitud, fa servir el JSON per derivar la indicació per al model de la manera següent (cal dir que una implementació personalitzada de l'API Responses podria prendre una decisió diferent):

Diagrama de captura que mostra un sol pas d'un bucle d'agent d'IA. Una sol·licitud d'usuari entra al model, que produeix un pensament, una acció amb un nom d'eina i una entrada d'eina. El diagrama destaca aquest pas intermedi de raonament abans que es cridi l'eina.

Com podeu veure, l'ordre dels tres primers elements de la indicació el determina el servidor, no el client. Dit això, d'aquests tres elements, només el contingut del missatge system també està controlat pel servidor, ja que tools i instructions els determina el client. A continuació hi ha l'input de la càrrega JSON per completar la indicació.

Ara que tenim la indicació, ja estem a punt per mostrejar el model.

El primer torn

Aquesta sol·licitud HTTP a l'API Responses inicia el primer «torn» d'una conversa a Codex. El servidor respon amb un flux de Server-Sent Events (SSE(s'obre en una finestra nova)). Les data de cada esdeveniment són una càrrega JSON amb un "type" que comença per "response", que podria ser una cosa així (es pot trobar una llista completa d'esdeveniments a la nostra documentació de l'API(s'obre en una finestra nova)):

Text sense format

1
data: {"type":"response.reasoning_summary_text.delta","delta":"ah ", ...}
2
data: {"type":"response.reasoning_summary_text.delta","delta":"ha!", ...}
3
data: {"type":"response.reasoning_summary_text.done", "item_id":...}
4
data: {"type":"response.output_item.added", "item":{...}}
5
data: {"type":"response.output_text.delta", "delta":"forty-", ...}
6
data: {"type":"response.output_text.delta", "delta":"two!", ...}
7
data: {"type":"response.completed","response":{...}}

Codex consumeix el flux d'esdeveniments(s'obre en una finestra nova) i els torna a publicar com a objectes d'esdeveniment interns que un client pot fer servir. Esdeveniments com response.output_text.delta es fan servir per donar suport a la transmissió en temps real a la IU, mentre que altres esdeveniments com response.output_item.added es transformen en objectes que s'afegeixen a input per a crides posteriors a l'API Responses.

Suposem que la primera sol·licitud a l'API Responses inclou dos esdeveniments response.output_item.done: un amb type=reasoning i un altre amb type=function_call. Aquests esdeveniments s'han de representar al camp input del JSON quan tornem a consultar el model amb la resposta a la crida d'eina: 

JavaScript

1
[
2
/* ... original 5 items from the input array ... */
3
{
4
"type": "reasoning",
5
"summary": [
6
"type": "summary_text",
7
"text": "**Adding an architecture diagram for README.md**\n\nI need to..."
8
],
9
"encrypted_content": "gAAAAABpaDWNMxMeLw..."
10
},
11
{
12
"type": "function_call",
13
"name": "shell",
14
"arguments": "{\"command\":\"cat README.md\",\"workdir\":\"/Users/mbolin/code/codex5\"}",
15
"call_id": "call_8675309..."
16
},
17
{
18
"type": "function_call_output",
19
"call_id": "call_8675309...",
20
"output": "<p align=\"center\"><code>npm i -g @openai/codex</code>..."
21
}
22
]

La indicació resultant utilitzada per mostrejar el model com a part de la consulta posterior tindria aquest aspecte:

Diagrama etiquetat «Snapshot 2» que mostra un agent d'IA després d'una crida d'eina. El model rep una observació de l'eina i produeix un nou pensament i una acció nova. Les fletxes connecten entrades, observacions i sortides per il·lustrar com l'agent itera el seu bucle de raonament.

En particular, observeu com l'antiga indicació és un prefix exacte de la nova indicació. Això és intencionat, ja que fa que les sol·licituds posteriors siguin molt més eficients perquè ens permet aprofitar la memòria cau d'indicacions (que comentarem a la secció següent sobre rendiment).

Tornant al nostre primer diagrama del bucle de l'agent, veiem que hi podria haver moltes iteracions entre la inferència i la crida d'eines. La indicació pot continuar creixent fins que finalment rebem un missatge d'assistent, que indica el final del torn:

Text sense format

1
data: {"type":"response.output_text.done","text": "I added a diagram to explain...", ...}
2
data: {"type":"response.completed","response":{...}}

A Codex CLI, presentem el missatge d'assistent a l'usuari i enfoquem el compositor per indicar a l'usuari que ara és el seu «torn» de continuar la conversa. Si l'usuari respon, tant el missatge d'assistent del torn anterior com el nou missatge de l'usuari s'han d'afegir a input de la sol·licitud a l'API Responses per iniciar el nou torn:

JavaScript

1
[
2
/* ... all items from the last Responses API request ... */
3
{
4
"type": "message",
5
"role": "assistant",
6
"content": [
7
{
8
"type": "output_text",
9
"text": "I added a diagram to explain the client/server architecture."
10
}
11
]
12
},
13
{
14
"type": "message",
15
"role": "user",
16
"content": [
17
{
18
"type": "input_text",
19
"text": "That's not bad, but the diagram is missing the bike shed."
20
}
21
]
22
}
23
]

Un altre cop, com que continuem una conversa, la longitud d'input que enviem a l'API Responses continua augmentant:

Diagrama etiquetat «Snapshot 3» que mostra l'etapa final d'un bucle d'agent d'IA. Després de rebre els resultats de les eines, el model genera un pensament de tancament i una resposta final retornada a l'usuari. Les fletxes il·lustren la transició de la sortida de l'eina a la resposta completada.

Examinem què significa aquesta indicació que no para de créixer per al rendiment.

Consideracions de rendiment

Potser us esteu preguntant: «Espera, el bucle de l'agent no és quadràtic pel que fa a la quantitat de JSON enviada a l'API Responses al llarg de la conversa?» I tindríeu raó. Tot i que l'API Responses sí que admet un paràmetre opcional previous_response_id(s'obre en una finestra nova) per mitigar aquest problema, Codex no el fa servir avui dia, principalment per mantenir les sol·licituds completament sense estat i per admetre configuracions de retenció de dades nul·la (ZDR).

Evitar previous_response_id simplifica les coses per al proveïdor de l'API Responses perquè garanteix que cada sol·licitud és sense estat. Això també fa que sigui senzill donar suport als clients que han optat per la retenció de dades nul·la (ZDR)(s'obre en una finestra nova), ja que emmagatzemar les dades necessàries per donar suport a previous_response_id aniria en contra de la ZDR. Tingueu en compte que els clients ZDR no renuncien a la possibilitat de beneficiar-se dels missatges de raonament propietaris de torns anteriors, ja que l'encrypted_content associat es pot desxifrar al servidor. (OpenAI conserva la clau de desxifrat d'un client ZDR, però no les seves dades.) Vegeu les PR #642(s'obre en una finestra nova) i #1641(s'obre en una finestra nova) per als canvis relacionats a Codex per donar suport a la ZDR.

En general, el cost de mostrejar el model domina el cost del trànsit de xarxa, cosa que fa que el mostreig sigui l'objectiu principal dels nostres esforços d'eficiència. Per això la memòria cau d'indicacions és tan important, ja que ens permet reutilitzar càlcul d'una crida d'inferència anterior. Quan obtenim encerts de memòria cau, mostrejar el model és lineal en lloc de quadràtic. La nostra documentació sobre memòria cau d'indicacions (s'obre en una finestra nova)ho explica amb més detall:

Els encerts de memòria cau només són possibles per a coincidències exactes de prefix dins d'una indicació. Per obtenir els beneficis de la memòria cau, col·loqueu contingut estàtic com instruccions i exemples al principi de la indicació, i poseu el contingut variable, com ara informació específica de l'usuari, al final. Això també s'aplica a les imatges i eines, que han de ser idèntiques entre sol·licituds.

Amb això present, considerem quins tipus d'operacions podrien causar un «error de memòria cau» a Codex:

  • Canviar les tools disponibles per al model al mig de la conversa.
  • Canviar el model objectiu de la sol·licitud a l'API Responses (a la pràctica, això canvia el tercer element de la indicació original, ja que conté instruccions específiques del model).
  • Canviar la configuració del sandbox, el mode d'aprovació o el directori de treball actual.

L'equip de Codex ha de ser diligent en introduir noves funcionalitats a Codex CLI que puguin comprometre la memòria cau d'indicacions. Com a exemple, el nostre suport inicial per a eines MCP va introduir un error en què no enumeràvem les eines en un ordre coherent(s'obre en una finestra nova), cosa que provocava errors de memòria cau. Tingueu en compte que les eines MCP poden ser especialment complicades perquè els servidors MCP poden canviar sobre la marxa la llista d'eines que proporcionen mitjançant una notificació notifications/tools/list_changed(s'obre en una finestra nova). Respectar aquesta notificació al mig d'una conversa llarga pot provocar un costós error de memòria cau.

Quan és possible, gestionem els canvis de configuració que passen a mitja conversa afegint un missatge nou a input per reflectir el canvi en lloc de modificar un missatge anterior:

Fem grans esforços per garantir encerts de memòria cau per al rendiment. Hi ha un altre recurs clau que hem de gestionar: la finestra de context.

La nostra estratègia general per evitar quedar-nos sense finestra de context és compactar la conversa un cop el nombre de segments supera un cert llindar. En concret, substituïm input per una llista nova i més petita d'elements que representa la conversa, cosa que permet a l'agent continuar entenent què ha passat fins aleshores. Una implementació inicial de la compactació(s'obre en una finestra nova) exigia que l'usuari invoqués manualment l'ordre /compact, que consultava l'API Responses fent servir la conversa existent més instruccions personalitzades per a la resumització(s'obre en una finestra nova). Codex feia servir el missatge d'assistent resultant amb el resum com a nou input(s'obre en una finestra nova) per als torns de conversa posteriors.

Des de llavors, l'API Responses ha evolucionat per admetre un punt final /responses/compact(s'obre en una finestra nova) especial que fa la compactació de manera més eficient. Retorna una llista d'elements(s'obre en una finestra nova) que es poden fer servir en lloc de l'input anterior per continuar la conversa mentre s'allibera la finestra de context. Aquesta llista inclou un element especial type=compaction amb un element opac encrypted_content que preserva la comprensió latent que té el model de la conversa original. Ara, Codex fa servir automàticament aquest punt final per compactar la conversa quan se supera l'auto_compact_limit(s'obre en una finestra nova).

Pròximament

Hem presentat el bucle de l'agent Codex i hem recorregut com Codex crea i gestiona el seu context quan consulta un model. Durant el recorregut, hem destacat consideracions pràctiques i bones pràctiques que s'apliquen a qualsevol persona que construeixi un bucle d'agent sobre l'API Responses.

Tot i que el bucle de l'agent proporciona la base de Codex, això només és el començament. En articles pròxims, aprofundirem en l'arquitectura de la CLI, explorarem com s'implementa l'ús d'eines i examinarem més de prop el model de sandboxing de Codex.

Autor

Michael Bolin

Agraïments

Agraïments especials a tot l'equip que va construir Codex CLI.