Переход к основному контенту
OpenAI

23 января 2026 г.

Инженерия

Разворачивание цикла агента Codex

Майкл Болин, член технического персонала

Загрузка…

Codex CLI(открывается в новом окне) — это наш кроссплатформенный локальный программный агент, предназначенный для создания высококачественных и надежных изменений в программном обеспечении, безопасно и эффективно работая на вашем компьютере. Мы узнали невероятно много о том, как создать программного агента мирового уровня с тех пор, как впервые запустили CLI в апреле. Чтобы разобраться с этими инсайтами, мы написали этот первый пост в продолжающейся серии, где мы рассмотрим различные аспекты работы Codex, а также уроки, полученные ценой большого опыта. (Чтобы получить ещё более детальное представление о том, как устроен Codex CLI, посетите наш репозиторий с открытым исходным кодом по адресу https://github.com/openai/codex(открывается в новом окне). Если вы хотите узнать больше, многие более тонкие детали наших дизайнерских решений задокументированы в GitHub issues и пулл-реквестах.)

Для начала мы сосредоточимся на цикле агента, который является основной логикой в Codex CLI и отвечает за координацию взаимодействия между пользователем, моделью и инструментами, которые модель вызывает для выполнения значимой работы по разработке программного обеспечения. Мы надеемся, что этот пост даст вам представление о роли, которую наш агент (или программа, «harness») играет в использовании LLM.

Прежде чем мы начнем, небольшое примечание о терминологии: в OpenAI «Codex» охватывает набор предложений программных агентов, включая Codex CLI, Codex Cloud и расширение Codex VS Code. Этот пост посвящен программе Codex, который обеспечивает основной цикл агента и логику выполнения, лежащие в основе всех возможностей Codex и доступные через Codex CLI. Для удобства здесь мы будем использовать термины «Codex» и «Codex CLI» взаимозаменяемо.

Цикл агента

В основе каждого ИИ-агента находится так называемый «цикл агента». Упрощённая иллюстрация цикла агента выглядит следующим образом:

Диаграмма под названием «Agent loop», иллюстрирующая, как система ИИ обрабатывает запрос пользователя, вызывает инструменты, наблюдает результаты, обновляет план и возвращает результаты. Стрелки соединяют этапы, такие как ввод пользователя, рассуждения модели, действия инструментов и окончательный ответ.

Для начала агент получает от пользователя ввод, который нужно включить в набор текстовых инструкций, подготавливаемых для модели и известных как промпт.

Следующий шаг — выполнить запрос к модели, отправив ей наши инструкции и попросив сгенерировать ответ; этот процесс называется получением выводов. Во время вывода текстовый промпт сначала преобразуется в последовательность входных токенов(открывается в новом окне) — целых чисел, которые индексируют словарь модели. Затем эти токены используются для выборки модели, в результате чего создается новая последовательность выходных токенов.

Выходные токены преобразуются обратно в текст, который становится ответом модели. Поскольку токены создаются постепенно, перевод может выполняться по мере работы модели, поэтому многие приложения на базе LLM отображают потоковый вывод. На практике вывод обычно инкапсулирован за API, который работает с текстом, абстрагируя детали токенизации.

В результате шага получения выводов модель либо (1) формирует окончательный ответ на исходный ввод пользователя, либо (2) запрашивает вызов инструмента, который агент должен выполнить (например, «выполни ls и сообщи результат»). В случае (2) агент выполняет вызов инструмента и добавляет его результат к исходному промпту. Этот результат используется для создания нового ввода, который применяется для повторного запроса модели; агент затем может учесть эту новую информацию и попробовать снова.

Этот процесс повторяется до тех пор, пока модель не перестанет выдавать вызовы инструментов и вместо этого не создаст сообщение для пользователя (в моделях OpenAI оно называется сообщением ассистента). Во многих случаях это сообщение напрямую отвечает на исходный запрос пользователя, но также может быть последующим вопросом для пользователя.

Поскольку агент может выполнять вызовы инструментов, которые изменяют локальную среду, его «вывод» не ограничивается сообщением ассистента. Во многих случаях основным результатом работы программного агента является код, который он пишет или редактирует на вашем компьютере. Тем не менее, каждый ход всегда заканчивается сообщением ассистента — например: «Я добавил architecture.md, который ты просил», — что сигнализирует о состоянии завершения в цикле агента. С точки зрения агента, его работа завершена, и управление возвращается пользователю.

Путь от ввода пользователя до ответа агента, показанный на диаграмме, называется одним ходом разговора (в Codex — поток (thread) ). Хотя этот ход беседы может включать множество итераций между выводами модели и вызовами инструментов. Каждый раз, когда вы отправляете новое сообщение в существующую беседу, история беседы включается в качестве части промпта для нового хода, который включает сообщения и вызовы инструментов из предыдущих ходов:

Диаграмма под названием «Многоходовой цикл агента», показывающая, как ИИ-агент итеративно принимает ввод пользователя, генерирует действия, обращается к инструментам, обновляет состояние и возвращает результаты. Включены помеченные шаги, стрелки и примеры выходных данных инструментов, иллюстрирующие цикл рассуждений агента.

Это означает, что по мере роста разговора увеличивается и длина промптов, используемых для выборки модели. Эта длина важна, потому что у каждой модели есть контекстное окно, что означает максимальное количество токенов, которые она может использовать за один вызов получения выводов. Обратите внимание, что это окно включает входные и выходные токены. Как вы можете представить, агент может решить сделать сотни вызовов инструментов за один ход, потенциально исчерпав окно контекста. По этой причине управление контекстным окном является одной из многих обязанностей агента. Теперь давайте углубимся, чтобы увидеть, как Codex выполняет цикл агента.

Формирование рассуждений модели

Codex CLI отправляет HTTP-запросы в Responses API(открывается в новом окне) для выполнения рассуждений модели. Мы рассмотрим, как информация проходит через Codex, который использует Responses API для управления циклом агента.

Конечная точка Responses API, используемая Codex CLI, настраиваемая(открывается в новом окне), поэтому её можно использовать с любой конечной точкой, которая реализует Responses API(открывается в новом окне):

Давайте изучим, как Codex формирует промпт для первого вызова вывода в беседе.

Создание начального промпта

Как конечный пользователь, вы не указываете промпт, используемый для дословного сэмплирования модели, когда вы отправляете запрос к Responses API. Вместо этого вы указываете различные типы входных данных как часть вашего запроса, а сервер Responses API решает, как структурировать эту информацию в промпт, который модель должна воспринимать. Вы можете думать о промпте как о «списке элементов»; в этом разделе будет объяснено, как ваш запрос преобразуется в этот список.

В начальном промпте каждый элемент списка ассоциируется с ролью. Параметр role указывает, насколько важен связанный контент, и принимает одно из следующих значений (в порядке убывания приоритета): system, developer, user, assistant.

Responses API(открывается в новом окне) принимает JSON-пакет с множеством параметров. Мы сосредоточимся на следующих трёх:

В Codex поле instructions считывается из model_instructions_file(открывается в новом окне) в ~/.codex/config.toml, если указано; в противном случае используются base_instructions, связанные с моделью(открывается в новом окне). Инструкции для конкретной модели находятся в репозитории Codex и включены в CLI (например, gpt-5.2-codex_prompt.md(открывается в новом окне)).

Поле tools — это список определений инструментов, которые соответствуют схеме, определенной Responses API. Для Codex это включает инструменты, предоставляемые Codex CLI, инструменты, предоставляемые Responses API, которые должны быть доступны Codex, а также инструменты, предоставляемые пользователем, обычно через серверы MCP:

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
]

Наконец, поле input в JSON-пакете представляет собой список элементов. Codex вставляет следующие элементы(открывается в новом окне) в input перед добавлением сообщения пользователя:

1. Сообщение с role=developer, которое описывает песочницу, применяемую только к предоставленному Codex shell инструменту, определённому в разделе tools. То есть другие инструменты, такие как те, которые предоставляются MCP-серверами, не изолированы в песочнице Codex и несут ответственность за обеспечение собственных защитных механизмов.

Сообщение создаётся из шаблона, где ключевые элементы содержимого берутся из фрагментов Markdown, включённых в Codex CLI, например workspace_write.md(открывается в новом окне) и 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. (Необязательно) Сообщение с role=developer, содержимое которого является значением developer_instructions, прочитанным из файла config.toml пользователя.

3. (Необязательно) Сообщение с role=user, содержимое которого — «инструкции пользователя», которые не берутся из одного файла, а собираются из нескольких источников(открывается в новом окне). В целом, более конкретные инструкции появляются позже:

4. Сообщение с role=user, которое описывает локальную среду, в которой агент в данный момент работает. Это определяет текущий рабочий каталог и оболочку пользователя(открывается в новом окне):

Простой текст

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-пакет для отправки в Responses API, он выполняет HTTP POST-запрос с заголовком Authorization, в зависимости от того, как настроена конечная точка Responses API в ~/.codex/config.toml (дополнительные HTTP-заголовки и параметры запроса добавляются, если они указаны).

Когда сервер OpenAI Responses API получает запрос, он использует JSON для формирования промпта для модели следующим образом (конечно, пользовательская реализация Responses API может сделать другой выбор):

Диаграмма, демонстрирующая один шаг в цикле агента ИИ. Запрос пользователя поступает в модель, которая генерирует мысль, действие с названием инструмента и ввод для инструмента. Диаграмма подчеркивает этот промежуточный шаг рассуждения перед вызовом инструмента.

Как вы можете видеть, порядок первых трех элементов в промпте определяется сервером, а не клиентом. Тем не менее, из этих трех элементов только содержимое system message также контролируется сервером, поскольку tools и instructions определяются клиентом. За ними следует input из JSON-пейлоада, чтобы завершить промпт.

Теперь, когда у нас есть промпт, мы готовы приступить к выборке модели.

Первый ход

Этот HTTP-запрос к Responses API инициирует первый «ход» разговора в Codex. Сервер отвечает потоком событий, отправляемых сервером (Server-Sent Events (SSE(открывается в новом окне))). Данные каждого события представляют собой JSON-полезную нагрузку с "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=рассуждения и одно с 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
]

Итоговый промпт, используемый для выборки модели в рамках последующего запроса, выглядел бы так:

Диаграмма с подписью «Snapshot 2», показывающая ИИ-агента после вызова инструмента. Модель получает наблюдение инструмента и создает новую мысль и действие. Стрелки соединяют входные данные, наблюдения и выходные данные, чтобы показать, как агент выполняет итерации своего цикла рассуждений.

В частности, обратите внимание на то, что старый промпт является точным префиксом нового промпта. Это сделано намеренно, так как это делает последующие промпты гораздо более эффективными, поскольку позволяет нам воспользоваться кэшированием промптов (о котором мы поговорим в следующем разделе о производительности).

Оглядываясь на нашу первую схему цикла агента, мы видим, что между выводом и вызовом инструментов может быть много итераций. Промпт может продолжать увеличиваться, пока мы наконец не получим сообщение от ассистента, указывающее на конец хода:

Простой текст

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

В Codex CLI мы показываем пользователю сообщение помощника и фокусируем композер, чтобы дать пользователю понять, что теперь его «очередь» продолжить разговор. Если пользователь отвечает, то и сообщение ассистента из предыдущего хода, и новое сообщение пользователя должны быть добавлены в 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, продолжает увеличиваться:

Диаграмма с меткой «Snapshot 3», показывающая финальную стадию цикла ИИ-агента. После получения результатов инструментов модель формирует заключительное суждение и окончательный ответ, который возвращается пользователю. Стрелки показывают переход от результата работы инструмента к завершенному ответу.

Давайте рассмотрим, что означает этот постоянно растущий промпт для производительности.

Соображения по производительности

Возможно, вы спрашиваете себя: «Подождите, разве цикл агента квадратичный с точки зрения объёма JSON, отправляемого в Responses API в ходе разговора?» Вы правы. Хотя Responses API поддерживает необязательный параметр previous_response_id(открывается в новом окне) для смягчения этой проблемы, Codex в настоящее время его не использует, главным образом для того, чтобы запросы оставались полностью статичными и поддерживались конфигурации нулевого хранения данных (ZDR).

Избегание использования previous_response_id упрощает задачу для поставщика Responses API, поскольку это гарантирует, что каждый запрос не фиксирует состояние. Это также упрощает поддержку клиентов, выбравших нулевое хранение данных (ZDR)(открывается в новом окне), поскольку хранение данных, необходимых для поддержки previous_response_id, противоречило бы принципу ZDR. Обратите внимание, что клиенты, выбравшие ZDR, не теряют возможности извлекать выгоду из проприетарных сообщений рассуждения из предыдущих ходов, так как связанный encrypted_content может быть расшифрован на сервере. (OpenAI сохраняет ключ дешифрования клиента с ZDR, но не его данные.) См. пулл-реквесты (PR) #642(открывается в новом окне) и #1641(открывается в новом окне) для соответствующих изменений в Codex для поддержки ZDR.

В целом, стоимость выборки модели превышает стоимость сетевого трафика, что делает выборку основной целью наших усилий по повышению эффективности. Вот почему кэширование промптов так важно, поскольку оно позволяет нам повторно использовать вычисления из предыдущего вызова вывода. Когда мы получаем попадания в кэш, выборка из модели становится линейной, а не квадратичной. Наша документация по кэшированию промптов (открывается в новом окне)объясняет это более подробно:

Попадания в кэш возможны только при точных совпадениях префикса в промпте. Чтобы воспользоваться преимуществами кэширования, размещайте статический контент, такой как инструкции и примеры, в начале вашего промпта, а переменный контент, такой как информация, специфичная для пользователя, — в конце. Это также относится к изображениям и инструментам, которые должны быть идентичными между запросами.

Имея это в виду, давайте рассмотрим, какие типы операций могут привести к «cache miss» в Codex:

  • Изменение доступных для модели tools в середине разговора.
  • Изменение модели, которая является целью запроса Responses API (на практике это изменяет третий элемент в исходном промпте, поскольку он содержит инструкции, специфичные для модели).
  • Изменение конфигурации песочницы, режима утверждения или текущего рабочего каталога.

Команда Codex должна проявлять особую осторожность при внедрении новых функций в Codex CLI, которые могут поставить под угрозу кэширование промптов. Пример: наша первоначальная поддержка инструментов MCP привела к ошибке, из-за которой мы не смогли перечислять инструменты в согласованном порядке(открывается в новом окне), что вызывало промахи кэша. Обратите внимание, что инструменты MCP могут быть особенно сложными, поскольку MCP-серверы могут изменять список предоставляемых инструментов на лету через уведомление notifications/tools/list_changed(открывается в новом окне). Принятие этого уведомления на обработку в середине длительного разговора может привести к затратному промаху кэша.

По возможности мы обрабатываем изменения конфигурации, происходящие в середине беседы, добавляя новое сообщение в input, чтобы отразить изменение, вместо изменения более раннего сообщения:

Мы прилагаем все усилия, чтобы обеспечить попадания в кэш для повышения производительности. Есть ещё один ключевой ресурс, которым мы должны управлять: контекстное окно.

Наша общая стратегия, чтобы избежать исчерпания контекстного окна, заключается в уплотнении диалога, как только количество токенов превышает определенный порог. В частности, мы заменяем input новым, более коротким списком элементов, который отражает суть обсуждения и позволяет агенту продолжать работу, понимая, что произошло до настоящего момента. Ранняя реализация уплотнения(открывается в новом окне) требовала, чтобы пользователь вручную вызывал команду /compact, которая выполняла запрос к Responses API, используя существующую беседу и пользовательские инструкции для резюмирования(открывается в новом окне). Codex использовал получившееся сообщение ассистента, содержащее сводку, в качестве нового ввода(открывается в новом окне) для последующих ходов в разговоре.

С тех пор Responses API эволюционировал и теперь поддерживает специальную конечную точку /responses/compact(открывается в новом окне), которая выполняет уплотнение более эффективно. Она возвращает список элементов(открывается в новом окне), который можно использовать вместо предыдущего input, чтобы продолжить разговор, освобождая окно контекста. Этот список включает специальный элемент type=compaction с непрозрачным элементом encrypted_content, который сохраняет скрытое понимание модели исходного разговора. Теперь Codex автоматически использует эту конечную точку для сжатия беседы, когда превышен auto_compact_limit(открывается в новом окне).

Далее

Мы представили цикл агента Codex и подробно рассмотрели, как Codex формирует и управляет своим контекстом при запросе к модели. Мы выделили практические соображения и лучшие практики, которые применимы ко всем, кто создает цикл агента на основе Responses API.

Хотя цикл агента обеспечивает основу для Codex, это только начало. В следующих публикациях мы подробно разберём архитектуру CLI, изучим, как реализовано использование инструментов, и внимательнее рассмотрим модель песочницы Codex.

Автор

Michael Bolin

Благодарности

Особая благодарность всей команде, создавшей Codex CLI.