Harness Engineering pour les AI Agents : concevoir des boucles de contrôle
Traduction automatique Cet article a été traduit automatiquement depuis la version originale en anglais.
La boucle de raisonnement d’un agent choisit l’action suivante. Son harness fournit le contexte, valide les tool calls proposés, les autorise, distribue ceux qu’il accepte, enregistre les résultats et décide si la tâche est terminée.
Les trois premières responsabilités font déjà l’objet d’articles. Memory fournit le contexte, tool use définit ce qui peut être proposé et la security décide ce qui s’exécute. Ces sujets ont fait l’objet d’articles distincts parce qu’ils correspondent à des problèmes d’ingénierie différents. Choisir entre Qdrant et pgvector n’a rien à voir avec l’écriture d’une règle deny PreToolUse.
Ils partagent toutefois un même moment : l’intervalle entre le moment où le modèle nomme une action et celui où la machine l’exécute. Chacun répond à une question sur cet intervalle, et le harness est le code qui le maintient ouvert assez longtemps pour poser les trois questions.
Pour les ingénieurs qui construisent ou évaluent des harnesses d’agents de programmation, le travail restant consiste à déterminer si le travail déclaré terminé l’est réellement, et à démontrer que chaque contrôle de la boucle justifie son coût. La partie 5 traitait du runtime qui maintient le processus en vie en dessous.
Au-delà du premier contrôle d’acceptation explicite, chaque retry, handoff ou evaluator ajouté constitue une hypothèse fondée sur un échec observé. Il ne mérite sa place que lorsqu’une comparaison contrôlée montre qu’il est utile.
Le contrôle d’acceptation le plus simple est facile à écrire pour un petit agent de recherche comme celui construit dans cette série — un agent LangGraph qui récupère des données de marché et rédige un rapport d’analyste. Un hook situé en dehors du modèle valide le rapport par rapport à un schéma et vérifie qu’il contient effectivement des tickers boursiers ; un rapport mal formé maintient l’exécution ouverte. Douze lignes de code ordinaire, et le modèle ne peut pas déclarer lui-même que sa sortie est bien formée. Le gate du dépôt est plus souple : un evaluator en fresh-context qui vote, puis un humain. (La partie 4 présente la version déterministe.)
Cet exemple ne permet pas de montrer la partie intéressante : ce qui se passe lorsque les éléments de preuve sont ambigus, lorsqu’un retry risque de facturer quelqu’un deux fois ou lorsque le travail dépasse la session qui l’a commencé. Ces cas nécessitent une tâche dont la frontière pass/fail est plus nette que celle d’un rapport de recherche. L’agent de recherche reste l’exemple du contrôle d’acceptation ; un petit dépôt fictif de boutique le rejoint pour les cas de retry et de handoff. La tâche de programmation consiste à abaisser de $100 à $75 le seuil de déclenchement d’une remise automatique de 10 % dans src/checkout.py. Le dépôt comporte deux checks obligatoires :
pytest tests/test_checkout.pyvérifie le calcul de la remise.pnpm playwright test tests/checkout_discount.spec.tsajoute un article à $80 dans une boutique de test locale et vérifie que la page de checkout affiche une remise de $8.
L’exemple est un support pédagogique, pas une application réelle ni un benchmark. Chaque tentative démarre à partir du même commit et des mêmes données de test initialisées. Le harness ne peut accepter la modification que lorsque les deux commandes réussissent et que la trace relie ces résultats au commit testé.
Le diagramme suit la modification de la remise, de la proposition aux éléments de preuve. Le harness fournit la tâche et les fichiers, vérifie les arguments et les permissions du edit_file proposé, puis distribue le call accepté. Une fois la modification appliquée par le runtime, le harness exécute les tests unitaires et les tests d’acceptation dans le navigateur nommés. Une commande en échec revient au modèle comme élément de preuve pour un tour supplémentaire ; deux commandes réussies rendent la modification éligible à l’acceptation.
Ce que le harness contrôle
Le parcours de la boucle Codex d’OpenAI décrit le cycle de base. Le harness assemble un prompt, demande au modèle l’action suivante, envoie un tool call accepté au runtime et ajoute le résultat. Il recommence ensuite. Le cycle se répète jusqu’à ce que le harness accepte le résultat ou rende le contrôle à l’utilisateur.
Les implémentations peuvent regrouper plusieurs responsabilités dans un même processus. Les frontières d’échec restent toutefois différentes :
| Terme | Responsabilité | Exemple pour un agent de programmation |
|---|---|---|
| Modèle | Propose du texte, un tool call ou une réponse finale | Suggère une modification de src/checkout.py |
| Boucle de raisonnement | Choisit l’action suivante à partir du contexte disponible | Inspecter, modifier, tester, inspecter à nouveau |
| Harness | Fournit le contexte, valide les propositions, les autorise, distribue les calls acceptés, enregistre les résultats et vérifie l’achèvement | Autorise les modifications sous src/ et exige les deux tests nommés |
| Runtime | Exécute les calls acceptés et maintient l’état en vie en dehors du processus worker | Journal de session, sandbox, magasin de checkpoints, backend de trace |
La ligne runtime recouvre quatre éléments : session, sandbox, checkpoint et trace. Tous quatre stockent un état ou confinent l’exécution. Le modèle propose l’action, et la boucle de raisonnement choisit l’étape suivante. Le harness décide si un call proposé peut être exécuté et si les éléments de preuve suffisent pour terminer, ce qui justifie un article dédié. La partie 5 compte le harness, avec ces quatre éléments, parmi les cinq primitives à positionner avant la mise en production ; cet article le sépare à nouveau.
Lorsqu’un échec survient, diagnostiquez la frontière qui devrait y répondre. Un mauvais plan peut nécessiter de meilleures instructions ou un meilleur raisonnement du modèle. Si edit_file cible un chemin situé en dehors de src/, le harness doit le rejeter. Un processus sandbox qui meurt avant l’exécution de la modification relève du runtime, qui doit redémarrer le worker ou signaler le crash.
Où se situent les parties précédentes
La ligne du harness ci-dessus accomplit l’essentiel du travail dans ce tableau ; c’est également là que se retrouvent les parties 2, 3 et 4. Chacune d’elles décide d’un élément concernant un tour donné :
| Partie précédente | Ce qu’elle décide pour ce tour | Où elle intervient dans le parcours de la section suivante |
|---|---|---|
| Partie 2 — memory | Quel état antérieur entre dans le prompt | Étape 1, le context builder |
| Partie 3 — tool use | Quelles actions existent et à quoi ressemble un résultat validé | Validation des arguments à l’étape 3 et forme du résultat à l’étape 4 |
| Partie 4 — security | Si ce call précis peut être exécuté maintenant | Étape 3, vérification du chemin et décision d’approbation |
| Partie 6 — cet article | Si les éléments de preuve mettent fin à l’exécution | Étapes 5 à 7, contrôles d’acceptation et trace |
Les parties 3 et 4 partagent l’étape 3, et ce chevauchement justifie à lui seul de les traiter comme un programme unique. La même couche de code du harness qui rejette un argument mal formé rejette également un call autorisé mais pas encore approuvé. Répartir ces deux rejets entre deux services les fait diverger ; un call qui passe la validation de schéma à un endroit peut être autorisé ailleurs, par un composant qui n’a jamais vu le schéma.
La séparation reste importante pour le debugging : une modification du mauvais fichier relève d’une règle de chemin de la partie 4, et non d’un problème de retrieval de la partie 2. Une section vers la fin de cet article transforme cela en table de routage.
La propre étude de cas sur le harness engineering d’OpenAI décrit une instance applicative démarrable pour chaque worktree. L’équipe a également intégré l’automatisation du navigateur à l’environnement de l’agent et exposé les logs, les métriques et les traces.
Une tâche telle que « aucun span dans ces quatre parcours utilisateur critiques ne dépasse deux secondes » est devenue testable parce que l’agent pouvait exécuter l’application et interroger les mêmes signaux qu’un ingénieur aurait examinés. L’étude de cas est spécifique au produit. Ce qui est transposable, c’est la condition qui a permis ce résultat : l’application et ses signaux de performance devaient être disponibles dans l’environnement de l’agent.
Lopopolo, l’auteur de cette étude de cas, tient un field guide consacré au harness engineering. Il nomme les deux leviers utilisés dans cet article : conserver le modèle et le coding agent comme une boîte noire, puis concevoir le contexte et les outils autour d’eux. Son approche explique également pourquoi une grande partie du harness prend la forme de code ordinaire.
Le niveau de qualité d’une organisation, ses procédures, l’historique de ses exceptions et ses relations d’autorité sont extérieurs à ce qu’un modèle généraliste peut connaître. Le harness les expose sous forme d’instructions de dépôt, de règles de permission et de contrôles d’acceptation. Chaque exécution acceptée peut réinjecter ses enseignements dans ces artefacts au lieu de compter sur la session suivante pour les redécouvrir.
Suivre la modification de la remise, de la proposition à l’acceptation
Pour la tâche de remise définie plus haut, le modèle propose de modifier calculate_discount dans src/checkout.py. Plusieurs choses se produisent avant que cette modification puisse être considérée comme une progression :
- Le context builder fournit la tâche, les instructions du dépôt, les fichiers pertinents, les résultats des tool calls précédents et le plan courant.
- Le modèle propose un call
edit_fileavec un chemin et le texte de remplacement. - La tool boundary (le code du harness situé entre la proposition et l’exécution) valide les arguments, vérifie le chemin par rapport au périmètre autorisé et demande une approbation si l’opération l’exige.
- Le runtime applique la modification dans le sandbox et renvoie un résultat structuré.
- Le harness exécute
pytest tests/test_checkout.py, puispnpm playwright test tests/checkout_discount.spec.ts, et lit les deux codes de sortie. Le test dans le navigateur vérifie la remise visible de $8 sur le panier initialisé à $80. - Le harness détermine ce que signifient les résultats. Un check en échec devient un nouveau contexte pour le tour suivant du modèle, tandis qu’une exécution réussie rend la tâche candidate à l’achèvement.
- Un résultat réussi ne devient un élément de preuve d’achèvement qu’après l’enregistrement, par le harness, de la commande, du code de sortie et de la version de l’artefact testé dans la trace.
Après l’étape 2, aucun fichier n’a encore changé. Le harness peut rejeter ../../secrets.env, exiger une approbation pour une commande destructive ou arrêter une exécution qui a épuisé son budget. C’est le dernier moment peu coûteux dont vous disposez. Une fois les tests exécutés, le harness lit lui-même leurs codes de sortie. Le modèle ne peut pas déclarer que sa propre modification passe.
La trace doit afficher le chemin et le texte de remplacement proposés, la décision de permission, les fichiers modifiés, le commit testé et les résultats des deux commandes. Un simple message final done sans ces enregistrements ne prouve pas que cette modification a passé les checks obligatoires.
Décider où chaque règle est appliquée
L’exigence selon laquelle tests/checkout_discount.spec.ts doit réussir relève du code déterministe, et non du prompt. Le harness distribue la commande Playwright au runtime, lit son code de sortie et refuse de terminer l’exécution tant qu’elle échoue. Un prompt peut rappeler au modèle d’exécuter le test. Il ne peut pas empêcher le modèle de déclarer le succès sans éléments de preuve.
D’autres règles conviennent à d’autres couches :
| Placer la règle dans | Cas d’usage adapté | Exemple |
|---|---|---|
| Prompt ou skill | Ordre de recherche, conventions de code et format du plan | Lire AGENTS.md avant de modifier le code du checkout |
| Tool boundary | Validation des arguments, chemins autorisés, approbations et accès aux outils | Autoriser les écritures uniquement sous src/ |
| Code déterministe | Budgets, timeouts, retries, codes de sortie des tests et gates de release | Maintenir l’exécution ouverte tant que le test Playwright échoue |
| Evaluator en fresh-context | Revue visuelle ou critères nécessitant un jugement proche de celui d’un humain | Comparer un diagramme généré à une grille d’évaluation écrite |
Les tool contracts séparent proposition et permission
La tâche de remise ne nécessite que des modifications de fichiers et des commandes de test. Une API qui modifie l’état présente un mode d’échec différent ; changeons donc d’exemple pour cette section. Supposons que l’agent puisse appeler create_test_order sur un service de commandes de paiement de staging lors de la préparation des données de test. Cet outil ne fait pas partie des checks d’acceptation de la tâche de remise. Il est utile ici parce qu’un timeout peut masquer le fait que le service a créé une commande.
La tool boundary a besoin de plus qu’une description en langage naturel. Elle a besoin d’un tool contract explicite. La partie 3 en défendait un du point de vue du modèle : actions claires, feedback concis, erreurs récupérables. Le harness a besoin du même contract pour une autre raison. Il doit décider, sans interroger le modèle, si un call peut être exécuté et si un call en échec peut être répété. Pour create_test_order, cela signifie un contract comprenant :
- des arguments validés, afin que les entrées mal formées soient rejetées avant l’exécution ;
- un résultat structuré tel que
{ "order_id": "123", "created": true }, afin que les vérifications ultérieures n’aient pas à parser du texte libre ; - une catégorie d’effet indiquant si le call se contente de récupérer des informations ou modifie un fichier, un enregistrement de base de données ou un service externe. Elle indique également si la répétition du call est sûre. Cette étiquette indique au harness si un retry automatique risque de dupliquer le travail. Le harness peut retenter
get_order_statuslorsque le service définit cette lookup comme étant en lecture seule. Il ne doit pas retenter aveuglémentcreate_test_order, car le premier call a peut-être déjà créé la commande ; - un timeout et une politique de retry, afin qu’une réponse perdue ne déclenche pas une séquence d’appels illimitée ;
- une règle de permission indiquant quelle approbation est requise. La lecture du statut d’une commande peut être exécutée automatiquement, tandis que la création d’une commande peut nécessiter une confirmation.
La description en langage naturel est le texte présenté au modèle. Elle pourrait dire : « Créer une commande de test pour la vérification du checkout. » Cette phrase aide le modèle à décider quand proposer create_test_order. Elle n’autorise pas le call. Dans cet exemple, le client Model Context Protocol (MCP) du harness valide les arguments, applique ses propres règles et vérifie la confiance accordée au serveur, les exigences d’approbation et la sûreté du retry avant de distribuer quoi que ce soit. Cette vérification correspond à l’échelle ordonnée deny/ask/hook/allow de la partie 4, avec une question supplémentaire : un call qui a déjà échoué peut-il être envoyé à nouveau ?
Un serveur MCP publie des descriptions d’outils et des annotations comportementales facultatives à destination du client. Un serveur défectueux ou malveillant pourrait décrire comme inoffensif un outil qui modifie l’état. Un client qui accepterait automatiquement cette déclaration pourrait exécuter ou retenter create_test_order sans approbation et créer un doublon. La spécification MCP exige donc que les clients traitent les annotations d’outils comme non fiables, sauf si le serveur lui-même est digne de confiance.
La spécification ne prescrit pas un paramètre universel de confiance. Vous devez donc définir une politique de confiance explicite pour votre déploiement ; un serveur ne peut pas rendre ses propres annotations fiables. Cette politique détermine quelles métadonnées peuvent influencer les décisions de permission ou de retry, et quelles annotations restent uniquement indicatives.
Le retry d’un call qui modifie l’état nécessite une protection contre la relecture
La partie 5 ajoute une clé d’idempotence à chaque tool call produisant un effet de bord. Le harness décide quand cette clé doit réellement jouer ce rôle. create_test_order crée la commande, mais sa réponse HTTP est perdue. Le harness observe un timeout et ne peut pas déterminer si le serveur a terminé la requête. Répéter le call peut créer une seconde commande.
Une status lookup peut être retentée lorsque le service la définit comme étant en lecture seule. Un call de création nécessite la clé : le client ajoute un identifiant de requête unique, et le service renvoie le premier résultat au lieu de créer une autre commande lorsqu’il retrouve cet identifiant. Sans cette protection, le harness doit vérifier si la commande existe ou demander une décision humaine avant toute nouvelle tentative. AWS documente ce pattern dans ses recommandations sur les APIs idempotentes.
L’acceptation nécessite des éléments de preuve indépendants
Une réponse create_test_order réussie établit uniquement que l’outil a renvoyé des données. Elle ne prouve pas qu’une tâche de programmation a passé ses tests. Si un test ultérieur dans le navigateur dépend de la commande mise en staging, le harness doit valider le schéma de la réponse et exécuter malgré tout ce test avant d’accepter la modification du code.
Certains critères ne peuvent pas être réduits à un code de sortie. Pour une tâche distincte de conception visuelle, un evaluator en fresh-context peut comparer une page ou un diagramme rendu à une grille écrite — « fresh-context » signifie qu’une deuxième session du modèle démarre sans historique de l’exécution et lit les artefacts produits plutôt que le transcript. Comparez cet evaluator à des revues humaines avant de l’autoriser à bloquer l’achèvement.
Une migration d’adaptateur de paiement nécessite un handoff
Changeons encore de tâche, tout en restant dans le dépôt fictif de boutique. L’agent doit maintenant migrer le checkout de l’adaptateur de paiement v1 vers la v2. Le travail couvre le handler du checkout, le client de paiement, la configuration et les tests ; il peut donc dépasser une session de modèle — une période continue de contexte du modèle, interrompue par un redémarrage ou par un nouveau départ explicite plutôt que conservée.
Avant que la première session n’atteigne sa limite de contexte, elle a modifié plusieurs fichiers, démarré un sandbox de paiement local et laissé tests/payment_migration.spec.ts en échec. Ce test d’acceptation dans le navigateur effectue un paiement via l’adaptateur v2 et vérifie l’identifiant du provider enregistré. Un résumé de conversation peut orienter la session suivante, mais il ne peut ni redémarrer le sandbox ni prouver quels fichiers sont actuellement modifiés.
La session suivante doit récupérer trois éléments :
| Élément à récupérer | Ce qu’il comprend | Comment il peut échouer |
|---|---|---|
| Historique de conversation | Messages, tool calls et résultats renvoyés | Les anciens détails prennent la place de la tâche courante |
| Environnement de travail | Fichiers, sandbox de paiement et état du test navigateur | Le transcript indique qu’un service fonctionne alors qu’il est arrêté |
| Progression de la tâche | Plan, checks terminés, approbation en attente, prochaine action | La session suivante répète du travail déjà effectué |
La compaction remplace les anciens messages par un résumé plus court afin que la session courante puisse continuer. Un progress handoff enregistre ce dont la session suivante a besoin : la branche courante, les fichiers modifiés, la dernière commande de test et sa sortie, ainsi que la prochaine étape non résolue.
Un fichier de handoff est une mémoire documentaire écrite pour un lecteur précis — la prochaine session du modèle — et constitue un artefact différent du checkpoint restauré par le runtime. Le checkpoint indique où l’exécution s’est arrêtée. Le handoff explique ce que signifie le travail et ce qu’il reste à faire. Restaurez un checkpoint sans handoff : la session suivante récupère un processus reprenable, mais ne sait pas lequel des quatre domaines touchés — handler, client, configuration, tests — est déjà terminé. C’est ainsi que le travail est dupliqué.
Si l’ancienne conversation contient des hypothèses obsolètes, le harness peut démarrer une nouvelle session du modèle avec ce handoff et l’espace de travail courant. Remplacer un worker qui a crashé et restaurer ses processus relève d’une tâche distincte de recovery du runtime.
Une petite modification documentaire peut ne nécessiter aucun de ces mécanismes. La migration de paiement nécessite un handoff dès que le travail franchit les limites d’une session, car la session suivante doit reconstruire à la fois l’espace de travail et l’état de la tâche.
Les expériences d’Anthropic sur les coding agents de longue durée utilisaient l’historique git et un fichier de progression entre les sessions. Le rapport ultérieur d’Anthropic sur la conception de harnesses sépare la compaction du handoff en fresh-context et indique que les handoffs ajoutent de l’orchestration, de l’utilisation de tokens et du temps d’exécution, sans publier de chiffres attribuant précisément cette surcharge au handoff lui-même.
Utiliser les traces pour distinguer trois échecs
Les trois lignes suivantes sont des esquisses de traces illustratives, et non des exécutions mesurées ni des sorties du companion lab. Chaque ligne montre un échec différent et donc une réponse différente du harness.
| Ce que la trace enregistre | Ce qui s’est produit | Réponse correcte |
|---|---|---|
Le call en lecture seule get_order_status renvoie 503 ; aucun call modifiant l’état n’est en cours | Une lookup transitoire a échoué | Retenter la lookup avec une limite et un backoff |
create_test_order expire, puis une status lookup trouve la commande 123 sous la clé d’idempotence checkout-42 | Le service a créé la commande, mais la réponse a été perdue | Renvoyer la commande existante ; ne pas en créer une autre |
La modification et le test unitaire réussissent, mais la trace ne contient aucun résultat pour tests/checkout_discount.spec.ts sur le commit testé | Les éléments de preuve d’acceptation requis manquent | Maintenir l’exécution ouverte et distribuer le test d’acceptation dans le navigateur |
Un échec d’apparence transitoire ne rend pas tous les calls sûrs à retenter. La première ligne correspond à une lookup en lecture seule. La deuxième correspond à une requête qui modifie l’état ; la clé d’idempotence et le statut côté serveur déterminent donc si une nouvelle tentative de création est autorisée. La troisième n’est pas un échec d’outil : le harness n’a pas encore recueilli les éléments de preuve requis pour accepter la modification de la remise.
Un transcript de chat enregistre ce que le modèle a vu. Il ne peut pas prouver si le service de commandes a validé une requête avant la disparition de la réponse. Le transcript est le récit des événements par l’agent ; la trace indique ce que la machine a réellement fait. En cas de désaccord, faites confiance à la trace. Celle-ci doit relier le call client, la décision d’approbation, la clé d’idempotence, le résultat du serveur ou la status lookup, le commit testé et le résultat du test d’acceptation. Ces champs indiquent au harness lequel des trois chemins suivre.
| Symptôme récurrent | Petite modification à essayer | Ce qu’il faut mesurer |
|---|---|---|
| Les lookups en lecture seule échouent temporairement | Retry borné avec backoff | Taux de récupération, calls supplémentaires, temps total |
| Les sessions reprises répètent du travail terminé | Progress handoff structuré | Actions d’outils dupliquées après reprise |
| Des tests requis manquent au moment de l’achèvement | Gate d’acceptation fail-closed | Tâches acceptées sans tous les checks requis |
| Des défauts visuels survivent aux checks déterministes | Evaluator en fresh-context avec une grille | Défauts détectés, faux rejets, temps de revue |
| L’agent modifie des éléments hors de son périmètre | Permission d’outil plus restrictive | Calls bloqués et dérogations manuelles |
| La mémoire rappelée éclipse la tâche courante | Plafonner les faits rappelés ; les classer avant injection | Tokens consacrés au rappel, tâches terminées, coût par tâche |
Avant d’ajouter un composant, nommez l’échec récurrent qu’il doit réduire et la valeur que vous suivrez. Supprimez le composant si une comparaison contrôlée ne fait pas suffisamment évoluer cette valeur pour compenser son coût. La plupart des harnesses que j’ai vus évoluent dans l’autre sens : quelqu’un rencontre une mauvaise exécution, ajoute une protection, puis celle-ci reste indéfiniment parce que personne ne peut démontrer qu’il est sûr de la supprimer. C’est ainsi qu’on finit avec une boucle que personne ne veut toucher.
Transformer ces échecs récurrents en suite de régressions versionnée est une tâche à part entière. Je l’ai traitée séparément dans AI Agent Evaluation in Production.
Mesurer une seule modification à la fois
Une ablation mesure si un composant du harness produit l’effet attendu, en modifiant ou en supprimant ce composant tandis que le reste de l’expérience demeure constant. Par exemple : le linting de l’éditeur aide-t-il ce modèle sur cette suite de tâches ?
Utilisez le protocole suivant :
- Figez la version du modèle, les instances de tâches, l’environnement, le grader et les prompts extérieurs au composant testé.
- Donnez aux deux variantes le même budget total en tokens, en temps et en dollars.
- Choisissez le nombre d’essais ou la règle d’arrêt avant d’exécuter la comparaison.
- Exécutez les mêmes instances de tâches dans les deux variantes. Comme les sorties du modèle varient, répétez chaque tâche plusieurs fois.
- Présentez la moyenne avec la dispersion ou l’intervalle de confiance.
- Comptez chaque essai démarré, y compris les timeouts, les arrêts de policy, les crashes du harness et les échecs de l’evaluator.
Le taux de réussite seul peut masquer un composant coûteux. Suivez au minimum le nombre de tâches défectueuses acceptées comme terminées, le coût et le temps total par tâche terminée, les erreurs d’outils, les commandes dupliquées, les minutes de revue et les overrides manuels de permission. Choisissez la métrique qui porte le coût réel pour votre produit. Une hausse de deux points du nombre de tâches terminées constitue un mauvais compromis si elle double votre file de revue.
Une expérience appariée sur la migration de paiement rend le progress handoff mesurable. Chaque paire contrôle/traitement démarre à partir du même commit du dépôt et du même checkpoint initialisé, avec le même modèle, la même tâche, le même grader et le même budget total. Le handoff est l’unique variable. La métrique principale compte les actions d’outils dupliquées après la reprise : une action est dupliquée lorsque son opération et son artefact correspondent à une étape déjà terminée par la session précédente.
L’article sur SWE-agent fixe GPT-4 Turbo sur le split de 300 tâches de SWE-bench Lite et rapporte 18,0 % de tâches résolues avec son interface complète, contre 11,0 % pour un agent limité au shell recevant une démonstration détaillée, et 7,3 % pour le même agent sans démonstration. L’écart principal de 10,7 points présenté dans l’article est mesuré par rapport à cette baseline de 7,3 % ; la partie 3 reprend les mêmes trois chiffres du point de vue de la conception d’interface. L’article a également modifié individuellement plusieurs fonctionnalités de l’interface :
| Modification de l’interface | Résolues |
|---|---|
| Interface SWE-agent complète (référence, inchangée) | 18,0 % |
| Éditeur sans linting | 15,0 % |
| Fichier complet au lieu d’un viewer de 100 lignes | 12,7 % |
| Historique complet des observations au lieu des cinq dernières | 15,0 % |
Ces chiffres correspondent à ce modèle, ce benchmark et ce plafond de $4 par tâche. Les trois lignes sous la référence sont les tests utiles à une fonctionnalité près : chacune a modifié une fonctionnalité d’interface tandis que le modèle et le protocole d’évaluation restaient constants.
LangChain a publié une comparaison à modèle fixe plus large pour deepagents-cli. Elle rapporte une progression de 52,8 % à 66,5 % sur Terminal-Bench 2.0 avec gpt-5.2-codex fixé, tandis que son équipe modifiait le system prompt, les outils et le middleware. L’article regroupe plusieurs changements et ne fournit ni intervalle de confiance, ni comparaison à budget total constant, ni tableau d’ablation par changement. Ce résultat ne permet donc pas d’identifier le changement responsable de l’amélioration. Les noms de modèles de cette section sont ceux que chaque étude a fixés au moment de son exécution ; c’est le protocole qui est transposable, et non la liste des modèles.
Le rapport d’Anthropic sur les applications de longue durée est une étude de cas qualitative et spécifique à un produit, non un benchmark contrôlé. L’application est RetroForge, un éditeur de jeux rétro en 2D ; lors du Sprint 3, l’evaluator du harness vérifiait 27 critères couvrant son éditeur de niveaux. Le travail avait commencé avec d’anciens modèles Opus et, lors de la sortie d’Opus 4.6, l’équipe a supprimé les composants du harness un par un afin d’identifier ceux que le nouveau modèle avait rendus redondants. Le rapport indique que les calls de l’evaluator devenaient une surcharge pour les tâches qu’Opus 4.6 pouvait accomplir de manière fiable seul, tout en restant utiles lorsque le modèle atteignait ses limites. Cet exemple montre pourquoi il faut réévaluer les scaffolding existants lorsqu’un modèle change ; il n’estime pas une taille d’effet générale.
Garder le harness modifiable après avoir justifié sa place
L’ablation permet de garder un harness compact, mais son code peut survivre au modèle pour lequel il a été ajusté. Une demande telle que « masquer les secrets dans chaque chemin de capture » décrit un comportement, pas un fichier. Dans un harness de production, ce comportement peut traverser plusieurs étapes d’exécution et un état partagé. Avant de pouvoir le modifier sans risque, il faut trouver tous ses sites d’implémentation — et le coding agent auquel vous déléguez cette tâche doit en faire autant.
Un preprint de 2026 de Wang et al., le Harness Handbook, appelle cette recherche la localisation de comportement. Le handbook construit une carte du codebase du harness centrée sur les comportements. Une analyse statique, qui ne nécessite aucun call de modèle, extrait un graphe de programme, puis un LLM organise ses unités en étapes d’exécution.
Le mainteneur ou le coding agent commence par une vue d’ensemble du système, ouvre l’étape d’exécution pertinente et descend jusqu’aux entrées ancrées dans le code source correspondant à une fonction ou à un fichier. Un registre d’état indique où l’état partagé est écrit et lu entre les étapes. Cette hiérarchie conserve une vue d’ensemble réduite tout en préservant un chemin vers le code source.
La fraîcheur constitue une règle distincte. Chaque locator doit se résoudre par rapport au dépôt vivant. Le handbook gèle les entrées obsolètes au lieu de deviner, et chaque diff non vide resynchronise les entrées qu’il affecte.
Le diagramme condense la boucle de modification : une demande portant uniquement sur le comportement descend les niveaux du handbook ; chaque locator candidat est vérifié par rapport au dépôt vivant avant la rédaction du plan ; chaque diff appliqué resynchronise la carte.
L’évaluation du Handbook suit le protocole défendu dans cet article. Elle couvre deux harnesses open source : Terminus-2 (six fichiers Python) et le monorepo Codex (2 267 fichiers Rust). Dans chacun, un planner en lecture seule alimenté par DeepSeek-V4-Pro explorait directement le dépôt ou passait par le handbook. Les demandes, le dépôt, les permissions des outils et le decoding étaient identiques dans les deux variantes. Trois juges (GPT-5.5, Opus 4.8, DeepSeek-V4-Pro) ont évalué chaque plan de modification selon la localisation, le contrôle du périmètre et le raisonnement — notez que l’un des juges est le même modèle que celui qui a produit les plans :
| Harness | Taux de victoire baseline | Avec handbook | Tokens du planner |
|---|---|---|---|
| Terminus-2 (6 fichiers) | 26,7 % | 45,6 % | −8,6 % |
| Monorepo Codex (2 267 fichiers) | 28,3 % | 38,3 % | −12,7 % |
Le planner assisté par le handbook a remporté davantage de comparaisons et utilisé moins de tokens de planner dans les deux dépôts. Les conditions restent indissociables de ce résultat : trois juges LLM ont évalué des plans de modification produits par un seul modèle planner sur deux harnesses. L’étude a évalué des plans, et non des diffs exécutés ou des taux de défauts en production.
Tester la méthode dans le companion lab
Le projet harness-demo au commit 517353f3 est un petit exercice déterministe comprenant 12 tâches synthétiques génériques couvrant des modifications de code telles que fix-parser-edge-case, split-large-module et wire-browser-test. Il n’implémente pas le dépôt fictif de boutique.
Chaque fixture de tâche déclare une difficulté ainsi que quatre conditions booléennes : un outil flaky, une progression perdue, une lacune d’implémentation non détectée et un achèvement ambigu. Le simulateur déduit une cinquième condition pour les tâches difficiles qui nécessitent également un fichier de progression : sans context_reset, la compaction préserve des hypothèses obsolètes. Un grader déterministe marque une tâche comme réussie uniquement lorsque la configuration sélectionnée gère toutes les conditions applicables. Aucun modèle ni service externe n’est exécuté.
Les commandes répondent à des questions différentes :
make checkexécute Ruff et sept tests unitaires, dont le validateur qui rejette toute paire d’ablation modifiant plusieurs composants.make runaffiche une matrice pédagogique cumulative, puis cinq comparaisons valides où un seul composant est retiré.make failuresindique la condition non gérée pour chaque tâche en échec. Le harness complet doit se terminer parall synthetic tasks pass.
make check
make run
make failures
La section causale de make run se présente ainsi :
component control treatment delta
retry_policy 8/12 12/12 +4
progress_handoff 7/12 12/12 +5
evaluator 8/12 12/12 +4
fail_closed_acceptance 7/12 12/12 +5
context_reset 10/12 12/12 +2
Pour chaque ligne, le contrôle est la configuration complète à laquelle un composant a été retiré ; le traitement rétablit uniquement ce composant. La matrice cumulative précédente est utile pour s’orienter, mais certaines de ses lignes adjacentes ajoutent plusieurs composants à la fois et ne peuvent donc pas identifier une cause.
Le lab valide chaque paire déclarée avant de l’exécuter. Ses tests de régression incluent également une paire intentionnellement invalide qui modifie à la fois la politique de retry et l’evaluator ; le validateur la rejette.
Le lab compare les cinq champs de composants lors de la validation d’une paire. Cet extrait exécutable montre le même garde-fou sur une paire valide concernant le progress handoff :
from dataclasses import dataclass, fields
@dataclass(frozen=True)
class Config:
progress_handoff: bool = False
evaluator: bool = False
retry_policy: bool = False
fail_closed_acceptance: bool = False
context_reset: bool = False
def changed_components(control: Config, treatment: Config) -> tuple[str, ...]:
return tuple(
field.name
for field in fields(control)
if getattr(control, field.name) != getattr(treatment, field.name)
)
control = Config(progress_handoff=False, evaluator=True, retry_policy=True)
treatment = Config(progress_handoff=True, evaluator=True, retry_policy=True)
assert changed_components(control, treatment) == ("progress_handoff",)
Quelle couche ouvrir lorsqu’une exécution échoue
La série avançait de l’intérieur vers l’extérieur, et c’est ici que cet ordre devient utile. Une exécution d’agent en échec a généralement un responsable :
| Ce que l’exécution a fait | Où se situe le correctif | Partie |
|---|---|---|
| A choisi une mauvaise étape suivante alors que les bonnes informations étaient déjà disponibles | Boucle de raisonnement ou modèle | 1 |
| A répété du travail ou perdu une décision prise une heure plus tôt | Assemblage du contexte et handoffs | 2 |
| N’a pas pu exprimer l’action nécessaire ou a mal interprété un résultat renvoyé | Tool contract | 3 |
| A effectué une action qu’il n’aurait jamais dû pouvoir effectuer | Règles de permission | 4 |
| A tout perdu lorsqu’un worker est mort au milieu d’un call | Session, checkpoint, sandbox | 5 |
| A déclaré le succès d’un travail qui n’était pas terminé | Contrôles d’acceptation et traces | 6 |
Quatre de ces six lignes correspondent à du code du harness. La ligne 5 concerne le runtime sous-jacent, et la ligne 1 est la seule qu’un prompt puisse encore influencer.
Commencer par une boucle et un contrôle d’acceptation
Je commencerais un harness d’agent de programmation avec un modèle compétent, des instructions de dépôt, quelques outils ciblés, un sandbox et un test d’acceptation explicite. J’enregistrerais les tool calls, les résultats, les coûts et ce test final dans une trace unique, afin que les premiers échecs utiles soient visibles sans devoir les reconstituer à partir des logs du terminal et des transcripts de chat. Il s’agit d’une baseline proposée, et non d’éléments de preuve provenant d’un système déployé.
À partir de là, n’ajoutez que ce qu’une trace justifie. Enregistrez le responsable de la maintenance de chaque composant, le nombre de tokens ou de secondes qu’il ajoute et le test de régression qui justifierait sa suppression après une mise à niveau du modèle.
Six mois plus tard, quelqu’un qui voit progress_handoff=True doit pouvoir retrouver les traces d’échec qui ont justifié son ajout ainsi que les cas de régression qui le maintiennent encore. Les traces expliquent pourquoi le composant existe ; une carte actuelle des comportements explique où intervenir.
Si vous êtes arrivé ici depuis une recherche, les cinq articles précédents ont construit un système autour d’une boucle de raisonnement :
- La boucle choisit l’étape suivante.
- Memory fournit le contexte, et un véritable magasin de checkpoints Postgres le préserve.
- Les tool contracts définissent les actions et les formes de résultats que les vérifications ultérieures peuvent lire.
- La security ajoute le hook deny et le validateur stop-hook. Tous deux restent des esquisses dans l’exemple, mais ils indiquent les points de contrôle.
- Le runtime maintient le processus en vie entre les sessions et malgré les échecs.
La série a également ajouté un sidecar MCP pour montrer où placer les tokens des data providers, ainsi qu’un nœud evaluator qui vérifie le rapport préliminaire avant qu’un humain ne le voie. Ce sont des éléments de code ordinaires autour d’un call de modèle. Le router est du code de harness pour la même raison : il choisit le pattern de raisonnement avant le démarrage de la boucle de raisonnement.
Votre prochaine étape consiste à instrumenter une petite boucle. Enregistrez les tool calls, les résultats, les coûts et un test d’acceptation explicite. N’ajoutez un contrôle qu’après qu’une trace a montré l’échec auquel il répond. Comparez-le à un contrôle fixe, puis supprimez-le lorsque le bénéfice mesuré disparaît.
Références
- OpenAI, Unrolling the Codex agent loop.
- OpenAI, Harness engineering: leveraging Codex in an agent-first world.
- Lopopolo, Harness engineering: anthology, field guide, and agent context bundle.
- Anthropic Engineering, Effective harnesses for long-running agents.
- Anthropic Engineering, Harness design for long-running application development.
- LangChain, Improving Deep Agents with harness engineering.
- Yang et al., SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering.
- Wang et al., Harness Handbook: Making Evolving Agent Harnesses Readable, Navigable, and Editable, arXiv:2607.13285, 2026.
- AWS, Making retries safe with idempotent APIs.
- Model Context Protocol, Tools specification.
- Market Analyst Agent Repository
Le code du Market Analyst Agent est disponible sur GitHub.