Normal path: execute → tool → observe.
Le problème (côté prod)
Tu construis un setup multi-agents. Et un jour en prod, un run ne se termine jamais parce que :
- A attend B
- B attend C
- C attend A
Personne n’est “buggé” dans sa tête. Tout le monde attend. Deadlock.
Le problème : ça ne crashe pas. Ça hang. Et un hang consomme budget + workers.
Pourquoi ça casse en prod
1) Les cycles sont faciles à créer
“Planner → researcher → reviewer → planner”. Et voilà.
2) Pas de timeouts sur “waiting”
On met des timeouts sur HTTP, pas sur les messages entre agents.
3) Ressources partagées sans leases
Sans TTL/lease, un crash peut bloquer le système indéfiniment.
4) “Demander à un autre agent” devient une loop
Incertitude → demande → re-demande → escalade.
5) La solution est l’orchestration
Un orchestrator (ou un leader), une state machine, des timeouts, des leases, des stop reasons.
Exemple d’implémentation (code réel)
Lease lock minimal :
from dataclasses import dataclass
import time
@dataclass
class Lease:
owner: str
expires_at: float
class LeaseLock:
def __init__(self) -> None:
self._leases: dict[str, Lease] = {}
def try_acquire(self, *, resource_id: str, owner: str, ttl_s: int) -> bool:
now = time.time()
lease = self._leases.get(resource_id)
if lease and lease.expires_at > now and lease.owner != owner:
return False
self._leases[resource_id] = Lease(owner=owner, expires_at=now + ttl_s)
return True
def release(self, *, resource_id: str, owner: str) -> None:
lease = self._leases.get(resource_id)
if lease and lease.owner == owner:
del self._leases[resource_id]
def run_work(orchestrator_id: str, resource_id: str, lock: LeaseLock) -> str:
if not lock.try_acquire(resource_id=resource_id, owner=orchestrator_id, ttl_s=30):
return "blocked: lease held"
try:
return orchestrate(resource_id) # (pseudo)
finally:
lock.release(resource_id=resource_id, owner=orchestrator_id)export class LeaseLock {
constructor() {
this.leases = new Map();
}
tryAcquire({ resourceId, owner, ttlS }) {
const now = Date.now();
const lease = this.leases.get(resourceId);
if (lease && lease.expiresAtMs > now && lease.owner !== owner) return false;
this.leases.set(resourceId, { owner, expiresAtMs: now + ttlS * 1000 });
return true;
}
release({ resourceId, owner }) {
const lease = this.leases.get(resourceId);
if (lease && lease.owner === owner) this.leases.delete(resourceId);
}
}Ça évite le deadlock “lock held forever”. Ça ne fixe pas les cycles logiques : ça, il faut les éviter.
Incident réel (avec chiffres)
Flow multi-agent de tri incident :
- A collecte les signaux
- B écrit l’hypothèse
- C valide via runbook
Le tool runbook a dégradé. C attend. B attend C. A attend B.
Impact :
- 43 runs bloqués
- workers saturés, nouvelle file d’attente
- ~2h on-call à annuler et nettoyer l’état
Fix :
- timeouts sur les waits
- leases orchestrator-owned par incident_id
- stop reasons claires (“blocked waiting for tool”)
- fallback single-agent quand deps dégradées
Compromis
- L’orchestration, c’est du code.
- Les leases peuvent expirer → idempotence + replay nécessaires.
- Fallback single-agent = moins de qualité, plus de liveness.
Quand NE PAS l’utiliser
- Petites tâches : multi-agent = overhead.
- Pas d’observabilité / orchestration : ne shippe pas ça en prod.
- Besoin de consistance : workflows + state machine explicite.
Checklist (copier-coller)
- [ ] Éviter cycles (graph)
- [ ] Timeouts sur waits
- [ ] Leases/TTLs pour ressources partagées
- [ ] Orchestrator owns transitions
- [ ] Idempotence sur writes
- [ ] Stop reasons + alerting
- [ ] Mode fallback
Config par défaut sûre (JSON/YAML)
multi_agent:
orchestrator: "single_owner"
wait_timeouts_s: { default: 30 }
leases:
ttl_s: 30
renew: true
fallback:
enabled: true
mode: "single_agent"
FAQ (3–5)
Utilisé par les patterns
Pannes associées
Gouvernance requise
Q : Multi-agent = toujours mauvais ?
R : Non, mais ça ajoute coordination + modes de panne. Il faut de l’orchestration.
Q : Les leases fixent les deadlocks ?
R : Ils fixent les deadlocks de locks (crash). Ils ne fixent pas les cycles logiques.
Q : La prévention la plus simple ?
R : Un orchestrator + des timeouts sur waits. Sans timeouts, waiting devient stuck.
Q : Comment déboguer ?
R : Logs de transitions + chaîne d’attente visible. Sinon tu devines.
Pages liées (3–6 liens)
- Foundations: Agents planificateurs vs réactifs · Pourquoi les agents échouent
- Failure: Outage partielle · Tool spam loops
- Governance: Tool permissions
- Production stack: Production stack