Gå direkt till huvudinnehåll
OpenAI

23 januari 2026

Teknik

En presentation av Codex-agent-loopen

Av Michael Bolin, medlem av den tekniska personalen

Laddar …

Codex CLI(öppnas i ett nytt fönster) är vår plattformsoberoende lokala agent, utformad för att producera högkvalitativa och tillförlitliga programvaruändringar samtidigt som den arbetar säkert och effektivt på din dator. Vi har lärt oss mycket om hur man bygger en agent i världsklass sedan vi först lanserade CLI i april. För att utforska dessa insikter är detta det första inlägget i en pågående serie där vi kommer att undersöka olika aspekter av hur Codex fungerar, samt de lärdomar vi har dragit. (För en ännu mer detaljerad vy över hur Codex CLI byggs, ta en titt på vårt öppna förvar på https://github.com/openai/codex(öppnas i ett nytt fönster). Många av de mer ingående detaljerna i våra designbeslut är dokumenterade i GitHub-ärenden och Pull-begäranden om du vill veta mer.

Till att börja med kommer vi att fokusera på agentloopen, som är den centrala logiken i Codex CLI och som ansvarar för att orkestrera interaktionen mellan användaren, modellen och de verktyg som modellen anropar för att utföra meningsfullt mjukvaruarbete. Vi hoppas att det här inlägget ger dig en bra bild av vilken roll vår agent (eller ”harness”) spelar i användningen av en LLM.

Innan vi börjar, en snabb anmärkning om terminologin: på OpenAI omfattar ”Codex” en uppsättning programvaruagenter, inklusive Codex CLI, Codex Cloud och Codex VS Code-tillägget. Det här inlägget fokuserar på Codex harness, som tillhandahåller den centrala agent-loopen och exekveringslogiken som ligger till grund för alla Codex-upplevelser och visas via Codex CLI. För enkelhets skull kommer vi här att omväxlande använda termerna ”Codex” och ”Codex CLI”.

Agentloopen

I hjärtat av varje AI-agent finns något som kallas ”Agentloopen”. En förenklad illustration av agentloopen ser ut så här:

Diagram med titeln ”Agent-loop” som visar hur ett AI-system hanterar en användarförfrågan, anropar verktyg, observerar resultat, uppdaterar sin plan och returnerar utdatan. Pilar kopplar samman steg som användarindata, modellresonemang, verktygsåtgärder och slutligt svar.

Till att börja med tar agenten indata från användaren, en så kallad prompt, och inkluderar den i uppsättningen textinstruktioner som den förbereder för modellen.

Nästa steg är att fråga modellen genom att skicka den våra instruktioner och be den generera ett svar, en process som kallas inferens. Under inferensen översätts textprompten först till en sekvens av indatatoken(öppnas i ett nytt fönster) – heltal som indexeras i modellens ordförråd. Dessa token används sedan för att sampla modellen, vilket ger en ny sekvens av utdatatoken.

Utdatatoken översätts tillbaka till text, vilket blir modellens svar. Eftersom token produceras stegvis kan denna översättning ske medan modellen körs, vilket är anledningen till att många LLM-baserade applikationer visar strömmande utdata. I praktiken är inferens vanligtvis inkapslad bakom ett API som arbetar med text och abstraherar bort detaljerna i tokenisering.

I inferenssteget producerar modellen antingen (1) ett slutgiltigt svar på användarens ursprungliga indata, eller (2) begär en verktygsanrop som agenten förväntas utföra (t.ex. “kör ls och rapportera utdata”). I fallet med (2) utför agenten verktygsanropet och lägger till sina utdata till den ursprungliga prompten. Denna utdata används för att skapa en ny indata som används för att fråga modellen på nytt. Agenten kan sedan beakta denna nya information och försöka igen.

Den här processen upprepas tills modellen slutar att köra verktygsanrop och istället producerar ett meddelande till användaren (kallas ett Assistant-meddelande i OpenAI-modeller). I många fall besvarar meddelandet användarens ursprungliga förfrågan, men det kan också vara en uppföljningsfråga till användaren.

Eftersom agenten kan utföra verktygsanrop som ändrar den lokala miljön, är dess utdata inte begränsade till assistentmeddelandet. I många fall är den primära utdatan från en agent den kod som den skriver eller redigerar på din dator. Ändå avslutas varje omgång alltid med ett assistentmeddelande – till exempel ”Jag lade till architecture.md som du bad om” – vilket signalerar ett avslutningstillstånd i agentloopen. Ur agentens perspektiv är dess arbete slutfört och kontrollen återgår till användaren.

Resan från användarindata till agentens svar som visas i diagrammet kallas för en omgång i ett samtal (en tråd i Codex). Även om denna samtalsomgång kan omfatta många iterationer mellan modellinferens och verktygsanrop). Varje gång du skickar ett nytt meddelande till en befintlig konversation inkluderas konversationshistoriken som en del av prompten för den nya omgången, vilket inkluderar meddelandena och verktygsanropen från tidigare omgångar:

Diagram med titeln ”Multi-turn agent loop” som visar hur en AI-agent iterativt tar emot användarinmatning, genererar åtgärder, konsulterar verktyg, uppdaterar tillstånd och returnerar resultat. Inkluderar märkta steg, pilar och exempel på verktygsutdata som illustrerar agentens resonemangscykel.

Detta innebär att när konversationen blir allt längre, ökar också längden på prompten som används för att sampla modellen. Den här längden är viktig eftersom varje modell har ett kontextfönster, vilket är det maximala antalet token som kan användas för ett inferensanrop. Observera att det här fönstret innehåller både indata- och utdata-token. Som du kanske kan föreställa dig kan en agent besluta att göra hundratals verktygsanrop i ett enda steg, vilket kan fylla kontextfönstret helt. Av denna anledning är hantering av kontextfönster en av agentens många ansvarsområden. Nu ska vi ta en titt på hur Codex kör agentloopen.

Modellinferens

Codex CLI skickar HTTP-förfrågningar till svars-API(öppnas i ett nytt fönster) för att utföra modellinferens. Vi ska undersöka hur information flödar genom Codex, som använder svars-API för att driva agentloopen.

Slutpunkten för svars-API som Codex CLI använder är konfigurerbar(öppnas i ett nytt fönster), så den kan användas med vilken slutpunkt som helst som implementerar svars-API(öppnas i ett nytt fönster):

Låt oss utforska hur Codex skapar en prompt för det första inferensanropet i en konversation.

Skapa den första prompten

Som slutanvändare specificerar du inte prompten som används för att sampla modellen ordagrant när du gör en förfrågan till svars-API. Istället anger du olika indatatyper som en del av din fråga, och svars-API-servern avgör hur den ska strukturera denna information till en prompt som modellen är utformad för att ta emot. Du kan tänka på prompten som en ”lista med objekt”, och i det här avsnittet förklaras hur din fråga omvandlas till den listan.

I den inledande prompten är varje objekt i listan kopplat till en roll. Rollen anger hur mycket vikt det associerade innehållet ska ha och är ett av följande värden (i fallande prioritetsordning): system, utvecklare, användare, assistant.

Svars-API:et(öppnas i ett nytt fönster) tar emot en JSON-nyttolast med många parametrar. Vi kommer att fokusera på de här tre:

I Codex läses fältet instructions från model_instructions_file(öppnas i ett nytt fönster) i ~/.codex/config.toml, om det anges, annars används base_instructions kopplade till en modell(öppnas i ett nytt fönster). Modellspecifika instruktioner finns i Codex-förrådet och är inkluderade i CLI:t (t.ex. gpt-5.2-codex_prompt.md(öppnas i ett nytt fönster)).

Fältet tools är en lista över verktygsdefinitioner som följer ett schema som definieras av svars-API. För Codex inkluderar detta verktyg som tillhandahålls av Codex CLI, verktyg som tillhandahålls av svars-API och som bör göras tillgängliga för Codex, samt verktyg som tillhandahålls av användaren, vanligtvis via MCP-servrar:

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
]

Slutligen är fältet input i JSON-nyttolasten en lista med objekt. Codex infogar följande objekt(öppnas i ett nytt fönster) i input innan användarmeddelandet läggs till:

1. Ett meddelande med role=developer som beskriver sandlådan som endast gäller för det Codex-tillhandahållna shell-verktyget som definieras i tools-avsnittet. Det vill säga, andra verktyg, såsom de som tillhandahålls från MCP-servrar, är inte isolerade av Codex och ansvarar för att upprätthålla sina egna säkerhetsåtgärder.

Meddelandet skapas från en mall där de centrala innehållsdelarna hämtas från Markdown-utdrag som är inkluderade i Codex CLI, såsom workspace_write.md(öppnas i ett nytt fönster) och on_request.md(öppnas i ett nytt fönster):

Ren text

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. (Valfritt) Ett meddelande med role=developer vars innehåll är developer_instructions-värdet som läses från användarens config.toml-fil.

3. (Valfritt) Ett meddelande med role=user vars innehåll är ”användarinstruktionerna”, vilka inte hämtas från en enda fil utan sammanställs från flera källor(öppnas i ett nytt fönster). Generellt sett visas mer specifika instruktioner senare:

4. Ett meddelande med role=user som beskriver den lokala miljön där agenten för närvarande är verksam. Detta anger den aktuella arbetskatalogen och användarens shell(öppnas i ett nytt fönster):

Ren text

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

När Codex har utfört alla ovanstående beräkningar för att initiera input, lägger den till användarmeddelandet för att påbörja konversationen.

De tidigare exemplen fokuserade på innehållet i varje meddelande. Observera dock att varje element i input är ett JSON-objekt med type, role(öppnas i ett nytt fönster) och content enligt följande:

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 byggt upp den fullständiga JSON-nyttolasten som ska skickas till svars-API gör den sedan en HTTP POST-begäran med en auktorisation-rubrik beroende på hur svars-API-slutpunkten är konfigurerad i ~/.codex/config.toml (ytterligare HTTP-rubriker och frågeparametrar läggs till om de anges).

När en OpenAI svars-API-server tar emot en begäran använder den JSON för att härleda prompten för modellen enligt följande (för att vara tydlig kan en anpassad implementering av svars-API göra ett annat val):

Ögonblicksbilddiagram som visar ett enskilt steg i en AI-agentloop. En användarförfrågan går in i modellen, som genererar en tanke, en åtgärd med ett verktygsnamn och verktygsindata. Diagrammet markerar detta mellanliggande resonemangssteg innan verktyget anropas.

Som du kan se bestäms ordningen på de tre första objekten i prompten av servern, inte av klienten. Med det sagt är det, av de tre objekten, endast innehållet i systemmeddelandet som också styrs av servern, eftersom tools och instructions bestäms av klienten. Dessa följs av input från JSON-nyttolasten för att slutföra prompten.

Nu när vi har prompten är vi redo att sampla modellen.

Den första omgången

Den här HTTP-begäran till svars-API initierar den första omgången i en konversation i Codex. Servern svarar med ett Server-Sent Events (SSE(öppnas i ett nytt fönster))-flöde. Datan för varje händelse är en JSON-payload med en ”type” som börjar med ”response”, vilket kan se ut så här (en fullständig lista över händelser finns i vår API-dokumentation(öppnas i ett nytt fönster)):

Ren text

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 konsumerar händelseflödet(öppnas i ett nytt fönster) och publicerar dem igen som interna händelseobjekt som kan användas av en klient. Händelser som response.output_text.delta används för att stödja streaming i användargränssnittet, medan andra händelser som response.output_item.added omvandlas till objekt som läggs till i input för efterföljande anrop till svars-API.

Anta att den första begäran till svars-API innehåller två response.output_item.done-händelser: en med type=Resonemang och en med type=function_call. Dessa händelser måste representeras i input-fältet i JSON när vi frågar modellen igen med svaret på verktygsanropet: 

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 resulterande prompten som användes för att sampla modellen som en del av den efterföljande frågan skulle se ut så här:

Diagram märkt ”Snapshot 2” som visar en AI-agent efter ett verktygsanrop. Modellen tar emot en observation av ett verktyg och genererar en ny tanke och handling. Pilar kopplar samman indata, observationer och utdata för att illustrera hur agenten itererar sin resonemangsslinga.

Notera särskilt hur den gamla prompten är ett exakt prefix till den nya prompten. Detta är avsiktligt, eftersom det gör efterföljande förfrågningar mycket mer effektiva genom att vi kan dra nytta av prompt-caching (vilket vi kommer att diskutera i nästa avsnitt om prestanda).

När vi ser tillbaka på vårt första diagram över agentloopen ser vi att det kan finnas många iterationer mellan inferens och verktygsanrop. Prompten kan fortsätta att bli längre tills vi till slut får ett assistentmeddelande, vilket indikerar slutet på omgången:

Ren text

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

I Codex CLI visar vi assistentmeddelandet för användaren och fokuserar på skrivverktyget för att indikera att det är användarens tur att fortsätta konversationen. Om användaren svarar ska både assistentens meddelande från föregående omgång och användarens nya meddelande läggas till i input i begäran till svars-API för att starta den nya omgången:

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
]

Återigen, eftersom vi fortsätter en konversation, ökar längden på den input vi skickar till svars-API ständigt:

Diagram med etiketten ”Snapshot 3” som visar det sista steget i en AI agent-loop. Efter att ha tagit emot verktygsresultaten genererar modellen en avslutande tanke och ett slutgiltigt svar som returneras till användaren. Pilar visar övergången från verktygsutdata till ett färdigt svar.

Låt oss undersöka vad denna allt längre prompt betyder för prestandan.

Prestandaöverväganden

Du kanske undrar: ”Är inte agentloopen kvadratisk när det gäller mängden JSON som skickas till svars-API under samtalets gång?” Och du har rätt. Även om svars-API stöder en valfri previous_response_id(öppnas i ett nytt fönster)-parameter för att åtgärda detta problem, använder Codex den inte idag, främst för att hålla förfrågningar helt tillståndslösa och för att stödja konfigurationer för noll datalagring (ZDR).

Att undvika previous_response_id förenklar för leverantören av svars-API eftersom det säkerställer att varje begäran är stateless. Detta gör det också enkelt att stödja kunder som har valt Noll datalagring (ZDR)(öppnas i ett nytt fönster), eftersom lagring av de data som krävs för att stödja previous_response_id skulle stå i strid med ZDR. Observera att ZDR-kunder inte förlorar möjligheten att dra nytta av proprietära resonemangsmeddelanden från tidigare omgångar, eftersom det associerade encrypted_content kan dekrypteras på servern. (OpenAI behåller en ZDR-kunds dekrypteringsnyckel, men inte dess data.) Se PR:erna #642(öppnas i ett nytt fönster) och #1641(öppnas i ett nytt fönster) för de relaterade ändringarna i Codex för att stödja ZDR.

Generellt sett överstiger kostnaden för att sampla modellen kostnaden för nätverkstrafik, vilket gör sampling till det primära målet för våra effektivitetsinsatser. Det är därför prompt-caching är så viktigt, eftersom det möjliggör återanvändning av beräkningar från ett tidigare inferensanrop. När vi får cacheträffar är modellens sampling linjär snarare än kvadratisk. Vår dokumentation om prompt-caching (öppnas i ett nytt fönster)förklarar detta mer i detalj:

Cacheträffar är endast möjliga för exakta prefixmatchningar i en prompt. För att dra nytta av cachelagringens fördelar, placera statiskt innehåll som instruktioner och exempel i början av prompten och placera variabelt innehåll, såsom användarspecifik information, i slutet. Detta gäller också för bilder och verktyg, som måste vara identiska mellan förfrågningarna.

Med detta i åtanke, låt oss överväga vilka typer av operationer som kan orsaka en ”cache miss” i Codex:

  • Ändra de verktyg som är tillgängliga för modellen mitt i samtalet.
  • Ändra de modeller som är målet för svars-API-begäran (i praktiken ändrar detta det tredje objektet i den ursprungliga prompten, eftersom det innehåller modellspecifika instruktioner).
  • Ändra sandbox-konfigurationen, godkännandeläget eller den aktuella arbetskatalogen.

Codex-teamet måste vara noggranna när de introducerar nya funktioner i Codex CLI som kan äventyra prompt-cachning. Som ett exempel introducerade vårt initiala stöd för MCP-verktyg en bugg där vi misslyckades med att lista verktygen i en konsekvent ordning(öppnas i ett nytt fönster), vilket ledde till cachemissar. Observera att MCP-verktyg kan vara särskilt knepiga eftersom MCP-servrar kan ändra listan över verktyg de tillhandahåller i farten via en notifications/tools/list_changed(öppnas i ett nytt fönster)-avisering. Att beakta denna avisering mitt i ett långt samtal kan leda till en kostsam cachemiss.

När det är möjligt hanterar vi konfigurationsändringar som sker mitt i en konversation genom att lägga till ett nytt meddelande till input för att återspegla ändringen, snarare än att ändra ett tidigare meddelande:

  • Om konfigurationen av sandlådan eller godkännandeläget ändras, infogar(öppnas i ett nytt fönster) vi ett nytt meddelande med role=developer med samma format som det ursprungliga <permissions instructions>-objektet.
  • Om den aktuella arbetskatalogen ändras, infogar(öppnas i ett nytt fönster) vi ett nytt role=user-meddelande med samma format som det ursprungliga <environment_context>.

Vi anstränger oss mycket för att säkerställa cacheträffar för prestanda. Det finns en annan viktig resurs vi måste hantera: kontextfönstret.

Vår allmänna strategi för att inte få slut på kontextfönstret är att komprimera konversationen när antalet token överstiger en viss tröskel. Specifikt ersätter vi input med en ny, mindre lista av objekt som representerar samtalet, vilket gör att agenten kan fortsätta med en förståelse för vad som har hänt hittills. En tidig implementering av komprimering(öppnas i ett nytt fönster) krävde att användaren manuellt anropade kommandot /compact, vilket skulle fråga svars-API med hjälp av den befintliga konversationen samt anpassade instruktioner för sammanfattning(öppnas i ett nytt fönster). Codex använde det resulterande assistentmeddelandet som innehöll sammanfattningen som den nya indatan(öppnas i ett nytt fönster) för efterföljande konversationsomgångar.

Sedan dess har svars-API utvecklats för att stödja en särskild /responses/compact-slutpunkt(öppnas i ett nytt fönster) som utför komprimering mer effektivt. Den returnerar en lista med objekt(öppnas i ett nytt fönster) som kan användas istället för den tidigare input för att fortsätta konversationen och samtidigt frigöra kontextfönstret. Den här listan innehåller ett särskilt type=compaction-objekt med ett ogenomskinligt encrypted_content-objekt som bevarar modellens latenta förståelse av den ursprungliga konversationen. Nu använder Codex automatiskt denna slutpunkt för att komprimera samtalet när auto_compact_limit(öppnas i ett nytt fönster) överskrids.

Kommer snart

Vi har introducerat Codex-agent-loopen och gått igenom hur Codex skapar och hanterar sin kontext när den gör förfrågningar till en modell. Längs vägen lyfte vi fram praktiska överväganden och bästa praxis som gäller för alla som bygger en agentloop ovanpå svars-API.

Även om agent-loopen utgör grunden för Codex är det bara början. I kommande inlägg kommer vi att fördjupa oss i CLI:ets arkitektur, utforska hur verktygsanvändning implementeras och ta en närmare titt på Codex sandlådemodell.

Författare

Michael Bolin

Erkännanden

Ett stort tack till hela teamet som utvecklade Codex CLI.