Kaskadierende Fehler: Wenn ein Agent-Fehler sich ausbreitet

Kaskadierende Fehler entstehen, wenn ein einzelner Tool-, Service- oder Agent-Fehler weitere Ausfälle nach sich zieht. Warum das in Agent-Systemen passiert.
Auf dieser Seite
  1. Das Problem
  2. Warum das passiert
  3. Welche Fehlermuster am häufigsten auftreten
  4. Retry-Verstärkung zwischen Schichten (Retry amplification)
  5. Sättigung eines gemeinsamen Pools (Shared pool saturation)
  6. Timeout-Domino in benachbarten Services (Timeout domino)
  7. Kostenkaskade auf technischen Ausfall (Cost cascade)
  8. Wie man diese Probleme erkennt
  9. Wie man kaskadierenden Ausfall von lokalem Tool-Fehler unterscheidet
  10. Wie man solche Ausfälle stoppt
  11. Wo das in der Architektur umgesetzt wird
  12. Selbstcheck
  13. FAQ
  14. Verwandte Seiten

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:

  1. ein tool degradiert (5xx, 429, timeout);
  2. retries starten gleichzeitig an mehreren Stellen (SDK, gateway, Agent);
  3. die queue wächst, worker blockieren im Warten;
  4. latency steigt auch für andere runs, selbst ohne diesen tool;
  5. 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.

MetrikSignal für cascading failureWas tun
retry_amplification_rateein Ausfall erzeugt viele doppelte retriesretries in einem gateway zentralisieren
circuit_open_ratebreaker öffnet häufig bei einem toolsafe-mode aktivieren und fan-out senken
queue_backlogqueue wächst trotz normalem Eingangstrafficbulkhead limits und run-timeout einführen
cross_service_timeout_ratetimeouts erscheinen in nicht verwandten Servicesdegradierten tool isolieren und Konkurrenz begrenzen
cascading_stop_reason_ratehäufige cascade:* stop reasonsbreaker/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 globalen queue_backlog erhöht;
  • timeouts in nicht verwandten workflows auftreten;
  • Kosten und Laufzeit von runs steigen, auch dort, wo dieser tool nicht verwendet wird.

Wie man solche Ausfälle stoppt

Praktisch bedeutet das:

  1. retries nur an einem choke point halten (tool gateway);
  2. per-tool circuit breaker + cooldown + bulkhead limits setzen;
  3. execution budgets für retries, tool calls, Zeit und Kosten definieren;
  4. bei Degradation run in safe-mode (partial/fallback) schalten, statt "weiter drücken".

Minimaler Guard gegen cascade:

PYTHON
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:

⏱️ 7 Min. LesezeitAktualisiert 12. März 2026Schwierigkeit: ★★☆
In OnceOnly umsetzen
Guardrails for loops, retries, and spend escalation.
In OnceOnly nutzen
# onceonly guardrails (concept)
version: 1
budgets:
  max_steps: 25
  max_tool_calls: 12
  max_seconds: 60
  max_usd: 1.00
policy:
  tool_allowlist:
    - search.read
    - http.get
controls:
  loop_detection:
    enabled: true
    dedupe_by: [tool, args_hash]
  retries:
    max: 2
    backoff_ms: [200, 800]
stop_reasons:
  enabled: true
logging:
  tool_calls: { enabled: true, store_args: false, store_args_hash: true }
Integriert: Production ControlOnceOnly
Guardrails für Tool-Calling-Agents
Shippe dieses Pattern mit Governance:
  • Budgets (Steps / Spend Caps)
  • Kill switch & Incident Stop
  • Audit logs & Nachvollziehbarkeit
  • Idempotenz & Dedupe
  • Tool-Permissions (Allowlist / Blocklist)
Integrierter Hinweis: OnceOnly ist eine Control-Layer für Production-Agent-Systeme.
Beispiel-Policy (Konzept)
# Example (Python — conceptual)
policy = {
  "budgets": {"steps": 20, "seconds": 60, "usd": 1.0},
  "controls": {"kill_switch": True, "audit": True},
}

Autor

Nick — Engineer, der Infrastruktur für KI-Agenten in Produktion aufbaut.

Fokus: Agent-Patterns, Failure-Modes, Runtime-Steuerung und Systemzuverlässigkeit.

🔗 GitHub: https://github.com/mykolademyanov


Redaktioneller Hinweis

Diese Dokumentation ist KI-gestützt, mit menschlicher redaktioneller Verantwortung für Genauigkeit, Klarheit und Produktionsrelevanz.

Der Inhalt basiert auf realen Ausfällen, Post-Mortems und operativen Vorfällen in produktiv eingesetzten KI-Agenten-Systemen.