Problème
Dans les traces d'un run, on voit une boucle d'attente :
Agent A -> Agent B -> Agent C -> Agent A.
En 20 minutes, le nombre de runs en statut waiting peut monter à 40+.
La file grossit, les workers restent occupés, et le travail utile devient presque nul.
Vu de l'extérieur, tout semble "calme" : pas d'erreur explicite, pas de crash du service. Mais le run ne se termine pas, car les trois agents attendent simplement les uns les autres.
Le système ne crash pas.
Il se fige et brûle les ressources en silence.
Analogie : imagine trois personnes dans une porte qui se laissent poliment passer. Personne ne se dispute, personne ne fait de "faute", mais personne ne passe. Un deadlock en système multi-agent ressemble exactement à ça.
Pourquoi cela arrive
Un deadlock apparaît non pas parce que les agents "réfléchissent trop longtemps", mais parce que le système ne sait pas qui doit faire avancer l'état.
En production, cela ressemble souvent à ceci :
- les agents échangent des messages et dépendent les uns des autres ;
- une attente est retardée (tool, approval, lock) ;
- les autres agents passent aussi en
waiting; - sans timeout et owner de l'état du workflow, le workflow se bloque.
Le problème n'est pas un agent isolé. Le problème est la coordination non contrôlée entre agents.
Pannes les plus fréquentes
Pour rester pratique, on retrouve surtout quatre patterns de deadlock.
Attente circulaire (Circular wait)
Agent A attend B, B attend C, C attend A. Tout le monde est "occupé", mais il n'y a aucun progrès.
Cause typique : le graphe de dépendances contient un cycle et il n'y a pas d'orchestrator unique.
Lock sans TTL (Lock without lease)
Un agent prend un lock sur document/ticket puis crash. Les autres agents attendent ce lock indéfiniment.
Cause typique : lock sans lease/TTL et sans mécanisme de recovery de l'owner.
Attentes non bornées (Unbounded waiting)
Il y a un timeout HTTP, mais pas de timeout sur les waits internes entre agents. Le workflow peut attendre "pour toujours".
Cause typique : les timeouts sont implémentés au niveau transport, pas au niveau des états d'orchestration.
Boucle de retry inter-agents (Cross-agent retry loop)
Les agents se renvoient la tâche avec "revérifie encore", et cela devient un ping-pong infini.
Cause typique : pas de limite de retry et pas de stop reason pour les scénarios de blocked state.
Comment détecter ces problèmes
Le deadlock se voit bien via la combinaison des métriques workflow et runtime.
| Métrique | Signal de deadlock | Action |
|---|---|---|
waiting_runs | le nombre de runs en waiting augmente de façon stable | ajouter un wait-timeout et un stop reason pour blocked state |
wait_duration_p95 | durée d'attente au-dessus de la normale | borner le temps d'attente à chaque state transition |
blocked_transition_rate | blocages fréquents entre les mêmes agents | vérifier le graphe de dépendances pour détecter les cycles |
lease_conflict_rate | conflits fréquents ou leases expirées | ajouter TTL, renew et policy de recovery |
queue_backlog | la file augmente avec un trafic entrant normal | nettoyer les runs figés, activer le fallback mode |
Comment distinguer deadlock et tâche réellement longue
Chaque run long n'est pas un deadlock. Le critère clé : y a-t-il des transitions d'état et un progrès utile ?
Normal si :
- le statut du workflow change comme prévu ;
- après l'attente, un nouvel artefact ou une nouvelle étape apparaît ;
- il existe un owner clair de la transition courante.
Dangereux si :
- le run reste longtemps dans le même état
waiting; - plusieurs agents "attendent" simultanément les uns les autres ;
- il n'y a pas de stop reason claire expliquant pourquoi le système n'avance pas.
Comment arrêter ces pannes
En pratique, cela ressemble à ceci :
- introduire un owner unique des transitions (orchestrator ou leader) ;
- poser un timeout sur chaque état
waiting; - utiliser lease/TTL pour les shared resources ;
- en cas de no-progress, terminer le run de façon contrôlée : stop reason + fallback.
Garde minimale pour un wait-state :
import time
class WaitGuard:
def __init__(self, wait_timeout_s: int = 30):
self.wait_timeout_s = wait_timeout_s
self.wait_started_at: dict[str, float] = {}
def mark_wait_start(self, run_id: str):
self.wait_started_at[run_id] = time.time()
def check_wait(self, run_id: str):
started = self.wait_started_at.get(run_id)
if started is None:
return None
if time.time() - started > self.wait_timeout_s:
return "deadlock_risk:wait_timeout"
return None
En production, mark_wait_start(...) est généralement appelé lors du passage à waiting,
et check_wait(...) est exécuté dans un scheduler ou une heartbeat loop pour terminer un run bloqué à temps.
Où c'est implémenté dans l'architecture
Le contrôle des deadlocks en production est généralement réparti sur plusieurs couches.
Agent Runtime gère le cycle de vie des runs :
timeouts, stop reasons, arrêt forcé des états figés et transitions de fallback.
C'est ici qu'on pose en général les règles deadlock_risk:*.
Orchestration Topologies définit qui possède les state transitions et comment les agents interagissent sans attente circulaire. Sans owner explicite de l'état dans la topologie, le deadlock devient une question de temps.
Tool Execution Layer couvre la partie technique : lease/TTL pour shared resources, retry policy unique et contrôle des attentes au niveau des tools.
Auto-vérification
Vérification rapide avant release. Coche les points et regarde le statut ci-dessous.
C'est un sanity-check court, pas un audit formel.
Progression: 0/9
⚠ Il y a des signaux de risque
Il manque des contrôles de base. Fermez les points clés de la checklist avant release.
FAQ
Q : Les deadlocks n'arrivent que dans de gros systèmes multi-agents ?
R : Non. Même 2-3 agents peuvent créer une boucle d'attente s'il n'y a pas d'owner d'état explicite.
Q : Ajouter des timeouts suffit ?
R : Les timeouts limitent le blocage, mais ne retirent pas la cause racine. Il faut aussi un orchestrator et des state transitions explicites.
Q : Les leases résolvent totalement le deadlock ?
R : Non. Les leases couvrent les problèmes de lock après crash, mais ne corrigent pas les cycles logiques entre agents.
Q : Que faire si un run est déjà bloqué en waiting ?
R : Forcer l'arrêt du run avec stop reason, libérer la lease, passer le workflow en fallback et analyser la chaîne d'attente dans les traces.
Un deadlock ressemble rarement à une panne bruyante. Le plus souvent, c'est un arrêt silencieux du progrès qui consomme workers et budget. C'est pourquoi les systèmes multi-agents en production ont besoin non seulement d'une répartition des rôles, mais aussi d'une discipline d'orchestration stricte.
Pages liées
Pour aller plus loin sur ce problème :
- Pourquoi les agents IA échouent - carte générale des pannes en production.
- Multi-agent chaos - comment une interaction non contrôlée des agents détruit la stabilité.
- Partial outage - comment une dégradation partielle des dépendances provoque des états
waiting. - Agent Runtime - où gérer stop reasons, timeouts et cycle de vie des runs.
- Orchestration Topologies - comment concevoir une coordination contrôlée entre agents.