Les cinq couches d’un agent d’encadrement, avec du code qui échoue en l’absence de celles-ci.
Les cinq couches d’un harnais d’agent, chacune contenant du code qui échoue en l’absence de cette couche : la frontière d’exécution, le sandboxing, la mémoire, les boucles de vérification et le pipeline de contexte.
Sources: `The Complete Guide to Agent Harnesses (With Code)`
Imaginez une équipe qui ajoute une ligne dans la configuration d’un agent : aucune modification n’est appliquée sans l’approbation humaine. La formulation est claire, la ligne se trouve en haut de la configuration, et l’agent la charge au début de chaque session.
Puis vient un après-midi où une seule session dépasse cette ligne à onze reprises et intègre environ soixante-seize modifications non approuvées dans le document destiné au client. Aucune erreur n’apparaît. Rien ne s’arrête pour demander confirmation. Deux heures plus tard, quelqu’un examine le document, met en évidence chaque ligne que l’agent a réécrite de sa propre initiative, et en trouve quarante-six, dans un fichier portant le nom du client.
La directive elle-même était correcte. Elle se trouvait simplement au mauvais endroit.
Ce qui fonctionne à la place, c’est un « hook », un petit script que l’outil exécute juste avant d’envoyer une demande à un outil externe. Il examine la demande en attente et peut y répondre par un refus. L’agent peut toujours vouloir effectuer la modification, mais celle-ci n’aboutit jamais dans le fichier. Un tel « hook » peut parfois se tromper, généralement parce que la règle a été définie de manière trop restrictive, et la différence réside dans le fait que l’on peut ouvrir le code et identifier précisément la condition qui a provoqué l’erreur. Une phrase dans une instruction ne permet pas d’identifier quoi que ce soit de concret.
La distance entre une règle annoncée et une règle appliquée est constituée par l’outil lui-même. Celui-ci couvre cinq domaines :
- Les actions que l’agent peut entreprendre
- Les endroits auxquels il peut accéder
- Le statut qui persiste après une redémarrage
- La personne qui examine ses résultats
- Le coût lié à ce qui lui est affiché
Chaque domaine dispose du code ci-dessous. Il existe un ensemble de démonstrations exécutables permettant à chaque couche de fonctionner sur votre propre ordinateur portable avant de rencontrer des problèmes en production. Cela s’adresse aux personnes qui ont déjà déployé un agent et l’ont vu échouer.
Ce que vous obtenez réellement avec un harness
Commençons par quelque chose d’à peine besoin d’être mentionné : le boucle principale d’un agent de codage ne compte que six lignes :
while agent.turns < max_turns:
call = agent.next_call(observations)
if call is None:
break
result = execute(call)
observations.append(result)
Ces lignes représentent l’agent. Avec la gestion des erreurs et les tentatives de répétition, on arrive à près de vingt. Quel que soit le framework que vous avez évalué, il encapsule ce cycle, et l’élément intéressant est execute. Cette fonction définit ce que le modèle peut faire, à quoi il peut accéder, ce qu’il conserve, qui l’audit, et ce qui finit devant lui.
Collectivement, ces décisions constituent le cadre de fonctionnement, et de nombreux commentaires affirment qu’il prime sur le choix du modèle. Les données soutiennent plutôt une affirmation plus limitée, qui se révèle en fait plus pratique.
Le harnais l’emporte en termes de coût. Un préprint de juin 2026 a comparé trois harnais (Goose, OpenCode, OpenHands-SDK) sur deux modèles en utilisant un échantillon stratifié de 50 tâches de Terminal-Bench Pro, la version la plus difficile de la suite d’agents Terminal-Bench. L’utilisation de tokens par tâche résolue variait jusqu’à 40 fois d’un harnais à l’autre. Le passage à un modèle plus performant ne modifiait cette valeur que de 1,0 à 1,3 fois.
Il ne l’emporte pas en termes de capacités. Les taux de réussite variaient de 0 à 8 points d’un harnais à l’autre, et, avec des intervalles de confiance bootstrap, tous les résultats sauf celui présentant l’écart le plus grand se situaient autour de zéro. Les auteurs eux-mêmes qualifient les résultats de précision de descriptifs, étant donné n=50.
En termes simples, une tâche effectuée par un modèle avec un certain taux de réussite peut coûter quarante fois plus sous un système que sous un autre. Tout ce qui suit vise à réduire les coûts et les surprises, sans pour autant ajouter de capacités intellectuelles à un modèle faible.
Intégrer la règle dans le code
Formuler une règle dans un prompt système revient à parier que le modèle s’en souviendra, l’interprétera comme prévu et s’y conformera même lorsque le contexte devient complexe. Généralement, le modèle gagne. C’est précisément ce taux de réussite qui rend la méthode risquée, car les gens cessent alors de vérifier.
Les règles au niveau du code s’appliquent à chaque appel, même lorsque le modèle a perdu le fil d’elles.
Un cycle d’agent comporte cinq points où le code peut bloquer l’exécution. Chaque section ci-dessous décrit son objectif, le code chargé de le faire respecter ainsi que le comportement en l’absence de cette couche. Personne n’a besoin des cinq en même temps. Choisissez la couche dont l’échec vous semble le plus susceptible de se produire cette semaine.
Couche 1 : la frontière d’exécution
Que l’agent est-il autorisé à faire ?
La frontière correspond à une fonction intercalée entre le choix du modèle et l’appel réel à l’outil. Commencez avec un agent non protégé. Sa consigne stipule de ne jamais supprimer un fichier sans demander d’abord, et trois appels à des outils attendent dans la file d’attente.
SYSTEM_PROMPT = "IMPORTANT: never delete a file without asking the user first."
def execute(call):
if call.name == "delete_file":
FILES.pop(call.args["path"], None)
return f"deleted {call.args['path']}"
Lors de son exécution, l’agent supprime cache.tmp, un fichier inoffensif, puis client-deliverable.md, comme son nom l’indique. Aucune question n’a été posée, bien que la consigne ait déjà mentionné la règle dès le début.
Une fonction placée avant l’outil résout ce problème :
def boundary(rules):
def wrap(execute):
def guarded(call):
for name, deny_if, reason in rules:
if deny_if(call):
return f"DENIED by {name}: {reason}"
return execute(call)
return guarded
return wrap
NEEDS_APPROVAL = [(
"delete-needs-approval",
lambda c: c.name == "delete_file" and not c.args.get("approved_by_human"),
"deletion requires an explicit human approval flag on the call",
)]
Avec le même agent, le même script et la même instruction, rien n’est supprimé.
Niveau 2 : sandboxing
Qu’est-ce à quoi l’agent peut accéder ?
Un environnement sandbox définit quels dossiers et quels hôtes le processus de l’agent peut atteindre. Il faut le construire à partir de zéro : partir de rien et n’accorder que ce dont le travail a besoin, via une liste de chemins autorisés, une liste d’hôtes autorisés, ainsi qu’un processus incapable d’accéder au reste.
L’alternative courante est une liste des chemins interdits :
DENY = ["secrets/"]
def execute(call):
path = call.args["path"]
if any(path.startswith(d) for d in DENY): # checks the spelling
return "DENIED by deny-list"
real = os.path.normpath(path) # the ../ collapses HERE, after the check
return DISK.get(real, "not found")
Lorsque vous le lancez, l’agent tente deux fois :
read('secrets/api_key') -> DENIED by deny-list
read('work/../secrets/api_key') -> sk-live-DO-NOT-LEAK
La vérification a examiné le texte tel qu’il avait été tapé, tandis que la recherche dans le fichier a pris en compte l’emplacement auquel ce texte renvoie. Deux requêtes, deux orthographes, un fichier.
La correction consiste d’abord à normaliser le chemin, puis à effectuer une comparaison :
ALLOW_ROOTS = ["work"]
def resolve(path):
real = os.path.normpath(path) # resolve FIRST
if not any(real == r or real.startswith(r + os.sep) for r in ALLOW_ROOTS):
return None
return real
Les deux orthographes se résument désormais à secrets/api_key, qui ne se trouve pas sous de racine autorisée, ce qui entraîne le rejet des deux. Ici, il est crucial d’autoriser plutôt que de refuser, car une liste de refus ne bloque que les orthographes imaginés par son créateur.
Ces exemples correspondent à des fonctions Python ordinaires, il n’est donc pas nécessaire d’utiliser un conteneur pour les tester. Dans un déploiement réel, la vérification identique est assurée par les limites du processus lui-même : un utilisateur ou un conteneur n’ayant pas de droits de lecture en dehors de work/, avec le même ordre d’opérations.
La couche 1 ne peut pas effectuer cette tâche. Une vérification intégrée au programme ne perçoit que les appels qu’il reconnaît, tandis qu’une commande shell, un lien symbolique ou un chemin contenant .. peuvent accéder au même fichier sans correspondre à aucun de ces appels.
Couche 3 : persistance de la mémoire
Que reste-t-il après un réinitialisation du contexte ?
Une seule habitude rapporte plus que la plupart : interrompre les conversations sans y réfléchir. Le contexte se remplit, la qualité diminue et l’agent commence à être en désaccord avec lui-même. Beaucoup de gens persistent dans ce type de pratique. Il vaut mieux relancer tôt, à condition que tout ce qui est important soit stocké à un endroit que le relancement ne pourra pas effacer, que ce soit du code commité, des configurations, le framework lui-même ou des notes résumées.
Décidez donc intentionnellement où chaque élément d’état doit être placé. Il existe trois options : la conversation, où il disparaît avec la session ; le disque, où il persiste et peut être récupéré plus tard ; et les configurations du framework, où il influence toutes les sessions ultérieures.
Une seule fonction de dispatch couvre les trois cas :
CONVERSATION, DISK, HARNESS_CONFIG = [], {}, {}
def remember(kind, key, value):
"""'chat' dies with the session, 'disk' survives it,
'config' shapes every session that follows."""
{"chat": lambda: CONVERSATION.append(value),
"disk": lambda: DISK.__setitem__(key, value),
"config": lambda: HARNESS_CONFIG.__setitem__(key, value)}[kind]()
La fonction n’a presque aucun effet. Ce qu’elle fournit, c’est une règle selon laquelle rien ne peut être enregistré sans indiquer dans quel des trois endroits il doit être stocké. Les équipes font rarement ce choix explicitement, ce qui explique pourquoi les agents semblent oublier certaines choses et se tromper obstinément sur d’autres. Lors de la démonstration, la liste des conversations revient vide, tandis que la décision et la règle restent sur le disque ainsi que dans les paramètres de configuration.
Couche 4 : boucles de vérification
Qui examine les résultats, et qu’est-ce qui empêche l’examinateur de simplement corriger les problèmes ?
Une boucle de vérification signifie un second examen du travail de l’agent, effectué par une entité autorisée à signaler les problèmes mais pas à les résoudre.
La seconde moitié est celle que les gens omettent. Laissez un sous-agent d’analyse écrire, et il corrige discrètement tout ce qu’il trouve. Cela semble efficace, mais cela efface les informations que vous vouliez conserver : vous ne pouvez plus distinguer le travail correct de celui qui était erroné et qui a été corrigé à votre insu.
Donc, maintenez le rôle de l’analyste en mode lecture seule. Cela produit une critique, et agir sur cette critique représente une étape supplémentaire avec ses propres traces. Les séparer permet de préserver un signal que vous pouvez lire.
Une deuxième tactique est la simulation. Pour toute action irréversible, l’outil accepte un indicateur, décrit par défaut ce qu’il aurait fait, et n’agit que lorsque quelqu’un désactive délibérément cet indicateur. Le comportement inoffensif est la norme, donc une erreur n’a aucun coût. Dans la démonstration, l’outil de correction annonce qu’il réécrirait total.py, ne écrit rien, et l’erreur de type « off-by-one » reste présente à la fin.
Une seule instruction execute contient les deux règles :
def execute(call):
if call.name == "review":
snapshot = dict(CODE) # a copy, so the reviewer cannot write
src = snapshot[call.args["path"]]
return f"VERDICT: {'off-by-one' if '+ 1' in src else 'looks good'}"
if call.name == "apply_fix":
if call.args.get("dry_run", True): # on by default, turned off on purpose
return f"DRY RUN: would rewrite {call.args['path']}, nothing written"
CODE[call.args["path"]] = call.args["new"]
return f"wrote {call.args['path']}"
L’examinateur repère l’erreur de type « off-by-one » mais n’a aucun moyen de la corriger.
Couche 5 : pipelines de contexte
Qu’est-ce qui parvient au modèle, et à quel prix ?
Le pipeline de contexte décrit la manière dont les informations pénètrent dans la fenêtre du modèle ainsi que la quantité d’informations qui y est autorisée.
C’est là que se situe le coût réel : chaque token qui entre est facturé à ce moment-là, puis à nouveau pour tous les tours suivants. C’est donc ici que se crée l’écart de quarante fois entre les différentes solutions, et non à la Couche 1.
Une configuration naïve charge tout : le fichier complet pour une question concernant une seule fonction, les résultats complets d’une recherche pour un besoin simple, ainsi que toute l’historique. Cela fonctionne, mais au vingtième tour, on paie à nouveau pour tout ce qui a déjà été lu aux tours un à dix-neuf.
La solution réside dans la délégation et la distillation. Un sous-agent travaille dans une fenêtre séparée, lit des dizaines de milliers de tokens puis en restitue un ou deux mille. Le thread principal ignore le résultat de recherche brut et ne reçoit que la réponse.
D’un point de vue technique, il s’agit de déterminer quel compteur augmente :
def subagent_search(query):
"""Its own window. The main thread never pays for this reading."""
global subagent_tokens
subagent_tokens += sum(len(v.split()) for v in CORPUS.values())
return next(f"{n}: {b.split('ANSWER:')[1].strip()}"
for n, b in CORPUS.items() if "ANSWER:" in b)
def execute(call):
global main_tokens
distilled = subagent_search(call.args["q"])
main_tokens += len(distilled.split()) # the only line that bills you
return distilled
La démonstration est délibérément simple et honnête. La question et la réponse sont identiques dans les deux exécutions, pourtant le thread principal traite 9 602 tokens dans l’une et 2 dans l’autre.
Cela éclaire également pourquoi les sous-agents enchaînés déçoivent les utilisateurs. Lorsque chaque étape attend la précédente, on obtient une isolation du contexte sans aucun gain de vitesse en parallèle. Les étapes véritablement indépendantes devraient s’exécuter en même temps. Sinon, il faut accepter que c’est l’isolation que l’on achète. Cela justifie souvent le prix, à condition de savoir ce que couvre la facture.
Ce que personne ne peut encore dire
La confiance du public sur ce sujet a dépassé les preuves qui la sous-tendent, c’est pourquoi les mises en garde sont importantes.
- Rien ici n’a fait l’objet d’une évaluation par des pairs. En août 2026, chaque étude qui examine le harnais n’est qu’un préprint arXiv sans processus d’évaluation par des experts. L’expérience la plus minutieusement décrite se présente comme un travail préliminaire soumis à l’examen d’un atelier, et le document de référence le plus connu se qualifie de document de position sans aucune expérience propre. Considérez tout cela, y compris ce texte, comme non évalué par des pairs, et répétez les comparaisons sur vos propres tâches avant de planifier des budgets en fonction.
- Les gains obtenus se transposent rarement à des tâches inconnues. Une étude de juillet 2026 a montré que laisser un système s’évoluer automatiquement ne permet pas de surpasser de manière fiable la simple augmentation en fonction du temps de test, et sa généralisation est médiocre, car la recherche et le test final s’appuient sur le même benchmark. Lors d’une évaluation en apprentissage continu sur Terminal-Bench 2.0, le taux GEPA est passé à 70,8 % sur les tâches pour lesquelles le système avait été ajusté, avant de chuter à 54,5 % sur l’ensemble plus large des tâches, ce qui est inférieur au taux de départ de 56,8 %. En fait, l’ajustement a nui aux performances. Testez tout système sur des tâches qui n’ont pas servi à son développement, car les améliorations observées uniquement sur l’ensemble d’entraînement disparaissent en conditions réelles.
- Une amélioration obtenue grâce au réglage fin est évaluée en fonction de son propre signal d’entraînement. Baseten a affiné Gemma 3 27B pour la rédaction de notes cliniques, faisant passer ses performances de 35 % en dessous de Claude Sonnet 4 à 60 % au-dessus. Le rapport indique clairement que l’outil d’évaluation servait également de récompense d’entraînement. Il s’agit d’une méthode valable, mais la note provient justement de ce qui a été utilisé pour l’enseignement. Lorsqu’un fournisseur affirme un gain grâce au réglage fin, demandez quel était l’outil d’évaluation utilisé pendant l’entraînement ; s’il s’agit du même outil, ce chiffre montre que le modèle a appris à reproduire cet outil d’évaluation.
- Un harnais ne s’adapte pas parfaitement d’un modèle à l’autre. Une étude de juin 2026 a montré que l’ajout d’un harnais après l’entraînement ne restitue que peu des bénéfices apportés par un entraînement effectué avec lui. Les modèles entraînés avec un harnais minimal ont affiché une erreur « Format d’outil invalide » dans 75,1 % des tentatives après modification des définitions des outils, tandis qu’une version Qwen2.5-7B a obtenu 10,8 points de moins par rapport à son modèle de base. Un harnais adapté à tous les modèles les rend en réalité un peu moins performants chacun. Choisissez le harnais avant l’entraînement, et si vous ne développez pas votre propre modèle, sachez qu’un changement de harnais au milieu d’un projet sera plus coûteux que ce à quoi on pourrait s’attendre.
- La terminologie n’est pas encore établie. En mai 2026, Hugging Face a publié un glossaire distinguant « harness » de « scaffold », tout en reconnaissant que Claude Code, Codex et d’autres outils utilisent néanmoins le terme « harness » pour désigner l’ensemble de l’infrastructure. La division en cinq couches n’est qu’une façon parmi d’autres de le structurer. Selon une définition plus restreinte, près de la moitié des éléments cités ci-dessus concernent en réalité quelque chose de différent. Choisissez un vocabulaire, restez cohérents au sein de votre équipe et examinez attentivement les comparaisons entre différentes études. C’est pourquoi la méthode des cinq couches convient le mieux comme liste de contrôle pour votre propre configuration, car une liste de contrôle perdure malgré les changements de vocabulaire, tandis qu’une taxinomie en vient souvent aux conflits. Avant de considérer qu’il existe une différence entre les résultats de deux agents, demandez quelles couches chacun d’eux prend en compte.
- Il semble que les effets des harnesses s’atténuent à mesure que les modèles s’améliorent. Harness-Bench indique que des backends de modèles plus puissants affichent des scores moyens plus élevés ainsi qu’une variation plus faible entre les harnesses. Une partie de ces structures de soutien pourrait simplement compenser une faiblesse en train de disparaître. Orientez vos investissements dans les harnesses vers le contrôle des coûts, qui est durable, et non vers la précision, que une nouvelle version de modèle pourrait fournir gratuitement.
Les scripts complémentaires
Dix scripts courts accompagnent cette idée. Chaque couche est présentée avec son mécanisme de protection, ainsi que sans celui-ci. Ils s’exécutent hors ligne sans clé API, car un substitut scripté remplace le modèle et génère une série fixe d’appels d’outils. Voir la différence lors de l’exécution vaut mieux que de se fier aveuglément à des affirmations.
git clone https://github.com/paoloap-py/agent-harness-guide
cd agent-harness-guide
python3 run_all.py # all five layers, guard off then on, side by side
python3 test_harness.py # asserts every difference above, 10 checks
Le README présente les cinq couches comme une liste de contrôle d’audit avec une question récurrente : le code impose-t-il cela, ou bien une phrase dans une instruction ? Tout champ que vous ne pouvez pas cocher en indiquant un chemin de fichier correspond à une règle que vous ne pouvez qu’espérer voir respectée.
Où cela s’applique
Une règle valable placée au mauvais endroit a peu d’impact. Dans le scénario décrit, cela a entraîné quinze violations, onze en une seule après-midi, ainsi qu’une journée que personne ne souhaite revivre. C’est le déplacement de la règle qui en a assuré le respect, sans que le moindre mot ne soit modifié.
C’est là l’essentiel. Le modèle est quelque chose que vous achetez. Tout ce qui l’entoure est quelque chose que vous construisez, et c’est cela qui détermine le coût de l’agent ainsi que la fréquence à laquelle il effectue des actions que vous n’avez jamais approuvées. Les deux relèvent de l’ingénierie, mais seul le second est à votre disposition pour être façonné.
FAQ
Qu’est-ce exactement qu’un harnais pour un agent ? Tout ce qui entoure le modèle : la boucle d’outil, les vérifications préalables à l’exécution, le sandbox, la persistance entre sessions, l’examinateur, ainsi que le pipeline qui détermine ce que contient chaque tour. La boucle se compose de six lignes, ou vingt avec le traitement des erreurs, et tout le reste fait partie de l’harnais.
L’harnais est-il plus important que le modèle ? En termes de coût, oui, car l’utilisation de tokens par tâche résolue peut varier jusqu’à 40 fois. En ce qui concerne la résolution des tâches, non. Les différences mesurées dans le taux de réussite entre les harnais vont de 0 à 8 points, avec des intervalles qui traversent zéro, et les auteurs de Terminal-Bench concluent que le choix du modèle a généralement plus d’impact sur les performances.
Où le harness coûte-t-il le plus cher ? Dans le contexte : chaque token affiché au modèle à chaque tour est facturé à chaque tour. C’est ce qui rend Layer 5 le plus coûteux et explique pourquoi confier une recherche à un sous-agent modifie la facture sans changer la réponse.
Les règles de permission comptent-elles comme une mesure de sécurité ?
Non. Une vérification du harness ne prend en compte que l’appel effectivement effectué par l’agent, tandis qu’une commande shell, un lien symbolique ou un chemin .. peut accéder au même fichier sans être inspecté. La véritable limite provient du système d’exploitation : lancez le processus sans aucun accès et ajoutez progressivement ce dont la tâche a besoin.
Créer un harness ou en adopter un ? Adoptez-en un et restez-y. Passer à un autre efface les connaissances accumulées dans vos fichiers de configuration, et chaque règle écrite après une véritable panne doit être acquise à nouveau.
Lectures complémentaires
- Les agents qui s’améliorent grâce à des compétences : réutilisation, sélection, correction et vérification — Une approche en quatre étapes pour des agents capables d’améliorer leurs propres compétences : enregistrement du code en cours d’exécution, affinement des descriptions, proposition de corrections vérifiées et vérification du résultat avant la transmission.
- Un harness de production pour Claude Code : six couches allant de Slack à Merged MR — Comment un harness à six couches transforme les tickets Slack en code intégré : traitement des événements, boucles d’agent contrôlées, état durable, capacité d’auto-réparation, observabilité et contrôle par l’opérateur.