Passer au contenu principal
OpenAI

23 janvier 2026

Ingénierie

Déroulement de la boucle de l'agent Codex

Par Michael Bolin, membre de l’équipe technique

Chargement…

La CLI Codex(s'ouvre dans une nouvelle fenêtre) est notre agent logiciel local multiplateforme, conçu pour produire des modifications logicielles de haute qualité et fiables, tout en fonctionnant de manière sécuritaire et efficace sur votre machine. Nous avons énormément appris sur la façon de créer un agent logiciel de calibre mondial depuis que nous avons lancé pour la première fois la CLI en avril. Pour décortiquer ces informations, voici le premier article d’une série continue où nous examinerons divers aspects du fonctionnement de Codex, ainsi que des leçons apprises à la dure. (Pour une vue encore plus détaillée de la façon dont la CLI Codex est construite, consultez notre dépôt open source à https://github.com/openai/codex(s'ouvre dans une nouvelle fenêtre). Bon nombre des détails les plus fins de nos décisions de conception sont consignés dans les issues GitHub et les pull requests si vous souhaitez en savoir plus.

Pour commencer, nous nous concentrerons sur la boucle d’agent, qui constitue la logique centrale dans la CLI Codex et qui est responsable de l’orchestration de l’interaction entre l’utilisateur, le modèle et les outils que le modèle invoque pour réaliser 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 « harnais ») dans l’exploitation d’un LLM.

Avant de commencer, une brève remarque sur la terminologie : chez OpenAI, « Codex » désigne une suite d'offres d'agents logiciels, y compris la CLI Codex, Codex Cloud et l'extension Codex pour VS Code. Ce billet se concentre sur le harnais de Codex, qui fournit la boucle d’agent principale et la logique d’exécution sous-jacente à toutes les expériences Codex, et qui est accessible via la CLi Codex. Pour simplifier ici, nous utiliserons les termes « Codex » et « CLI Codex » de manière interchangeable.

La boucle d'agent

Au cœur de chaque agent IA se trouve un élément appelé « la boucle d’agent ». Une illustration simplifiée de la boucle d'agent se présente ainsi :

Diagramme intitulé « Boucle d'agent » illustrant comment un système d’IA traite une demande d’utilisateur, appelle des outils, observe les résultats, met à jour son plan et renvoie des résultats. Des flèches relient des étapes telles que l'entrée de l'utilisateur, le raisonnement du modèle, les actions des outils et la réponse finale.

Pour commencer, l’agent prend les entrées de l’utilisateur à inclure dans l’ensemble d’instructions textuelles qu’il prépare pour le modèle, connu sous le nom de invite.

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 connu sous le nom d’ inférence. Lors de l’inférence, l’invite textuelle est d’abord traduite en une séquence de tokens(s'ouvre dans une nouvelle fenêtre) d’entrée—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 traduits en texte, ce qui constitue la réponse du modèle. Étant donné que les tokens sont générés de manière incrémentielle, cette traduction peut se faire pendant que le modèle fonctionne, ce qui explique pourquoi de nombreuses applications basées sur des LLM affichent un flux de sortie continu. En pratique, l’inférence est généralement encapsulée derrière une API qui opère sur du texte, en masquant les détails de la tokenisation.

À la suite de l’étape d’inférence, le modèle (1) produit une réponse finale à l’entrée originale de l’utilisateur ou (2) demande un appel d’outil que l’agent est censé effectuer (p. ex. « exécuter ls et communiquer la sortie »). Dans le cas de (2), l’agent exécute l’appel d’outil et ajoute son résultat à l’invite 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 plutôt un message pour l’utilisateur (appelé un 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 aussi s’agir d’une question de suivi pour l’utilisateur.

Comme l’agent peut exécuter des appels d’outil qui modifient l’environnement local, sa « sortie » ne se limite pas au message de l’assistant. Dans de nombreux cas, la principale production 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 architecture.md que vous avez demandé », qui signale un état de terminaison dans la boucle d’agent. Du point de vue de l’agent, son travail est terminé et le contrôle revient à l’utilisateur.

Le parcours de l’entrée utilisateur à la réponse de l’agent présenté dans le diagramme est appelé un tour de conversation (un fil dans Codex). Bien que ce tour de conversation puisse 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 l’invite du nouveau tour, comprenant les messages et les appels d’outils des tours précédents :

Diagramme intitulé « Boucle d’agent multi-tour » montrant comment un agent IA reçoit de manière itérative les entrées de l’utilisateur, génère des actions, consulte des outils, met à jour l’état et retourne les résultats. Comprend des étapes étiquetées, des flèches et des exemples de résultats d’outils illustrant le cycle de raisonnement de l’agent.

Cela signifie qu'à mesure que la conversation s'allonge, la longueur de l'invite utilisée pour échantillonner le modèle s'accroît également. Cette longueur est importante, car chaque modèle possède une fenêtre de contexte, qui représente le nombre maximal de tokens qu'il peut utiliser pour un seul 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 épuiser la fenêtre de contexte. Pour cette raison, la gestion de la fenêtre de contexte fait partie des nombreuses responsabilités de l’agent. Maintenant, plongeons-nous pour voir comment Codex exécute la boucle d'agent.

Inférence de modèle

Le CLI Codex envoie des demandes HTTP à l'API Responses(s'ouvre dans une nouvelle fenêtre) pour exécuter l'inférence du modèle. Nous examinerons comment l’information circule dans Codex, qui utilise l'API Responses pour piloter la boucle d’agent.

Le endpoint de l'API Responses utilisé par le CLI Codex est configurable(s'ouvre dans une nouvelle fenêtre), il peut donc être utilisé avec n'importe quel endpoint qui implémente l'API Responses(s'ouvre dans une nouvelle fenêtre) :

Explorons comment Codex génère l’invite pour le premier appel d’inférence dans une conversation.

Développement de l’invite initiale

En tant qu'utilisateur final, vous ne spécifiez pas l'invite utilisée pour échantillonner le modèle mot à mot lorsque vous interrogez l'API Responses. Au lieu, 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 une invite que le modèle est conçu pour consommer. Vous pouvez considérer l’invite comme une « liste d’éléments »; cette section expliquera comment votre requête est transformée en cette liste.

Dans l’invite initiale, 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 décroissant de priorité) : système, développeur, utilisateur, assistant.

L’API Responses(s'ouvre dans une nouvelle fenêtre) accepte une charge utile JSON avec plusieurs paramètres. Nous nous concentrerons sur ces trois éléments :

Dans Codex, le champ instructions est lu à partir du model_instructions_file(s'ouvre dans une nouvelle fenêtre) dans ~/.codex/config.toml, s’il est spécifié; sinon, les base_instructions associées à un modèle(s'ouvre dans une nouvelle fenêtre) sont utilisées. Les instructions spécifiques au modèle se trouvent dans le dépôt Codex et sont intégrées dans l’interface de ligne de commande (p. ex., gpt-5.2-codex_prompt.md(s'ouvre dans une nouvelle fenêtre)).

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 le CLI Codex, les outils fournis par l'API Responses qui devraient être mis à la disposition de Codex, ainsi que les outils fournis par l'utilisateur, généralement via des serveurs MCP :

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
]

Enfin, le champ entrée de la charge utile JSON est une liste d'éléments. Codex insère les éléments suivants(s'ouvre dans une nouvelle fenêtre) dans l'entrée avant d’ajouter le message de l’utilisateur :

1. Un message avec role=developer qui décrit le bac à sable qui s'applique uniquement à l'outil coquille</code> 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 construit à partir d’un modèle où les éléments clés du contenu proviennent d’extraits de Markdown intégrés dans la CLI Codex, tels que workspace_write.md(s'ouvre dans une nouvelle fenêtre) et on_request.md(s'ouvre dans une nouvelle fenêtre):

Texte en clair

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. (Facultatif) Un message avec role=developer dont le contenu est la valeur developer_instructions lue à partir du 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(s'ouvre dans une nouvelle fenêtre). En général, des instructions plus précises apparaissent plus tard :

4. Un message avec role=user qui décrit l’environnement local dans lequel l’agent est actuellement en fonctionnement. Cela spécifie le répertoire de travail actuel et la coquille de l’utilisateur(s'ouvre dans une nouvelle fenêtre):

Texte en clair

1
<environment_context>
2
<cwd>/Users/mbolin/code/codex5</cwd>
3
<shell>zsh</shell>
4
</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 portaient sur le contenu de chaque message, mais veuillez noter que chaque élément d'entrée est un objet JSON avec type, role(s'ouvre dans une nouvelle fenêtre) et content comme suit :

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
}

Une fois que Codex a construit la charge utile JSON complète à envoyer à l'API Responses, il effectue ensuite la demande HTTP POST avec un en-tête Authorization selon la configuration de l'endpoint de l'API Responses dans ~/.codex/config.toml (des en-têtes HTTP supplémentaires et des paramètres de requête sont ajoutés si spécifiés).

Lorsqu’un serveur de l’API Responses d’OpenAI reçoit une demande, il utilise le JSON pour dériver l’invite pour le modèle comme suit (à noter qu’une implémentation personnalisée de l’API Responses pourrait faire un choix différent) :

Diagramme instantané montrant une seule étape dans une boucle d'agent IA. Une demande d'utilisateur entre dans le modèle et produit une pensée, une action avec un nom d’outil et une entrée d’outil. Le diagramme met en lumière cette étape intermédiaire de raisonnement avant que l'outil ne soit appelé.

Comme vous pouvez le constater, l’ordre des trois premiers éléments dans l’invite 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 outils et les instructions sont déterminés par le client. Celles-ci sont suivies de l’entrée de la charge utile JSON pour compléter l’invite.

Maintenant que nous avons notre invite, nous sommes prêts à tester le modèle.

Le premier tour

Cette demande HTTP à 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(s'ouvre dans une nouvelle fenêtre)). Les données de chaque événement sont une charge utile JSON avec un « type » qui commence par « répondre », ce qui pourrait ressembler à ceci (une liste complète des événements se trouve dans notre documentation API(s'ouvre dans une nouvelle fenêtre)) :

Texte en clair

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 consomme le flux d’événements(s'ouvre dans une nouvelle fenêtre) et les republie sous forme d’objets d’événements internes pouvant être utilisés par un client. Des événements tels que response.output_text.delta sont utilisés pour prendre en charge la lecture 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 input pour les appels API Responses suivants.

Supposons que la première demande à l'API Responses inclue deux événements response.output_item.done : l'un avec type=raisonnement et l'autre avec type=function_call. Ces événements doivent être représentés dans le champ entrée du JSON lorsque nous interrogeons de nouveau le modèle avec la réponse à l'appel de l'outil : 

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
]

L'invite résultante utilisée pour échantillonner le modèle dans le cadre de la requête suivante ressemblerait à ceci :

Diagramme intitulé « Snapshot 2 » montrant un agent IA après un appel d’outil. Le modèle reçoit une observation d’outil et génère une nouvelle pensée et action. Les flèches relient les entrées, les observations et les sorties pour illustrer comment l’agent itère sa boucle de raisonnement.

Notez en particulier que l'ancienne invite est un préfixe exact de la nouvelle invite. C’est intentionnel, car cela rend les demandes ultérieures beaucoup plus efficaces, car cela nous permet de tirer parti de la mise en cache des invites (dont nous parlerons dans la section suivante sur les performances).

En revenant à notre premier diagramme de la boucle d’agent, nous constatons qu’il pourrait y avoir de nombreuses itérations entre l’inférence et l’appel d’outils. L’invite peut continuer à s’allonger jusqu’à ce que nous recevions enfin un message de l’assistant, indiquant la fin du tour :

Texte en clair

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

Dans la CLI Codex, nous présentons le message de l’assistant à l’utilisateur et activons 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 et le nouveau message de l’utilisateur doivent être ajoutés à l’entrée dans la demande d’API Responses pour commencer le nouveau tour :

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
]

Une fois encore, comme nous poursuivons une conversation, la longueur du input que nous envoyons à l'API Responses ne cesse d'augmenter :

Diagramme intitulé « Snapshot 3 » montrant l’étape finale d’une boucle d’agent IA. Après avoir reçu les résultats des outils, le modèle génère une réflexion finale et une réponse définitive qui est renvoyée à l'utilisateur. Les flèches illustrent la transition de la sortie de l’outil à la réponse complétée.

Examinons ce que cette invite en constante expansion implique pour la performance.

Considérations de performance

Vous vous demandez peut-être : « Attendez, la boucle d’agent n’est-elle pas quadratique en fonction de la quantité de JSON envoyée à l'API Responses au fil de la conversation? » Et vous auriez raison. Bien que l’API Responses prenne en charge un paramètre previous_response_id(s'ouvre dans une nouvelle fenêtre) facultatif pour atténuer ce problème, Codex ne l’utilise pas aujourd’hui, principalement pour garder les demandes entièrement sans état et pour prendre en charge les configurations d’aucune conservation des données (ZDR).

Éviter previous_response_id simplifie les choses pour le fournisseur de l’API Responses, car cela garantit que chaque demande est sans état. Cela simplifie également l'assistance aux clients ayant opté pour aucune conservation des données (ZDR)(s'ouvre dans une nouvelle fenêtre), car le stockage des données nécessaires pour prendre en charge previous_response_id serait contraire à la ZDR. Notez que les clients ZDR ne perdent pas la possibilité de profiter 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(s'ouvre dans une nouvelle fenêtre) et #1641(s'ouvre dans une nouvelle fenêtre) pour les modifications connexes apportées à Codex afin de prendre en charge ZDR.

En général, le coût de l’échantillonnage du modèle dépasse celui du trafic réseau, ce qui fait de l’échantillonnage la principale cible de nos efforts d’efficacité. C’est pourquoi la mise en cache des invites est si importante, car elle nous permet de réutiliser les calculs d’un appel d’inférence précédent. Lorsque nous obtenons des succès de cache, l’échantillonnage du modèle est linéaire plutôt que quadratique. Notre documentation sur la mise en cache des invites (s'ouvre dans une nouvelle fenêtre)explique cela plus en détail :

Les accès au cache ne sont possibles que pour des correspondances exactes de préfixe dans une invite. 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 invite, 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.

En gardant cela à l’esprit, examinons quels types d’opérations pourraient entraîner un « cache miss » dans Codex :

  • Changer les outils disponibles pour le modèle au milieu de la conversation.
  • Modification du modèle qui est la cible de la demande d'API Responses (en pratique, cela modifie le troisième élément de l'invite d'origine, car il contient des instructions spécifiques au modèle).
  • Modification de la configuration du bac à sable, du mode d’approbation ou du répertoire de travail actuel.

L’équipe Codex doit faire preuve de diligence lors de l’introduction de nouvelles fonctionnalités dans la CLI Codex qui pourraient compromettre la mise en cache des invites. À titre d’exemple, notre prise en charge initiale des outils MCP a introduit un bogue qui nous a empêchés d’énumérer les outils dans un ordre cohérent(s'ouvre dans une nouvelle fenêtre), ce qui a entraîné des défauts de cache. Notez que les outils MCP peuvent être particulièrement complexes, car les serveurs MCP peuvent modifier à la volée la liste des outils qu'ils fournissent via une notification notifications/tools/list_changed(s'ouvre dans une nouvelle fenêtre). Respecter cette notification au milieu d’une longue conversation peut entraîner un raté de cache coûteux.

Dans la mesure du possible, nous gérons les changements de configuration qui surviennent au milieu d'une conversation en ajoutant un nouveau message à entrée afin de refléter le changement plutôt que de modifier un message antérieur :

Nous faisons tout notre possible pour garantir des succès de cache afin d'assurer des performances optimales. Il y a une autre ressource clé que nous devons gérer : la limite de contexte.

Notre stratégie générale pour éviter de manquer de fenêtre de contexte consiste à réduire la conversation une fois que le nombre de token dépasse un certain seuil. Plus précisément, nous remplaçons l'entrée par une nouvelle liste d’éléments plus petite, représentative de la conversation, permettant ainsi à l’agent de poursuivre avec une compréhension de ce qui s’est passé jusqu’à présent. Une première implémentation de la compaction(s'ouvre dans une nouvelle fenêtre) exigeait que l’utilisateur invoque manuellement la commande /compact, qui interrogerait l’API Responses en utilisant la conversation existante ainsi que des instructions personnalisées pour le résumé(s'ouvre dans une nouvelle fenêtre). Codex a utilisé le message de l’assistant résultant contenant le résumé comme la nouvelle entrée(s'ouvre dans une nouvelle fenêtre) pour les tours de conversation suivants.

Depuis, l’API Responses a évolué pour prendre en charge un /responses/compact endpoint spécial(s'ouvre dans une nouvelle fenêtre) qui effectue la compaction plus efficacement. Il renvoie une liste d’éléments(s'ouvre dans une nouvelle fenêtre) qui peuvent être utilisés à la place de l’entrée précédente pour poursuivre la conversation tout en libérant la fenêtre de contexte. Cette liste comprend un élément spécial type=compaction avec un élément encrypted_content opaque qui conserve la compréhension latente du modèle de la conversation originale. Désormais, Codex utilise automatiquement cet endpoint pour compacter la conversation lorsque la limite auto_compact_limit(s'ouvre dans une nouvelle fenêtre) est dépassée.

À venir

Nous avons introduit la boucle d’agent Codex et parcouru comment Codex élabore et gère son contexte lors de l’interrogation d’un modèle. En cours de route, nous avons mis en lumière des considérations pratiques et des meilleures pratiques applicables à toute personne construisant une boucle d'agent sur l'API Responses.

Bien que la boucle d’agent serve de base à Codex, ce n’est que le début. Dans les prochains articles, nous nous pencherons sur l'architecture de la CLI, explorerons la manière dont l'utilisation des outils est mise en œuvre et examinerons de plus près le modèle de bac à sable de Codex.

Auteur

Michael Bolin

Remerciements

Remerciements spéciaux à toute l'équipe qui a développé la CLI Codex.