Engineering the Agentic Stack · Partie 6

Harness Engineering pour les agents AI : concevoir des boucles de contrôle

Traduction automatique Cet article a été traduit automatiquement depuis la version originale en anglais.

Mise à jour de l’article

Publié initialement le 22 juillet 2026. Relu et mis à jour le 6 septembre 2026. Cette mise à jour ajoute des éléments plus récents issus de benchmarks de harness et des cas d’intervention du fournisseur, et précise ce que les résultats rapportés permettent d’établir.

Un agent peut terminer son tour alors que le travail est encore incomplet. Pour un agent de programmation, les éléments probants utiles sont l’artefact modifié et les résultats des tests requis. Un message final indiquant « terminé » n’établit ni l’un ni l’autre.

Le harness est le code de contrôle qui entoure la boucle de raisonnement. Il fournit le contexte, valide et autorise les tool calls, enregistre les résultats et décide si les éléments probants suffisent pour accepter le travail. Le runtime maintient quant à lui l’exécution et l’état actifs en dessous de cette couche.

Lorsqu’on examine un harness, deux questions s’imposent : qu’est-ce qui l’empêche d’accepter un travail incomplet, et quels échecs justifient l’ajout de ses contrôles ? Cet article parcourt les contrôles d’acceptation, les retries et les handoffs, puis montre comment comparer un contrôle à une baseline fixe. Les exemples de boutique sont fictifs ; le lab associé est une simulation déterministe, pas une mesure effectuée sur un agent en production.

Le contrôle d’acceptation le plus simple s’écrit facilement pour un petit agent de recherche comme celui développé 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 repo utilise un contrôle plus souple : un évaluateur en fresh context vote, puis un humain examine le résultat. (La partie 4 esquisse la version déterministe.)

Ce que cet exemple ne montre pas, c’est la partie intéressante : que se passe-t-il lorsque les éléments probants 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 réussite/échec est plus nette que celle d’un rapport de recherche. L’agent de recherche reste l’exemple de contrôle d’acceptation ; un petit dépôt de boutique fictif s’y ajoute pour les cas de retry et de handoff. La tâche de programmation consiste à abaisser le seuil d’une remise automatique de 10 %, de $100 à $75, dans src/checkout.py. Le dépôt comporte deux contrôles requis :

  • pytest tests/test_checkout.py vérifie le calcul de la remise.
  • pnpm playwright test tests/checkout_discount.spec.ts ajoute un article à $80 dans une boutique de test locale et vérifie que la page de paiement affiche une remise de $8.

L’exemple est un fixture pédagogique, pas une application réelle ni un benchmark. Chaque tentative part 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 qu’un enregistrement d’acceptation durable relie les deux résultats à un candidat proprement commité ou au digest de l’instantané complet testé, y compris les fichiers non suivis pertinents.

Le diagramme suit la modification de la remise, de la proposition jusqu’aux éléments probants. Le harness fournit la tâche et les fichiers, vérifie les arguments et les permissions du edit_file proposé, puis dispatche le call accepté. Après que le runtime a appliqué la modification, le harness exécute les tests unitaires et les tests d’acceptation navigateur nommés. Une commande en échec renvoie son résultat au modèle comme élément probant pour un nouveau tour ; deux commandes réussies rendent la modification éligible à l’acceptation.

Une modification de remise à travers la boucle de contrôle du harnessUne modification de remise à travers la boucle de contrôle du harness


Ce que contrôle le harness

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. Puis il recommence. 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 néanmoins différentes :

TermeRôleExemple avec un agent de programmation
ModèlePropose du texte, un tool call ou une réponse finaleSuggère une modification de src/checkout.py
Boucle de raisonnementChoisit l’action suivante à partir du contexte disponibleInspecter, modifier, tester, inspecter à nouveau
HarnessFournit le contexte, valide les propositions, les autorise, dispatche les calls acceptés, enregistre les résultats et vérifie la finAutorise les modifications sous src/ et exige les deux tests nommés
RuntimeExécute les calls acceptés et maintient l’état actif en dehors du processus workerJournal de session, sandbox, checkpoint store, trace backend

La ligne runtime recouvre quatre éléments : session, sandbox, checkpoint et trace. Tous quatre stockent de l’état ou confinent l’exécution. Le modèle propose l’action, et la boucle de raisonnement choisit le mouvement suivant. Le harness décide si un call proposé peut être exécuté et si les éléments probants suffisent pour terminer ; c’est pourquoi il fait l’objet de son propre article. La partie 5 compte le harness, avec ces quatre éléments, parmi les cinq primitives à mettre en place avant la mise en production ; cet article le traite séparément.

Lorsqu’un échec apparaît, diagnostiquez la frontière qui doit 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 harness du tableau ci-dessus concentre l’essentiel du travail ; c’est là qu’aboutissent les parties 2, 3 et 4. Chacune décide d’un point concernant un tour unique :

Partie précédenteCe qu’elle décide pour ce tourOù elle intervient dans le parcours de la section suivante
Partie 2 — mémoireQuel état antérieur entre dans le promptÉtape 1, le context builder
Partie 3 — tool useQuelles 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 — sécuritéSi ce call précis peut être exécuté maintenantÉtape 3, contrôle du chemin et décision d’approbation
Partie 6 — cet articleSi les éléments probants obtenus mettent fin à l’exécutionÉtapes 5 à 7, contrôles d’acceptation et trace

Où se situe chaque partie de la série Engineering the Agentic StackOù se situe chaque partie de la série Engineering the Agentic Stack

Les parties 3 et 4 partagent l’étape 3, et ce chevauchement justifie précisément de les traiter comme un seul programme. La même couche de code du harness qui rejette un argument mal formé rejette également un call autorisé mais pas encore approuvé. Si la validation et l’autorisation s’exécutent dans des services distincts, préservez les arguments validés au passage de cette frontière afin que la décision d’autorisation s’applique au call qui sera effectivement exécuté.

Cette séparation reste utile pour le débogage : une modification du mauvais fichier relève d’une règle de chemin de la partie 4, pas d’un problème de retrieval de la partie 2. Une section vers la fin de cet article transforme cela en table de routage.

L’étude de cas sur le harness engineering d’OpenAI décrit une instance applicative bootable pour chaque worktree. L’équipe a également intégré l’automatisation navigateur à l’environnement de l’agent et exposé les logs, métriques et 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 inspectés. L’étude de cas est spécifique au produit. Ce qui est généralisable, c’est la condition à l’origine du 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 guide pratique du harness engineering. Il y nomme les deux leviers utilisés dans cet article : maintenir le modèle et l’agent de programmation fixes, comme des boîtes noires, et concevoir le contexte et les outils autour d’eux. Son approche explique également pourquoi une grande partie du harness finit par être du code ordinaire.

Le niveau de qualité, les procédures, l’historique des exceptions et les relations d’autorité d’une organisation se trouvent en dehors de ce qu’un modèle général 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, plutôt que de compter sur la session suivante pour les redécouvrir.


Suivre la modification de remise, de la proposition à l’acceptation

Pour la tâche de remise définie ci-dessus, 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 un progrès :

  1. 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.
  2. Le modèle propose un call edit_file avec un chemin et un texte de remplacement.
  3. La frontière des outils (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 le nécessite.
  4. Le runtime applique la modification dans le sandbox et renvoie un résultat structuré.
  5. Le harness exécute pytest tests/test_checkout.py, puis pnpm playwright test tests/checkout_discount.spec.ts, et lit les deux codes de sortie. Le test navigateur vérifie la remise visible de $8 sur le panier initialisé à $80.
  6. Le harness décide de la signification des résultats. Un contrôle 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 à la finalisation.
  7. Un résultat réussi ne devient un élément probant de fin qu’après l’enregistrement durable, par le harness, de la commande, du code de sortie, de l’instantané testé, du grader et des versions de l’environnement ; une trace peut pointer vers cet enregistrement.

Après l’étape 2, aucun fichier n’a 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 où le coût de contrôle est faible. Après l’exécution des tests, le harness lit lui-même leurs codes de sortie. Le modèle ne peut pas déclarer lui-même que sa modification passe les tests.

L’enregistrement d’acceptation doit identifier l’instantané testé, les deux commandes et leurs résultats, ainsi que les versions du grader et de l’environnement ; les traces peuvent pointer vers cet enregistrement. Gardez les tests requis en dehors du périmètre accessible en écriture par l’agent, ou approuvez indépendamment les modifications avant le grading. Toute modification ultérieure d’un fichier invalide le résultat. Ces contrôles mettent en œuvre les principes d’environnement stable et de grader résistant aux contournements décrits dans les recommandations d’évaluation d’Anthropic. Un message final done sans ces enregistrements ne prouve pas que cette modification a passé les contrôles requis.


Décider où chaque règle doit être appliquée

L’exigence selon laquelle tests/checkout_discount.spec.ts doit réussir relève du code déterministe, pas du prompt. Le harness dispatche la commande Playwright vers le 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 la réussite sans éléments probants.

D’autres règles correspondent à d’autres couches :

Placer la règle dansCas d’usage appropriéExemple
Prompt ou skillOrdre de recherche, conventions de code et format du planLire AGENTS.md avant de modifier le code du checkout
Frontière des outilsValidation des arguments, chemins autorisés, approbations et accès aux outilsAutoriser les écritures uniquement sous src/
Code déterministeBudgets, timeouts, retries, codes de sortie des tests et exigences de releaseMaintenir l’exécution ouverte tant que le test Playwright échoue
Évaluateur en fresh contextRevue visuelle ou critères nécessitant un jugement comparable à celui d’un humainComparer un diagramme généré à une grille d’évaluation écrite

Les contrats d’outils 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 staging lors de la préparation des données de test. Cet outil ne fait pas partie des contrôles 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éé ou non une commande.

La frontière des outils a besoin de plus qu’une description en langage naturel. Elle nécessite un contrat d’outil 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 contrat 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 contrat 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 contrôles ultérieurs 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. Ce label indique au harness si un retry automatique risque de dupliquer le travail. Le harness peut retenter get_order_status lorsque le service définit cette lookup comme read-only. Il ne doit pas retenter aveuglément create_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 illimitée de calls ;
  • une règle de permission indiquant l’approbation 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 montré au modèle. Elle peut dire : « Créer une commande de test pour vérifier le 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é des retries avant de dispatche quoi que ce soit. Cela combine les règles de permission et les contrôles préalables aux outils présentés dans la partie 4, en ajoutant une question : un call qui a déjà échoué peut-il être envoyé à nouveau ?

Un serveur MCP publie des descriptions d’outils et des annotations comportementales optionnelles à destination du client. Un serveur défectueux ou malveillant pourrait décrire un outil qui modifie l’état comme inoffensif. Un client qui accepterait automatiquement cette affirmation 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 approuvé.

La spécification ne prescrit pas un réglage 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 seulement consultatives.

Le retry d’un call qui modifie l’état nécessite une protection contre la relecture

La partie 5 exige une identité d’opération durable pour les effets de bord susceptibles d’être dupliqués lors d’un retry. C’est le harness qui décide quand cette clé doit porter cette responsabilité. create_test_order crée la commande, mais sa réponse HTTP est perdue. Le harness constate un timeout et ne peut pas savoir si le serveur a terminé la requête. Répéter le call peut créer une seconde commande.

Persistez un ID d’opération géré par l’application avant le dispatch et liez-le aux arguments approuvés. Réutilisez-le lors de la récupération de la même commande intentionnelle, même si le modèle génère un nouvel ID de tool call ; conservez séparément les IDs du modèle pour la corrélation. Réconciliez les payloads modifiés ou les fenêtres de déduplication du fournisseur arrivées à expiration au lieu de renvoyer aveuglément la requête. Le contrat de Stripe autorise par exemple la suppression des clés après au moins 24 heures.

Une lookup de statut peut être retentée lorsque le service la définit comme read-only. Un call de création nécessite une clé : le client ajoute un identifiant de requête unique, et le service renvoie le premier résultat au lieu de créer une nouvelle 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 une nouvelle tentative. AWS documente ce modèle dans ses recommandations sur les APIs idempotentes.

L’acceptation nécessite des éléments probants indépendants

Une réponse create_test_order réussie établit seulement 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 navigateur ultérieur dépend de la commande préparée, 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 design visuel, un évaluateur en fresh context peut comparer une page ou un diagramme rendu à une grille écrite — « fresh context » signifie une seconde session de modèle qui démarre sans l’historique de l’exécution et lit les artefacts produits plutôt que le transcript. Comparez cet évaluateur à des revues humaines avant de lui permettre de décider si la tâche est terminée.


Une migration d’adaptateur de paiement nécessite un handoff

Changeons à nouveau de tâche, tout en restant dans le dépôt de boutique fictif. L’agent doit maintenant migrer le checkout de l’adaptateur de paiement v1 vers v2. Le travail s’étend au handler de checkout, au client de paiement, à la configuration et aux tests ; il peut donc dépasser une session de modèle — une séquence continue de contexte du modèle, interrompue par un redémarrage ou par un nouveau départ délibérément fresh, plutôt que prolongée telle quelle.

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 navigateur effectue un paiement complet via l’adaptateur v2 et vérifie l’ID du fournisseur 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érerContenuMode d’échec possible
Historique de conversationMessages, tool calls et résultats renvoyésLes anciens détails encombrent la tâche actuelle
Environnement de travailFichiers, sandbox de paiement et état du test navigateurLe transcript indique qu’un service tourne alors qu’il est mort
Progression de la tâchePlan, contrôles terminés, approbation en attente, prochaine actionLa session suivante répète du travail déjà terminé

La compaction remplace les anciens messages par un résumé plus court afin que la session actuelle puisse continuer. Un progress handoff enregistre ce dont la session suivante a besoin : branche courante, fichiers modifiés, dernière commande de test et sa sortie, ainsi que prochaine étape non résolue.

Un fichier de handoff est une mémoire documentaire destinée à la prochaine session du modèle. Un checkpoint peut déjà préserver le plan, les étapes terminées, les résultats et le travail restant. Ajoutez un handoff lorsque ces détails sont absents ou inutilisables dans le contexte suivant, et vérifiez les fichiers et services actifs par rapport à l’environnement réel.

Si l’ancienne conversation contient des hypothèses obsolètes, le harness peut démarrer une nouvelle session de modèle avec ce handoff et le workspace courant. Remplacer un worker qui a crashé et restaurer ses processus relève d’un travail distinct de récupération du runtime.

Une petite modification documentaire peut ne nécessiter aucun de ces mécanismes. La migration de paiement nécessite un handoff si son état sauvegardé ne conserve pas une progression exploitable de la tâche, car la session suivante doit reconstruire à la fois le workspace et l’état d’avancement.

Les expériences d’Anthropic avec des agents de programmation longue durée utilisaient l’historique git et un fichier de progression entre les sessions. Le rapport ultérieur d’Anthropic sur la conception des harness sépare la compaction du handoff en fresh context et indique que les handoffs ajoutent de l’orchestration, de l’usage de tokens et du temps de bout en bout, sans publier de chiffres attribuant cette surcharge au handoff lui-même.


Utiliser les traces pour distinguer trois échecs

Les trois lignes suivantes sont des esquisses de traces illustratives, pas des exécutions mesurées ni des sorties du lab associé. Chaque ligne montre un échec différent et donc une réponse différente du harness.

Ce que la trace enregistreCe qui s’est produitRéponse correcte
Le call read-only get_order_status renvoie 503 ; aucun call qui modifie l’état n’est en coursUne lookup transitoire a échouéRetenter la lookup avec une limite et un backoff
create_test_order expire, puis une lookup de statut trouve la commande 123 sous la clé d’idempotence checkout-42Le service a créé la commande, mais la réponse a été perdueRenvoyer 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 l’instantané testéLes éléments probants d’acceptation requis sont absentsMaintenir l’exécution ouverte et dispatche le test d’acceptation navigateur

Un échec d’apparence transitoire ne rend pas tous les calls sûrs à retenter. La première ligne concerne une lookup read-only. La deuxième concerne 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 ligne ne correspond pas du tout à un échec d’outil ; le harness n’a simplement pas encore recueilli les éléments probants nécessaires pour accepter la modification de 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. Une trace peut fournir cette preuve uniquement si elle inclut le résultat serveur pertinent ou la lookup de statut ; un timeout côté client ne suffit pas et laisse l’issue indéterminée. Les enregistrements durables d’opération et d’acceptation doivent relier le call client, la décision d’approbation, l’identité de l’opération, le résultat serveur ou la lookup de statut, l’instantané testé et le résultat du test d’acceptation. Les traces peuvent exposer ces liens pour le débogage sans devenir le registre de récupération. Ces champs indiquent au harness lequel des trois chemins suivre.

Symptôme récurrentPetite modification à essayerCe qu’il faut mesurer
Les lookups read-only échouent transitoirementRetry borné avec backoffTaux de récupération, calls supplémentaires, temps de bout en bout
Les sessions reprises répètent du travail terminéProgress handoff structuréActions d’outils dupliquées après la reprise
Des tests requis manquent à la finRefuser la finalisation tant que chaque contrôle requis n’a pas réussiTâches acceptées sans tous les contrôles requis
Des défauts visuels survivent aux contrôles déterministesÉvaluateur en fresh context avec une grilleDéfauts détectés, faux rejets, temps de revue
L’agent modifie des éléments hors de son périmètrePermission d’outil plus restrictiveCalls bloqués et dérogations manuelles
La mémoire rappelée éclipse la tâche actuelleLimiter les faits rappelés ; les classer avant injectionTokens consacrés au rappel, tâches terminées, coût par tâche

Pour les assistances optionnelles telles que les aides à la planification, les résumés et les évaluateurs supplémentaires, nommez l’échec et mesurez si le composant rembourse son coût. Les exigences d’autorisation, d’isolation, de confidentialité et les contrôles d’acceptation obligatoires restent applicables même si les tâches ordinaires réussissent sans eux. Testez ces contraintes avec des cas adversariaux et des invariants explicites ; un petit benchmark de réussite ne peut pas justifier leur suppression.

Transformer ces échecs récurrents en suite de régression versionnée est un travail à part entière. J’en ai parlé séparément dans Évaluer les agents AI en 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, tout le reste de l’expérience demeurant fixe. Par exemple : le linting de l’éditeur aide-t-il ce modèle sur cette suite de tâches ?

Utilisez le protocole suivant :

  1. Figez la version du modèle, les instances de tâches, l’environnement, le grader et les prompts situés en dehors du composant testé.
  2. Donnez aux deux variantes le même budget total en tokens, en temps et en devise.
  3. Choisissez le nombre d’essais ou la règle d’arrêt avant d’exécuter la comparaison.
  4. Exécutez les mêmes instances de tâches dans les deux variantes. Les sorties du modèle variant, répétez chaque tâche plusieurs fois.
  5. Rapportez la moyenne ainsi que la dispersion ou l’intervalle de confiance.
  6. Comptez chaque essai démarré, y compris les timeouts, les arrêts de politique, les crashes du harness et les échecs de l’évaluateur.

Le taux de réussite seul peut masquer un composant coûteux. Suivez au minimum les tâches défectueuses acceptées comme terminées, le coût et le temps de bout en bout par tâche terminée, les erreurs d’outils, les commandes dupliquées, les minutes de revue et les dérogations manuelles aux permissions. Choisissez la métrique qui porte le coût réel de votre produit. Une hausse de deux points du nombre de tâches terminées est une mauvaise transaction si elle double votre file de revue.

Une expérience appariée de migration de paiement rend mesurable le progress handoff. Chaque paire contrôle/traitement part 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 différence. 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.

Un test d’ablation apparié du progress handoffUn test d’ablation apparié du progress handoff

L’article 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 guidée, et 7,3 % pour le même agent sans démonstration. L’écart principal de 10,7 points rapporté dans l’article est mesuré par rapport à cette baseline de 7,3 % ; la partie 3 reprend ces trois chiffres sous l’angle de la conception de l’interface. L’article a également modifié individuellement certaines fonctionnalités de l’interface :

Modification de l’interfaceRésolues
Interface SWE-agent complète (référence, inchangée)18,0 %
Éditeur sans linting15,0 %
Fichier complet au lieu d’un viewer de 100 lignes12,7 %
Historique complet des observations au lieu des cinq dernières15,0 %

Ces chiffres appartiennent à 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 à un facteur : chacune a modifié une fonctionnalité de l’interface, le modèle et le protocole d’évaluation restant fixes.

LangChain a publié une comparaison à modèle fixe plus large pour deepagents-cli. Elle rapporte une hausse de 52,8 % à 66,5 % sur Terminal-Bench 2.0 avec gpt-5.2-codex fixé, tandis que l’équipe modifiait le system prompt, les outils et le middleware. L’article regroupe plusieurs modifications et ne fournit ni intervalle de confiance, ni comparaison à budget total fixe, ni tableau d’ablation par modification. Ce résultat ne permet donc pas d’identifier la modification qui a aidé. 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 transférable, pas la liste des modèles.

Une comparaison plus récente montre pourquoi la configuration de l’API doit appartenir à la baseline gelée. Dans son rapport ARC-AGI-3 du 29 juillet 2026, OpenAI indique que le score de GPT-5.6 Sol sur le jeu public est passé de 13,3 % à 38,3 % lorsque son harness conservait le raisonnement et utilisait la compaction, au lieu de supprimer le raisonnement et de tronquer l’historique. La métrique est l’efficacité relative des actions humaines, et non la fraction des tâches résolues. Il s’agit d’une comparaison groupée rapportée par le fournisseur ; elle n’isole pas les deux réglages et n’établit pas de taille d’effet en production. Lors d’une mise à niveau, consignez l’API, la conservation du raisonnement, la politique de compaction et les budgets avec l’ID du modèle. Sinon, une régression apparente du modèle peut être due à une capacité manquante dans l’adaptateur.

Incluez les interventions du fournisseur dans la suite d’échecs. Un misalignment_policy_violation doit atteindre un chemin d’arrêt et de revue même après une sortie streamée ; ce n’est pas un cas de retry transitoire. La partie 4 couvre son périmètre dépendant de l’API. Testez que le harness cesse de dispatche les calls et enregistre les effets déjà réalisés.

Le rapport d’Anthropic sur les applications longue durée est une étude de cas qualitative, spécifique à un produit, plutôt qu’un benchmark contrôlé. L’application est RetroForge, un éditeur de jeux rétro 2D ; lors du Sprint 3, l’évaluateur du harness vérifiait 27 critères couvrant son éditeur de niveaux. Le travail avait commencé avec des modèles Opus antérieurs et, à la sortie d’Opus 4.6, l’équipe a supprimé les composants du harness un par un afin d’identifier ceux que le modèle plus récent avait rendus redondants. Le rapport indique que les calls de l’évaluateur devenaient une surcharge sur les tâches qu’Opus 4.6 pouvait terminer de manière fiable seul, tout en restant utiles à la limite de ses capacités. Cet exemple justifie la réévaluation des scaffolds existants lors d’un changement de modèle ; 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 s’étendre sur plusieurs étapes d’exécution et plusieurs états partagés. Avant de pouvoir le modifier en toute sécurité, il faut trouver chaque site d’implémentation — tout comme l’agent de programmation auquel vous déléguez la tâche.

Une option adaptée à la phase de recherche est un preprint de 2026 de Wang et al., le Harness Handbook, qui appelle cette recherche la localisation des comportements. 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 l’agent de programmation commence par une vue d’ensemble du système, ouvre l’étape d’exécution pertinente et descend jusqu’à des entrées ancrées dans le code source pour 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 compacte tout en préservant un chemin vers le code source.

La fraîcheur constitue une règle distincte. La carte est une aide à la navigation ; le code source actif établit le comportement. Chaque locator doit pouvoir être résolu dans le dépôt actif. Le handbook gèle les entrées obsolètes plutôt que 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 un comportement descend les niveaux du handbook, chaque locator candidat est vérifié dans le dépôt actif avant la rédaction du plan, et chaque diff appliqué resynchronise la carte.

Routage d’une modification de comportement à travers un handbook de harnessRoutage d’une modification de comportement à travers un handbook de harness

L’évaluation du Handbook compare des bras appariés sur 30 requêtes par dépôt. Elle n’établit ni l’égalité des budgets totaux, ni des essais stochastiques répétés, ni des estimations d’incertitude, et ses comparaisons rapportées excluent les sorties manquantes et les erreurs du planner. Elle illustre donc une partie du protocole ci-dessus, pas son intégralité. Elle couvre deux harness open source : Terminus-2 (six fichiers Python) et le monorepo Codex (2 267 fichiers Rust). Dans chaque cas, un planner read-only propulsé par DeepSeek-V4-Pro explorait directement le dépôt ou passait par le handbook. Les requêtes, le dépôt, les permissions des outils et le decoding étaient identiques dans les deux bras. Trois juges (GPT-5.5, Opus 4.8, DeepSeek-V4-Pro) notaient chaque plan de modification sur 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. Une victoire signifie que le score de qualité de l’un des bras, compris entre 0 et 100, dépassait celui de l’autre d’au moins trois points ; sinon, la comparaison juge–requête était considérée comme nulle. Le taux rapporté correspond au nombre de victoires divisé par le nombre de comparaisons juge–requête valides :

HarnessTaux de victoire baselineTaux de victoire assisté par le HandbookTokens 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 gagné plus souvent et utilisé moins de tokens de planner dans les deux dépôts. Les conditions restent attachées à ce résultat : trois juges LLM ont noté des plans de modification produits par un seul modèle planner sur deux harness. L’étude a évalué des plans, pas des diffs exécutés ni des taux de défauts en production.


Essayer la méthode dans le lab associé

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 de boutique fictif.

Chaque fixture de tâche déclare une difficulté ainsi que quatre conditions booléennes : outil flaky, progression perdue, lacune d’implémentation non détectée et fin 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 check exécute Ruff et sept tests unitaires, dont le validateur qui rejette toute paire d’ablation modifiant plus d’un composant.
  • make run affiche une matrice pédagogique cumulative, puis cinq comparaisons valides où un seul composant est retiré.
  • make failures indique la condition non gérée pour chaque tâche en échec. Le harness complet doit se terminer par all synthetic tasks pass.
make check
make run
make failures

La section causale de make run ressemble à ceci :

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 lignes adjacentes y ajoutent plusieurs composants à la fois et ne permettent donc pas d’identifier une cause.

Le lab valide chaque paire déclarée avant de l’exécuter. Ses tests de régression comprennent également une paire volontairement invalide qui modifie simultanément la politique de retry et l’évaluateur ; 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 de 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 est partie de la boucle de raisonnement vers l’extérieur. Commencez par le premier échec observé, puis examinez le composant qui possède cette responsabilité. Une exécution peut impliquer plusieurs composants :

Ce que l’exécution a faitOù se trouve le correctifPartie
A choisi une mauvaise étape suivante alors que les bonnes informations étaient déjà disponiblesBoucle de raisonnement ou modèle1
A répété du travail ou perdu une décision prise une heure plus tôtAssemblage du contexte et handoffs2
N’a pas pu exprimer l’action nécessaire ou a mal interprété un résultat renvoyéContrat d’outil3
A effectué une action qu’il n’aurait jamais dû pouvoir effectuerRègles de permission4
A tout perdu lorsqu’un worker est mort au milieu d’un callSession, checkpoint, sandbox5
A déclaré la réussite d’un travail qui n’était pas terminéContrôles d’acceptation et traces6

Quatre lignes pointent vers le code du harness, tandis que la ligne 5 pointe vers le runtime. Les instructions peuvent influencer le comportement, mais elles ne remplacent ni un contrôle de permission, ni un checkpoint durable, ni un test d’acceptation.


Commencer par une boucle et un contrôle d’acceptation

Je commencerais le harness d’un agent de programmation avec un modèle capable, des instructions de dépôt, quelques outils étroitement 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 même trace 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, pas d’un résultat issu d’un système déployé.

À partir de là, n’ajoutez que ce qu’une trace justifie. Notez qui maintient chaque composant, combien de tokens ou de secondes il ajoute, et quel test de régression justifierait sa suppression après une mise à niveau du modèle.

Six mois plus tard, toute personne qui voit progress_handoff=True devrait pouvoir retrouver les traces d’échec qui ont justifié sa présence ainsi que les cas de régression qui la maintiennent. Les traces expliquent pourquoi le composant existe ; une carte actuelle des comportements explique où intervenir.

Si vous êtes arrivé ici via une recherche, les cinq articles précédents ont construit un système autour d’une boucle de raisonnement :

  1. La boucle choisit le mouvement suivant.
  2. La mémoire fournit le contexte et un véritable checkpoint store Postgres le préserve.
  3. Les contrats d’outils définissent les actions et les formes de résultats que les contrôles ultérieurs peuvent lire.
  4. La sécurité ajoute le deny hook et le validateur du stop-hook. Les deux restent à l’état d’esquisses dans l’exemple, mais indiquent les points de contrôle.
  5. Le runtime maintient le processus actif entre les sessions et les échecs.

La série a également ajouté une surface de serveur MCP optionnelle et un nœud évaluateur qui vérifie le rapport préliminaire avant qu’un humain ne le voie. Le routage du worker via un proxy détenant les credentials reste une extension proposée. Ce sont des éléments de code ordinaires autour d’un call de modèle. Le routeur 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.

Pour le prochain composant d’assistance optionnel, conservez ensemble la trace d’échec, la règle d’acceptation et la comparaison avec le composant désactivé. Supprimez-le si vous ne pouvez pas identifier son bénéfice. Les contraintes de sécurité et d’acceptation requises ne dépendent pas de cette comparaison.


Références


Le code de Market Analyst Agent se trouve sur GitHub.