Normal path: execute → tool → observe.
Problem (aus der Praxis)
Du baust ein Multi-Agent Setup:
- “research agent”
- “planner agent”
- “executor agent”
- “reviewer agent”
Im Diagramm sieht’s super aus.
In Production hängt ein Request für immer, weil:
- Agent A wartet auf Agent B
- Agent B wartet auf Agent C
- Agent C wartet auf Agent A
Niemand ist “falsch”. Alle warten.
Das ist ein Deadlock.
Deadlocks sind fies, weil sie nicht crashen. Sie hängen. Und Hängen verbrennt Budgets leise.
Warum das in Production bricht
Multi-Agent erbt jedes Distributed-Systems Failure Mode, plus LLM Ambiguität.
1) Circular Dependencies sind easy
“Planner fragt Researcher, Researcher fragt Reviewer, Reviewer fragt Planner.” Cycle gebaut.
2) Keine Timeouts auf “waiting”
HTTP bekommt Timeouts. Agent Messages oft nicht. Also wartet der Worker forever.
3) Shared Resources ohne Leases
Shared Ticket/Doc/Lock ohne TTL → Crash lässt System blockiert zurück.
4) “Frag einen anderen Agent” wird Retry Loop
Unsicherheit → anderer Agent → nochmal → dritter Agent. Deadlock wird Tool Spam.
5) Fix = Orchestration, nicht Prompting
Du promptest dich nicht aus Deadlocks heraus. Du brauchst:
- Orchestrator/Leader
- explizite State Machine
- Timeouts/Leases
- Stop Reason wenn kein Progress möglich
Implementierungsbeispiel (echter Code)
Minimaler Lease-Lock:
- acquire lease für
resource_id - lease expire’t bei Crash
- orchestrator kann recovern
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(); // resourceId -> { owner, expiresAtMs }
}
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);
}
}Das verhindert die schlimmste Klasse: “System stuck, weil ein Agent beim Lock gestorben ist”. Und: Timeouts auf “agent waits”. Ein Wait ohne Timeout ist Schlaf, den du bezahlst.
Echter Incident (mit Zahlen)
Multi-Agent “incident triage”:
- Agent A sammelt Signale
- Agent B schreibt Hypothese
- Agent C checkt Runbook
Runbook Tool degradete. Agent C wartete. Agent B wartete auf C. Agent A wartete auf B.
Impact:
- 43 Runs stuck in “waiting”
- Worker saturiert, neue Requests queued
- on-call verbrannte ~2 Stunden mit canceln + State cleanup
Fix:
- Timeouts auf inter-agent waits
- orchestrator-owned leases pro incident id
- stop reasons: “blocked waiting for tool” vs “blocked waiting for approval”
- fallback: single-agent mode bei degraded deps
Multi-Agent macht Coordination zu deinem Problem. Nicht zu dem des LLM.
Abwägungen
- Orchestration Code kostet Zeit. Spart Deadlocks.
- Leases können mid-work expir’n; du brauchst Idempotency + Replay.
- Single-agent fallback senkt Qualität, erhöht Liveness.
Wann du es NICHT nutzen solltest
- Kleine Tasks: Multi-Agent ist Overhead.
- Kein Orchestration+Observability? Nicht shippen.
- Strikte Order/Consistency? Workflows mit expliziten State Machines.
Checkliste (Copy/Paste)
- [ ] Cycles vermeiden (Graph aufzeichnen)
- [ ] Timeouts auf waits
- [ ] Leases/TTLs für shared resources
- [ ] Orchestrator owns state transitions
- [ ] Idempotency keys für writes
- [ ] Stop reasons + alerting
- [ ] Fallback mode bei degraded deps
Sicheres Default-Config-Snippet (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)
Von Patterns genutzt
Verwandte Failures
Q: Sind Multi-Agent Systeme immer schlecht?
A: Nein, aber sie adden Coordination + Failure Modes. Plane Orchestration ein.
Q: Fixen Leases Deadlocks?
A: Sie fixen lock-based deadlocks durch Crashes. Logische Cycles fixen sie nicht — Cycles vermeiden.
Q: Einfachste Prevention?
A: Ein Orchestrator + Timeouts auf waits. Ohne Timeouts wird “waiting” zu “stuck”.
Q: Wie debugge ich Deadlocks?
A: State transitions loggen + wait chain sichtbar machen. Ohne das rätst du.
Verwandte Seiten (3–6 Links)
- Foundations: Planning vs Reactive Agents · Warum Agents scheitern
- Failure: Partial Outage Handling · Tool-Spam Loops
- Governance: Tool Permissions
- Production stack: Production Stack