Description technique détaillée du fonctionnement interne de l'agent de codage d'OpenAI Codex CLI, un outil de codage IA qui écrit du code, exécute des tests et corrige des bogues, par Michael Bolin Dans le but d'injecter l'IA dans une plus grande partie du processus de programmation, OpenAI a lancé en avril 2025 Codex CLI, un "agent" de codage conçu pour fonctionner localement à partir d'un logiciel terminal. Annoncé en même temps que les nouveaux modèles d'IA d'OpenAI, o3 et o4-mini, Codex CLI relie les modèles d'OpenAI au code local et aux tâches informatiques. Grâce à Codex CLI, les modèles d'OpenAI peuvent écrire et modifier du code sur un bureau et effectuer certaines actions, comme déplacer des fichiers.
Codex CLI est notre agent logiciel local multiplateforme, conçu pour produire des modifications logicielles fiables et de haute qualité tout en fonctionnant de manière sûre et efficace sur votre machine. Nous avons acquis une grande expertise dans la création d'un agent logiciel de classe mondiale depuis le lancement initial de la CLI en avril. Afin de partager ces connaissances, voici le premier article d'une série dans laquelle nous explorerons divers aspects du fonctionnement de Codex, ainsi que les leçons que nous avons apprises. (Pour une vue encore plus détaillée de la conception de Codex CLI, veuillez consulter notre référentiel open source à l'adresse https://github.com/openai/codex. Si vous souhaitez en savoir plus, de nombreux détails concernant nos décisions de conception sont disponibles dans les problématiques GitHub et les pull requests.)
Pour commencer, nous nous concentrerons sur la boucle de l'agent, qui est la logique centrale de Codex CLI chargée d'orchestrer l'interaction entre l'utilisateur, le modèle et les outils que le modèle invoque pour effectuer un travail logiciel significatif. Nous espérons que cet article vous donnera une bonne vue d'ensemble du rôle que joue notre agent (ou « harness ») dans l'exploitation d'un LLM.
Avant de commencer, une brève remarque sur la terminologie : chez OpenAI, « Codex » désigne une suite d'agents logiciels, comprenant Codex CLI, Codex Cloud et l'extension Codex VS Code. Cet article se concentre sur Codex harness, qui fournit la boucle d'agent centrale et la logique d'exécution sous-jacentes à toutes les expériences Codex et qui est accessible via Codex CLI. Pour simplifier, nous utiliserons ici les termes « Codex » et « Codex CLI » comme synonymes.
La boucle de l'agent
Au cœur de chaque agent IA se trouve ce que l'on appelle « la boucle de l'agent ». Voici une illustration simplifiée de la boucle de l'agent :
Pour commencer, l'agent recueille les données fournies par l'utilisateur afin de les inclure dans l'ensemble d'instructions textuelles qu'il prépare pour le modèle, appelé prompt .
L'étape suivante consiste à interroger le modèle en lui envoyant nos instructions et en lui demandant de générer une réponse, un processus appelé inférence. Lors de l'inférence, le prompt textuel est d'abord traduit en une séquence de tokens d'entrée, c'est-à-dire des entiers qui indexent le vocabulaire du modèle. Ces tokens sont ensuite utilisés pour échantillonner le modèle, produisant une nouvelle séquence de tokens de sortie.
Les tokens de sortie sont reconvertis en texte, qui devient la réponse du modèle. Les tokens étant produits de manière incrémentielle, cette traduction peut se faire pendant l'exécution du modèle, ce qui explique pourquoi de nombreuses applications basées sur le LLM affichent des résultats en continu. En pratique, l'inférence est généralement intégrée à une API qui fonctionne sur du texte, en faisant abstraction des détails de la tokenisation.
À l'issue de l'étape d'inférence, le modèle (1) produit une réponse finale à l'entrée originale de l'utilisateur ou (2) sollicite un appel d'outil à l'agent (par exemple : « exécuter ls et consigner la sortie »). Dans le cas de (2), l'agent exécute l'appel de l'outil et ajoute son résultat au prompt d'origine. Cette sortie est utilisée pour générer une nouvelle entrée qui sert à réinterroger le modèle ; l'agent peut alors prendre en compte ces nouvelles informations et essayer à nouveau.
Ce processus se répète jusqu'à ce que le modèle cesse d'émettre des appels d'outil et produise à la place un message pour l'utilisateur (appelé message d'assistant dans les modèles OpenAI). Dans de nombreux cas, ce message répond directement à la demande initiale de l'utilisateur, mais il peut également s'agir d'une question complémentaire destinée à l'utilisateur.
Étant donné que l'agent peut exécuter des appels d'outils qui modifient l'environnement local, sa « sortie » ne se limite pas au message de l'assistant. Dans de nombreux cas, la sortie principale d'un agent logiciel est le code qu'il écrit ou modifie sur votre machine. Néanmoins, chaque tour se termine toujours par un message de l'assistant, tel que « J'ai ajouté le fichier architecture.md que vous avez demandé », qui signale un état de fin dans la boucle de l'agent. Du point de vue de l'agent, son travail est terminé et il redonne la main à l'utilisateur.
Le parcours entre la saisie utilisateur et la réponse de l'agent illustré dans le diagramme est appelé un tour de conversation (un thread dans Codex). Ce tour de conversation peut inclure de nombreuses itérations entre l'inférence du modèle et les appels d'outils. Chaque fois que vous envoyez un nouveau message dans une conversation existante, l'historique de la conversation est inclus dans le prompt du nouveau tour, comprenant les messages et les appels d'outils des tours précédents :
Cela signifie que plus la conversation est longue, plus la longueur de l'invite utilisée pour échantillonner le modèle augmente. Cette longueur est importante car chaque modèle a une fenêtre de contexte, qui est le nombre maximal de tokens qu'il peut utiliser pour un appel d'inférence. Notez que cette fenêtre inclut à la fois des tokens d'entrée et de sortie. Comme vous pouvez l'imaginer, un agent pourrait décider d'effectuer des centaines d'appels d'outils en un seul tour, ce qui pourrait saturer la fenêtre contextuelle. Pour cette raison, la gestion de la fenêtre contextuelle fait partie des nombreuses responsabilités de l'agent. Maintenant, découvrons comment Codex exécute la boucle de l'agent.
Inférence de modèle
Codex CLI envoie des requêtes HTTP à l'API Responses pour exécuter l'inférence du modèle. Nous examinerons comment les informations circulent dans Codex, qui utilise l'API Responses pour piloter la boucle de l'agent.
L'endpoint de l'API Responses que Codex CLI utilise est configurable, il peut donc être utilisé avec n'importe quel endpoint qui implémente l'API Responses :
- Lors de l'utilisation de la connexion à ChatGPT avec Codex CLI, il utilise https://chatgpt.com/backend-api/codex/responses comme endpoint
- Lors de l'utilisation de l'authentification par clé API avec les modèles hébergés par OpenAI, il utilise https://api.openai.com/v1/responses comme endpoint
- Lors de l'exécution de Codex CLI avec --oss pour utiliser gpt-oss avec ollama 0.13.4+ ou LM Studio 0.3.39+, il se connecte par défaut à http://localhost:11434/v1/responses exécuté localement sur votre ordinateur
- Codex CLI peut être utilisé avec l'API Responses hébergée par un fournisseur de services cloud tel qu'Azure
Voyons comment Codex génère l'invite pour le premier appel d'inférence dans une conversation.
Élaboration du prompt initial
En tant qu'utilisateur final, vous ne spécifiez pas textuellement le prompt utilisé pour échantillonner le modèle lorsque vous interrogez l'API Responses. Au lieu de cela, vous spécifiez divers types d'entrée dans votre requête, et le serveur de l'API Responses décide comment structurer ces informations en un prompt que le modèle est conçu pour traiter. Vous pouvez considérer le prompt comme une « liste d'éléments » ; cette section expliquera comment votre requête est transformée en cette liste.
Dans le prompt initial, chaque élément de la liste est associé à un rôle. Le role indique l'importance que le contenu associé doit avoir et correspond à l'une des valeurs suivantes (par ordre de priorité décroissant) : système, développeur, utilisateur, assistant.
L'API Responses accepte une charge utile JSON avec de nombreux paramètres. Nous nous concentrerons sur les trois ci-dessous :
- Instructions : message système (ou message du développeur) intégré au contexte du modèle
- Outils : une liste d'outils que le modèle peut appeler lors de la génération d'une réponse
- Entrée : une liste d'entrées de texte, d'image ou de fichier pour le modèle
Dans Codex, le champ instructions est lu à partir du model_instructions_file dans ~/.codex/config.toml, s'il est spécifié ; sinon, les base_instructions associées à un modèle sont utilisées. Les instructions spécifiques au modèle se trouvent dans le référentiel Codex et sont intégrées à la CLI (par exemple, gpt-5.2-codex_prompt.md).
Le champ outils est une liste de définitions d'outils qui respectent un schéma défini par l'API Responses. Pour Codex, cela inclut les outils fournis par Codex CLI, les outils fournis par l'API Réponses qui doivent être mis à la disposition de Codex, ainsi que les outils fournis par l'utilisateur, généralement via des serveurs MCP :
| Code JavaScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | [ // Codex's [default shell tool](https://github.com/openai/codex/blob/99f47d6e9a3546c14c43af99c7a58fa6bd130548/codex-rs/core/src/tools/spec.rs#L278-L340) for spawning new processes locally. { "type": "function", "name": "shell", "description": "Runs a shell command and returns its output...", "strict": false, "parameters": { "type": "object", "properties": { "command": {"type": "array", "description": "The command to execute", ...}, "workdir": {"description": "The working directory...", ...}, "timeout_ms": {"description": "The timeout for the command...", ...}, ... }, "required": ["command"], } } // Codex's [built-in plan tool](https://github.com/openai/codex/blob/99f47d6e9a3546c14c43af99c7a58fa6bd130548/codex-rs/core/src/tools/handlers/plan.rs#L20-L60). { "type": "function", "name": "update_plan", "description": "Updates the task plan...", "strict": false, "parameters": { "type": "object", "properties": {"plan":..., "explanation":...}, "required": ["plan"] } }, // [Web search tool](https://platform.openai.com/docs/guides/tools-web-search) provided by the Responses API. { "type": "web_search", "external_web_access": false }, // MCP server for getting weather as configured in the // user's ~/.codex/config.toml. { "type": "function", "name": "mcp__weather__get-forecast", "description": "Get weather alerts for a US state", "strict": false, "parameters": { "type": "object", "properties": {"latitude": {...}, "longitude": {...}}, "required": ["latitude", "longitude"] } } ] |
Enfin, le champ Entrée de la charge utile JSON est une liste d'éléments. Codex insère les éléments suivants dans l'entrée avant d'ajouter le message de l'utilisateur :
1. Un message avec role=developer qui décrit la sandbox qui s'applique uniquement à l'outil shell fourni par Codex défini dans la section outils. Autrement dit, d'autres outils, tels que ceux fournis par les serveurs MCP, ne sont pas isolés par Codex et doivent appliquer leurs propres garde-fous.
Le message est conçu à partir d'un modèle où les éléments clés du contenu proviennent d'extraits de Markdown intégrés dans Codex CLI, tels que workspace_write.md et on_request.md :
| Code Texte brut : | Sélectionner tout |
1 2 3 4 5 | <permissions instructions> - description of the sandbox explaining file permissions and network access - instructions for when to ask the user for permissions to run a shell command - list of folders writable by Codex, if any </permissions instructions> |
2. (Facultatif) Un message avec role=developer dont le contenu est la valeur developer_instructions lue dans le fichier config.toml de l'utilisateur.
3. (Facultatif) Un message avec role=user dont le contenu correspond aux « instructions utilisateur », qui ne proviennent pas d'un seul fichier mais sont agrégées à partir de plusieurs sources. En général, des instructions plus spécifiques apparaissent par la suite :
- Contenu de AGENTS.override.md et AGENTS.md dans $CODEX_HOME.
- Sous réserve d'une limite (32 KiB, par défaut), recherchez dans chaque dossier depuis la source Git/du projet du cwd (s'il existe) jusqu'au cwd lui-même : ajoutez le contenu de tout fichier AGENTS.override.md, AGENTS.md, ou tout nom de fichier spécifié par project_doc_fallback_filenames dans config.toml.
- Si des compétences ont été configurées :
- un court préambule sur les compétences
- les métadonnées des compétences pour chaque compétence
- une section pour savoir comment utiliser les compétences
4. Un message avec role=user qui décrit l'environnement local dans lequel l'agent opère actuellement. Il spécifie le répertoire de travail actuel et le shell de l'utilisateur :
| Code Texte Brut : | Sélectionner tout |
1 2 3 4 | <environment_context> <cwd>/Users/mbolin/code/codex5</cwd> <shell>zsh</shell> </environment_context> |
Une fois que Codex a effectué tous les calculs ci-dessus pour initialiser l'entrée, il ajoute le message de l'utilisateur pour commencer la conversation.
Les exemples précédents se concentraient sur le contenu de chaque message, mais veuillez noter que chaque élément de l'entrée est un objet JSON avec type, role et contenu comme suit :
| Code JSON : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | { "type": "message", "role": "user", "content": [ { "type": "input_text", "text": "Add an architecture diagram to the README.md" } ] } |
Une fois que Codex a constitué la charge utile JSON complète à envoyer à l'API Responses, il effectue la requête HTTP POST avec un en-tête Authorization en fonction de la configuration de l'endpoint de l'API Responses dans ~/.codex/config.toml (des en-têtes HTTP et des paramètres de requête supplémentaires sont ajoutés s'ils sont spécifiés).
Lorsqu'un serveur API OpenAI Responses reçoit la requête, il utilise le JSON pour dériver le prompt pour le modèle comme suit (bien entendu, une implémentation personnalisée de l'API Responses pourrait faire un choix différent) :
Comme vous pouvez le constater, l'ordre des trois premiers éléments du prompt est déterminé par le serveur, et non par le client. Cela dit, parmi ces trois éléments, seul le contenu du message système est également contrôlé par le serveur, car les tools et les instructions sont déterminés par le client. Ceux-ci sont suivis de l'input provenant de la charge utile JSON pour compléter le prompt.
Maintenant que nous disposons de notre prompt, nous sommes prêts à tester le modèle.
Le premier tour
Cette requête HTTP adressée à l'API Responses initie le premier « tour » d'une conversation dans Codex. Le serveur répond avec un flux d'événements envoyés par le serveur (SSE). Les données de chaque événement sont une charge utile JSON avec un "type" qui commence par "response", ce qui pourrait ressembler à ceci (une liste complète des événements est disponible dans notre documentation API) :
| Code Texte Brut : | Sélectionner tout |
1 2 3 4 5 6 7 | data: {"type":"response.reasoning_summary_text.delta","delta":"ah ", ...}
data: {"type":"response.reasoning_summary_text.delta","delta":"ha!", ...}
data: {"type":"response.reasoning_summary_text.done", "item_id":...}
data: {"type":"response.output_item.added", "item":{...}}
data: {"type":"response.output_text.delta", "delta":"forty-", ...}
data: {"type":"response.output_text.delta", "delta":"two!", ...}
data: {"type":"response.completed","response":{...}} |
Codex traite le flux d'événements et les republie sous forme d'objets d'événements internes pouvant être utilisés par un client. Les événements tels que response.output_text.delta sont utilisés pour prendre en charge la diffusion en continu dans l'interface utilisateur, tandis que d'autres événements tels que response.output_item.added sont transformés en objets qui sont ajoutés à l'entrée pour les appels API Responses ultérieurs.
Supposons que la première requête adressée à l'API Responses inclue deux événements response.output_item.done : l'un avec type=reasoning et l'autre avec type=function_call. Ces événements doivent être représentés dans le champ input du JSON lorsque nous interrogeons à nouveau le modèle avec la réponse à l'appel de l'outil :
| Code JavaScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | [ /* ... original 5 items from the input array ... */ { "type": "reasoning", "summary": [ "type": "summary_text", "text": "**Adding an architecture diagram for README.md**\n\nI need to..." ], "encrypted_content": "gAAAAABpaDWNMxMeLw..." }, { "type": "function_call", "name": "shell", "arguments": "{\"command\":\"cat README.md\",\"workdir\":\"/Users/mbolin/code/codex5\"}", "call_id": "call_8675309..." }, { "type": "function_call_output", "call_id": "call_8675309...", "output": "<p align=\"center\"><code>npm i -g @openai/codex</code>..." } ] |
Le prompt résultant utilisé pour échantillonner le modèle dans le cadre de la requête suivante ressemblerait à ceci :
Notez en particulier que l'ancien prompt est un préfixe exact du nouveau prompt. C'est intentionnel, car cela rend les requêtes suivantes beaucoup plus efficaces, car cela nous permet de tirer parti de la mise en cache des prompts (que nous aborderons dans la section suivante sur la performance).
En revenant sur notre premier diagramme de la boucle de l'agent, nous constatons qu'il peut y avoir de nombreuses itérations entre l'inférence et l'appel d'outils. e prompt peut continuer à s'allonger jusqu'à ce que nous recevions finalement un message d'assistant, indiquant la fin du tour :
| Code Texte Brut : | Sélectionner tout |
1 2 | data: {"type":"response.output_text.done","text": "I added a diagram to explain...", ...}
data: {"type":"response.completed","response":{...}} |
Dans Codex CLI, nous présentons le message de l'assistant à l'utilisateur et concentrons notre attention sur la zone de saisie pour indiquer à l'utilisateur que c'est à son tour de poursuivre la conversation. Si l'utilisateur répond, le message de l'assistant du tour précédent ainsi que le nouveau message de l'utilisateur doivent être ajoutés à l'input dans la requête API Responses pour commencer le nouveau tour :
| Code JavaScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | [ /* ... all items from the last Responses API request ... */ { "type": "message", "role": "assistant", "content": [ { "type": "output_text", "text": "I added a diagram to explain the client/server architecture." } ] }, { "type": "message", "role": "user", "content": [ { "type": "input_text", "text": "That's not bad, but the diagram is missing the bike shed." } ] } ] |
Une fois de plus, comme nous poursuivons une conversation, la longueur de l'input que nous envoyons à l'API Responses continue d'augmenter :
Examinons ce que ce prompt en constante évolution signifie pour les performances.
Enjeux de performance
Vous vous demandez peut-être : « Attendez, la boucle de l'agent n'est-elle pas quadratique en termes de quantité de JSON envoyée à l'API Responses au cours de la conversation ? » Et vous auriez raison. Bien que l'API Responses prenne en charge un paramètre facultatif previous_response_id pour atténuer ce problème, Codex ne l'utilise pas actuellement, principalement pour maintenir des requêtes entièrement sans état et pour prendre en charge les configurations de la politique de non-conservation des données (ZDR).
Éviter previous_response_id simplifie les choses pour le fournisseur de l'API Responses, car cela garantit que chaque requête est sans état. Cela facilite également l'assistance aux clients qui ont opté pour la politique de non-conservation des données (ZDR), car conserver les données nécessaires pour prendre en charge previous_response_id serait contraire à la ZDR. Notez que les clients ZDR ne renoncent pas à la possibilité de bénéficier des messages de raisonnement propriétaires des tours précédents, car le encrypted_content associé peut être déchiffré sur le serveur. (OpenAI conserve la clé de déchiffrement d'un client ZDR, mais pas ses données.) Consultez les PR #642 et #1641 pour les modifications associées à Codex afin d'assurer la conformité avec la ZDR.
Les accès au cache ne sont possibles que pour des correspondances exactes de préfixe dans un prompt. Pour tirer parti des avantages de la mise en cache, placez le contenu statique tel que les instructions et les exemples au début de votre prompt, et placez le contenu variable, comme les informations spécifiques à l'utilisateur, à la fin. Cela s'applique également aux images et aux outils, qui doivent être identiques entre les demandes.
Dans cette optique, examinons les types d'opérations susceptibles de provoquer un « cache miss » (erreur de mise en cache) dans Codex :
- Modifier les tools disponibles pour le modèle en cours de conversation.
- Modifier le model qui est la cible de la requête de l'API Responses (en pratique, cela modifie le troisième élément du prompt d'origine, car il contient des instructions spécifiques au modèle).
- Modifier la configuration de la sandbox, du mode d'approbation ou du répertoire de travail actuel.
L'équipe Codex doit faire preuve de diligence lorsqu'elle introduit de nouvelles fonctionnalités dans CLI Codex qui pourraient compromettre la mise en cache rapide. À titre d'exemple, notre prise en charge initiale des outils MCP a introduit un bug où nous n'avons pas réussi à énumérer les outils dans un ordre cohérent, ce qui a entraîné des échecs de mise en cache. Notez que les outils MCP peuvent être particulièrement délicats, car les serveurs MCP peuvent modifier à la volée la liste des outils qu'ils fournissent via une notification notifications/tools/list_changed. Tenir compte de cette notification au milieu d'une longue conversation peut entraîner une perte de mise en cache coûteuse.
Lorsque cela est possible, nous gérons les modifications de configuration qui surviennent en cours de conversation en ajoutant un nouveau message à l'input pour refléter la modification plutôt que de modifier un message antérieur :
- Si la configuration de la sandbox ou le mode d'approbation changent, nous insérons un nouveau message role=developer avec le même format que l'élément <permissions instructions> d'origine.
- Si le répertoire de travail actuel change, nous insérons un nouveau message role=user avec le même format que le <environment_context> d'origine.
Nous déployons des efforts considérables pour garantir des performances optimales en matière de mise en cache. Il y a une autre ressource clé que nous devons gérer : la fenêtre contextuelle.
Notre stratégie générale pour éviter de dépasser la limite de la fenêtre contextuelle consiste à compresser la conversation une fois que le nombre de tokens dépasse un certain seuil. Plus précisément, nous remplaçons l'input par une nouvelle liste d'éléments plus petite, représentative de la conversation, ce qui permet à l'agent de poursuivre en comprenant ce qui s'est passé jusqu'à présent. Une première implémentation de la compression nécessitait que l'utilisateur exécute manuellement la commande /compact, qui interrogeait l'API Responses en utilisant la conversation existante ainsi que des instructions personnalisées pour la synthèse. Codex a utilisé le message de l'assistant résultant contenant la synthèse comme nouvelle input pour les tours de conversation suivants.
Depuis, l'API Responses a évolué pour prendre en charge un endpoint /responses/compact spécial qui effectue la compression plus efficacement. Il renvoie une liste d'éléments qui peuvent être utilisés à la place de l'input précédent pour poursuivre la conversation sans impacter la fenêtre contextuelle. Cette liste comprend un élément spécial type=compaction avec un élément encrypted_content opaque ui préserve la compréhension latente du modèle de la conversation originale. Désormais, Codex utilise automatiquement cet endpoint pour compresser la conversation lorsque la limite auto_compact_limit est dépassée.
À venir
Nous avons présenté la boucle d'agent Codex et expliqué comment Codex élabore et gère son contexte lors de l'interrogation d'un modèle. Nous avons également mis en avant les considérations pratiques et les meilleures pratiques applicables à toute personne développant une boucle d'agent sur l'API Responses.
Bien que la boucle d'agent fournisse la base de Codex, ce n'est que le début. Dans les prochains articles, nous examinerons l'architecture de la CLI, explorerons comment l'utilisation des outils est mise en œuvre et analyserons de plus près le modèle de sandboxing de Codex.
Source : "Unrolling the Codex agent loop"
Et vous ?
Pensez-vous que cette présentation est crédible ou pertinente ?
Quel est votre avis sur le sujet ?Voir aussi :
Mise à l'échelle de PostgreSQL pour prendre en charge 800 millions d'utilisateurs ChatGPT, par Bohan Zhang
Apprendre à raisonner avec le nouveau LLM OpenAI o1 formé avec l'apprentissage par renforcement pour effectuer des raisonnements complexes car o1 réfléchit avant de répondre
OpenAI lance une application autonome pour son assistant de codage alimenté par l'IA, Codex, permettant aux développeurs de logiciels de gérer plusieurs agents IA à la fois, disponible sur macOS
Vous avez lu gratuitement 1 022 articles depuis plus d'un an.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.