Пређите на главни садржај
OpenAI

23. јануар 2026.

Инжењеринг

Unrolling the Codex agent loop

By Michael Bolin, Member of the Technical Staff

Учитавање…

Codex CLI(отвара се у новом прозору) 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(отвара се у новом прозору). 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:

Дијаграм под насловом „Петља агента“ који илуструје како AI систем обрађује кориснички захтев, позива алате, посматра резултате, ажурира план и враћа излазе. Стрелице повезују кораке као што су кориснички улаз, резоновање модела, радње алата и коначан одговор.

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(отвара се у новом прозору)—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:

Дијаграм под насловом „Вишепотезна петља агента“ који приказује како AI агент итеративно преузима кориснички улаз, генерише радње, консултује алате, ажурира стање и враћа резултате. Укључује означене кораке, стрелице и примерне излазе алата који илуструју циклус резоновања агента.

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(отвара се у новом прозору) 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(отвара се у новом прозору), so it can be used with any endpoint that implements the Responses API(отвара се у новом прозору):

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(отвара се у новом прозору) 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(отвара се у новом прозору) in ~/.codex/config.toml, if specified; otherwise, the base_instructions associated with a model(отвара се у новом прозору) are used. Model-specific instructions live in the Codex repo and are bundled into the CLI (e.g., gpt-5.2-codex_prompt.md(отвара се у новом прозору)).

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(отвара се у новом прозору) 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(отвара се у новом прозору) and on_request.md(отвара се у новом прозору):

Обичан текст

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(отвара се у новом прозору). 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(отвара се у новом прозору):

Обичан текст

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

Када Codex обави све горе наведене прорачуне да иницијализује input, додаје корисничку поруку да започне разговор.

Претходни примери били су усредсређени на садржај сваке поруке, али имајте у виду да је сваки елемент input JSON објекат са type, role(отвара се у новом прозору) и content на следећи начин:

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
}

Када Codex изгради цео JSON payload за слање Responses API-ју, затим шаље HTTP POST захтев са заглављем Authorization у зависности од тога како је крајња тачка Responses API-ја подешена у ~/.codex/config.toml (додатна HTTP заглавља и параметри упита додају се ако су наведени).

Када сервер OpenAI Responses API-ја прими захтев, користи JSON да изведе инструкцију за модел на следећи начин (да будемо прецизни, прилагођена имплементација Responses API-ја могла би да направи другачији избор):

Дијаграм снимка који приказује један корак у петљи AI агента. Кориснички захтев улази у модел, који производи мисао, радњу са именом алата и улаз алата. Дијаграм истиче овај међукорак резоновања пре него што се алат позове.

Као што видите, редослед прве три ставке у инструкцији одређује сервер, а не клијент. Ипак, од те три ставке, само садржај system message-а контролише и сервер, јер tools и instructions одређује клијент. За њима следи input из JSON payload-а да би се инструкција употпунила.

Сада када имамо нашу инструкцију, спремни смо да узоркујемо модел.

Први потез

Овај HTTP захтев ка Responses API-ју покреће први „потез“ разговора у Codex-у. Сервер одговара током Server-Sent Events (SSE(отвара се у новом прозору)). data сваког догађаја је JSON payload са "type" који почиње са "response", што би могло изгледати овако (потпуна листа догађаја може се наћи у нашој API документацији(отвара се у новом прозору)):

Обичан текст

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 конзумира ток догађаја(отвара се у новом прозору) и поново их објављује као интерне објекте догађаја које клијент може да користи. Догађаји као што је response.output_text.delta користе се за подршку стримингу у UI-ју, док се други догађаји као што је response.output_item.added трансформишу у објекте који се додају у input за наредне позиве Responses API-ја.

Претпоставимо да први захтев ка Responses API-ју укључује два догађаја response.output_item.done: један са type=reasoning и један са type=function_call. Ови догађаји морају да буду представљени у пољу input JSON-а када поново испитујемо модел са одговором на позив алата:

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
]

Резултујућа инструкција која се користи за узорковање модела као део наредног упита изгледала би овако:

Дијаграм означен као „Снимак 2“ који приказује AI агента после позива алата. Модел прима запажање алата и производи нову мисао и радњу. Стрелице повезују улазе, запажања и излазе како би илустровале како агент понавља своју петљу резоновања.

Посебно, обратите пажњу на то како је стара инструкција тачан префикс нове инструкције. То је намерно, јер наредне захтеве чини знатно ефикаснијим зато што нам омогућава да искористимо кеширање инструкција (о чему ћемо говорити у следећем одељку о перформансама).

Ако се вратимо на наш први дијаграм петље агента, видимо да може бити много итерација између инференције и позивања алата. Инструкција може наставити да расте док коначно не добијемо assistant message, што означава крај потеза:

Обичан текст

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

У Codex CLI-ју представљамо assistant message кориснику и фокусирамо composer да бисмо кориснику указали да је сада његов „потез“ да настави разговор. Ако корисник одговори, и assistant message из претходног потеза, као и нова корисникова порука, морају бити додати у input у захтеву ка Responses API-ју да би се започео нови потез:

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
]

Још једном, пошто настављамо разговор, дужина input који шаљемо Responses API-ју наставља да расте:

Дијаграм означен као „Снимак 3“ који приказује завршну фазу петље AI агента. Након пријема резултата алата, модел генерише закључну мисао и коначан одговор који се враћа кориснику. Стрелице илуструју прелаз са излаза алата на завршени одговор.

Хајде да испитамо шта ова непрестано растућа инструкција значи за перформансе.

Разматрања у вези са перформансама

Можда се питате: „Чекајте, зар петља агента није квадратна у погледу количине JSON-а послатог Responses API-ју током разговора?“ И били бисте у праву. Иако Responses API подржава опциони параметар previous_response_id(отвара се у новом прозору) да ублажи овај проблем, Codex га данас не користи, првенствено да би захтеви остали потпуно без стања и да би подржао конфигурације незадржавања података (ZDR).

Избегавање previous_response_id поједностављује ствари за добављача Responses API-ја јер обезбеђује да је сваки захтев без стања. То такође олакшава подршку клијентима који су се определили за незадржавање података (ZDR)(отвара се у новом прозору), јер би складиштење података потребних за подршку previous_response_id било у супротности са ZDR-ом. Имајте у виду да ZDR клијенти не губе могућност да имају корист од proprietary reasoning порука из претходних потеза, јер повезани encrypted_content може да се дешифрује на серверу. (OpenAI чува кључ за дешифровање ZDR клијента, али не и њихове податке.) Погледајте PR-ове #642(отвара се у новом прозору) и #1641(отвара се у новом прозору) за повезане измене у Codex-у ради подршке за ZDR.

Уопштено, трошак узорковања модела доминира над трошком мрежног саобраћаја, што узорковање чини примарном метом наших напора за ефикасност. Зато је кеширање инструкција толико важно, јер нам омогућава да поново употребимо прорачун из претходног позива инференције. Када добијемо погодак у кешу, узорковање модела је линеарно, а не квадратно. Наша документација о кеширању инструкција(отвара се у новом прозору) ово детаљније објашњава:

Погоци у кешу могући су само за тачна поклапања префикса унутар инструкције. Да бисте остварили користи од кеширања, ставите статички садржај као што су инструкције и примери на почетак инструкције, а променљиви садржај, као што су кориснички специфичне информације, на крај. Ово се такође односи на слике и алате, који морају бити идентични између захтева.

Имајући то у виду, размотримо које врсте операција би могле да изазову „промашај кеша“ у Codex-у:

  • Промена tools доступних моделу усред разговора.
  • Промена model који је циљ захтева ка Responses API-ју (у пракси, ово мења трећу ставку у оригиналној инструкцији, јер она садржи инструкције специфичне за модел).
  • Промена sandbox конфигурације, режима одобравања или тренутног радног директоријума.

Тим Codex-а мора бити пажљив приликом увођења нових функција у Codex CLI које би могле угрозити кеширање инструкција. Као пример, наша почетна подршка за MCP алате увела је грешку због које нисмо набрајали алате у доследном редоследу(отвара се у новом прозору), што је узроковало промашаје кеша. Имајте у виду да MCP алати могу бити посебно незгодни јер MCP сервери могу у ходу да мењају листу алата које пружају преко обавештења notifications/tools/list_changed(отвара се у новом прозору). Уважавање тог обавештења усред дугог разговора може изазвати скуп промашај кеша.

Када је могуће, промене конфигурације које се десе усред разговора обрађујемо тако што додајемо нову поруку у input да одрази промену, уместо да мењамо ранију поруку:

Улажемо велики труд да обезбедимо поготке у кешу ради перформанси. Постоји још један кључни ресурс којим морамо да управљамо: контекстуални прозор.

Наша општа стратегија да избегнемо остајање без контекстуалног прозора јесте да компактујемо разговор када број токена пређе неки праг. Конкретно, заменимо input новом, мањом листом ставки која репрезентује разговор, омогућавајући агенту да настави са разумевањем онога што се до тада догодило. Рана имплементација компакције(отвара се у новом прозору) захтевала је да корисник ручно позове команду /compact, која би испитивала Responses API користећи постојећи разговор плус прилагођене инструкције за сумирање(отвара се у новом прозору). Codex је користио резултујућу assistant message са сажетком као нови input(отвара се у новом прозору) за наредне потезе разговора.

Од тада је Responses API еволуирао тако да подржава посебну /responses/compact крајњу тачку(отвара се у новом прозору) која ефикасније обавља компакцију. Она враћа листу ставки(отвара се у новом прозору) које се могу користити уместо претходног input за наставак разговора уз ослобађање контекстуалног прозора. Та листа укључује посебну ставку type=compaction са непрозирном ставком encrypted_content која чува латентно разумевање модела о оригиналном разговору. Сада Codex аутоматски користи ову крајњу тачку за компакцију разговора када се прекорачи auto_compact_limit(отвара се у новом прозору).

Следи у наставку

Представили смо петљу Codex агента и прошли кроз то како Codex обликује и управља својим контекстом када испитује модел. Успут смо истакли практична разматрања и најбоље праксе које важе за свакога ко гради петљу агента поврх Responses API-ја.

Иако петља агента даје основу за Codex, то је тек почетак. У наредним текстовима дубље ћемо ући у архитектуру CLI-ја, истражити како је имплементирана употреба алата и детаљније погледати Codex модел sandbox-а.

Аутор

Michael Bolin

Захвалнице

Посебна захвалност целом тиму који је изградио Codex CLI.