AI

AgentOps : arrêter une boucle de handoff entre agents avant qu'elle ne multiplie les actions

Un runbook de production pour détecter une boucle de délégation entre agents, contenir les effets de bord, reconstruire les traces et décider reprise contrôlée ou rollback mono-agent.

12 août 2026 aiagentopsagentsmulti-agenthandofforchestrationobservabilitytracesguardrailsidempotencyautomationrunbookrollbackproduction

Un agent de triage transmet un incident réseau à un agent cloud. Celui-ci demande une vérification à un agent d’automatisation, qui renvoie le dossier au triage faute d’approbation. La conversation semble avancer, mais le même travail recommence sous de nouveaux identifiants. Les appels d’outils augmentent, plusieurs tickets sont préparés et un retry peut finir par déclencher deux fois la même action.

Le problème n’est pas seulement la qualité de la réponse. Une boucle de handoff est un incident d’orchestration : l’état de la tâche, la responsabilité et les effets déjà produits ne suivent plus le transfert. Ce runbook part d’un agent interne d’exploitation capable de lire des runbooks, d’interroger des logs et de préparer des actions soumises à validation. L’objectif est d’arrêter la multiplication sans perdre les preuves, puis de décider si le flux multi-agent peut reprendre ou doit revenir temporairement à un seul agent.

Reconnaître une boucle avant l’épuisement des limites

Une délégation légitime change le propriétaire parce qu’une compétence manque. Une boucle répète la même intention sans réduire l’incertitude, sans ajouter de preuve et sans rapprocher la tâche d’un état terminal.

text handoff-loop-signals.txt
Signaux a correler
meme task_id ou meme empreinte metier sur plusieurs traces
retour vers un agent deja visite sans preuve nouvelle
profondeur ou nombre de handoffs qui depasse le budget
meme outil appele avec des arguments equivalents
nouveaux tickets ou jobs prepares pour la meme intention
retries alors que l'etat precedent reste inconnu
hausse de latence et de cout sans progression d'etat

Le timeout utilisateur est un signal tardif. La plateforme doit mesurer la profondeur, les agents visités, les empreintes d’action et le dernier résultat utile. Une réponse différente en prose ne constitue pas une progression si elle prépare la même opération.

Geler les effets de bord, pas les preuves

Dès que la boucle est probable, bloquez les nouveaux handoffs et les outils en écriture pour le périmètre concerné. Conservez la lecture, les traces et les appels de diagnostic sans effet. N’effacez pas les files ni les conversations avant d’avoir identifié les actions préparées, approuvées ou exécutées.

yaml handoff-containment.yml
incident_scope:
workflow: incident-triage
task_id: task-7f31
correlation_id: corr-91ab

containment:
new_handoffs: blocked
write_tools: prepare_only
automatic_retries: disabled
read_only_diagnostics: allowed
trace_retention: preserved

operator_checks:
- list prepared and approved actions
- cancel duplicate pending work
- identify any executed side effect
- assign one human incident owner

Le contrôle doit porter sur une tâche, un workflow ou un type d’action, pas forcément sur tous les agents. Si l’orchestrateur ne sait pas isoler ce périmètre, repassez les outils sensibles en lecture seule globalement jusqu’à ce que les actions en vol soient qualifiées.

Reconstruire la chaîne de responsabilité

Une trace exploitable relie la tâche logique aux exécutions, handoffs et outils. Chaque transfert doit indiquer qui délègue, à qui, pourquoi, avec quel état et quel résultat attendu. Sans cette enveloppe, deux runtimes peuvent recréer la même tâche et contourner un simple compteur local.

json handoff-envelope.json
{
"taskId": "task-7f31",
"correlationId": "corr-91ab",
"parentRunId": "run-triage-04",
"runId": "run-cloud-05",
"fromAgent": "incident-triage",
"toAgent": "cloud-diagnostics",
"reasonCode": "missing-network-evidence",
"expectedOutcome": "read-only route evidence",
"visitedAgents": ["incident-triage", "cloud-diagnostics"],
"hop": 2,
"hopBudget": 4,
"actionFingerprint": "diagnose:subscription:resource:route",
"stateVersion": 6,
"deadline": "<timestamp>",
"sideEffectMode": "none"
}

taskId reste stable pendant toute l’intention métier. runId change à chaque exécution. actionFingerprint déduplique une opération logique, même si le prompt ou l’ordre des arguments varie. stateVersion permet de refuser un agent qui tente d’écrire à partir d’un état périmé.

Lire la boucle dans les traces

Le schéma réel dépend de la plateforme, mais la requête doit regrouper les événements par tâche et reconstruire l’ordre des agents. Adaptez les noms de table et de colonnes à votre télémétrie.

kusto 01-detect-handoff-loops.kql
let Window = 2h;
AgentOrchestrationEvents
| where TimeGenerated > ago(Window)
| where EventType in ("handoff", "tool_call", "terminal_state")
| summarize
  FirstSeen = min(TimeGenerated),
  LastSeen = max(TimeGenerated),
  Handoffs = countif(EventType == "handoff"),
  Agents = make_list_if(ToAgent, EventType == "handoff", 32),
  DistinctAgents = dcountif(ToAgent, EventType == "handoff"),
  RepeatedActions = count() - dcount(ActionFingerprint),
  TerminalStates = countif(EventType == "terminal_state")
by TaskId, CorrelationId
| extend SuspectedLoop = Handoffs > 4
  or (Handoffs > DistinctAgents and TerminalStates == 0)
  or RepeatedActions > 1
| where SuspectedLoop
| order by Handoffs desc, LastSeen desc

Ne concluez pas sur le seul nombre de transferts. Un workflow complexe peut traverser plusieurs spécialistes. La preuve forte combine revisite, absence de nouvel état, action répétée et absence de sortie terminale.

Imposer le contrat d’orchestration hors du prompt

Demander aux agents de « ne pas boucler » ne suffit pas. L’orchestrateur doit refuser une transition invalide avant d’appeler le runtime suivant.

yaml orchestration-guardrails.yml
handoff_policy:
max_hops: 4
revisit_agent: require_new_evidence
deadline: required
terminal_states: [resolved, refused, needs_human, failed]

progress_policy:
require_one_of:
  - new_evidence_id
  - narrower_scope
  - validated_state_transition
reject_same_action_fingerprint: true

side_effect_policy:
idempotency_key: required
stale_state_write: reject
write_after_handoff: require_fresh_approval
unknown_tool_result: never_retry_automatically

Le budget de sauts borne l’exécution, mais l’idempotence protège la production. Chaque outil d’écriture doit accepter une clé dérivée de la tâche et de l’action logique, puis retourner le résultat déjà connu au lieu de recommencer. Si un appel expire avec un état inconnu, l’orchestrateur doit interroger son statut avant tout retry.

Corriger la cause, pas seulement le compteur

Une boucle révèle souvent un contrat incomplet : responsabilités qui se chevauchent, raisons de handoff libres, état non partagé, absence de sortie needs_human, ou outil qui ne distingue pas préparation et exécution. Classez l’incident avant de modifier les prompts.

Si deux agents revendiquent le même domaine, clarifiez une règle de propriété. Si aucun agent ne peut terminer faute d’information, ajoutez une sortie humaine explicite. Si l’état se perd, centralisez la tâche logique et imposez des écritures conditionnelles sur stateVersion. Si un outil est appelé deux fois, corrigez l’idempotence avant de rouvrir les handoffs.

Rejouer sans action réelle

Reproduisez la trace fautive avec des outils simulés ou read-only. Le test doit vérifier la progression, les sorties terminales et l’absence de doublon, pas seulement la réponse finale.

yaml handoff-regression-cases.yml
cases:
- name: missing_evidence_returns_to_previous_agent
  expected:
    - second_visit_requires_new_evidence
    - otherwise_needs_human
    - no_write_tool

- name: tool_timeout_with_unknown_result
  expected:
    - query_execution_status
    - no_automatic_retry
    - same_idempotency_key

- name: hop_budget_exhausted
  expected:
    - terminal_state_needs_human
    - full_handoff_summary
    - pending_duplicates_cancelled

promotion_gate:
repeated_action_fingerprints: 0
writes_from_stale_state: 0
unbounded_tasks: 0
trace_completeness: required

Ajoutez un cas légitime où plusieurs agents coopèrent et terminent correctement. Un garde-fou qui bloque toute délégation supprime la boucle, mais aussi la valeur de l’architecture.

Décider reprise ou rollback mono-agent

Réactivez d’abord les diagnostics read-only sur un petit périmètre. Ouvrez ensuite les handoffs avec budget et traces complètes, puis les outils de préparation. Les écritures ne reviennent qu’après validation de l’idempotence, des approbations et des cas de timeout.

Revenez temporairement à un agent unique si l’état partagé reste ambigu, si les outils ne garantissent pas l’idempotence ou si une action exécutée ne peut pas être reliée à une tâche. Le rollback doit fixer un propriétaire, désactiver les routes multi-agent touchées et conserver les mêmes contrôles d’outils. Il ne doit pas transformer l’agent unique en raccourci vers des écritures non bornées.

Conclusion

Une boucle de handoff se traite comme une panne de système distribué : identité stable de la tâche, état versionné, échéance, budget de sauts, sorties terminales et effets idempotents. Les traces servent à prouver la progression, pas seulement à compter les tokens.

La décision finale est opérationnelle : reprendre progressivement quand les transferts ajoutent une preuve et que les doublons sont impossibles, ou restaurer un flux mono-agent tant que l’orchestration ne garantit pas ces propriétés. L’objectif n’est pas de faire déléguer davantage, mais de rendre chaque délégation explicable et réversible.