Accueil / Articles

Sept modèles de boucles d’agents et les situations où chacun échoue : ReAct, Maker-Checker et d’autres.

Un catalogue pratique des schémas de boucles d’agents, allant de plan-agir-vérifier à l’escalade humaine, avec des indications sur les cas où chacun convient, ceux où il faut l’éviter, et comment faire son choix.

Éditeur : REACTAPP.TOP9 min de lecture1958 mots

Sources: Agentic Loops & Design Patterns

Nul ne devrait juger un agent en fonction de l’élégance de ses paroles. Ce qui compte, c’est son comportement en cas de forte charge, lorsque quelque chose tombe en panne ou lorsqu’un auditeur vient poser des questions. C’est dans ces situations que les modèles de conception en boucle l’emportent sur n’importe quelle instruction isolée. Les boucles fonctionnent le mieux lorsqu’elles sont traitées comme des primitives architecturales : chacune cible une catégorie particulière de problèmes, présente des compromis connus et doit être choisie intentionnellement. Les modèles présentés ci-dessous sont accompagnés d’exemples adaptés à Claude ainsi que des situations dans lesquelles il convient d’éviter chacun d’eux.

1. Planifier, agir, vérifier

C’est le modèle de base. L’agent planifie une petite étape, entreprend une action, puis vérifie si cette action l’a rapproché d’un objectif clairement défini. Il trouve ainsi un équilibre entre flexibilité et contrôle.

// Pseudo-code: Node/TypeScript orchestrating Claude as an agent

async function planActVerifyLoop(ticket) {
  let iteration = 0;
  const maxIterations = 5;

  while (iteration < maxIterations) {
    iteration++;

    // 1. PLAN: ask Claude for the next step
    const plan = await claude.chat({
      model: "claude-3-opus",
      messages: [
        {
          role: "user",
          content: `You are a coding agent working on ticket ${ticket.id}.
Goal: Make tests pass for this ticket without changing public APIs.

Current context:
${ticket.description}
${ticket.latestFailureLog}

What is the single most useful next action?`
        }
      ]
    });

    // 2. ACT: execute the suggested action if it's in the allowed action space
    const action = parseAction(plan);
    const result = await executeAction(action); // run tests, edit file, etc.

    // 3. VERIFY: use tests as verification
    const verification = await runTests(ticket.testSuite);

    if (verification.allPassing) {
      return { status: "done", iterations: iteration };
    }

    // Attach the failure output back into ticket context
    ticket.latestFailureLog = verification.failureOutput;
  }

  return { status: "budget_exhausted" };
}

Dans ce schéma, Claude s’occupe de la micro-planification de l’étape suivante, tandis que le boucle elle-même gère la vérification par des tests et impose une limite d’itérations. Cette séparation empêche le système d’annoncer un succès simplement parce que le modèle se sent confiant.

Les cas où ce schéma convient bien sont les tâches d’ingénierie en plusieurs étapes avec des progrès mesurables, les scripts de déploiement et les modifications de configuration qui doivent atteindre un état fonctionnel vérifié, ainsi que les outils internes où des contrôles rigoureux tels que des tests ou une validation de schéma peuvent être intégrés.

Les cas où ce schéma ne convient pas incluent les travaux purement créatifs comme le texte de marketing ou l’écriture longue, les décisions subjectives où la vérification ne ferait que refléter des préférences personnelles, et les flux en temps réel qui ne peuvent pas se permettre plusieurs itérations sur le chemin critique.

2. ReAct (raisonner, puis agir)

ReAct alterne de brèves séquences de réflexion avec des actions individuelles. Plutôt que de planifier l’ensemble du parcours à l’avance, l’agent examine l’état le plus récent, choisit un outil, l’utilise, puis réfléchit à nouveau. Cela en fait un choix naturel pour des tâches exploratoires ou diagnostiques, où chaque nouvelle observation peut légitimement remodeler le plan.

# Pseudo-code: Python coordinator orchestrating Claude tool calls

def react_loop(incident):
    iteration = 0
    max_iterations = 6

    while iteration < max_iterations:
        iteration += 1

        # REASON: Claude decides what to inspect next
        reasoning = claude.chat(
            model="claude-3-opus",
            messages=[
                {
                    "role": "user",
                    "content": f"""
You are an SRE assistant triaging a payment incident.

Incident summary:
{incident.summary}

Recent metrics:
{incident.latest_metrics}

Logs snippet:
{incident.logs_snippet}

Decide one next diagnostic action from:
- CHECK_METRICS
- CHECK_LOGS
- CHECK_DB_HEALTH
- SUMMARIZE_FINDINGS_AND_RECOMMEND_ACTION

Explain your reasoning briefly and output JSON with 'action' and 'target'.
"""
                }
            ]
        )

        action = parse_json(reasoning)
        if action["action"] == "SUMMARIZE_FINDINGS_AND_RECOMMEND_ACTION":
            return claude.chat(... )  # final summary + recommended steps

        # ACT: run the selected diagnostic
        observation = run_diagnostic(action, incident)

        # Update incident state for the next reasoning step
        incident.update_with_observation(observation)

Chaque étape de diagnostic reste ciblée, et Claude prend connaissance d’un contexte mis à jour après chaque action.

Utilisez-le pour le diagnostic des incidents et l’analyse des causes racines, dans des workflows de support complexes où chaque réponse modifie les éléments à examiner ensuite, ainsi que pour les enquêtes de sécurité où les preuves arrivent par morceaux et de manière incomplète.

Évitez-le lorsque le plan est stable et la procédure évidente, comme dans le traitement de documents de type ETL, lorsque le débit est suffisamment élevé pour que la réflexion étape par étape devienne trop coûteuse, ou lorsque l’éventail des actions possibles est si restreint que toute réflexion supplémentaire ne sert à rien.

3. Réfléchir et réviser

Ici, deux phases, la génération et la critique, sont suivies d’une étape de révision. Cela constitue une valeur par défaut pertinente pour tout ce qui nécessite une révision avant d’être transmis à un client, à un régulateur ou à un système de production. Le point important est la séparation : le critique peut être une autre instruction, un modèle moins coûteux, ou même un moteur de règles.

async function reflectReviseLoop(draftInput: string) {
  // 1. GENERATE
  const draft = await claude.chat({
    model: "claude-3-opus",
    messages: [
      {
        role: "user",
        content: `
Write a customer-facing email explaining a declined payment due to suspected fraud.
Constraints:
- empathetic but clear
- no admission of fault
- no promises about future approvals

Context:
${draftInput}
`
      }
    ]
  });

  // 2. CRITIQUE (using a cheaper model as checker)
  const critique = await claude.chat({
    model: "claude-3-haiku",
    messages: [
      {
        role: "user",
        content: `
You are a compliance checker.

Review the following email for:
- policy violations
- misleading statements
- over-commitments

Output a JSON with:
- issues: list of strings
- safe: boolean

Email:
${draft.content}
`
      }
    ]
  });

  const review = JSON.parse(critique.content);

  if (review.safe) {
    return draft.content;
  }

  // 3. REVISE
  const revised = await claude.chat({
    model: "claude-3-opus",
    messages: [
      {
        role: "user",
        content: `
You wrote this email:
${draft.content}

Compliance issues:
${review.issues.join("\n")}

Rewrite the email to resolve all issues while preserving intent.
`
      }
    ]
  });

  return revised.content;
}

Cette structure convient aux messages des clients concernant des sujets sensibles tels que la fraude, les remboursements ou les changements réglementaires, aux analyses post-mortem internes susceptibles d’être auditées, ainsi qu’à tout contenu où le respect des politiques prime sur l’esthétique.

Elle n’est pas adaptée aux réunions de brainstorming internes à faible risque, où les critiques créent des frictions sans offrir de véritable sécurité, aux réponses très courtes dans les chats où une seconde révision serait superflue, ou aux flux à latence critique comme les messages en temps réel pendant une transaction, où il faut s’en tenir à une seule tentative.

4. Ébaucher, tester, corriger

Les équipes d’ingénierie privilégient souvent ce cycle, car le résultat est objectif et indépendant du modèle : l’ensemble de tests passe ou reste en échec. À chaque étape, l’agent propose des modifications, exécute les tests et identifie ce qui a échoué. Il corrige ensuite les problèmes et recommence, jusqu’à ce que le système soit dans un état connu comme fonctionnel ou qu’un plafond d’itérations soit atteint.

async function draftTestFixLoop(issue: Issue) {
  const maxIterations = 4;
  let iteration = 0;

  while (iteration < maxIterations) {
    iteration++;

    // DRAFT: Claude proposes code changes
    const patch = await claude.chat({
      model: "claude-3-opus",
      messages: [
        {
          role: "user",
          content: `
You are an autonomous coding agent.

Ticket:
${issue.title}
${issue.body}

Current failing tests:
${issue.failingTests}

Propose a minimal patch as a diff that makes tests pass
without changing public APIs.`
        }
      ]
    });

    applyPatchToGitRepo(patch.content);

    // TEST: run CI locally or via API
    const testResult = await runCi(issue.branchName);

    if (testResult.success) {
      // FIX_DONE: open a PR with the diff
      await openPullRequest(issue, patch.content);
      return { status: "done" };
    }

    // FEEDBACK: update failingTests for next iteration
    issue.failingTests = testResult.failureSummary;
  }

  return { status: "needs_human_review" };
}

Cela fonctionne pour des tickets à faible risque, bien encadrés et marqués comme prêts pour un agent, sur des outils internes où le retrait est simple, ainsi que pour des parcours non critiques capables de supporter la latence et les coûts associés.

Évitez de l’utiliser pour toute modification impliquant un transfert direct d’argent via des parcours de transaction essentiels, pour les changements de schéma, les migrations et les refacteurs transversaux, ainsi que pour des logiques réglementées où une modification automatisée pourrait enfreindre les règles.

5. Critiqueur et créateur

Un agent crée le contenu et un autre le vérifie. Dans le domaine des paiements, cela correspond au système de contrôle « créateur-vérificateur » déjà utilisé pour les opérations à haut risque, ce qui facilite sa mise en œuvre auprès des équipes de gestion des risques et de conformité.

Cette approche convient partout où l’intervention d’un deuxième regard est déjà attendue, dans les systèmes proches de la production où l’automatisation intervient (configuration, routage ou notifications), ainsi que pour les rapports ou tableaux de bord à haute importance consultés par la direction.

Ce n’est pas adapté aux tâches triviales où le coût supplémentaire d’une vérification dépasse l’avantage qu’elle apporte, aux processus sensibles à la latence qui doivent répondre en une seule étape, ou aux pipelines où des règles faibles forceraient le vérificateur à accepter tout ce qui semble suffisamment bon.

6. Réessayer avec mémorisation

La plupart des boucles naïves se contentent de réessayer. Une boucle de réessai avec mémorisation enregistre les états d’échec et les utilise pour éviter de reproduire la même erreur.

def retry_with_memory_loop(task, max_attempts=3):
    failures = []

    for attempt in range(1, max_attempts + 1):
        # Ask Claude to consider past failures before deciding the next move
        decision = claude.chat(
            model="claude-3-opus",
            messages=[
                {
                    "role": "user",
                    "content": f"""
You are handling a task with a flaky external API.

Task:
{task.description}

Past failures:
{failures}

Decide whether to:
- RETRY_API
- FALLBACK_TO_CACHE
- ESCALATE_TO_HUMAN

Explain briefly and output JSON: {{ "choice": "...", "reason": "..." }}
"""
                }
            ]
        )

        choice = parse_json(decision)

        if choice["choice"] == "RETRY_API":
            result = call_api(task)
        elif choice["choice"] == "FALLBACK_TO_CACHE":
            result = use_cache(task)
        else:
            return {"status": "escalated", "failures": failures}

        if result.success:
            return {"status": "success", "attempts": attempt}

        failures.append(result.error_summary)

    return {"status": "max_attempts_exhausted", "failures": failures}

Les cibles appropriées sont les intégrations fragiles, les API à limitation de vitesse et les systèmes externes hors de votre contrôle, le parsing de documents présentant un format incohérent, ainsi que les workflows non critiques où les tentatives de réessai sont acceptables.

Celles qui ne conviennent pas sont les flux de transactions essentiels où un réessai pourrait dupliquer des effets, les opérations qui ne sont pas idempotentes et pour lesquelles un réessai n’est pas considéré comme sûr, ainsi que les scénarios où l’échec doit être immédiat et visible, sans qu’un réessai silencieux ne soit effectué.

7. Escalade humaine

C’est le modèle de protection à utiliser pour tout ce qui concerne de l’argent réel, des clients réels ou une exposition réglementaire. Le processus fonctionne de manière autonome jusqu’à un certain point, puis passe le relais de façon propre dès que la confiance diminue ou que le risque augmente.

async function triageLoop(ticket: Ticket) {
  const autoActions = ["LABEL", "ROUTE_TO_QUEUE", "REQUEST_MORE_INFO"];

  const decision = await claude.chat({
    model: "claude-3-opus",
    messages: [
      {
        role: "user",
        content: `
You are a support triage agent in a payments company.

Ticket:
${ticket.body}

Decide one of:
- LABEL (low risk)
- ROUTE_TO_QUEUE (medium risk)
- ESCALATE_TO_HUMAN (high risk / unclear)

Return JSON with:
- choice
- risk_level
- rationale
`
      }
    ]
  });

  const choice = JSON.parse(decision.content);

  if (choice.choice === "ESCALATE_TO_HUMAN") {
    await createHumanTask(ticket, choice.rationale);
    return { status: "escalated" };
  }

  // LABEL or ROUTE_TO_QUEUE are automated but bounded
  await applyAutomatedTriage(ticket, choice);
  return { status: "auto_treated" };
}

Utilisez-le pour toute automatisation capable d’améliorer l’expérience client ou la perception d’équité, pour les processus où une automatisation partielle est utile mais où une autonomie totale présente des risques, ainsi que pour les déploiements initiaux qui doivent apprendre avant que les contrôles ne soient assouplis.

Évitez de l’utiliser pour les tâches entièrement internes et à faible risque où les humains ne feraient qu’ajouter du bruit, pour le travail en batch en arrière-plan où la révision humaine ne peut pas être étendue, et dans les situations où l’escalade surchargerait les équipes sans améliorer les résultats.

Choix en pratique

Lorsqu’une nouvelle opportunité d’agent apparaît, la question « Comment dois-je formuler cette demande ? » n’est pas la bonne première question. Voici quatre questions plus pertinentes :

  1. Quel signal permet de valider le résultat : des tests, des schémas, des règles, des métriques ou le jugement humain ?
  2. Combien d’étapes la tâche nécessite-t-elle réellement ?
  3. Que se passe-t-il en cas de défaillance du système ?
  4. Qui est affecté si des erreurs surviennent ?

Les réponses indiquent un schéma récurrent :

  • Une vérification stricte et binaire suggère la méthode : ébaucher, tester, corriger.
  • Une approche exploratoire suggère ReAct.
  • Les préoccupations liées aux politiques et au ton suggèrent de réfléchir et de réviser, ou d’adopter les approches critiqueur et constructeur.
  • Un environnement instable exige de réessayer en conservant la mémoire des tentatives précédentes.
  • Un risque élevé implique d’intégrer dès le départ une procédure d’escalade humaine.

L’ingénierie de boucles repose moins sur une orchestration astucieuse que sur une réflexion disciplinée concernant les systèmes.