Hopp til hovedinnhold
OpenAI

23. januar 2026

Teknisk arbeid

Codex-agentløkken i detalj

Av Michael Bolin, medlem av den tekniske staben

Laster inn …

Codex CLI(åpnes i et nytt vindu) er vår lokale programvareagent som fungerer på alle plattformer, og er utviklet for å produsere pålitelige programvareendringer av høy kvalitet, samtidig som den opererer trygt og effektivt på maskinen din. Vi har lært enormt mye om hvordan man bygger en programvareagent i verdensklasse siden vi først lanserte CLI-en i april. Dette er det første innlegget i en serie der vi vil vise deg denne innsikten og utforske diverse aspekter av hvordan Codex fungerer, i tillegg til det vi lærte gjennom prøving og feiling. (For å få en enda mer detaljert oversikt over hvordan Codex CLI er bygget opp, kan du se åpen-kildekode-lageret vårt på https://github.com/openai/codex(åpnes i et nytt vindu). Mange av de finere detaljene av designbeslutningene er dokumentert i GitHub-saker og pull-forespørsler, hvis du vil vite mer.)

Vi starter med et fokus på agentløkken, som er kjernelogikken i Codex CLI og ansvarlig for orkestrering av samspillet mellom brukeren, modellen og verktøyene modellen bruker, til å utføre meningsfullt programvarearbeid. Vi håper dette innlegget gir deg et godt innblikk i rollen agenten vår (eller styringsrammeverket), har i bruken av en LLM.

Før vi kaster oss inn i det, har vi en kort merknad om terminologi: i OpenAI omfatter «Codex» en pakke med programvareagenttilbud, inkludert Codex CLI, Codex Cloud og Codex VS Code-utvidelsen. Dette innlegget fokuserer på Codex-en styringsrammeverket, som gir den grunnleggende agentløkken og utførelseslogikken som ligger til grunn for alle Codex-opplevelser og gjøres tilgjengelig gjennom Codex CLI. For enkelhets skyld her bruker vi begrepene «Codex» og «Codex CLI» om hverandre.

Agentløkken

I kjernen av enhver AI-agent er det noe som kalles «agentløkken». En forenklet illustrasjon av agentløkken ser slik ut:

Diagram med tittelen «Agent loop» som viser hvordan et AI-system behandler en brukerforespørsel, bruker verktøy, observerer resultater, oppdaterer planen sin og returnerer utdata. Piler kobler sammen trinn som brukerinndata, modellens resonnering, verktøyhandlinger og endelig svar.

Agenten starter ved å ta inndata fra brukeren for å inkludere dem i tekstinstruksjoner den klargjør for modellen. Dette er kjent som en prompt.

Neste trinn er å spørre modellen ved å sende den instruksjonene våre og be den om å generere et svar. Denne prosessen kalles en inferens. Under inferensen blir tekstprompten først oversatt til en sekvens av inndata-tokener(åpnes i et nytt vindu) – heltall som indekseres inn i modellens vokabular. Disse tokenene brukes deretter til å sample modellen, og så produserer de en ny sekvens av utdata-tokener.

Tokenene oversettes tilbake til tekst, som blir modellens svar. Fordi tokener produseres trinnvis, kan denne oversettelsen skje mens modellen kjører, og derfor viser mange LLM-løsninger løpende resultater. I praksis er inferens vanligvis innkapslet bak en API som jobber med tekst, og som skjuler detaljene ved tokenisering.

Som et resultat av inferenstrinnet, vil modellen enten (1) produsere et endelig svar på brukerens opprinnelige inndata, eller (2) be om et verktøykall som agenten skal utføre (f.eks. «kjør ls og rapporter utdataene»). I tilfelle (2) utfører agenten verktøykallet og legger til utdataene i den opprinnelige prompten. Disse utdataene brukes til å generere nye inndata som brukes til å spørre modellen på nytt. Agenten kan deretter ta denne nye informasjonen i betraktning og prøve igjen.

Denne prosessen gjentas til modellen slutter å sende ut verktøykall og i stedet produserer en melding til brukeren (kalt en assistentmelding i OpenAI-modeller). I mange tilfeller besvarer denne meldingen den opprinnelige forespørselen direkte, men den kan også være et oppfølgingsspørsmål til brukeren.

Fordi agenten kan utføre verktøykall som endrer det lokale miljøet, er utdata ikke begrenset til assistentens melding. I mange tilfeller er de primære utdataene fra en programvareagent koden den skriver eller redigerer på maskinen din. Likevel ender hver runde alltid med en assistentmelding – som for eksempel «Jeg la til architecture.md som du ba om» – som signaliserer en avslutningstilstand i agentløkken. Fra agentens perspektiv er arbeidet fullført, og kontrollen går tilbake til brukeren.

Reisen fra brukerens inndata til agentens svar som vist i diagrammet, kalles én runde i en samtale (en tråd i Codex). Men denne samtalerunden kan inkludere mange iterasjoner mellom modell og verktøykall. Hver gang du sender en ny melding til en eksisterende samtale, inkluderes samtalehistorikken som en del av prompten for den nye runden, som inkluderer meldingene og verktøykallene fra tidligere runder:

Diagram med tittelen «Multi-turn agent loop» som viser hvordan en AI-agent gjentatte ganger tar imot brukerinndata, genererer handlinger, konsulterer verktøy, oppdaterer tilstand og returnerer resultater. Inkluderer merkede trinn, piler og eksempelutdata fra verktøy som illustrerer agentens resonneringssyklus.

Dette betyr at etter hvert som samtalen vokser, øker også lengden på prompten som brukes til å sample modellen. Denne lengden er viktig fordi hver modell har et kontekstvindu, som er det maksimale antallet tokener den kan bruke på én inferenskall. Merk at dette vinduet inkluderer både inndata- og utdatatokener. Som du kanskje kan forestille deg, kan en agent velge å foreta hundrevis av verktøykall i én enkelt runde, noe som potensielt kan fylle opp hele kontekstvinduet. Derfor er håndtering av kontekstvinduet en av agentens mange ansvarsområder. Nå skal vi se nærmere på hvordan Codex kjører agentløkken.

Modellinferens

Codex CLI sender HTTP-forespørsler til Responses API-en(åpnes i et nytt vindu) for å kjøre modellinferens. Vi skal undersøke hvordan informasjon flyter gjennom Codex, som bruker Responses API-en til å drive agentløkken.

Responses API-endepunktet som Codex CLI bruker, er konfigurerbart(åpnes i et nytt vindu), så det kan brukes med et hvilket som helst endepunkt som implementerer Responses API-en(åpnes i et nytt vindu):

La oss se nærmere på hvordan Codex lager prompten til den første inferensforespørselen i en samtale.

Oppbygging av den innledende prompten

Som sluttbruker behøver du ikke å angi den eksakte prompten som brukes til å sample modellen ordrett, når du sender en forespørsel til Responses API-en. I stedet spesifiserer du ulike inndatatyper som en del av forespørselen din, og Responses API-serveren bestemmer hvordan denne informasjonen skal struktureres til en prompt som modellen kan bruke. Du kan tenke på prompten som en «liste over elementer». Denne delen forklarer hvordan forespørselen blir omgjort til en slik liste.

I den innledende prompten er hvert element i listen tilknyttet en rolle. Role angir hvor mye vekt det tilknyttede innholdet skal ha, og er en av følgende verdier (i synkende rekkefølge etter prioritet): system, developer, user, assistant.

Responses API-en(åpnes i et nytt vindu) tar en JSON-nyttelast med mange parametere. Vi skal fokusere på disse tre:

I Codex leses feltet instructions fra model_instructions_file(åpnes i et nytt vindu) i ~/.codex/config.toml, hvis det er spesifisert; ellers brukes base_instructions som er tilknyttet en modell(åpnes i et nytt vindu). Modellspesifikke instruksjoner finnes i Codex-lageret og er inkludert i CLI-en (f.eks. gpt-5.2-codex_prompt.md(åpnes i et nytt vindu)).

Feltet tools er en liste over verktøydefinisjoner som samsvarer med et skjema definert av Responses API-en. For Codex inkluderer dette verktøy som gjøres tilgjengelig av Codex CLI, verktøy som gjøres tilgjengelig av Responses API-en og som bør være tilgjengelig for Codex, samt verktøy som gjøres tilgjengelig av brukeren, vanligvis via MCP-servere:

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
]

Endelig er input-feltet i JSON-nyttelasten en liste over elementer. Codex setter inn følgende elementer(åpnes i et nytt vindu) i input før den legger til brukermeldingen:

1. En melding med role=developer som beskriver sandkassen som at den bare gjelder for det Codex-leverte shell-verktøyet definert i tools-delen. Det vil si at andre verktøy, som de som leveres fra MCP-servere, ikke blir beskyttet i sandkasse av Codex og er ansvarlige for å håndheve sine egne sikkerhetsmekanismer.

Meldingen er bygget på en mal der nøkkelinnholdet kommer fra utdrag av Markdown som er inkludert i Codex CLI, som for eksempel workspace_write.md(åpnes i et nytt vindu) og on_request.md(åpnes i et nytt vindu):

Ren 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. (Valgfritt) En melding med role=developer der innholdet er verdien developer_instructions lest fra brukerens config.toml-fil.

3. (Valgfritt) En melding med role=user der innholdet er «brukerinstruksjonene», som ikke er hentet fra én enkelt fil, men samlet fra flere kilder(åpnes i et nytt vindu). Som regel kommer mer spesifikke instruksjoner senere:

4. En melding med role=user som beskriver det lokale miljøet der agenten driver nå. Dette spesifiserer den gjeldende arbeidskatalogen og brukerens shell(åpnes i et nytt vindu):

Ren tekst

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

Når Codex har utført alle de ovennevnte beregningene for å initialisere input, legger den til brukermeldingen for å starte samtalen.

De tidligere eksemplene fokuserte på innholdet i hver melding, men merk at hvert element i input er et JSON-objekt med type, role(åpnes i et nytt vindu) og content som følger:

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
}

Når Codex har bygget opp hele JSON-nyttelasten som skal sendes til Responses API-en, sender den en HTTP POST-forespørsel med en Authorization-overskrift avhengig av hvordan Responses API-endepunktet er konfigurert i ~/.codex/config.toml (ytterligere HTTP-overskrifter og spørringsparametere legges til hvis det er spesifisert).

Når en OpenAIs Responses API-server mottar forespørselen, bruker den JSON-en til å lage prompten for modellen slik (en tilpasset implementering av Responses API kan selvfølgelig gjøre et annet valg):

Snapshot-diagram som viser et enkelt trinn i en AI-agentløkke. En brukerforespørsel går inn i modellen, som genererer en tanke, en handling med et verktøynavn og inndata til verktøyet. Diagrammet fremhever dette mellomliggende resonneringstrinnet før verktøyet brukes.

Som du ser, bestemmes rekkefølgen på de tre første elementene i prompten av serveren, ikke klienten. Når det er sagt, så er det av disse tre elementene bare innholdet i systemmeldingen som også styres av serveren, da tools og instructions bestemmes av klienten. Disse følges av input fra JSON-nyttelasten for å fullføre prompten.

Nå som vi har prompten vår, er vi klare til å prøve modellen.

Første runde

Denne HTTP-forespørselen til Responses API starter den første «runden» i en samtale i Codex. Serveren svarer med en Server-Sent Events-strøm (SSE(åpnes i et nytt vindu)). Data-feltet i hver hendelse er en JSON-nyttelast med en "type" som starter med "response", som ser omtrent sånn ut (en fullstendig liste over hendelser finner du i API-dokumentasjonen(åpnes i et nytt vindu) vår):

Ren 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 tar imot hendelsesstrømmen(åpnes i et nytt vindu) og gjør den om til interne hendelsesobjekter som kan brukes av en klient. Hendelser som response.output_text.delta brukes til å støtte strømming i brukergrensesnittet, mens andre hendelser som response.output_item.added gjøres om til objekter som legges til i input for påfølgende kall til Responses API-en.

Tenk deg at den første forespørselen til Responses API-en inkluderer to response.output_item.done-hendelser: én med type=reasoning og én med type=function_call. Disse hendelsene må representeres i input-feltet i JSON når vi spør modellen igjen med svaret på verktøykallet: 

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
]

Den resulterende prompten som brukes til å sample modellen som en del av den påfølgende forespørselen, ser da slik ut:

Diagram merket «Snapshot 2» som viser en AI-agent etter et verktøykall. Modellen mottar en observasjon av et verktøy og genererer en ny tanke og handling. Piler kobler sammen inndata, observasjoner og utdata for å illustrere hvordan agenten gjentar resonneringssløyfen sin.

Legg spesielt merke til hvordan den gamle prompten er en nøyaktig prefiks i den nye prompten. Dette er med hensikt, da dette gjør påfølgende forespørsler mye mer effektive ved at vi kan dra nytte av prompt-bufring (som vi skal snakke om i neste avsnitt om ytelse).

Når vi igjen ser på vårt første diagram av agentløkken, ser vi at det kan være mange iterasjoner mellom inferens og verktøykall. Prompten kan fortsette å vokse til vi endelig mottar en assistentmelding, som indikerer slutten på runden:

Ren tekst

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

I Codex CLI viser vi assistentmeldingen til brukeren og setter fokuset på skrivefeltet for å fortelle brukeren at det er vedkommendes tur til å fortsette samtalen. Hvis brukeren svarer, må både assistentmeldingen fra forrige runde og brukerens nye melding legges til i input i Responses API-forespørselen for å starte den nye runden:

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
]

Nok en gang øker lengden på input vi sender til Responses API, fordi vi fortsetter en samtale:

Diagram merket «Snapshot 3» som viser sluttfasen i en AI-agentløkke. Etter å ha mottatt verktøyresultater, genererer modellen en avsluttende tanke og et endelig svar som returneres til brukeren. Piler illustrerer overgangen fra verktøyets utdata til et fullført svar.

La oss undersøke hva denne stadig voksende prompten betyr for ytelsen.

Hensyn vedrørende ytelse

Du tenker kanskje: «Stopp en hal, er ikke agent-loopen kvadratisk med hensyn til mengden JSON som sendes til Responses API-en i løpet av samtalen?» I så fall har du rett. Selv om Responses API-en støtter en valgfri previous_response_id(åpnes i et nytt vindu)-parameter for å redusere dette problemet, bruker ikke Codex den i dag. Dette er hovedsakelig for å holde forespørsler helt uavhengige og for å støtte konfigurasjoner for ZDR (Zero Data Retention – ingen oppbevaring av data).

Det er enklere for leverandøren av Responses API å unngå previous_response_id fordi det sikrer at alle forespørsler er uavhengige. Dette gjør det også enkelt å støtte kunder som har valgt ZDR (ingen oppbevaring av data)(åpnes i et nytt vindu), siden lagring av dataene som kreves for å støtte previous_response_id, ville være i strid med ZDR. ZDR-kunder mister ikke muligheten til å dra nytte av proprietær resonnering fra tidligere runder, ettersom tilknyttet encrypted_content kan dekrypteres på serveren. (OpenAI lagrer dekrypteringsnøkkelen til ZDR-kunder, men ikke dataene deres.) Se PR-er #642(åpnes i et nytt vindu) og #1641(åpnes i et nytt vindu) for de relaterte endringene i Codex for å støtte ZDR.

Som regel er kostnaden av å sample modellen større enn kostnaden av nettverkstrafikk, noe som gjør at sampling er hovedfokuset for effektiviseringstiltakene våre. Dette er grunnen til at prompt-bufring er så viktig, siden det gjør oss i stand til å gjenbruke beregninger fra et tidligere inferenskall. Når vi får treff i hurtigbufferen, er sampling av modellen lineær i stedet for kvadratisk. Dokumentasjonen vår for prompt-bufring (åpnes i et nytt vindu)forklarer dette mer detaljert:

Treff i hurtigbufferen er bare mulig for eksakte prefiks-treff i en prompt. For å oppnå fordelene med hurtigbufring, kan du plassere statisk innhold som instruksjoner og eksempler på begynnelsen av prompten og variabelt innhold, som brukerspesifikk informasjon, på slutten. Dette gjelder også bilder og verktøy, som må være identiske fra den ene forespørselen til den andre.

Med dette i bakhodet kan vi se på hvilke typer operasjoner som kan forårsake en «hurtigbuffer-miss» i Codex:

  • Endring av tools som er tilgjengelige for modellen, midt i samtalen.
  • Endring av model som er målet for Responses API-forespørselen (i praksis endrer dette det tredje elementet i den opprinnelige prompten, da det inneholder modellspesifikke instruksjoner).
  • Endring av sandkassekonfigurasjon, godkjenningsmodus eller gjeldende arbeidskatalog.

Codex-teamet må være forsiktige når de introduserer nye funksjoner i Codex CLI som kan svekke prompt-bufringen. Som et eksempel kan vi nevne at vår første støtte for MCP-verktøy introduserte en feil der vi ikke klarte å liste opp verktøyene i en konsekvent rekkefølge(åpnes i et nytt vindu), noe som førte til hurtigbuffer-misser. MCP-verktøy kan være spesielt lumske fordi MCP-servere kan endre listen over verktøy de tilbyr fortløpende, via et notifications/tools/list_changed(åpnes i et nytt vindu)-varsel. Å ta hensyn til dette varselet midt i en lang samtale kan føre til en kostbar hurtigbuffer-miss.

Når det er mulig, håndterer vi konfigurasjonsendringer som skjer midt i en samtale, ved å legge til en ny melding i input for å gjenspeile endringen, i stedet for å endre en tidligere melding:

Vi strekker oss langt for å sikre hurtigbuffertreff for bedre ytelse. En annen viktig ressurs vi må håndtere, er kontekstvinduet.

Vår generelle strategi for å unngå å fylle opp kontekstvinduet er å komprimere samtalen når antall tokener overstiger en viss terskel. Vi erstatter input med en ny, mindre liste over elementer som er representative for samtalen, slik at agenten kan fortsette med en forståelse av hva som har skjedd så langt. En tidlig implementering av komprimering(åpnes i et nytt vindu) krevde at brukeren kjørte /compact-kommandoen manuelt, som sendte en forespørsel til Responses API-en ved å bruke den eksisterende samtalen pluss tilpassede instruksjoner for oppsummering(åpnes i et nytt vindu). Codex brukte den resulterende assistentmeldingen som inneholdt sammendraget, som ny input(åpnes i et nytt vindu) for påfølgende samtalerunder.

Siden den gang har Responses API-en utviklet seg til å støtte et spesielt /responses/compact-endepunkt(åpnes i et nytt vindu) som utfører komprimering mer effektivt. Den returnerer en liste over elementer(åpnes i et nytt vindu) som kan brukes i stedet for den forrige input, for å fortsette samtalen, samtidig som den frigjør plass i kontekstvinduet. Denne listen inkluderer et spesielt type=compaction-element med et ugjennomsiktig encrypted_content-element som bevarer modellens latente forståelse av den opprinnelige samtalen. Nå bruker Codex automatisk dette endepunktet for å komprimere samtalen når auto_compact_limit(åpnes i et nytt vindu) overskrides.

Kommer snart

Vi har introdusert Codex' agentløkke og gått gjennom hvordan Codex utformer og administrerer konteksten sin når den sender en forespørsel til en modell. Underveis fremhevet vi praktiske hensyn og beste praksis som gjelder for alle som bygger en agentløkke basert på Responses API-en.

Selv om agentløkken legger grunnlaget for Codex, er det bare starten. I kommende innlegg vil vi utforske CLI-arkitekturen, undersøke hvordan verktøybruk er implementert, og ta en nærmere titt på Codex' sandkassemodell.

Forfatter

Michael Bolin

Anerkjennelser

En spesiell takk til hele teamet som utviklet Codex CLI.