Preskočite na glavni sadržaj
OpenAI

23. januar 2026.

Inženjering

Razmotavanje petlje agenta Codex

Michael Bolin, član tehničkog osoblja

Učitavanje…

Codex CLI(otvara se u novom prozoru) je naš softverski agent za više platformi, dizajniran da proizvodi visokokvalitetne i pouzdane promjene softvera, dok sigurno i efikasno radi na vašem računaru. Naučili smo mnogo o tome kako izgraditi softverskog agenta svjetske klase otkako smo prvi put pokrenuli CLI u aprilu. Da bismo razjasnili te uvide, ovo je prva objava u seriji u kojoj ćemo istražiti različite aspekte kako Codex funkcioniše, kao i teško stečene lekcije. (Za još detaljniji uvid u to kako je Codex CLI izgrađen, posjetite naš repozitorij otvorenog koda na https://github.com/openai/codex(otvara se u novom prozoru). Mnogi od finijih detalja naših dizajnerskih odluka zabilježeni su u GitHub issue-ima i zahtjevima za spajanje ako želite saznati više.)

Za početak, fokusirat ćemo se na agent loop, koja je osnovna logika u Codex CLI-ju odgovorna za orkestriranje interakcije između korisnika, modela i alata koje model poziva kako bi obavio smislen softverski rad. Nadamo se da vam ovaj post pruža dobar uvid u ulogu koju naš agent (ili „harness“) ima u korištenju LLM-a.

Prije nego što zaronimo, kratka napomena o terminologiji: u OpenAI-u, “Codex” obuhvata niz ponuda softverskih agenata, uključujući Codex CLI, Codex Cloud i Codex VS Code ekstenziju. Ovaj post se fokusira na Codex harness, koji pruža osnovnu petlju agenta i logiku izvršavanja koja je temelj svih Codex iskustava i dostupna je putem Codex CLI. Radi lakšeg razumijevanja, ovdje ćemo koristiti termine “Codex” i “Codex CLI” naizmjenično.

Petlja agenta

U središtu svakog AI agenta nalazi se nešto što se naziva „petlja agenta“. Pojednostavljena ilustracija petlje agenta izgleda ovako:

Dijagram pod nazivom “Agent loop” koji prikazuje kako AI sistem obrađuje korisnički zahtjev, poziva alate, posmatra rezultate, ažurira svoj plan i vraća izlaze. Strelice povezuju korake kao što su unos korisnika, rezonovanje modela, radnje alata i konačni odgovor.

Za početak, agent uzima input od korisnika kako bi ga uključio u skup tekstualnih uputa koje priprema za model poznat kao upit.

Sljedeći korak je da modelu pošaljemo naše upute i zatražimo od njega da generira odgovor, proces poznat kao inference. Tokom inferencije, tekstualni upit se prvo prevodi u niz ulaznih tokena(otvara se u novom prozoru)—cijelih brojeva koji indeksiraju vokabular modela. Ovi tokeni se zatim koriste za uzorkovanje modela, stvarajući novi niz izlaznih tokena.

Izlazni tokeni se prevode natrag u tekst, što postaje odgovor modela. Budući da se tokeni proizvode postepeno, prijevod se može odvijati dok model radi, zbog čega mnoge aplikacije zasnovane na LLM-u prikazuju strimovani izlaz. U praksi, zaključivanje je obično enkapsulirano iza API-ja koji radi na tekstu, apstrahujući detalje tokenizacije.

Kao rezultat koraka izvođenja zaključaka, model ili (1) daje konačan odgovor na korisnikov originalni unos, ili (2) traži poziv alata koji agent treba izvršiti (npr., “pokreni ls i prijavi rezultat”). U slučaju (2), agent izvršava poziv alata i dodaje njegov izlaz izvornom upitu. Ovaj izlaz se koristi za generisanje novog unosa koji se koristi za ponovno upitivanje modela; agent zatim može uzeti ove nove informacije u obzir i pokušati ponovo.

Ovaj proces se ponavlja sve dok model ne prestane emitovati pozive alata i umjesto toga proizvede poruku za korisnika (u OpenAI modelima naziva se poruka asistenta). U mnogim slučajevima, ova poruka direktno odgovara na izvorni zahtjev korisnika, ali može biti i dodatno pitanje za korisnika.

Budući da agent može izvršavati pozive alata koji mijenjaju lokalno okruženje, njegov 'izlaz' nije ograničen samo na poruku asistenta. U mnogim slučajevima, primarni rezultat rada softverskog agenta je kod koji on piše ili uređuje na vašem računaru. Ipak, svaki potez uvijek završava porukom asistenta—poput “Dodao sam architecture.md koji ste tražili”—što signalizira završno stanje u petlji agenta. Iz perspektive agenta, njegov posao je završen i kontrola se vraća korisniku.

Putovanje od korisničkog unosa do odgovora agenta prikazano na dijagramu naziva se jednim turnom razgovora (a nit u Codexu). Iako ovaj korak razgovora može uključivati mnogo iteracija između model inferencije i poziva alata. Svaki put kada pošaljete novu poruku u postojeći razgovor, historija razgovora se uključuje kao dio upita za novi korak, koji uključuje poruke i pozive alata iz prethodnih koraka:

Dijagram pod nazivom “Višekoračna petlja agenta” koji prikazuje kako AI agent iterativno prima korisnički unos, generiše radnje, konsultuje alate, ažurira stanje i vraća rezultate. Uključuje označene korake, strelice i primjere izlaza alata koji prikazuju ciklus rezonovanja agenta.

To znači da kako razgovor raste, tako se povećava i dužina upita koji se koristi za uzorkovanje modela. Ova dužina je važna jer svaki model ima kontekstni prozor, što je maksimalan broj tokena koje može koristiti za jedan poziv inferencije. Imajte na umu da ovaj prozor uključuje i ulazne i izlazne tokene. Kao što možete zamisliti, agent može odlučiti napraviti stotine poziva alata u jednom potezu, što bi moglo iscrpiti kontekstni prozor. Iz tog razloga, upravljanje kontekstnim prozorom je jedna od mnogih odgovornosti agenta. Hajde da zaronimo i vidimo kako Codex pokreće petlju agenta.

Zaključivanje modela

Codex CLI šalje HTTP zahtjeve na Responses API(otvara se u novom prozoru) kako bi pokrenuo izvođenje modela. Ispitat ćemo kako informacije prolaze kroz Codex, koji koristi Responses API za pokretanje petlje agenta.

Krajnja tačka Responses API-ja koju koristi Codex CLI je podesiva(otvara se u novom prozoru), tako da se može koristiti s bilo kojom krajnjom tačkom koja implementira Responses API(otvara se u novom prozoru):

Hajde da istražimo kako Codex kreira upit za prvi poziv inferencije u razgovoru.

Kreiranje početnog upita

Kao krajnji korisnik, ne specificirate upit koji se koristi za doslovno uzorkovanje modela kada šaljete upit Responses API-ju. Umjesto toga, vi navedete različite tipove unosa kao dio vašeg upita, a server Responses API odlučuje kako strukturirati te informacije u upit koji je model dizajniran da koristi. Možete razmišljati o upitu kao o „listi stavki“; ovaj dio će objasniti kako se vaš upit pretvara u tu listu.

U početnom upitu svaka stavka na listi povezana je s ulogom. Role pokazuje koliku važnost pridruženi sadržaj treba imati i predstavlja jednu od sljedećih vrijednosti (po opadajućem redoslijedu prioriteta): system, developer, user, assistant.

Responses API(otvara se u novom prozoru) prima JSON payload s mnogim parametrima. Fokusirat ćemo se na ova tri područja:

U Codexu, polje instructions se čita iz model_instructions_file(otvara se u novom prozoru) u ~/.codex/config.toml, ako je navedeno; inače se koriste base_instructions povezane s modelom(otvara se u novom prozoru). Upute specifične za model nalaze se u repozitoriju Codex i uključene su u CLI (npr., gpt-5.2-codex_prompt.md(otvara se u novom prozoru)).

Polje tools je lista definicija alata koje su u skladu sa šemom definisanom od strane Responses API-ja. Za Codex, ovo uključuje alate koje pruža Codex CLI, alate koje pruža Responses API koji bi trebali biti dostupni Codexu, kao i alate koje korisnik obično pruža putem MCP servera:

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
]

Na kraju, polje input u JSON payloadu je lista stavki. Codex ubacuje sljedeće stavke(otvara se u novom prozoru) u input prije dodavanja korisničke poruke:

1. Poruka sa role=developer koja opisuje sandbox koji se primjenjuje samo na Codex-ov shell alat definisan u odjeljku tools. To jest, drugi alati, kao što su oni koje pružaju MCP serveri, nisu u sandboxu od strane Codexa i odgovorni su za provođenje vlastitih zaštitnih mjera.

Poruka je izrađena iz predloška gdje ključni dijelovi sadržaja dolaze iz isječaka Markdowna integriranih u Codex CLI, kao što su workspace_write.md(otvara se u novom prozoru) i on_request.md(otvara se u novom prozoru):

Običan tekst.

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. (Opcionalno) Poruka s role=developer čiji sadržaj predstavlja vrijednost developer_instructions pročitanu iz korisničke datoteke config.toml.

3. (Opcionalno) Poruka s role=user čiji sadržaj su “korisničke upute”, koje nisu preuzete iz jedne datoteke, već su prikupljene iz više izvora(otvara se u novom prozoru). Općenito, detaljnija uputstva se pojavljuju kasnije:

4. Poruka s role=user koja opisuje lokalno okruženje u kojem agent trenutno djeluje. Ovo određuje trenutni radni direktorij i korisnički shell(otvara se u novom prozoru):

Običan tekst.

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

Kada Codex završi sve gore navedene proračune za inicijalizaciju input, dodaje korisničku poruku kako bi započeo razgovor.

Prethodni primjeri su se fokusirali na sadržaj svake poruke, ali imajte na umu da je svaki element input JSON objekt sa type, role(otvara se u novom prozoru) i content kako slijedi:

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
}

Kada Codex izgradi puni JSON payload za slanje na Responses API, zatim šalje HTTP POST zahtjev sa Authorization zaglavljem, u zavisnosti od toga kako je konfigurisana krajnja tačka Responses API-ja u ~/.codex/config.toml (dodatna HTTP zaglavlja i parametri upita se dodaju ako su navedeni).

Kada server OpenAI Responses API primi zahtjev, koristi JSON da generiše upit za model na sljedeći način (da budemo sigurni, prilagođena implementacija Responses API-ja mogla bi napraviti drugačiji izbor):

Dijagram snimka koji prikazuje jedan korak u petlji AI agenta. Zahtjev korisnika ulazi u model, koji generira misao, akciju s nazivom alata i unos za alat. Dijagram ističe ovaj međukorak rezonovanja prije nego što se alat pozove.

Kao što možete vidjeti, redoslijed prve tri stavke u upitu određuje server, a ne klijent. Međutim, od te tri stavke, samo sadržaj sistemske poruke također kontrolira server, dok tools i instructions određuje klijent. Nakon ovoga slijedi input iz JSON payloada da bi se upotpunio upit.

Sada kada imamo naš upit, spremni smo uzorkovati model.

Prvo skretanje

Ovaj HTTP zahtjev upućen Responses API-ju pokreće prvi "okret" u razgovoru u Codexu. Server odgovara sa strujom događaja poslanih od servera (SSE(otvara se u novom prozoru)). Data svakog događaja je JSON payload sa "type" koji počinje sa "response", što bi moglo izgledati ovako (potpuna lista događaja može se pronaći u našoj API dokumentaciji(otvara se u novom prozoru)):

Običan tekst.

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 konzumira tok događaja(otvara se u novom prozoru) i ponovo ih objavljuje kao interne objekte događaja koje klijent može koristiti. Događaji kao što su response.output_text.delta koriste se za podršku strimovanju u korisničkom interfejsu, dok se drugi događaji, poput response.output_item.added, transformišu u objekte koji se dodaju u input za naredne pozive API-ja za odgovore.

Pretpostavimo da prvi zahtjev prema Responses API-ju uključuje dva response.output_item.done događaja: jedan s type=reasoning i jedan s type=function_call. Ovi događaji moraju biti predstavljeni u polju input JSON-a kada ponovo upitamo model s odgovorom na poziv alata: 

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
]

Rezultirajući upit korišten za uzorkovanje modela kao dio narednog upita izgledao bi ovako:

Dijagram označen kao „Snapshot 2“ prikazuje AI agenta nakon poziva alata. Model prima opažanje alata i generiše novu misao i akciju. Strelice povezuju unose, zapažanja i izlaze kako bi prikazale kako agent ponavlja svoj ciklus rezonovanja.

Posebno obrati pažnju na to kako je stari upit tačan prefiks novog upita. Ovo je namjerno, jer to čini naredne zahtjeve mnogo efikasnijim zato što nam omogućava da iskoristimo keširanje upita (o čemu ćemo govoriti u sljedećem dijelu o performansama).

Osvrćući se na naš prvi dijagram petlje agenta, vidimo da može biti mnogo iteracija između zaključivanja i pozivanja alata. Upit može nastaviti rasti sve dok konačno ne primimo poruku asistenta, što ukazuje na kraj poteza:

Običan tekst.

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

U Codex CLI-u prikazujemo poruku asistenta korisniku i fokusiramo kompozitor kako bismo korisniku naznačili da je na njima red da nastave razgovor. Ako korisnik odgovori, poruka asistenta iz prethodnog poteza i nova poruka korisnika moraju biti dodane u input u zahtjevu za Responses API kako bi se započeo novi potez:

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
]

Još jednom, kako nastavljamo razgovor, dužina input koji šaljemo Responses API-ju se povećava:

Dijagram s oznakom „Snapshot 3“ koji prikazuje završnu fazu petlje AI agenta. Nakon što primi rezultate alata, model generira zaključnu misao i konačan odgovor koji se vraća korisniku. Strelice prikazuju prijelaz od rezultata alata do završenog odgovora.

Hajde da ispitamo šta ovaj sve veći upit znači za performanse.

Razmatranja performansi

Možda se pitate: „Čekajte, zar petlja agenta nije kvadratna u smislu količine JSON-a poslanog na Responses API tokom razgovora?” I bili biste u pravu. Iako Responses API podržava opcioni parametar previous_response_id(otvara se u novom prozoru) za ublažavanje ovog problema, Codex ga trenutno ne koristi, prvenstveno kako bi zahtjevi ostali potpuno bez stanja i kako bi se podržale konfiguracije bez zadržavanja podataka (ZDR).

Izbjegavanje previous_response_id pojednostavljuje stvari za pružatelja API-ja za odgovore jer osigurava da je svaki zahtjev stateless. Ovo također olakšava podršku klijentima koji su se odlučili za bez zadržavanja podataka (ZDR)(otvara se u novom prozoru), jer bi pohranjivanje podataka potrebnih za podršku previous_response_id bilo u suprotnosti sa ZDR. Imajte na umu da ZDR korisnici ne gube mogućnost da iskoriste vlasničke poruke o rezonovanju iz prethodnih koraka, jer se povezani encrypted_content može dešifrovati na serveru. (OpenAI čuva ključ za dešifriranje ZDR korisnika, ali ne i njihove podatke.) Pogledajte PR-ove #642(otvara se u novom prozoru) i #1641(otvara se u novom prozoru) za povezane promjene u Codexu kako bi se podržao ZDR.

Općenito, trošak uzorkovanja modela je veći od troška mrežnog saobraćaja, što uzorkovanje čini glavnom metom naših napora za poboljšanje efikasnosti. Zato je keširanje upita toliko važno, jer omogućava ponovno korištenje računanja iz prethodnog poziva zaključivanja. Kada dobijemo pogotke keša, uzorkovanje modela je linearno, a ne kvadratno. Naša dokumentacija o keširanju upita (otvara se u novom prozoru)objašnjava ovo u više detalja:

Pogoci keša su mogući samo za tačna podudaranja prefiksa unutar upita. Da biste ostvarili prednosti keširanja, postavite statični sadržaj poput uputa i primjera na početak svog upita, a promjenjivi sadržaj, kao što su informacije specifične za korisnika, stavite na kraj. Ovo se također odnosi na slike i alate, koji moraju biti identični među zahtjevima.

Imajući to na umu, razmotrimo koje vrste operacija bi mogle uzrokovati „promašaj predmemorije“ u Codexu:

  • Promjena tools dostupnih modelu tokom razgovora.
  • Promjena model a koji je cilj zahtjeva za Responses API (u praksi, ovo mijenja treću stavku u originalnom upitu, jer sadrži upute specifične za model).
  • Promjena konfiguracije sandboxa, načina odobravanja ili trenutnog radnog direktorija.

Tim Codex mora biti pažljiv pri uvođenju novih funkcija u Codex CLI koje bi mogle ugroziti keširanje upita. Kao primjer, naša početna podrška za MCP alate uvela je grešku zbog koje nismo uspjeli nabrojati alate u dosljednom redoslijedu(otvara se u novom prozoru), što je uzrokovalo promašaje keša. Imajte na umu da MCP alati mogu biti posebno nezgodni jer MCP serveri mogu u hodu promijeniti listu alata koje pružaju putem notifications/tools/list_changed(otvara se u novom prozoru) obavijesti. Poštivanje ove obavijesti usred dugog razgovora može uzrokovati skup promašaj predmemorije.

Kada je to moguće, promjene konfiguracije koje se dogode usred razgovora rješavamo dodavanjem nove poruke u input kako bismo odrazili promjenu, umjesto da mijenjamo raniju poruku:

Ulažemo velike napore kako bismo osigurali pogotke keša radi poboljšanja performansi. Imamo još jedan ključni resurs kojim trebamo upravljati: kontekstni prozor.

Naša opća strategija da izbjegnemo da ostanemo bez kontekstnog prozora je sažimanje razgovora kada broj tokena premaši određeni prag. Konkretno, zamjenjujemo input novom, manjom listom stavki koja predstavlja razgovor, omogućavajući agentu da nastavi s razumijevanjem onoga što se do sada dogodilo. Rana implementacija kompakcije(otvara se u novom prozoru) zahtijevala je da korisnik ručno pokrene naredbu /compact, koja bi ispitivala Responses API koristeći postojeći razgovor i prilagođene upute za sažimanje(otvara se u novom prozoru). Codex je koristio rezultirajuću poruku asistenta koja sadrži sažetak kao novi unos(otvara se u novom prozoru) za naredne korake razgovora.

Od tada se Responses API razvio kako bi podržao posebnu /responses/compact krajnju tačku(otvara se u novom prozoru) koja efikasnije obavlja sažimanje. Vraća listu stavki(otvara se u novom prozoru) koje se mogu koristiti umjesto prethodnog input za nastavak razgovora, dok oslobađa kontekstni prozor. Ova lista uključuje posebnu stavku type=compaction s neprozirnom stavkom encrypted_content koja čuva latentno razumijevanje modela o izvornom razgovoru. Sada Codex automatski koristi ovu krajnju tačku za sažimanje razgovora kada se prekorači auto_compact_limit(otvara se u novom prozoru).

Sljedeće dolazi

Predstavili smo petlju agenta Codex i objasnili kako Codex kreira i upravlja svojim kontekstom prilikom upita modelu. Usput smo istakli praktična razmatranja i najbolje prakse koje se primjenjuju na svakoga ko gradi petlju agenta na osnovu Responses API-ja.

Iako petlja agenta pruža osnovu za Codex, to je tek početak. U nadolazećim objavama, istražit ćemo arhitekturu CLI-ja, kako je implementirana upotreba alata, i detaljno razmotriti Codexov model sandboxinga.

Autor

Michael Bolin

Priznanja

Posebna zahvala cijelom timu koji je razvio Codex CLI.