Das Problem
Die Anfrage wirkt normal: ein Kundenprofil sammeln und eine kurze Antwort vorbereiten.
In Traces sieht man etwas anderes: ein externer tool begann timeout zurückzugeben,
der Agent wechselte in retries, überlastete nach 4 Minuten den worker pool,
und nach weiteren 7 Minuten degradierten bereits nicht verwandte workflows und services.
Der erste Ausfall war lokal. Durch den Agent-Loop wurde er aber systemisch.
Das System fällt nicht sofort aus.
Es zieht nach und nach immer mehr Abhängigkeiten mit nach unten.
Analogie: Stell dir Stau auf einer Spur einer Brücke vor. Zuerst bremst nur eine Spur. Dann erreicht die Stauwelle alle Zufahrten zur Brücke. Ein kaskadierender Ausfall beim Agent funktioniert genauso: ein lokales Problem ohne Grenzen wird schnell zum gemeinsamen Systemproblem.
Warum das passiert
Ein kaskadierender Ausfall entsteht nicht wegen einer einzelnen "schlechten" Tool-Antwort, sondern weil sich der Fehler über mehrere Schichten gleichzeitig verstärkt.
In production sieht das typischerweise so aus:
- ein
tooldegradiert (5xx,429,timeout); - retries starten gleichzeitig an mehreren Stellen (SDK, gateway, Agent);
- die queue wächst, worker blockieren im Warten;
- latency steigt auch für andere runs, selbst ohne diesen
tool; - ohne fail-fast und safe-mode vervielfacht das System weiter Aufrufe.
Das Problem ist nicht nur ein instabiler Service. Runtime stoppt die Welle nicht, solange sie noch lokal ist.
Welche Fehlermuster am häufigsten auftreten
In production sieht man am häufigsten vier Muster von cascading failures.
Retry-Verstärkung zwischen Schichten (Retry amplification)
Ein Ausfall wird im HTTP-Client, im tool gateway und im reasoning loop des Agenten wiederholt.
Die Anzahl der Aufrufe wächst geometrisch.
Mini-Beispiel: 1 failure -> 3 retries im SDK -> 3 retries im gateway -> 3 retries im agent loop = 27 Aufrufe.
Typische Ursache: retry policy ist auf mehrere Stellen verteilt.
Sättigung eines gemeinsamen Pools (Shared pool saturation)
Ein degradierter tool belegt den Großteil der worker.
Andere runs warten in der queue, obwohl ihre Abhängigkeiten gesund sind.
Typische Ursache: keine per-tool bulkhead limits.
Timeout-Domino in benachbarten Services (Timeout domino)
Wenn die queue wächst, steigt auch wait time.
Dadurch laufen upstream/downstream services häufiger in timeout.
Typische Ursache: kein hartes max_seconds und kein fail-fast bei degradierten Abhängigkeiten.
Kostenkaskade auf technischen Ausfall (Cost cascade)
Der Kaskadeneffekt erhöht auch die Run-Kosten: mehr retries, mehr Tokens, längerer Run-Lebenszyklus. Sogar "erfolgreiche" Abschlüsse werden zu teuer.
Typische Ursache: fehlende execution budgets (max_tool_calls, max_retries, max_usd).
Wie man diese Probleme erkennt
Kaskadierende Ausfälle sind am besten über die Kombination aus gateway-, runtime- und queue-Metriken sichtbar.
| Metrik | Signal für cascading failure | Was tun |
|---|---|---|
retry_amplification_rate | ein Ausfall erzeugt viele doppelte retries | retries in einem gateway zentralisieren |
circuit_open_rate | breaker öffnet häufig bei einem tool | safe-mode aktivieren und fan-out senken |
queue_backlog | queue wächst trotz normalem Eingangstraffic | bulkhead limits und run-timeout einführen |
cross_service_timeout_rate | timeouts erscheinen in nicht verwandten Services | degradierten tool isolieren und Konkurrenz begrenzen |
cascading_stop_reason_rate | häufige cascade:* stop reasons | breaker/bulkhead und fallback-Strategie prüfen |
Wie man kaskadierenden Ausfall von lokalem Tool-Fehler unterscheidet
Nicht jeder tool_timeout bedeutet cascade.
Die Kernfrage: bleibt der Ausfall lokal oder beeinflusst er bereits andere Teile des Systems.
Normal ist, wenn:
- der Ausfall auf ein Tool isoliert bleibt;
- queue und latency anderer runs stabil bleiben;
- das System nach kurzem cooldown auf baseline zurückkehrt.
Gefährlich ist, wenn:
- ein
tool-Fehler globalenqueue_backlogerhöht; - timeouts in nicht verwandten workflows auftreten;
- Kosten und Laufzeit von runs steigen, auch dort, wo dieser
toolnicht verwendet wird.
Wie man solche Ausfälle stoppt
Praktisch bedeutet das:
- retries nur an einem choke point halten (tool gateway);
- per-tool circuit breaker + cooldown + bulkhead limits setzen;
- execution budgets für retries, tool calls, Zeit und Kosten definieren;
- bei Degradation run in safe-mode (partial/fallback) schalten, statt "weiter drücken".
Minimaler Guard gegen cascade:
from dataclasses import dataclass
import time
RETRYABLE = {408, 429, 500, 502, 503, 504}
@dataclass(frozen=True)
class CascadeLimits:
max_steps: int = 25
max_seconds: int = 90
max_tool_calls: int = 18
max_retries: int = 4
max_in_flight_per_tool: int = 8
open_circuit_after: int = 3
circuit_cooldown_s: int = 30
class CascadeGuard:
def __init__(self, limits: CascadeLimits = CascadeLimits()):
self.limits = limits
self.steps = 0
self.tool_calls = 0
self.retries = 0
self.in_flight: dict[str, int] = {}
self.fail_streak: dict[str, int] = {}
self.circuit_open_until: dict[str, float] = {}
self.started_at = time.time()
def on_step(self) -> str | None:
self.steps += 1
if self.steps > self.limits.max_steps:
return "cascade:budget_max_steps"
if (time.time() - self.started_at) > self.limits.max_seconds:
return "cascade:budget_timeout"
return None
def before_tool_call(self, tool: str) -> str | None:
if time.time() < self.circuit_open_until.get(tool, 0.0):
return "cascade:circuit_open"
current = self.in_flight.get(tool, 0)
if current >= self.limits.max_in_flight_per_tool:
return "cascade:bulkhead_full"
self.tool_calls += 1
if self.tool_calls > self.limits.max_tool_calls:
return "cascade:budget_tool_calls"
self.in_flight[tool] = current + 1
return None
def after_tool_call(self, tool: str, status_code: int) -> str | None:
self.in_flight[tool] = max(0, self.in_flight.get(tool, 1) - 1)
if status_code in RETRYABLE:
self.retries += 1
if self.retries > self.limits.max_retries:
return "cascade:retry_budget"
streak = self.fail_streak.get(tool, 0) + 1
self.fail_streak[tool] = streak
if streak >= self.limits.open_circuit_after:
self.circuit_open_until[tool] = time.time() + self.limits.circuit_cooldown_s
return "cascade:circuit_open"
return "cascade:retry_allowed"
self.fail_streak[tool] = 0
return None
Das ist ein Basis-Guard.
In dieser Version zählt tool_calls Aufrufversuche, nicht nur erfolgreich zugelassene Aufrufe.
In production wird das meist um Request-Priorisierung,
separate Limits für kritische tool und eine explizite safe-mode-Route erweitert.
before_tool_call(...) wird vor dem externen Aufruf ausgeführt,
after_tool_call(...) direkt nach der Antwort, damit cascade möglichst früh gestoppt wird.
Wo das in der Architektur umgesetzt wird
In production wird die Kontrolle von cascading failures meistens auf drei Systemschichten verteilt.
Tool Execution Layer ist die erste Barriere: retry policy, circuit breaker, bulkhead, timeout und Fehlernormalisierung. Wenn diese Schicht schwach ist, wird lokaler Ausfall schnell zur Welle.
Agent Runtime steuert Budgets,
stop reasons (cascade:*) und safe-mode-Übergänge.
Hier muss ein run vor der Systemsättigung gestoppt werden.
Orchestration Topologies definiert, wie degradierte workflow-Zweige isoliert werden, damit ein degradierter Pfad nicht den ganzen workflow blockiert.
Selbstcheck
Schneller Check vor dem Release. Hake die Punkte ab und sieh dir den Status unten an.
Das ist ein kurzer Sanity-Check, kein formales Audit.
Fortschritt: 0/8
⚠ Es gibt Risikosignale
Grundlegende Kontrollen fehlen. Schließen Sie die wichtigsten Checklist-Punkte vor dem Release.
FAQ
Q: Retries sind doch hilfreich. Warum können sie das System beschädigen?
A: Hilfreich nur mit backoff, caps und einer zentralen Steuerstelle. Wenn retries über Schichten dupliziert werden, steigt die Last schneller als die Erholung.
Q: Warum sind Agent-Systeme anfälliger für cascade als normale APIs?
A: Weil der Agent einen reasoning loop hat und denselben tool_call mehrfach wiederholen kann. Der Abhängigkeitsausfall verstärkt sich mit jedem Run-Schritt.
Q: Timeout reicht nicht? Warum zusätzlich breaker und bulkhead?
A: Timeout begrenzt nur einen Aufruf. Breaker stoppt die Wiederholungswelle, und bulkhead verhindert, dass ein tool alle worker nimmt.
Q: Verschlechtert safe-mode nicht die Antwortqualität?
A: Teilweise ja, aber es ist kontrollierte Degradation. Besser ein korrekter Teiloutput als ein kompletter outage.
Cascading failure sieht fast nie wie ein einzelner großer Fehler aus. Meist ist es eine Kette kleiner Ausfälle, die das System selbst verstärkt. Kernprinzip: agent loops verstärken Ausfälle (agents amplify failures). Darum brauchen production-Agents nicht nur starke Modelle, sondern harte Grenzen auf runtime- und gateway-Ebene.
Verwandte Seiten
Wenn dieses Problem in production auftritt, helfen auch diese Seiten:
- Warum AI-Agenten scheitern - allgemeine Karte von Ausfällen in production.
- Tool failure - wie lokaler Tool-Ausfall in cascade übergeht.
- Tool spam - wie wiederholte Aufrufe Degradation beschleunigen.
- Budget explosion - wie cascade zum finanziellen Incident wird.
- Partial outage - wie man bei teilweiser Degradation von Abhängigkeiten arbeitet.
- Agent Runtime - wo Budgets, stop reasons und safe-mode implementiert werden.
- Tool Execution Layer - wo retries, breaker und bulkhead hingehören.