Ingénierie des boucles : Création de boucles d’agents à l’aide de vérificateurs et de règles d’arrêt.
Un guide pratique pour créer quatre boucles d’agents empilées : les bases de ReAct, les évaluateurs de critères, le routage LangGraph, un contrat de boucle, les déclencheurs d’événements, la méthode de montée en pente et les mécanismes de protection contre les échecs.
La logique sous-jacente à tout cela est simple à expliquer. Cessez de diriger une IA instruction par instruction et concevez plutôt le cycle dans lequel elle évolue. L’idée principale est que c’est le vérificateur, et non le modèle, qui limite ce en quoi on peut avoir confiance ; il faut se représenter quatre cycles imbriqués les uns dans les autres. Voici comment le construire : les composants d’un cycle, le code pour chaque couche, les raisons pour lesquelles les cycles échouent, ainsi que les règles d’arrêt qui empêchent qu’ils épuisent votre budget. À la fin, vous saurez suffisamment pour laisser un cycle préparer une demande de fusion de code pendant que vous dormez.
Les exemples de code suivent les API documentées, les identifiants des modèles sont des placeholders, et tout extrait qui n’est qu’un pseudocode le précise dans un commentaire en tête. Un exemple est présenté tout au long, le point de départ habituel dans ce domaine : un cycle de codage qui reproduit un test échoué, le corrige, fait vérifier cette correction par un élément autre que l’agent lui-même, et ouvre une proposition de fusion de code en version brouillon.
Les cinq composants d’une boucle
Quel que soit le framework utilisé, une boucle fonctionnelle se résume toujours à cinq éléments. En omettre un fait que la boucle dérive, s’arrête ou tourne indéfiniment.
- Un déclencheur. Ce qui la met en marche, qu’il s’agisse d’un message humain, d’une fréquence programmée, d’un webhook ou d’une nouvelle ligne de tableau.
- Un objectif que la machine peut vérifier. « Faire passer les tests » correspond, tandis que « améliorer le code » ne le fait pas. Si le logiciel ne peut pas déterminer si l’objectif a été atteint, la boucle non plus.
- Des outils fournissant des retours concrets. L’agent doit agir sur le monde et observer les résultats en exécutant du code, en lisant stderr ou en appelant une API.
- Un état maîtrisé et minimaliste. Conservez l’objectif, un carnet de notes et un historique des tentatives effectuées. Un état surchargé est la cause pour laquelle les boucles ralentissent, dérivent et dépassent leur fenêtre de contexte.
Prévoyez trois sorties et non deux. Succès : le vérificateur a validé, donc procédez à l’envoi. Échec : le budget ou la limite d’itérations est atteint, donc arrêtez-vous et résumez ce qui reste. Transfert : l’agent est confronté à une décision qui ne devrait être prise que par un humain, donc l’affaire est escaladée. Faire partie de la conception d’une bonne boucle, c’est reconnaître les appels qu’elle ne devrait pas effectuer elle-même.
Boucle 1 : la boucle de l’agent
Au cœur se trouve le cycle que tous les agents exécutent déjà, le modèle ReAct de 2022 : réfléchir, agir en appelant une outil, observer le résultat, et répéter jusqu’à completion. En 2026, il est conçu délibérément et n’est plus géré manuellement. Dans sa forme la plus simple, il s’agit d’un seul appel de fonction :
from langchain.agents import create_agent
def run_tests() -> str:
"""Run the project's test suite and return the raw output."""
…
agent = create_agent(
model="anthropic:claude-opus-5",
tools=[run_tests],
system_prompt="You are a careful Python engineer. Fix failing tests.",
)
# create_agent returns a graph that calls tools in a loop, repeating
# until the model replies with no tool calls. The loop runs on .invoke()
Cette brève description cache une boucle, et vous devriez pouvoir vous en faire une idée. Ci-dessous se trouve la même structure de contrôle sous forme de pseudocode, sans aucun framework. Notez que chaque sortie est délimitée :
# PSEUDOCODE - shows the control structure a harness implements.
# The helper names below are yours to supply, not a real library API.
def run_loop(goal):
state = init_state(goal) # goal + scratchpad + attempt log
for step in range(MAX_STEPS): # hard cap: never loop forever
thought = model.reason(state) # ReAct: reason…
action = model.choose_action(state) # …then pick a tool call
result = tools.execute(action) # touch the real environment
state = update(state, thought, action, result)
state = compact(state) # keep context under budget
if verifier.passes(state): # deterministic check = reward
return success(state)
if no_progress(state) or budget.exhausted():
return escalate_to_human(state) # third exit: hand-off
return escalate_to_human(state)
Parmi toutes les formes présentées ici, souvenez-vous de celle-ci. Les autres ne sont que des variantes affinées d’elle.
Boucle 2 : ajouter un vérificateur
Un agent simple décide par lui-même quand il a terminé, et c’est en faisant confiance à cela que l’on obtient des résultats erronés. Il faut donc envelopper cet agent dans un évaluateur qui vérifie son travail selon des critères prédéfinis. Lorsque le travail échoue, les retours d’erreur sont réutilisés et la boucle se poursuit, jusqu’à atteindre une limite maximale. Le middleware de vérification se trouve dans le package deepagents.
from deepagents import RubricMiddleware, create_deep_agent
from langchain.messages import HumanMessage
from langgraph.checkpoint.memory import InMemorySaver
RUBRIC = """
- All tests pass in run_tests
- No test is skipped or deleted to force a pass
"""
agent = create_deep_agent(
model="anthropic:claude-opus-5",
system_prompt="You are a careful Python engineer.",
middleware=[
RubricMiddleware(
model="anthropic:claude-haiku-4–5", # a cheap grader is fine
system_prompt="Grade against the rubric. Be strict.",
max_iterations=5, # the stop rule (default is 3)
),
],
checkpointer=InMemorySaver(),
)
# The rubric is passed at invoke time - not to the constructor.
result = agent.invoke(
{
"messages": [HumanMessage("Make the failing tests pass.")],
"rubric": RUBRIC,
},
config={"configurable": {"thread_id": "fix-tests-session"}},
)
C’est ainsi que cela fonctionne. Chaque fois que l’agent est sur le point de terminer, un sous-agent évaluateur distinct lit la transcription en fonction du critère d’évaluation. Un verdict needs_revision renvoie les retours dans la conversation sous forme de HumanMessage, permettant à l’agent de continuer. Les seules issues possibles sont satisfied, failed, max_iterations_reached et grader_error ; ainsi, la boucle se ferme et s’arrête toujours. Si l’évaluateur doit effectuer ses propres vérifications, fournissez-lui des outils sous forme d’instances BaseTool.
Il existe deux types de vérificateurs, et choisir le bon est l’essentiel de la compétence requise.
- Déterministe : exécute les tests, valide le schéma et confirme la note. C’est peu coûteux, rapide et impossible à tromper ; essayez-les en premier.
Transformer en graphe
Lorsqu’une tâche dépasse le stade du trivial, un graphe explicite est préférable à une boucle cachée : les nœuds, les arêtes ainsi que le point où s’arrête l’exécution sont tous visibles. Un agent de recherche peut être représenté par trois nœuds, chaque décision d’arrêt étant prise par le routeur :
from typing import Literal, TypedDict
from pydantic import BaseModel, Field
from langgraph.graph import END, StateGraph
class ResearchState(TypedDict):
question: str
draft: str
verdict: str
feedback: str
iterations: int
class Verdict(BaseModel):
status: str = Field(description='"verified" or "needs_work"')
feedback: str = Field(description="claims lacking support, if any")
def decide(state: ResearchState) -> Literal["research", "__end__"]:
if state["verdict"] == "verified": # success exit
return "__end__"
if state["iterations"] >= MAX_ITERATIONS: # surrender exit
return "__end__"
return "research" # else: loop again with the feedback
builder = StateGraph(ResearchState)
builder.add_node("research", research_node) # generator
builder.add_node("verify", verify_node) # verifier
builder.add_edge("research", "verify")
builder.add_conditional_edges(
"verify", decide, {"research": "research", "__end__": END}
)
builder.set_entry_point("research")
graph = builder.compile()
Aucun prompt ingénieux n’apparaît dans ce code. L’intelligence réside dans la structure : un nœud génère, un autre vérifie, et un routeur transforme le résultat en une nouvelle tentative avec des retours d’information ou en arrêt. LangGraph considère les sentinelles __end__ et END comme équivalentes.
Le contrat de boucle
C’est ici que le changement de mentalité devient concret. Au lieu d’écrire des instructions, on rédige une spécification de « fin », c’est-à-dire une fonction de récompense associée à une règle d’arrêt. C’est cet artefact que l’on examine, que l’on compare et que l’on enregistre :
Objectif : une compilation CI verte sur la branche payments-refactor. Cela signifie que tous les tests passent et qu’aucun n’a été sauté ou supprimé de manière frauduleuse. Étapes : lire le test qui échoue, corriger la cause probable, puis relancer les tests. Mémoire : un journal des approches déjà tentées afin d’éviter toute répétition. Arrêter lorsque tout est vert en ouvrant un PR provisoire, après six essais en résumant ce qui reste à faire, ou dès que le travail concerne la logique d’authentification ou de paiement en le transférant à une autre personne.
Aucune instruction n’a été transmise au modèle. Ce qui a été écrit, c’est l’objectif, les contrôles, la règle de mémoire ainsi que les trois conditions d’arrêt ; ce contrat constitue en fait une ingénierie de boucles.
Boucles 3 et 4 : empilement vers l’extérieur
Déjà, une seule boucle bien conçue est utile. Les gains s’accumulent lorsque l’on en enveloppe d’autres, chacune fonctionnant à un rythme plus lent. Les boucles 1 et 2 assurent l’exécution correcte des tâches, tandis que les boucles 3 et 4 en font un processus continu et auto-améliorant.
Boucle 3 : événements
Un événement externe transforme votre boucle, d’un outil que vous appelez en un service qui s’exécute automatiquement. C’est également l’endroit idéal pour intégrer une étape d’approbation humaine avant toute mise en production :
# Loop 3: an event turns the loop into a service
from pydantic import BaseModel
class CIEvent(BaseModel):
branch: str
@app.post("/webhook/ci-failed")
def on_ci_failure(event: CIEvent):
if not event.branch.startswith("fix/"): # cheap triage first
return {"skipped": True}
result = agent.invoke(
{
"messages": [HumanMessage(
f"Fix the failing test on {event.branch}.")],
"rubric": TEST_GREEN_RUBRIC,
},
config={"configurable": {"thread_id": event.branch}},
)
open_draft_pr(result) # a human approves the merge, not the loop
return {"opened_pr": True}
Boucle 4 : montée en pente
La boucle la plus extérieure est la plus lente, et elle agit directement sur la boucle elle-même. Chaque exécution laisse une trace derrière elle. Un agent d’analyse lit ces traces, identifie les échecs qui se reproduisent constamment et propose des modifications au système : le prompt, les outils, la grille d’évaluation que les humains examinent. Imaginez la flèche de retour : au lieu de ne renvoyer des informations qu’au point de départ, elle atteint la boucle intérieure et la modifie. Le système améliore ainsi le système lui-même.
Un point d’attention : les boucles extérieures multiplient tout ce qui se trouve à l’intérieur d’elles. N’ajoutez-les qu’une fois que le vérificateur intérieur est fiable, car une quatrième boucle autour d’un vérificateur faible ne fait que transformer des erreurs en masse en processus automatisés.
Pourquoi cela compte comme de l’ingénierie
Les garde-fous peuvent sembler être de la bureaucratie. Ils perdent cet aspect dès que l’on considère un agent comme un contrôleur à boucle fermée, c’est-à-dire une correspondance point par point. L’objectif est la valeur cible, l’agent constitue le contrôleur, et les outils ainsi que l’environnement forment la plante. Le vérificateur agit comme le capteur, et son verdict « nécessite des travaux » représente le signal d’erreur qui déclenche la correction suivante. Une tâche terminée signifie convergence. Le contrôleur est piloté par l’écart entre la valeur cible et la lecture du capteur, que la plante soit un moteur à réaction ou un ensemble de tests.
Le même vocabulaire décrit deux manières dont une boucle peut s’interrompre. Elle peut osciller, en répétant indéfiniment une même action, ou diverger, s’éloignant de l’objectif tandis que les ressources s’épuisent. Les mécanismes de contrôle ne sont rien d’autre que les conditions de stabilité qui la maintiennent dans des limites définies, et la règle suprême est que le système doit garantir une fin, tâche pour laquelle on ne peut pas se fier à l’agent.
Une journée avec les quatre boucles
Observez comment les quatre boucles fonctionnent au cours d’une matinée. À 09:14, un test relatif à payments-refactor échoue et un webhook est déclenché, ce qui correspond à la boucle 3, sans que personne n’ait tapé quoi que ce soit. Pendant les sept minutes suivantes, l’agent lit l’échec, applique des correctifs, relance le test, lit à nouveau l’échec et apporte de nouveaux correctifs, tout en tenant un journal des tentatives (boucle 1). Après chaque série d’essais, un outil d’évaluation déterministe exécute l’ensemble des tests pour s’assurer qu’aucun n’a été sauté afin de forcer un résultat positif (boucle 2). La quatrième série d’essais se termine par un résultat positif.
À 09:22, le cycle se termine correctement, avec un projet de modification préliminaire indiquant ce qui a changé et pourquoi. Une personne doit l’approuver car la modification concerne les paiements. Pendant la nuit, le cycle de détection des problèmes constate qu’il s’agit du troisième bug lié aux fuseaux horaires de la semaine et élabore une nouvelle vérification, qui passe par votre examen (cycle 4). La séance de demain commence de manière plus efficace que celle d’aujourd’hui.
Vous avez dormi pendant la majeure partie de ce processus. Le matin, il y a un projet de modification préliminaire et une décision à prendre, sans clavier sous la main pour taper.
Lorsque les cycles échouent
Les boucles amplifient, et l’amplification fonctionne dans les deux sens. Chaque mode de défaillance affecte la boucle en un point précis et comporte une protection standard, avec un thème récurrent : ce qui génère ne doit pas également évaluer. La protection la moins chère consiste à faire fonctionner le vérificateur sur des entrées connues comme bonnes et connues comme mauvaises avant de laisser la boucle fonctionner seule. Vérifiez qu’il rejette les entrées mauvaises, car un vérificateur non testé n’est qu’une opinion accompagnée d’un coût.
Quand ne pas créer de boucle
Se hâter de mettre tout en boucle constitue à son tour un mode de défaillance. Préférez une seule appelation dans ces situations :
- Une seule passe suffit, comme pour un résumé, une traduction ou un extrait propre. Une boucle ne fait qu’augmenter les coûts et la latence.
- Aucun objectif ne peut être vérifié. Sans moyen de détecter si la tâche est terminée, il n’y a pas de signal d’erreur, ce qui laisse une boucle ouverte consommant des ressources inutilement.
L’ingénierie des boucles consiste à savoir quels problèmes méritent d’être traités par une boucle, tout autant que à savoir comment en construire une.
Construisez-la comme si vous deviez garder ce poste
Ces sont les outils : anatomie, boucle d’agent, vérificateur, graphe, contrat, boucles extérieures et catalogue des pannes. Équipez la boucle d’un vérificateur fiable, d’une arrêt automatique et de limites qu’elle ne peut pas dépasser. Ajoutez des points de contrôle humains là où les enjeux sont réels, et gardez à l’esprit qu’un correctif fourni par une boucle ne signifie pas que vous la comprenez. Laissez-la ensuite fonctionner, comme le ferait quelqu’un qui compte rester ingénieur et non simplement la personne qui l’a lancée.
Le niveau suivant
Une question mérite d’être posée avant de laisser un boucle sans surveillance : à quel point un vérificateur peut-il être faible et pourtant afficher une couleur verte ? Une étude de référence a été réalisée pour le découvrir. Elle recense des cas d’usurpation de récompenses et évalue les systèmes de contrôle en fonction du nombre d’usurpations qu’ils permettent. Un système de contrôle qui ignore le rapport fourni par l’agent lui-même et réexécute l’ensemble des tests, c’est-à-dire la vérification déterministe suggérée précédemment comme première solution, n’a empêché aucune des huit usurpations. La réexécution de l’ensemble des tests ne fait que confirmer que le signal de récompense a été satisfait, et c’est précisément ce qu’accomplit une usurpation de récompenses.
Ainsi, si le vérificateur limite le système, la compétence essentielle qui devient alors cruciale est évidente : concevoir, gérer les versions et améliorer les vérifications elles-mêmes. On l’appelle l’ingénierie d’évaluation, c’est-à-dire la tâche de définir les critères que le cycle doit respecter. Un vérificateur correspond à une étape au sein d’un seul cycle. L’ingénierie d’évaluation englobe de nombreux cycles, à travers les ensembles de données et les grilles d’évaluation qui permettent de savoir si le système dans son ensemble s’améliore ou se détériore.
Renforcer une étape de vérification revient à construire un escalier, dont le sommet est imprévisible. Il s’avère que la meilleure vérification n’est pas du tout un code sophistiqué, mais simplement quelques assertions que l’agent n’a jamais eu le droit de voir. Ainsi, la limite de la vérification se situe à un endroit délicat. Elle dépend moins de la qualité de construction d’une étape que de la qualité des données mises de côté et du fait de les empêcher de fuir à nouveau dans le cycle.