Codex CLI(отваря се в нов прозорец) е нашият междуплатформен локален софтуерен агент, създаден да извършва висококачествени и надеждни софтуерни промени, като работи безопасно и ефективно на Вашето устройство. Научихме изключително много за това как да изградим софтуерен агент от световна класа откакто за първи път стартирахме CLI през април. Ще разгледаме тези изводи в тази публикация, която е първата от продължителна поредица, разглеждаща различни аспекти на работата на Codex, както и трудно извоювани уроци. (За още по-подробен поглед върху това как е изграден Codex CLI, посетете нашето хранилище с отворен код на https://github.com/openai/codex(отваря се в нов прозорец). Много от по-дребните подробности за нашите решения относно дизайна са документирани относно инциденти и искания за предложения в GitHub, ако желаете да научите повече.)
Като начало ще се фокусираме върху агентен цикъл, който е основната логика в Codex CLI, отговорна за оркестрирането на взаимодействието между потребителя, модела и инструментите, които моделът извиква, за да извършва смислена софтуерна работа. Надяваме се, че тази публикация Ви дава добра представа за ролята, която нашият агент, или harness („поводите“) играе при използването на големи езикови модели.
Преди да започнем, кратка бележка относно терминологията. В OpenAI „Codex“ обхваща набор от предложения за софтуерни агенти, включително Codex CLI, Codex Cloud и разширението Codex за VS Code. Тази публикация се фокусира върху Codex harness, който предоставя основния цикъл на агента и логиката за изпълнение, които са в основата на всички изживявания с Codex и се предоставят чрез Codex CLI. За удобство тук ще използваме термините „Codex“ и „Codex CLI“ взаимозаменяемо.
В основата на всеки интелигентен агент е нещо, наречено „цикъл на агента“. Опростена илюстрация на цикъла на агента изглежда по следния начин:
За да започне, агентът взема входни данни от потребителя, за да ги включи в набора от текстови инструкции, които подготвя за модела, известен като подкана.
Следващата стъпка е да направите заявка към модела, като му изпратите нашите указания и го помолите да генерира отговор – процес, известен като инференция. По време на инференция текстовата подкана първо се преобразува в последователност от входни токени(отваря се в нов прозорец) – цели числа, които служат като индекси в речника на модела. След това тези токени се използват за изпробване на модела, като се генерира нова последователност от изходни токени.
Изходните токени се превеждат обратно в текст, който е отговорът на модела. Тъй като токените се генерират постепенно, преводът може да се извършва, докато моделът работи, което е причината много приложения, базирани на големи езикови модели, да показват поточни изходни данни. На практика, извеждането обикновено е капсулирано зад ППИ (API), който работи с текст, като скрива детайлите на токенизацията.
В резултат на стъпката на инференция моделът или (1) създава окончателен отговор на първоначалните входни данни на потребителя, или (2) прави заявка за инструмент, която агентът се очаква да изпълни (напр. „изпълнете ls и докладвайте изходни те данни“). Във втория случай агентът изпълнява заявката за инструмента и добавя изходните му данни към оригиналната подкана. Тези изходни данни се използват за генериране на нови входни данни, които се използват за повторно запитване към модела. След това агентът може да вземе предвид тази нова информация и да опита отново.
Този процес се повтаря, докато моделът не спре да прави заявки за инструменти и вместо това не създаде съобщение за потребителя (наричано съобщение на асистента в моделите на OpenAI). В много случаи това съобщение директно отговаря на първоначалната заявка на потребителя, но може също така да бъде последващ въпрос към потребителя.
Тъй като агентът може да изпълнява заявки за инструменти, които променят локалната среда, неговият „резултат“ не се ограничава само до съобщението на асистента. В много случаи основният резултат от работата на софтуерен агент е кодът, който той пише или редактира на вашата машина. Въпреки това, всеки ход винаги завършва със съобщение от асистента – например „Добавих architecture.md, който поискахте“ – което сигнализира за състояние на прекратяване в цикъла на агента. От гледна точка на агента, работата му е завършена и контролът е обратно в ръцете на потребителя.
Пътят от потребителски входни данни до отговор на агент, показан на диаграмата, се нарича един ход на разговор (един низв Codex). Въпреки това този ход на разговора може да включва множество итерации между извеждането на заключения от модела и заявките за инструменти. Всеки път, когато изпращате ново съобщение в съществуващ разговор, историята на разговора се включва като част от подкана за новия ход, която съдържа съобщенията и заявките за инструменти от предишни ходове:
Това означава, че с напредването на разговора се увеличава и дължината на подканата, използвана за изпробване на модела. Тази дължина е важна, защото всеки модел има контекстен прозорец, който представлява максималния брой токени, които може да използва за една заявка за инференция. Имайте предвид, че този прозорец включва както входни, така и изходни токени. Както вероятно си представяте, един агент може да реши да направи стотици заявки за инструменти в един ход, като потенциално изчерпи контекстния прозорец. Поради тази причина управлението на контекстния прозорец е една от многото отговорности на агента. Сега нека разгледаме това по-отблизо, за да видим как Codex изпълнява цикъла на агента.
Codex CLI изпраща HTTP заявки към Responses API(отваря се в нов прозорец), за да извърши инференция на модел. Ще разгледаме как информацията протича през Codex, който използва Responses API, за да задвижва цикъла на агента.
Крайната точка на API за отговори, която Codex CLI използва, може да се конфигурира(отваря се в нов прозорец), така че може да се използва с всяка крайна точка, която прилага 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 може да се използва с API за отговори, хостван от доставчик на облачни услуги като Azure.
Нека разгледаме как Codex създава подкана за първото извикване на инференция в разговор.
Като краен потребител Вие не посочвате дословно подкана, използвана за изпробване на модела, когато правите заявка към Responses API. Вместо това Вие посочвате различни типове входни данни като част от Вашата заявка, а сървърът на API за отговори решава как да структурира тази информация в подкана, която моделът е проектиран да обработва. Можете да разглеждате подканата като „списък с елементи“; този раздел ще обясни как Вашата заявка се преобразува в този списък.
В първоначалната подкана всеки елемент от списъка е свързан с роля. Ролята показва колко голяма тежест трябва да има свързаното съдържание и е една от следните стойности (в низходящ ред на приоритети): система, разработчик, потребител, асистент.
Responses API(отваря се в нов прозорец) приема данни от JSON с много параметри. Ще се съсредоточим върху тези три:
указания(отваря се в нов прозорец): системно съобщение или съобщение на разработчика, вмъкнато в контекста на моделаинструменти(отваря се в нов прозорец): списък с инструменти, които моделът може да използва, докато генерира отговорвходни данни(отваря се в нов прозорец): списък с уводни данни към модела, под формата на текст, изображение или файлове
В Codex полето указания се чете от model_instructions_file(отваря се в нов прозорец) в ~/.codex/config.toml, ако е зададено; в противен случай се използват base_instructions свързани с модел(отваря се в нов прозорец). Указания, специфични за модела, се намират в хранилището Codex и са включени в CLI (напр., gpt-5.2-codex_prompt.md(отваря се в нов прозорец)).
Полето инструменти е списък с дефиниции на инструменти, които съответстват на схема, определена от Responses API. За Codex това включва инструменти, предоставени от Codex CLI, инструменти, предоставени от API за отговори, които трябва да бъдат направени достъпни за Codex, както и инструменти, предоставени от потребителя, обикновено чрез MCP сървъри:
И накрая, полето входни данни на JSON данните е списък с елементи. Codex вмъква следните елементи(отваря се в нов прозорец) във входни данни преди да добави съобщението на потребителя:
1. Съобщение с role=developer, което описва изолираната среда, която се прилага само за предоставения от Codex инструмент на командния ред, дефиниран в секцията инструменти. Тоест, други инструменти, като тези, предоставени от 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 извърши всички горепосочени изчисления за стартиране на входни данни, той добавя съобщението на потребителя, за да започне разговора.
Предишните примери се фокусираха върху съдържанието на всяко съобщение, но имайте предвид, че всеки елемент на входни данни е JSON обект с тип, роля(отваря се в нов прозорец) и съдържание, както следва:
След като Codex изгради пълните данни в JSON за изпращане към Responses API, той прави HTTP POST заявка със заглавка упълномощаване, в зависимост от това как е конфигурирана крайната точка на Responses API в ~/.codex/config.toml (допълнителни HTTP заглавки и параметри на заявката се добавят, ако са посочени).
Когато сървър на OpenAI Responses API получи заявката, той използва JSON, за да извлече подкана за модела, както следва (за да е сигурно, персонализирана имплементация на Responses API може да направи различен избор):
Както можете да видите, редът на първите три елемента в подканата се определя от сървъра, а не от клиента. Въпреки това, от тези три елемента, само съдържанието на системното съобщение се контролира от сървъра, тъй като инструментите и инструкциите се определят от клиента. След това следват входни данни от данните в JSON, за да се завърши подкана.
Сега, след като имаме нашата подкана, сме готови да изпробваме модела.
Тази HTTP заявка към Responses API инициира първия „етап“ на разговор в Codex. Сървърът отговаря с поток от събития, изпратени от сървъра (SSE(отваря се в нов прозорец)). Данните на всяко събитие са данни в JSON с "тип", който започва с "отговор", което може да бъде нещо подобно на ето това (пълен списък на събитията можете да намерите в нашата API документация(отваря се в нов прозорец)):
Codex консумира потока от събития(отваря се в нов прозорец) и ги публикува отново като обекти на вътрешни събития, които могат да бъдат използвани от клиент. Събития като response.output_text.delta се използват за поддръжка на поточно предаване в потребителския интерфейс, докато други събития като response.output_item.added се преобразуват в обекти, които се добавят към входни данни за последващи заявки към API за отговори.
Да предположим, че първата заявка към Responses API включва две събития response.output_item.done: едно с type=reasoning и едно с type=function_call. Тези събития трябва да бъдат представени в полето входни данни на JSON, когато отново направим запитване към модела с отговора на заявката за инструмента:
Получената подкана, използвана за извадка на модела като част от последващата заявка, би изглеждала така:
По-специално, обърнете внимание как старата подкана е точен начален низ на новата подкана. Това е умишлено, тъй като прави следващите заявки много по-ефективни, защото ни позволява да се възползваме от кеширане на подкани (което ще обсъдим в следващия раздел за производителност).
Като се върнем към първата ни диаграма на цикъла на агента, виждаме, че може да има много итерации между извеждането на заключения и заявките на инструменти. Подканата може да продължи да се разраства, докато накрая не получим съобщение от асистента, което да показва края на хода:
В Codex CLI представяме съобщението на асистента на потребителя и фокусираме композера, за да покажем на потребителя, че е негов „ред“ да продължи разговора. Ако потребителят отговори, както съобщението на асистента от предишния ход, така и новото съобщение на потребителя трябва да бъдат добавени към входни данни в заявката към Responses API, за да започне новият ход:
И отново, тъй като продължаваме разговора, дължината на входните данни, които изпращаме към Responses API, продължава да се увеличава:
Нека разгледаме как се отразява на производителността тази непрекъснато нарастваща подкана.
Може би се питате: „Чакайте, нали цикълът на агента е квадратичен по отношение на количеството JSON, изпратено към Responses API по време на разговора?“ Бихте били прави. Въпреки че API за отговори поддържа незадължителен параметър previous_response_id(отваря се в нов прозорец), за да смекчи този проблем, Codex не го използва в момента, главно за да поддържа заявките напълно без състояние и да поддържа конфигурации за нулево задържане на данни (ZDR).
Избягването на previous_response_id опростява нещата за доставчика на API за отговори, защото гарантира, че всяка заявка е без състояние. Това също така улеснява подпомагането на клиенти, които са избрали нулево задържане на данни (ZDR)(отваря се в нов прозорец), тъй като съхраняването на данните, необходими за поддръжка на previous_response_id, би било в противоречие със ZDR. Имайте предвид, че клиентите на ZDR не жертват възможността да се възползват от собствени съобщения за структурирано анализиране от предишни ходове, тъй като свързаното encrypted_content може да бъде декриптирано на сървъра. (OpenAI съхранява ключа за декриптиране на клиент на ZDR, но не и данните му.) Вижте PRs #642(отваря се в нов прозорец) и #1641(отваря се в нов прозорец) за свързаните промени в Codex за поддръжка на ZDR.
Обикновено разходите за изпробване на модела надвишават разходите за мрежов трафик, което прави изпробването основен фокус на нашите усилия за ефективност. Ето защо кеширането на подкани е толкова важно, тъй като ни позволява да използваме повторно изчисления от предишна заявка за инференция. Когато получаваме попадения в кеша, изпробването на модела е линейно, вместо квадратично. Нашата документация за кеширане на подкани (отваря се в нов прозорец)обяснява това по-подробно:
Попадения в кеша са възможни само при точни съвпадения на префикса в рамките на подкана. За да се възползвате от предимствата на кеширането, поставете статично съдържание, като указания и примери, в началото на Вашата подкана, а променливото съдържание, като информация, специфична за потребителя, поставете в края. Това се отнася и за изображенията и инструментите, които трябва да бъдат идентични между заявките.
Имайки това предвид, нека разгледаме какви типове операции биха могли да причинят „пропуски в кеша“ в Codex:
- Промяна на наличните за модела
инструментипо средата на разговора. - Промяна на
модела, който е цел на заявката към Responses API (на практика това променя третия елемент в оригиналната подкана, тъй като съдържа инструкции, специфични за модела). - Промяна на конфигурацията на изолираната среда, режима на одобрение или текущата работна директория.
Екипът на Codex трябва да внимава, когато въвежда нови функции в Codex CLI, които могат да компрометират кеширането на подкани. Например, първоначалната ни поддръжка за MCP инструменти въведе грешка, при която не успяхме да изброим инструментите в последователен ред(отваря се в нов прозорец), което доведе до пропуски в кеша. Имайте предвид, че инструментите на MCP могат да бъдат особено сложни, защото сървърите на MCP могат да променят списъка с инструменти, които предоставят, в движение чрез известие notifications/tools/list_changed(отваря се в нов прозорец). Спазването на това известие по средата на дълъг разговор може да доведе до скъп пропуск в кеша.
Когато е възможно, ние обработваме промените в конфигурацията, които се случват по средата на разговора, като добавяме ново съобщение към входни данни, за да отразим промяната, вместо да променяме по-ранно съобщение:
- Ако конфигурацията на изолираната среда или режимът на одобрение се променят, ние вмъкваме(отваря се в нов прозорец) ново съобщение
role=developerсъс същия формат като оригиналния елемент<permissions instructions>. - Ако текущата работна директория се промени, ние вмъкваме(отваря се в нов прозорец) ново съобщение
role=userсъс същия формат като оригиналното<environment_context>.
Полагаме големи усилия, за да осигурим попадения в кеша за по-добра производителност. Има още един ключов ресурс, който трябва да управляваме: контекстният прозорец.
Нашата обща стратегия, за да избегнем изчерпване на контекстния прозорец, е да уплътним разговора, след като броят на токените надхвърли определен праг. По-конкретно, заменяме входните данни с нов, по-малък списък от елементи, който е представителен за разговора, като по този начин позволяваме на агента да продължи с разбиране за това какво се е случило до момента. Ранна реализация на компресиране(отваря се в нов прозорец) изискваше потребителят ръчно да извика командата /compact, която правеше заявка към Responses API, използвайки съществуващия разговор плюс персонализирани инструкции за обобщение(отваря се в нов прозорец). Codex използва полученото съобщение от асистента, съдържащо резюмето като нови входни данни(отваря се в нов прозорец) за следващите ходове на разговора.
Оттогава API за отговори се е развил, за да поддържа специална /responses/compact крайна точка(отваря се в нов прозорец), която извършва по-ефективно компресиране. Това връща списък с елементи(отваря се в нов прозорец), които могат да се използват вместо предишните входни данни, за да продължите разговора, като същевременно освобождавате контекстния прозорец. Този списък включва специален елемент type=compaction с непрозрачен елемент encrypted_content, който запазва латентното разбиране на модела за оригиналния разговор. Сега Codex автоматично използва този крайна точка, за да уплътни разговора, когато auto_compact_limit(отваря се в нов прозорец) бъде превишен.
Представихме цикъла на агента на Codex и разгледахме как Codex създава и управлява своя контекст при заявка към модел. По пътя подчертахме практически съображения и най-добри практики, които важат за всеки, който изгражда агентен цикъл върху Responses API.
Въпреки че цикълът на агента в Codex е основата, това е само началото. В предстоящите публикации ще се задълбочим в архитектурата на CLI, ще разгледаме как е реализирано използването на инструменти и ще разгледаме по-подробно модела за изолирана среда на Codex.


