Codex CLI(відкривається у новому вікні) — це наш кросплатформний локальний агент програмного забезпечення, створений для внесення високоякісних і надійних змін у програмне забезпечення, безпечно та ефективно працюючи на вашому комп’ютері. Ми дізналися надзвичайно багато про те, як створити програмного агента світового класу відтоді, як запустили CLI у квітні. Щоб розібрати ці висновки, ми створили цей перший допис у тривалій серії, де ми досліджуватимемо різні аспекти роботи Codex, а також розглянемо здобутий досвід. (Щоб отримати ще детальніше уявлення про те, як створено Codex CLI, перегляньте наш відкритий репозиторій за адресою https://github.com/openai/codex(відкривається у новому вікні). Якщо ви хочете дізнатися більше, багато тонких деталей наших дизайнерських рішень задокументовано в issues і запитах на злиття на GitHub.)
Для початку ми зосередимося на циклі агента, який є основною логікою в Codex CLI, що відповідає за координацію взаємодії між користувачем, моделлю та інструментами, які модель викликає для виконання змістовної роботи з програмним забезпеченням. Сподіваємося, що цей допис дасть вам чітке уявлення про роль, яку наш агент (або програма, «harness») відіграє у використанні LLM.
Перш ніж ми зануримося, коротка примітка щодо термінології: в OpenAI «Codex» охоплює набір програмних агентів, включаючи Codex CLI, Codex Cloud і розширення Codex для VS Code. Цей допис зосереджено на програмі Codex, яка забезпечує основний цикл агента та логіку виконання, що лежить в основі всіх можливостей Codex і доступна через Codex CLI. Для зручності ми будемо використовувати терміни «Codex» і «Codex CLI» як взаємозамінні.
У центрі кожного ШІ-агента знаходиться так званий «цикл агента». Спрощена ілюстрація циклу агента виглядає наступним чином:
Для початку агент отримує вхідні дані від користувача, щоб включити їх до набору текстових інструкцій, які він готує для моделі, відомої як запит, або ж промпт.
Наступний крок — надіслати моделі наші інструкції та попросити її згенерувати відповідь — процес, відомий як інференція. Під час інференції текстовий запит спочатку перетворюється на послідовність вхідних токенів(відкривається у новому вікні)—цілих чисел, які індексують словник моделі. Потім ці токени використовуються для вибірки моделі, створюючи нову послідовність вихідних токенів.
Вихідні токени перекладаються назад у текст, який стає відповіддю моделі. Оскільки токени генеруються поступово, переклад може виконуватися під час роботи моделі, тому багато застосунків на основі великих мовних моделей (LLM) відображають потоковий вивід. На практиці інференс зазвичай інкапсульовано за API, що працює з текстом, абстрагуючи деталі токенізації.
У результаті кроку інференції модель або (1) формує остаточну відповідь на початковий запит користувача, або (2) запитує виклик інструмента, який агент має виконати (наприклад, «запусти ls і повідом про результат»). У випадку (2) агент виконує виклик інструмента та додає його результати до оригінального промпту. Цей результат використовується для створення нового введення, яке використовується для повторного запиту до моделі; агент може врахувати цю нову інформацію та спробувати ще раз.
Цей процес повторюється, доки модель не припинить генерувати виклики інструментів і не створить повідомлення для користувача (в моделях OpenAI це називається повідомленням асистента). У багатьох випадках це повідомлення безпосередньо відповідає на початковий запит користувача, але також може бути додатковим запитанням для користувача.
Оскільки агент може виконувати виклики інструментів, що змінюють локальне середовище, його «результат» не обмежується повідомленням асистента. У багатьох випадках основним результатом роботи програмного агента є код, який він пише або редагує на вашому комп'ютері. Проте кожен хід завжди завершується повідомленням асистента — наприклад, «Я додав architecture.md, який ти просив», — що сигналізує про стан завершення в циклі агента. З точки зору агента, його робота завершена, і керування повертається до користувача.
Шлях від введення користувача до відповіді агента, показаний на діаграмі, називається одним ходом розмови (у Codex — тредом ). Хоча цей хід розмови може включати багато ітерацій між висновками моделі та викликами інструментів. Щоразу, коли ви надсилаєте нове повідомлення в існуючу розмову, історія розмови включається як частина промпту для нового ходу, що містить повідомлення та виклики інструментів з попередніх ходів:
Це означає, що в міру зростання об'єму розмови збільшується й довжина запиту, який використовується для вибірки моделі. Ця довжина має значення, оскільки кожна модель має контекстне вікно, що є максимальною кількістю токенів, які вона може використати для одного виклику інференції. Зверніть увагу, що це вікно включає вхідні та вихідні токени. Як ви можете уявити, агент може вирішити зробити сотні викликів інструментів за один хід, що може вичерпати контекстне вікно. З цієї причини управління контекстним вікном є одним із багатьох обов’язків агента. Тепер дізнаємося, як Codex виконує цикл агента.
Codex CLI надсилає HTTP-запити до Responses API(відкривається у новому вікні), щоб виконати інференцію моделі. Ми розглянемо, як інформація проходить через Codex, що використовує Responses API для керування циклом агента.
Кінцева точка Responses API, яку використовує Codex CLI, є налаштовуваною(відкривається у новому вікні), тому її можна використовувати з будь-якою кінцевою точкою, яка реалізує Responses API(відкривається у новому вікні):
- Під час входу в ChatGPT(відкривається у новому вікні) за допомогою Codex CLI, використовується
https://chatgpt.com/backend-api/codex/responsesяк кінцева точка - Під час використання автентифікації за допомогою ключа API(відкривається у новому вікні) з моделями, розміщеними на платформі OpenAI, використовується
https://api.openai.com/v1/responsesяк кінцева точка - Під час запуску Codex CLI з
--ossдля використання gpt-oss з ollama 0.13.4+(відкривається у новому вікні) або LM Studio 0.3.39+(відкривається у новому вікні), за замовчуванням використовуєтьсяhttp://localhost:11434/v1/responses, що працює локально на вашому комп’ютері - Codex CLI можна використовувати з Responses API, розміщеним у хмарного провайдера, наприклад, Azure
Розглянемо, як Codex створює промпт для першого виклику інференції в розмові.
Як кінцевий користувач, ви не уточнюєте промпт, що використовується для дослівного семплювання моделі, коли ви надсилаєте запит до Responses API. Натомість ви вказуєте різні типи введення у своєму запиті, а сервер Responses API вирішує, як структурувати цю інформацію у промпт, який модель призначена споживати. Ви можете уявити промпт як «список елементів»; у цьому розділі буде пояснено, як ваш запит перетворюється на цей список.
У початковому запиті кожен елемент списку пов’язаний з роллю. Роль вказує на те, яку вагу має мати пов’язаний контент, і може приймати одне з таких значень (у порядку спадання пріоритету): system, developer, user, assistant.
Responses API(відкривається у новому вікні) приймає JSON-повідомлення з багатьма параметрами. Ми зосередимося на цих трьох:
instructions(відкривається у новому вікні): системне (або розробницьке) повідомлення, вставлене в контекст моделіtools(відкривається у новому вікні): список інструментів, які модель може використовувати під час створення відповідіinput(відкривається у новому вікні): список текстових, графічних або файлових введень для моделі
У 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 servers:
Нарешті, поле input у JSON-пейлоаді — це список елементів. Codex вставляє наступні елементи(відкривається у новому вікні) у input перед додаванням повідомлення користувача:
1. Повідомлення з role=developer, яке описує пісочницю, що застосовується лише до інструмента shell, наданого Codex, визначеного в розділі tools. Тобто інші інструменти, такі як ті, що надаються серверами MCP, не ізольовані в пісочниці Codex і повинні самостійно забезпечувати свої бар'єри.
Повідомлення створюється на основі шаблону, де ключові фрагменти вмісту беруться з фрагментів Markdown, вбудованих у Codex CLI, таких як workspace_write.md(відкривається у новому вікні) і on_request.md(відкривається у новому вікні):
2. (Необов’язково) Повідомлення з role=developer, вміст якого — це значення developer_instructions, прочитане з файлу config.toml користувача.
3. (Необов’язково) Повідомлення з role=user, вмістом якого є «інструкції користувача», які не беруться з одного файлу, а збираються з кількох джерел(відкривається у новому вікні). Загалом, більш конкретні інструкції з'являються пізніше:
- Вміст файлів
AGENTS.override.mdтаAGENTS.mdу$CODEX_HOME. - З урахуванням обмеження (32 KiB, за замовчуванням), переглядається кожна папку від кореня Git/проєкту
cwd(якщо він існує) до самогоcwd: додається вміст будь-якого зAGENTS.override.md,AGENTS.mdабо будь-яке ім’я файлу, вказане вproject_doc_fallback_filenames у config.toml - Якщо було налаштовано будь-які навички(відкривається у новому вікні):
- короткий вступ про навички
- метадані навичок(відкривається у новому вікні) для кожної навички
- розділ про те, як користуватися навичками(відкривається у новому вікні)
4. Повідомлення з role=user, яке описує локальне середовище, в якому агент зараз працює. Це вказує на поточний робочий каталог і оболонку користувача(відкривається у новому вікні):
Після того як Codex виконає всі наведені вище обчислення для ініціалізації input, він додає повідомлення користувача, щоб розпочати розмову.
Попередні приклади зосереджувалися на вмісті кожного повідомлення, але зверніть увагу, що кожен елемент input є об’єктом JSON з type, role(відкривається у новому вікні) і content, як показано нижче:
Як тільки Codex формує повне JSON-навантаження для відправки в Responses API, він виконує HTTP POST-запит із заголовком Authorization, залежно від того, як кінцева точка Responses API налаштована в ~/.codex/config.toml (додаткові HTTP-заголовки та параметри запиту додаються, якщо вони вказані).
Коли сервер OpenAI Responses API отримує запит, він використовує JSON для формування промпту для моделі наступним чином (авжеж, користувацька реалізація Responses API може зробити інший вибір):
Як ви можете бачити, порядок перших трьох елементів у запиті визначається сервером, а не клієнтом. Водночас із цих трьох елементів лише вміст системного повідомлення також контролюється сервером, оскільки інструменти і інструкції визначаються клієнтом. Після цього йде input з JSON-повідомлення для завершення запиту.
Тепер, коли у нас є наш промпт, ми готові розпочати семплювання моделі.
Цей HTTP-запит до Responses API ініціює перший «етап» розмови в Codex. Сервер відповідає потоком подій сервера (SSE(відкривається у новому вікні)). Дані кожної події — це JSON-повідомлення з "type", що починається з "response", яке може виглядати приблизно так (повний список подій можна знайти в нашій документації API(відкривається у новому вікні)):
Codex споживає потік подій(відкривається у новому вікні) і повторно публікує їх як внутрішні об’єкти подій, які може використовувати клієнт. Події, такі як response.output_text.delta, використовуються для підтримки потокового передавання в інтерфейсі користувача, тоді як інші події, такі як response.output_item.added, перетворюються на об’єкти, які додаються до input для наступних викликів Responses API.
Припустімо, що перший запит до Responses API містить дві події response.output_item.done: одну з type=reasoning та одну з type=function_call. Ці події повинні бути представлені в полі input JSON, коли ми знову запитуємо модель з відповіддю на виклик інструмента:
Отриманий промпт для вибірки моделі в наступному запиті виглядатиме так:
Зокрема, зверніть увагу, що старий промпт є точним префіксом нового промпту. Це зроблено навмисно, оскільки це робить наступні запити набагато ефективнішими, адже дає нам змогу скористатися кешуванням запитів (про що ми поговоримо в наступному розділі про продуктивність).
Озираючись на нашу першу діаграму циклу агента, ми бачимо, що між процесом інференції та викликом інструментів може бути багато ітерацій. Запит може продовжувати збільшуватися, доки ми нарешті не отримаємо повідомлення помічника, яке вказує на завершення ходу:
У Codex CLI ми показуємо користувачеві повідомлення асистента та фокусуємо композер, щоб вказати користувачеві, що тепер його «черга» продовжити розмову. Якщо користувач відповідає, то як повідомлення асистента з попереднього ходу, так і нове повідомлення користувача потрібно додати до input у запиті Responses API, щоб розпочати новий хід:
Знову ж таки, оскільки ми продовжуємо розмову, довжина input, який ми надсилаємо до Responses API, постійно збільшується:
Розглянемо, що означає цей постійно зростаючий запит для продуктивності.
Можливо, ви запитуєте себе: «Зачекайте, хіба цикл агента не є квадратичним з точки зору обсягу JSON, надісланого до Responses API протягом розмови?» Так, ви маєте рацію. Хоча Responses API підтримує необов’язковий параметр previous_response_id(відкривається у новому вікні) для пом’якшення цієї проблеми, Codex наразі його не використовує, головним чином для збереження запитів повністю без фіксування стану та для підтримки конфігурацій нульового збереження даних (ZDR).
Уникання previous_response_id спрощує роботу для постачальника Responses API, оскільки гарантує, що кожен запит не зберігає стан. Це також спрощує підтримку клієнтів, які обрали нульове збереження даних (ZDR)(відкривається у новому вікні), оскільки зберігання даних, необхідних для підтримки previous_response_id, суперечило б принципам ZDR. Зверніть увагу, що клієнти, які обрали ZDR, не втрачають можливості отримувати вигоду з пропрієтарних повідомлень міркування з попередніх кроків, оскільки пов’язаний encrypted_content можна розшифрувати на сервері. (OpenAI зберігає ключ розшифрування клієнта, але не його дані.) Перегляньте пул-реквести #642(відкривається у новому вікні) та #1641(відкривається у новому вікні) щодо відповідних змін у Codex для підтримки ZDR.
Загалом, вартість вибірки моделі перевищує вартість мережевого трафіку, що робить вибірку основною метою наших зусиль з підвищення ефективності. Ось чому кешування промптів настільки важливе, адже воно дозволяє повторно використовувати обчислення з попереднього виклику інференції. Коли ми отримуємо влучання в кеш, вибірка моделі є лінійною, а не квадратичною. Наша документація з кешування промптів (відкривається у новому вікні)пояснює це докладніше:
Влучання в кеш можливі лише для точних збігів префікса в межах промпту. Щоб скористатися перевагами кешування, розміщуйте статичний контент, такий як інструкції та приклади, на початку запиту, а змінний контент, наприклад, інформацію, специфічну для користувача, — наприкінці. Це також стосується зображень та інструментів, які повинні бути ідентичними між запитами.
З огляду на це, розгляньмо, які типи операцій можуть спричинити «промах кешу» у Codex:
- Зміна доступних для моделі
toolsпосеред розмови. - Зміна
моделі, яка є ціллю запиту до Responses API (на практиці це змінює третій елемент в оригінальному промпті, оскільки він містить інструкції, специфічні для моделі). - Зміна конфігурації пісочниці, режиму затвердження або поточного робочого каталогу.
Команда Codex повинна бути уважною при впровадженні нових функцій у Codex CLI, які можуть скомпрометувати кешування промптів. Наприклад, наша початкова підтримка інструментів MCP призвела до помилки, через яку ми не змогли перелічити інструменти в узгодженому порядку(відкривається у новому вікні), що спричиняло промахи кешу. Зверніть увагу, що інструменти MCP можуть бути особливо складними, оскільки сервери MCP можуть змінювати список інструментів, які вони надають, на ходу через сповіщення notifications/tools/list_changed(відкривається у новому вікні). Обробка цього сповіщення посеред довгої розмови може спричинити вартісний промах кешу.
Коли це можливо, ми обробляємо зміни конфігурації, що відбуваються під час розмови, додаючи нове повідомлення до input, щоб відобразити зміну, а не змінюючи попереднє повідомлення:
- Якщо змінюється конфігурація пісочниці або режим затвердження, ми вставляємо(відкривається у новому вікні) нове повідомлення
role=developerу тому ж форматі, що й оригінальний елемент<permissions instructions>. - Якщо поточний робочий каталог змінюється, ми вставляємо(відкривається у новому вікні) нове повідомлення
role=userу тому ж форматі, що й оригінальне<environment_context>.
Ми докладаємо значних зусиль, щоб забезпечити влучання в кеш для підвищення продуктивності. Є ще один важливий ресурс, яким ми повинні керувати: контекстне вікно.
Наша загальна стратегія уникання вичерпання контекстного вікна полягає в тому, щоб ущільнювати розмову, коли кількість токенів перевищує певний поріг. Зокрема, ми замінюємо 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.


