Codex CLI(opent in een nieuw venster) is onze platformonafhankelijke lokale software-agent. Hij is ontworpen om betrouwbare softwarewijzigingen van hoge kwaliteit door te voeren, terwijl hij veilig en efficiënt op je eigen machine draait. Sinds we de CLI in april lanceerden, hebben we enorm veel geleerd over het bouwen van een software-agent van wereldklasse. Om die inzichten te delen, is dit de eerste post in een doorlopende reeks waarin we de werking van Codex en onze harde lessen onder de loep nemen. (Wil je tot in detail weten hoe de Codex CLI is gebouwd? Bekijk dan onze open source repository op https://github.com/openai/codex(opent in een nieuw venster). Veel van onze ontwerpkeuzes zijn gedocumenteerd in GitHub-issues en pull requests.)
We beginnen met de agent loop: de kernlogica in Codex CLI die de interactie coördineert tussen de gebruiker, het model en de tools die het model inschakelt om nuttig werk te verrichten. We hopen dat dit artikel duidelijk maakt welke rol onze agent (of 'harness') speelt bij het gebruik van een LLM.
Voordat we beginnen, een korte opmerking over terminologie: bij OpenAI omvat 'Codex' verschillende software-agents, waaronder Codex CLI, Codex Cloud en de Codex VS Code-extensie. Dit artikel richt zich op de Codex harness: de basislaag die de agent loop en uitvoeringslogica bevat voor alle Codex-ervaringen, en die direct zichtbaar is in de Codex CLI. Voor het gemak gebruiken we hier de termen 'Codex' en 'Codex CLI' door elkaar.
De kern van elke AI-agent wordt gevormd door de zogenoemde 'agent loop'. Een vereenvoudigde weergave ziet er als volgt uit:
Allereerst neemt de agent de input van de gebruiker en verwerkt deze in een set tekstuele instructies voor het model: de prompt.
Vervolgens wordt het model aangeroepen door de instructies te versturen en om een antwoord te vragen. Dit proces heet inferentie. Tijdens inferentie wordt de tekstuele prompt eerst vertaald naar een reeks input-tokens(opent in een nieuw venster) (integers die verwijzen naar de vocabulaire van het model). Deze tokens worden gebruikt om het model te samplen, wat een nieuwe reeks output-tokens oplevert.
De output-tokens worden terugvertaald naar tekst, wat het antwoord van het model vormt. Omdat tokens stuk voor stuk worden gegenereerd, kan deze vertaling plaatsvinden terwijl het model nog draait. Daarom tonen veel LLM-applicaties de tekst als een stream. In de praktijk zit inferentie meestal achter een API die op tekst werkt en de tokenisatiedetails afschermt.
Na de inferentiestap produceert het model ofwel: (1) een definitieve reactie op de input van de gebruiker, of (2) een vraag om een tool call die de agent moet uitvoeren (bijv. 'voer ls uit en rapporteer de output'). In het tweede geval voert de agent de opdracht uit en voegt de output toe aan de oorspronkelijke prompt. Deze output wordt gebruikt om een nieuwe input te genereren waarmee het model opnieuw wordt aangeroepen. De agent kan deze nieuwe informatie vervolgens meenemen in een volgende poging.
Dit proces herhaalt zich totdat het model stopt met het genereren van tool calls, maar een bericht voor de gebruiker genereert (in OpenAI-modellen noemen we dit een assistant message). In veel gevallen geeft dit bericht direct antwoord op het oorspronkelijke verzoek van de gebruiker, maar het kan ook een vervolgvraag zijn.
Omdat de agent tool calls kan uitvoeren die de lokale omgeving aanpassen, blijft zijn 'output' niet beperkt tot het bericht van de assistent. Vaak is de belangrijkste output van een software-agent namelijk de code die hij op je machine schrijft of bewerkt. Toch eindigt elke turn altijd met een assistent-bericht, zoals 'Ik heb architecture.md toegevoegd, zoals gevraagd', waarmee de agent aangeeft dat de taak binnen de agent loop is afgerond. Vanuit het perspectief van de agent zit het werk erop en ligt de controle weer bij de gebruiker.
De route van gebruikersinput naar agent-respons, zoals weergegeven in het diagram, noemen we één turn in een conversatie (of een thread in Codex). Zo'n conversation turn kan echter vele iteraties tussen de model-inferentie en tool calls bevatten. Elke keer dat je een nieuw bericht naar een bestaande conversatie stuurt, wordt de volledige conversatiegeschiedenis meegenomen in de prompt voor de nieuwe turn. Dit omvat ook de berichten en toolaanroepen uit eerdere turns:
Dit betekent dat naarmate de conversatie vordert, de prompt die naar het model wordt gestuurd steeds langer wordt. Die lengte is van belang omdat elk model een contextvenster heeft: het maximale aantal tokens dat het kan verwerken in één inferentie-aanroep. Let op: dit venster omvat zowel de input- als de output-tokens. Zoals je je kunt voorstellen, kan een agent besluiten om in één turn honderden tool calls te doen, waardoor het contextvenster vol kan lopen. Daarom is het beheren van dit contextvenster een van de vele verantwoordelijkheden van de agent. Laten we nu de diepte in gaan en kijken hoe Codex de agent loop precies uitvoert.
De Codex CLI stuurt HTTP-verzoeken naar de Responses API(opent in een nieuw venster) om model-inferentie uit te voeren. We zullen bekijken hoe de informatiestroom door Codex loopt, waarbij de Responses API wordt gebruikt om de agent loop aan te sturen.
Het Responses API-endpoint dat de Codex CLI gebruikt is configureerbaar(opent in een nieuw venster). Hierdoor werkt de CLI met elk endpoint dat de Responses API implementeert(opent in een nieuw venster):
- Bij gebruik van een ChatGPT‑login(opent in een nieuw venster) met de Codex CLI, wordt
https://chatgpt.com/backend-api/codex/responsesgebruikt als endpoint - Bij gebruik van authenticatie met een API-sleutel(opent in een nieuw venster) met door OpenAI gehoste modellen, wordt
https://api.openai.com/v1/responsesgebruikt als endpoint - Wanneer je de Codex CLI draait met de optie
--ossom gpt-oss te gebruiken met ollama 0.13.4+(opent in een nieuw venster) of LM Studio 0.3.39+(opent in een nieuw venster), is de standaardhttp://localhost:11434/v1/responses, die lokaal op je computer draait - Codex CLI kan ook gebruikt worden met de Responses API gehost door een cloudprovider zoals Azure
Laten we onderzoeken hoe Codex de prompt opbouwt voor de allereerste inferentie-aanroep in een conversatie.
Als eindgebruiker geef je niet letterlijk de prompt op die wordt gebruikt om het model te samplen wanneer je de Responses API bevraagt. In plaats daarvan geef je verschillende soorten input mee als onderdeel van je query. De Responses API-server bepaalt vervolgens hoe deze informatie wordt omgezet in een prompt die het model kan verwerken. Je kunt de prompt zien als een lijst met items. In dit gedeelte leggen we uit hoe je query wordt omgezet in die lijst.
In de initiële prompt is elk item in de lijst gekoppeld aan een rol. De rol geeft aan hoeveel gewicht de bijbehorende inhoud moet hebben en bestaat uit één van de volgende waarden (in afnemende volgorde van prioriteit): system, developer, user, assistant.
De Responses API(opent in een nieuw venster) accepteert een JSON-payload met vele parameters. We richten ons hier op de volgende drie:
instructions(opent in een nieuw venster): een systeem- of ontwikkelaarsbericht dat wordt toegevoegd aan de context van het modeltools(opent in een nieuw venster): een lijst met tools die het model mag aanroepen tijdens het genereren van een antwoordinput(opent in een nieuw venster): een lijst met tekst-, afbeeldings- of bestandsinputs voor het model
Binnen Codex wordt het veld instructions uitgelezen uit de model_instructions_file(opent in een nieuw venster) in ~/.codex/config.toml, indien opgegeven; anders worden de base_instructions die aan het model zijn gekoppeld(opent in een nieuw venster) gebruikt. Modelspecifieke instructies bevinden zich in de Codex-repository en zijn gebundeld in de CLI (bijvoorbeeld gpt-5.2-codex_prompt.md(opent in een nieuw venster)).
Het veld tools bevat een lijst met tooldefinities die voldoen aan een schema van de Responses API. Voor Codex omvat dit tools die worden geleverd door de Codex CLI, tools vanuit de Responses API die beschikbaar moeten zijn voor Codex, en tools die door de gebruiker worden aangedragen, meestal via MCP-servers:
Tot slot is het veld input van de JSON-payload een lijst met items. Codex voegt de volgende items toe(opent in een nieuw venster) aan de input voordat het bericht van de gebruiker wordt toegevoegd:
1. Een bericht met role=developer dat de sandbox beschrijft die alleen van toepassing is op de shell-tool die door Codex zelf wordt geleverd (beschreven in het gedeelte tools). Dat betekent dat andere tools, zoals die van MCP-servers, niet door Codex in een sandbox worden geplaatst. Zij zijn zelf verantwoordelijk voor het handhaven van hun eigen veiligheidsmaatregelen.
Het bericht wordt opgebouwd vanuit een template, waarbij de belangrijkste inhoud afkomstig is uit Markdown-fragmenten die in de Codex CLI zijn gebundeld, zoals workspace_write.md(opent in een nieuw venster) en on_request.md(opent in een nieuw venster):
2. (Optioneel) Een bericht met role=developer waarvan de inhoud bestaat uit de waarde van developer_instructions, uitgelezen uit het config.toml-bestand van de gebruiker.
3. (Optioneel) Een bericht met role=user dat de 'gebruikersinstructies' bevat. Deze zijn niet afkomstig uit één enkel bestand, maar worden samengevoegd uit meerdere bronnen(opent in een nieuw venster). Over het algemeen verschijnen specifiekere instructies later:
- Inhoud van
AGENTS.override.mdenAGENTS.mdin$CODEX_HOME - Met een limiet (standaard 32 KiB) wordt gezocht in elke map vanaf de Git-/projectroot van de
cwd(als deze bestaat) tot aan decwdzelf. Hierbij wordt de inhoud toegevoegd vanAGENTS.override.md,AGENTS.md, of een bestandsnaam die is opgegeven bijproject_doc_fallback_filenames in config.toml - Als er skills(opent in een nieuw venster) zijn ingesteld:
- een korte inleiding over skills
- de metadata(opent in een nieuw venster) voor elke skill
- een gedeelte over hoe de skills gebruikt moeten worden(opent in een nieuw venster)
4. Een bericht met role=user dat de lokale omgeving beschrijft waarin de agent momenteel actief is. Dit specificeert de huidige werkmap en de shell van de gebruiker(opent in een nieuw venster):
Zodra Codex al deze verwerkingsstappen heeft uitgevoerd om de input te initialiseren, wordt het bericht van de gebruiker toegevoegd om de conversatie te starten.
De voorgaande voorbeelden richtten zich op de inhoud van elk bericht, maar elk element van de input is een JSON-object met een type, role(opent in een nieuw venster) en content:
Zodra Codex de volledige JSON-payload heeft opgebouwd, wordt het HTTP POST-verzoek verstuurd naar de Responses API. Hierbij wordt een Authorization-header gebruikt die afhankelijk is van de configuratie in ~/.codex/config.toml (indien gespecificeerd worden extra HTTP-headers en query-parameters toegevoegd).
Wanneer een OpenAI Responses API-server het verzoek ontvangt, gebruikt deze de JSON om de prompt voor het model af te leiden (let op: een aangepaste implementatie van de Responses API zou hier een andere keuze kunnen maken):
Zoals je ziet, wordt de volgorde van de eerste drie items in de prompt bepaald door de server, niet door de client. Van die drie items wordt echter alleen de inhoud van het systeembericht ook daadwerkelijk door de server beheerd; de tools en instructions worden bepaald door de client. Deze worden gevolgd door de input uit de JSON-payload om de prompt compleet te maken.
Nu we onze prompt hebben, zijn we klaar om het model te samplen.
Dit HTTP-verzoek naar de Responses API initieert de eerste turn van een conversatie in Codex. De server antwoordt met een Server-Sent Events (SSE(opent in een nieuw venster))-stream. De data van elk event is een JSON-payload met een type dat begint met response, wat er ongeveer zo uit kan zien (een volledige lijst met events is te vinden in onze API-documentatie(opent in een nieuw venster)):
Codex verwerkt de stroom van events(opent in een nieuw venster) en publiceert ze opnieuw als interne event-objecten die door een client gebruikt kunnen worden. Events zoals response.output_text.delta worden gebruikt om streaming in de UI te ondersteunen, terwijl andere events zoals response.output_item.added worden omgezet in objecten die aan de input worden toegevoegd voor volgende aanroepen naar de Responses API.
Stel dat het eerste verzoek aan de Responses API twee response.output_item.done events bevat: één met type=reasoning en één met type=function_call. Deze events moeten in het input-veld van de JSON worden opgenomen wanneer we het model opnieuw bevragen met het resultaat van de tool call:
De resulterende prompt die wordt gebruikt om het model te samplen tijdens deze vervolgvraag, ziet er dan als volgt uit:
Let er vooral op dat de oude prompt een exacte prefix is van de nieuwe prompt. Dit is opzettelijk: het maakt vervolgverzoeken veel efficiënter omdat we gebruik kunnen maken van prompt caching (dit bespreken we in het volgende gedeelte over prestaties).
Als we terugkijken naar ons eerste schema van de agent loop, zien we dat er vele iteraties kunnen plaatsvinden tussen inferentie en het aanroepen van tools. De prompt kan blijven groeien totdat we uiteindelijk een assistentbericht ontvangen, wat het einde van de turn markeert:
In de Codex CLI tonen we dit bericht aan de gebruiker en plaatsen we de focus in het invoerveld om aan te geven dat het nu de beurt is aan de gebruiker om de conversatie voort te zetten. Als de gebruiker reageert, moeten zowel het assistentbericht van de vorige turn als het nieuwe bericht van de gebruiker aan de input worden toegevoegd in het nieuwe verzoek aan de Responses API om de nieuwe turn te starten:
Omdat we een conversatie voortzetten, neemt de lengte van de input die we naar de Responses API sturen steeds verder toe:
Laten we eens kijken wat deze alsmaar groeiende prompt betekent voor de prestaties.
Misschien vraag je je af: "Wacht even, verloopt de groei van de hoeveelheid JSON die tijdens een conversatie naar de Responses API wordt gestuurd niet kwadratisch binnen de agent loop?" En daar heb je gelijk in. Hoewel de Responses API een optionele parameter previous_response_id(opent in een nieuw venster) ondersteunt om dit probleem te verhelpen, maakt Codex daar momenteel geen gebruik van. De belangrijkste redenen zijn om verzoeken volledig stateless te houden en om configuraties voor Zero Data Retention (ZDR) te ondersteunen.
Het vermijden van previous_response_id vereenvoudigt de zaken voor de aanbieder van de Responses API, omdat het garandeert dat elk verzoek stateless is. Dit maakt het ook eenvoudig om klanten te ondersteunen die hebben gekozen voor Zero Data Retention (ZDR)(opent in een nieuw venster), aangezien het opslaan van de data die nodig is voor previous_response_id strijdig zou zijn met ZDR. Let wel: ZDR-klanten hoeven niet in te leveren op de voordelen van propriëtaire redeneerberichten uit eerdere turns, omdat de bijbehorende encrypted_content op de server ontsleuteld kan worden. (OpenAI bewaart wel de decryptiesleutel van een ZDR-klant, maar niet hun data.) Zie PR's #642(opent in een nieuw venster) en #1641(opent in een nieuw venster) voor de gerelateerde wijzigingen in Codex ter ondersteuning van ZDR.
Over het algemeen wegen de kosten voor het samplen van het model zwaarder dan de kosten van het netwerkverkeer. Daarom richten we onze efficiëntie-inspanningen vooral op het samplen. Dit is waarom prompt caching zo belangrijk is: het stelt ons in staat om rekenwerk van een eerdere inferentie-aanroep te hergebruiken. Wanneer we cache hits krijgen, verloopt het samplen van het model lineair in plaats van kwadratisch. Onze documentatie over prompt caching(opent in een nieuw venster) legt dit in detail uit:
Cache hits zijn alleen mogelijk bij exacte prefix matches binnen een prompt. Om te profiteren van caching, moet je statische inhoud (zoals instructies en voorbeelden) aan het begin van je prompt plaatsen en variabele inhoud (zoals gebruikersspecifieke informatie) aan het einde. Dit geldt ook voor afbeeldingen en tools: deze moeten identiek zijn tussen de verschillende verzoeken.
Met dit in het achterhoofd kijken we naar welke bewerkingen in Codex een 'cache miss' kunnen veroorzaken:
- Het wijzigen van de beschikbare
toolsvoor het model midden in een conversatie. - Het wijzigen van het
modelwaarop het verzoek aan de Responses API is gericht (in de praktijk verandert dit het derde item in de oorspronkelijke prompt, omdat daar modelspecifieke instructies staan). - Het wijzigen van de sandbox-configuratie, de goedkeuringsmodus of de huidige werkmap.
Het Codex-team moet zorgvuldig zijn bij het introduceren van nieuwe functies in de Codex CLI die prompt caching kunnen ondermijnen. Zo introduceerde onze initiële ondersteuning voor MCP-tools een bug waarbij de tools niet in een consistente volgorde werden aangeboden(opent in een nieuw venster), wat leidde tot cache misses. Let op: MCP-tools kunnen extra lastig zijn, omdat MCP-servers de lijst met aangeboden tools on the fly kunnen wijzigen via een notifications/tools/list_changed(opent in een nieuw venster)-melding. Het verwerken van zo'n wijziging midden in een lang gesprek kan een kostbare cache miss veroorzaken.
Waar mogelijk verwerken we configuratiewijzigingen halverwege een conversatie door een nieuw bericht aan de input toe te voegen dat de wijziging weerspiegelt, in plaats van een eerder bericht aan te passen:
- Als de sandbox-configuratie of goedkeuringsmodus verandert, voegen(opent in een nieuw venster) we een nieuw bericht met
role=developertoe in hetzelfde formaat als het oorspronkelijke<permissions instructions>-item. - Als de huidige werkmap verandert, voegen(opent in een nieuw venster) we een nieuw bericht met
role=usertoe in hetzelfde formaat als de oorspronkelijke<environment_context>.
We doen er alles aan om cache hits te garanderen voor optimale prestaties. Er is echter nog een belangrijke bron die we moeten beheren: het contextvenster.
Onze algemene strategie om te voorkomen dat het contextvenster vol raakt, is het comprimeren van de conversatie zodra het aantal tokens een bepaalde drempel overschrijdt. Concreet vervangen we de input door een nieuwe, kortere lijst met items die representatief is voor de conversatie. Hierdoor kan de agent verdergaan met behoud van inzicht in wat er tot nu toe is gebeurd. Een vroege implementatie van deze compressie(opent in een nieuw venster) vereiste dat de gebruiker handmatig het commando /compact uitvoerde. Dit stuurde een verzoek naar de Responses API met de bestaande conversatie plus instructies om samen te vatten(opent in een nieuw venster). Codex gebruikte vervolgens het antwoord met de samenvatting als de nieuwe input(opent in een nieuw venster) voor het vervolg van de conversatie.
Sindsdien is de Responses API doorontwikkeld en ondersteunt nu een speciaal /responses/compact-endpoint(opent in een nieuw venster) dat compaction efficiënter uitvoert. Het retourneert een lijst met items(opent in een nieuw venster) die de vorige input vervangt, zodat het gesprek kan worden voortgezet terwijl er ruimte in het contextvenster wordt vrijgemaakt. Deze lijst bevat een speciaal item met type=compaction en een onleesbaar encrypted_content -veld, waarin het impliciete begrip van het model over de oorspronkelijke conversatie bewaard blijft. Codex gebruikt dit endpoint nu automatisch om de conversatie te comprimeren wanneer de auto_compact_limit(opent in een nieuw venster) wordt overschreden.
We hebben de Codex agent loop geïntroduceerd en laten zien hoe Codex zijn context opbouwt en beheert bij het bevragen van een model. Onderweg hebben we praktische overwegingen en best practices belicht die relevant zijn voor iedereen die een agent loop bouwt bovenop de Responses API.
Hoewel de agent loop de fundering vormt voor Codex, is dit slechts het begin. In komende artikelen duiken we dieper in de architectuur van de CLI, onderzoeken we hoe toolgebruik is geïmplementeerd en nemen we het sandboxing-model van Codex onder de loep.


