Codex CLI(otwiera nowe okno) jest naszym wieloplatformowym lokalnym agentem oprogramowania zaprojektowanym w celu wprowadzania godnych zaufania zmian wysokiej jakości w oprogramowaniu, przy jednoczesnym zapewnieniu bezpiecznego i wydajnego działania na komputerze użytkownika. Od momentu wprowadzenia CLI w kwietniu nauczyliśmy się bardzo wiele na temat tworzenia agenta oprogramowania światowej klasy. Aby omówić te spostrzeżenia, publikujemy pierwszy wpis z serii, w której będziemy analizować różne aspekty działania Codex oraz trudno zdobyte doświadczenia. (Bardziej szczegółowe informacje na temat struktury interfejsu CLI Codex można znaleźć w naszym repozytorium open source pod adresem https://github.com/openai/codex(otwiera nowe okno). Jeśli chcesz dowiedzieć się więcej, szczegółowe informacji dotyczące naszych decyzji projektowych zostały zapisane w zgłoszeniach GitHub i żądaniach pull request).
Na początek skupimy się na pętli agenta, która stanowi podstawową logikę w Codex CLI odpowiedzialną za koordynację interakcji między użytkownikiem, modelem i narzędziami wywoływanymi przez model w celu wykonania znaczących zadań programowych. Mamy nadzieję, że ten wpis pozwoli Ci dobrze zrozumieć rolę, jaką nasz agent (lub „kontroler”) odgrywa w wykorzystaniu modelu LLM.
Zanim przejdziemy do sedna, warto wyjaśnić kilka pojęć: w OpenAI termin „Codex” obejmuje pakiet agentów oprogramowania, w tym Codex CLI, Codex Cloud i rozszerzenie Codex VS Code. Ten post skupia się na kontrolerze Codex, który zapewnia podstawową pętlę agenta i logikę wykonania stanowiącą podstawę wszystkich środowisk Codex i udostępnianą za pośrednictwem interfejsu CLI Codex. Dla ułatwienia będziemy używać terminów „Codex” i „Codex CLI” zamiennie.
U podstaw każdego agenta AI leży coś, co określa się mianem „pętli agenta”. Uproszczona ilustracja pętli agenta wygląda następująco:
Na początku agent pobiera dane wejściowe od użytkownika, aby uwzględnić je w zestawie instrukcji tekstowych przygotowywanych dla modelu, określanych jako polecenie.
Kolejnym krokiem jest wysłanie do modelu instrukcji i poproszenie go o wygenerowanie odpowiedzi, co nazywamy wnioskowaniem. Podczas wnioskowania polecenie tekstowe jest najpierw przekształcane w sekwencję wejściowych tokenów(otwiera nowe okno)— liczb całkowitych, które indeksują słownictwo modelu. Te tokeny są następnie używane do próbkowania modelu, co prowadzi do wygenerowania nowej sekwencji tokenów wyjściowych.
Tokeny wyjściowe są przekształcane z powrotem na tekst, który staje się odpowiedzią modelu. Ponieważ tokeny są generowane stopniowo, tłumaczenie może odbywać się w trakcie działania modelu, dlatego wiele aplikacji opartych na LLM wyświetla dane wyjściowe formie strumienia. W praktyce wnioskowanie jest zazwyczaj ukryte za interfejsem API, który operuje na tekście, oddzielając szczegóły tokenizacji.
W wyniku etapu wnioskowania model albo (1) generuje ostateczną odpowiedź na pierwotne dane wejściowe użytkownika, albo (2) żąda wywołania narzędzia, które agent ma wykonać (e.g., “uruchom ls i zgłoś dane wyjściowe”). W przypadku (2) agent wykonuje wywołanie narzędzia i dołącza jego wynik do pierwotnego polecenia. Te dane wyjściowe są wykorzystywane do wygenerowania nowych danych wejściowych, które służą do ponownego wysłania zapytania do modelu; agent może następnie uwzględnić te nowe informacje i spróbować ponownie.
Proces ten powtarza się do momentu, w którym model przestaje wysyłać wywołania narzędzi i zamiast tego generuje komunikat dla użytkownika (określany jako komunikat asystenta w modelach OpenAI). W wielu przypadkach wiadomość ta stanowi bezpośrednią odpowiedź na pierwotną prośbę użytkownika, ale może również być pytaniem uzupełniającym skierowanym do użytkownika.
Ponieważ agent może wykonywać wywołania narzędzi, które modyfikują środowisko lokalne, jego „dane wyjściowe” nie ogranicza się do komunikatu asystenta. W wielu przypadkach głównym wynikiem działania agenta programowego jest kod, który zapisuje lub edytuje na komputerze użytkownika. Niemniej jednak każda kolejka zawsze kończy się komunikatem asystenta – takim jak „dodałem plik architecture.md, o które prosiłeś” – który sygnalizuje stan zakończenia pętli agenta. Z punktu widzenia agenta jego praca została zakończona, po czym kontrolę przejmuje użytkownik.
Droga od wprowadzenia danych przez użytkownika do odpowiedzi agenta pokazana na diagramie jest określana jako jedna tura rozmowy (wątek w Codex). Taka tura rozmowy może jednak obejmować wiele iteracji pomiędzy wnioskowaniem modelu a wywołaniami narzędzi. Za każdym razem, gdy użytkownik wysyła nową wiadomość w ramach istniejącej konwersacji, historia konwersacji jest uwzględniana jako część polecenia dla nowej tury, które obejmuje wiadomości i wywołania narzędzi z poprzednich tur:
Oznacza to, że wraz z rozwojem konwersacji rośnie długość polecenia używanego do próbkowania modelu. Długość ta ma znaczenie, ponieważ każdy model posiada okno kontekstowe, które stanowi maksymalną liczbę tokenów, jaką może wykorzystać dla jednego wywołania wnioskowania. Należy pamiętać, że to okno obejmuje zarówno tokeny wejściowe, jak i wyjściowe. Jak można sobie wyobrazić, agent może zdecydować się na wykonanie setek wywołań narzędzi w jednej turze, co potencjalnie może wyczerpać okno kontekstowe. Z tego powodu jednym z wielu obowiązków agenta jest zarządzanie oknem kontekstu. Teraz przyjrzyjmy się temu, jak Codex uruchamia pętlę agenta.
CLI Codex wysyła żądania HTTP do interfejsu Responses API(otwiera nowe okno) w celu uruchomienia wnioskowania modelu. Zobaczymy, jak przepływają informacje przez Codex, który wykorzystuje interfejs API Responses do sterowania pętlą agenta.
Punkt końcowy API Responses używany przez Codex CLI jest konfigurowalny(otwiera nowe okno), więc można go używać z dowolnym punktem końcowym, który implementuje interfejs API Responses(otwiera nowe okno):
- Podczas korzystania z ChatGPT po zalogowaniu się(otwiera nowe okno) za pomocą Codex CLI adres
https://chatgpt.com/backend-api/codex/responsesjest używany jako punkt końcowy. - Podczas korzystania z uwierzytelniania kluczem API(otwiera nowe okno) w przypadku modeli hostowanych przez OpenAI adres
https://api.openai.com/v1/responsesjest używany jako punkt końcowy. - Podczas uruchamiania Codex CLI z opcją
--ossw celu użycia gpt-oss z ollama 0.13.4+(otwiera nowe okno) lub LM Studio 0.3.39+(otwiera nowe okno) domyślnie uruchamia sięhttp://localhost:11434/v1/responseslokalnie na komputerze użytkownika. - Z Codex CLI można korzystać z interfejsem API Responses hostowanym przez dostawcę usług w chmurze, takiego jak Azure.
Przyjrzyjmy się, w jaki sposób Codex tworzy polecenie dla pierwszego wywołania wnioskowania w konwersacji.
Jako użytkownik końcowy nie określasz polecenia używanego do dosłownego próbkowania modelu podczas zapytania do interfejsu API Responses. Zamiast tego określasz różne typy danych wejściowych jako część zapytania, a serwer interfejsu Responses API decyduje, jak ustrukturyzować te informacje w polecenie, które model ma przetworzyć. Możesz postrzegać polecenie jako „listę elementów”. W tej sekcji wyjaśnimy, w jaki sposób Twoje zapytanie jest przekształcane w tę listę.
W początkowym poleceniu każdy element na liście jest powiązany z rolą. Pole role wskazuje, jaką wagę powinny mieć powiązane treści oraz jest jedną z następujących wartości (w kolejności malejącego priorytetu): system, developer, user, assistant.
API Responses(otwiera nowe okno) przyjmuje ładunek JSON z wieloma parametrami. Skupimy się na trzech następujących:
instructions(otwiera nowe okno): komunikat systemu (lub programisty) wstawiony do kontekstu modelu,tools(otwiera nowe okno): lista narzędzi, które model może wywołać podczas generowania odpowiedziinput(otwiera nowe okno): lista tekstów, obrazów lub plików wprowadzonych do modelu
W Codex pole instructions jest odczytywane z model_instructions_file(otwiera nowe okno) w ~/.codex/config.toml, jeśli zostało określone; w przeciwnym razie stosuje się base_instructions powiązane z modelem(otwiera nowe okno). Instrukcje specyficzne dla modelu znajdują się w repozytorium Codex i są dołączane do CLI (np. gpt-5.2-codex_prompt.md(otwiera nowe okno)).
Pole tools stanowi listę definicji narzędzi zgodnych ze schematem określonym przez interfejs API Responses API. W przypadku Codex obejmuje to narzędzia dostarczane przez Codex CLI, narzędzia dostarczane przez Responses API, które powinny być udostępnione Codex, a także narzędzia dostarczane przez użytkownika, zazwyczaj za pośrednictwem serwerów MCP:
Wreszcie pole input w ładunku JSON stanowi listę elementów. Codex wstawia następujące elementy(otwiera nowe okno) do pola input przed dodaniem wiadomości użytkownika:
1. Wiadomość z role=developer, która opisuje piaskownicę dotyczącą wyłącznie narzędzia powłoki dostarczanego przez Codex, zdefiniowanego w sekcji narzędzia. Oznacza to, że inne narzędzia, takie jak te dostarczane przez serwery MCP, nie są uruchamiane w piaskownicy przez Codex i odpowiadają za egzekwowanie własnych zabezpieczeń.
Wiadomość jest tworzona na podstawie szablonu, w którym kluczowe elementy treści pochodzą z fragmentów Markdown dołączonych do Codex CLI, takich jak workspace_write.md(otwiera nowe okno) and on_request.md(otwiera nowe okno):
2. (Opcjonalnie) Wiadomość z role=developer, której zawartością jest wartość developer_instructions odczytana z pliku config.toml użytkownika.
3. (Opcjonalnie) Wiadomość z role=user zawierająca „instrukcje użytkownika”, które nie pochodzą z jednego pliku, lecz są zagregowane z wielu źródeł(otwiera nowe okno). Zasadniczo bardziej szczegółowe instrukcje pojawiają się później:
- Zawartość plików
AGENTS.override.mdiAGENTS.mdw katalogu$CODEX_HOME - Z zastrzeżeniem limitu (32 KiB, domyślnie), przeszukaj każdy folder od katalogu głównego Git/projektu
cwd(jeśli istnieje) aż do samegocwd: dodaj zawartość dowolnego z plikówAGENTS.override.md,AGENTS.mdlub dowolną nazwę pliku określoną przezproject_doc_fallback_filenames w config.toml - Jeśli jakiekolwiek umiejętności(otwiera nowe okno) zostały skonfigurowane:
- krótki wstęp dotyczący umiejętności,
- metadane każdej umiejętności(otwiera nowe okno),
- sekcja jak korzystać z umiejętności(otwiera nowe okno).
4. Wiadomość z role=user, która opisuje środowisko lokalne, w którym agent obecnie działa. Określa to bieżący katalog roboczy oraz powłokę użytkownika(otwiera nowe okno):
Po wykonaniu wszystkich powyższych obliczeń w celu zainicjowania parametru input Codex dołącza wiadomość użytkownika, aby rozpocząć konwersację.
Poprzednie przykłady koncentrowały się na treści każdej wiadomości, ale należy pamiętać, że każdy element danych wejściowych jest obiektem JSON z parametrami type, role(otwiera nowe okno) i content, jak opisano poniżej:
Po utworzeniu pełnego ładunku JSON do wysłania do interfejsu API Responses Codex wysyła żądanie HTTP POST z nagłówkiem Authorization w zależności od konfiguracji punktu końcowego API Responses w pliku ~/.codex/config.toml (dodatkowe nagłówki HTTP i parametry zapytania są dodawane, jeśli zostały określone).
Po otrzymaniu żądania serwer OpenAI Responses API wykorzystuje format JSON do wygenerowania polecenia dla modelu w następujący sposób (oczywiście niestandardowa implementacja Responses API może dokonać innego wyboru):
Jak widać, kolejność pierwszych trzech pozycji w poleceniu jest określana przez serwer, a nie klienta. Należy jednak pamiętać, że z tych trzech elementów jedynie treść komunikatu systemowego jest kontrolowana przez serwer, gdyż parametry tools i instructions określane są przez klienta. Następnie pojawiają się dane wejściowe z ładunku JSON, które uzupełniają polecenie.
Teraz, gdy dysponujemy już poleceniem, jesteśmy gotowi do przetestowania modelu.
To żądanie HTTP skierowane do interfejsu API Responses inicjuje pierwszą „turę” konwersacji w Codex. Serwer odpowiada strumieniem SSE (Server-Sent Events(otwiera nowe okno)).” Dane każdego zdarzenia to ładunek JSON z „typem”, który zaczyna się od „odpowiedzi”, co może wyglądać mniej więcej tak (pełną listę zdarzeń można znaleźć w naszych dokumentacji API):(otwiera nowe okno)
Codex przetwarza strumień zdarzeń(otwiera nowe okno) i ponownie publikuje je jako wewnętrzne obiekty zdarzeń, które mogą być używane przez klienta. Zdarzenia takie jak response.output_text.delta są używane do obsługi przesyłania strumieniowego w interfejsie użytkownika, podczas gdy inne zdarzenia, takie jak response.output_item.added, są przekształcane w obiekty, które są dołączane do parametru input dla kolejnych wywołań interfejsu API Responses.
Załóżmy, że pierwsze żądanie do interfejsu API Responses zawiera dwa zdarzenia response.output_item.done: jedno z type=reasoning i jedno z type=function_call. Zdarzenia te muszą być przedstawione w polu input JSON, gdy ponownie wysyłamy zapytanie do modelu z odpowiedzią na wywołanie narzędzia:
Wynikowe polecenie użyte do próbkowania modelu w ramach kolejnego zapytania wyglądałoby następująco:
W szczególności należy zwrócić uwagę, że stare polecenie jest dokładnym prefiksem nowego polecenia. Jest to zamierzone, ponieważ sprawia, że kolejne żądania są znacznie bardziej wydajne, ponieważ umożliwia nam to wykorzystanie buforowania poleceń (które omówimy w następnej sekcji poświęconej wydajności).
Patrząc wstecz na nasz pierwszy diagram pętli agenta, widzimy, że między wnioskowaniem a wywołaniem narzędzia może wystąpić wiele iteracji. Polecenie może się rozrastać, aż w końcu otrzymamy wiadomość od asystenta, co będzie oznaczać koniec tury:
W Codex CLI wyświetlamy użytkownikowi komunikat asystenta i skupiamy uwagę na polu tworzenia wiadomości, aby wskazać użytkownikowi, że „teraz jego kolej” na kontynuowanie konwersacji. Jeśli użytkownik odpowie, zarówno wiadomość asystenta z poprzedniej tury, jak i nowa wiadomość użytkownika muszą zostać dołączone do parametru input w żądaniu API Responses, aby rozpocząć nową turę:
Ponownie, z uwagi na kontynuację konwersacji, długość parametru input, który wysyłamy do Responses API, stale się zwiększa:
Zobaczmy, co to ciągle rosnące polecenie oznacza dla wydajności.
Być może zadajesz sobie pytanie: „Chwila, czy pętla agenta nie rośnie kwadratowo pod względem liczby plików JSON wysyłanego do Responses API w trakcie rozmowy?” I masz rację, tak właśnie jest. Interfejs Responses API obsługuje opcjonalny parametr previous_response_id(otwiera nowe okno) w celu złagodzenia tego problemu, jednak Codex nie stosuje go obecnie – głównie po to, aby żądania pozostawały całkowicie bezstanowe, oraz aby umożliwić obsługę konfiguracji nieprzechowywania danych (ZDR).
Unikanie previous_response_id upraszcza sprawę dostawcy API Responses, ponieważ zapewnia, że każde żądanie jest bezstanowe. Ułatwia to również obsługę klientów, którzy zdecydowali się na zasadę nieprzechowywanie danych (ZDR)(otwiera nowe okno), ponieważ przechowywanie danych wymaganych do obsługi previous_response_id byłoby sprzeczne z tą zasadą. Należy pamiętać, że klienci stosujący zasadę ZDR nie tracą możliwości korzystania z zastrzeżonych komunikatów rozumowania z poprzednich tur, ponieważ powiązane treści encrypted_content można odszyfrować na serwerze. (OpenAI przechowuje klucz deszyfrujący klienta ZDR, ale nie przechowuje jego danych). Zobacz żądania pull request #642(otwiera nowe okno) i #1641(otwiera nowe okno) dla powiązanych zmian w Codex, które wspierają ZDR.
Ogólnie rzecz biorąc, koszt próbkowania modelu przewyższa koszt ruchu sieciowego, co sprawia, że próbkowanie jest głównym celem naszych działań mających na celu poprawę wydajności. Dlatego tak ważne jest buforowanie poleceń, ponieważ umożliwia nam ponowne wykorzystanie obliczeń z poprzedniego wywołania wnioskowania. W przypadku trafień w pamięci podręcznej, próbkowanie modelu jest liniowe, a nie kwadratowe. Więcej informacji można znaleźć w dokumentacji dotyczącej buforowania poleceń (otwiera nowe okno):
Trafienia w pamięci podręcznej są możliwe tylko w przypadku dokładnych dopasowań prefiksów w poleceniach. Aby wykorzystać zalety buforowania, należy umieścić treści statyczne, takie jak instrukcje i przykłady, na początku polecenia, a treści zmienne, takie jak informacje specyficzne dla użytkownika, na końcu. Dotyczy to również obrazów i narzędzi, które muszą być identyczne między żądaniami.
Mając to na uwadze, zastanówmy się, jakie rodzaje operacji mogą powodować „braki trafień w pamięci podręcznej” w Codex:
- Zmiana
narzędzidostępnych dla modelu w trakcie rozmowy. - Zmiana
modelu, który jest celem żądania Responses API (w praktyce zmienia to trzeci element oryginalnego polecenia, ponieważ zawiera instrukcje specyficzne dla modelu). - Zmiana konfiguracji piaskownicy, trybu zatwierdzania lub bieżącego katalogu roboczego.
Zespół Codex musi zachować ostrożność podczas wprowadzania nowych funkcji do interfejsu CLI Codex, które mogłyby zakłócić buforowanie poleceń. Na przykład nasze początkowe wsparcie dla narzędzi MCP spowodowało błąd, w wyniku którego nie udało nam się wyliczyć narzędzi w spójnej kolejności(otwiera nowe okno), co spowodowało braki trafień w pamięci podręcznej. Należy pamiętać, że narzędzia MCP mogą być szczególnie problematyczne, ponieważ serwery MCP mogą na bieżąco zmieniać listę udostępnianych narzędzi za pomocą powiadomienia notifications/tools/list_changed(otwiera nowe okno). Uwzględnienie tego powiadomienia w trakcie długiej konwersacji może spowodować kosztowne braki trafień w pamięci podręcznej.
W miarę możliwości obsługujemy zmiany konfiguracji, które mają miejsce w trakcie konwersacji, poprzez dołączenie nowej wiadomości do danych wejściowych odzwierciedlenia zmiany, zamiast modyfikowania wcześniejszej wiadomości:
- Jeśli konfiguracja piaskownicy lub tryb zatwierdzania się zmienią, wstawiamy(otwiera nowe okno) nową wiadomość
role=developerw tym samym formacie co oryginalny element<permissions instructions>. - Jeśli bieżący katalog roboczy się zmieni, wstawiamy(otwiera nowe okno) nową wiadomość
role=userw tym samym formacie co oryginalny element<environment_context>.
Dokładamy wszelkich starań, aby zapewnić trafienia w pamięci podręcznej w celu uzyskania wysokiej wydajności. Jest jeszcze jeden kluczowy zasób wymagający zarządzania: okno kontekstowe.
Nasza ogólna strategia pozwalająca uniknąć wyczerpania się okna kontekstowego polega na kompresowaniu konwersacji, gdy liczba tokenów przekroczy określony próg. W szczególności zastępujemy dane wejściowe nową, mniejszą listą elementów reprezentatywnych dla rozmowy, co pozwala agentowi kontynuować pracę ze zrozumieniem tego, co działo się do tej pory. Wczesna implementacja kompresji(otwiera nowe okno) wymagała od użytkownika ręcznego wywołania polecenia /compact, które wysyłało zapytanie do interfejsu API Responses, wykorzystując istniejącą konwersację oraz niestandardowe instrukcje dotyczące podsumowania(otwiera nowe okno). Codex użył wiadomości asystenta zawierającej podsumowanie jako nowych danych wejściowych(otwiera nowe okno) do kolejnych tur rozmowy.
Od tego czasu interfejs API Responses ewoluował i może obsługiwać specjalny punkt końcowy /responses/compact(otwiera nowe okno), który wykonuje kompresję bardziej wydajnie. Zwraca listę elementów(otwiera nowe okno), których można użyć w miejsce poprzednich danych wejściowych, aby kontynuować konwersację, oczyszczając przy tym okno kontekstowe. Ta lista zawiera specjalny element type=compaction z nieprzezroczystym elementem encrypted_content, który zachowuje utajone rozumienie modelu dotyczące oryginalnej konwersacji. Teraz Codex automatycznie używa tego punktu końcowego do kompresji rozmowy, gdy wartość auto_compact_limit(otwiera nowe okno) zostanie przekroczona.
Przedstawiliśmy pętlę agenta Codex i omówiliśmy sposób, w jaki Codex tworzy i zarządza kontekstem podczas wysyłania zapytania do modelu. W międzyczasie zwróciliśmy uwagę na praktyczne kwestie i najlepsze praktyki, które mają zastosowanie dla wszystkich tworzących pętlę agenta w oparciu o API Responses.
Pętla agentów stanowi podstawę Codex, ale to dopiero początek. W kolejnych postach zajmiemy się architekturą CLI, przyjrzymy się, jak działa używanie narzędzi, i przyjrzymy się bliżej modelowi tworzenia piaskownic Codex.


