Cascading Tool Failures (Wie Agents Outages verstärken) + Code

  • Erkenne den Fehler früh, bevor die Rechnung steigt.
  • Verstehe, was in Prod bricht – und warum.
  • Guardrails kopieren: Budgets, Stop-Reasons, Validation.
  • Wissen, wann das nicht die Root Cause ist.
Erkennungs-Signale
  • Tool-Calls pro Run steigen (oder wiederholen sich mit args-hash).
  • Kosten/Tokens pro Request steigen ohne bessere Ergebnisse.
  • Retries kippen von selten zu konstant (429/5xx).
Wenn Tools degradieren, verstärken naive Retries und Agent-Loops die Outage. Circuit Breaker, Bulkheads und Safe-Mode verhindern, dass dein Agent deine Dependencies DDoS’t.
Auf dieser Seite
  1. Problem (aus der Praxis)
  2. Warum das in Production bricht
  3. 1) Naive Retries
  4. 2) Agent retry’t *und* Tool retry’t
  5. 3) Kein Circuit Breaker
  6. 4) Keine Bulkheads (Concurrency Limits)
  7. 5) Kein Safe-Mode/Fallback
  8. Implementierungsbeispiel (echter Code)
  9. Echter Incident (mit Zahlen)
  10. Abwägungen
  11. Wann du es NICHT nutzen solltest
  12. Checkliste (Copy/Paste)
  13. Sicheres Default-Config-Snippet (JSON/YAML)
  14. FAQ (3–5)
  15. Verwandte Seiten (3–6 Links)
Interaktiver Ablauf
Szenario:
Schritt 1/2: Execution

Normal path: execute → tool → observe.

Problem (aus der Praxis)

Eine Dependency wird flaky.

Dein Agent reagiert, indem er sie öfter callt.

Jetzt ist die Dependency noch flakier.

Jetzt callt der Agent sie noch öfter.

So sehen cascading failures in Agent-Systemen aus: sie verstärken.

In Production ist der Schaden nicht nur “der Agent failte”. Es ist:

  • Rate Limits für andere Services
  • Queues bauen sich auf
  • on-call kann “echte” Incidents nicht mehr von Agent Noise trennen
  • dein Agent wird ein Load Test, den niemand wollte

Warum das in Production bricht

Agents sind Loops. Loops verstärken Feedback. Das ist keine AI, das ist Control Systems.

1) Naive Retries

Retries sind nötig. Retries ohne backoff/jitter sind eine Herde.

Wenn 1,000 Runs alle gleichzeitig retry’n, hast du eine zweite Outage gebaut.

2) Agent retry’t und Tool retry’t

Typisch:

  • HTTP client retries
  • tool wrapper retries
  • agent loop “try again”

Multipliziere das → Storms.

3) Kein Circuit Breaker

Wenn ein Tool klar degraded ist, musst du für eine Cooling Period fail fast. Sonst verschlimmerst du die Outage.

4) Keine Bulkheads (Concurrency Limits)

Wenn ein Tool langsam ist, soll es nicht alles andere blockieren. Per-tool concurrency limits verhindern Worker-Saturation.

5) Kein Safe-Mode/Fallback

Manchmal ist korrekt:

  • partial results
  • stop early mit reason
  • cached/last-known-good

Agents, die “müssen succeed’n”, thrashen.

Implementierungsbeispiel (echter Code)

Mini Circuit Breaker + Bulkhead vor einem Tool:

PYTHON
from dataclasses import dataclass
import time
from typing import Callable, Any


@dataclass
class Breaker:
  fail_threshold: int = 5
  open_for_s: int = 30
  failures: int = 0
  opened_at: float | None = None

  def allow(self) -> bool:
      if self.opened_at is None:
          return True
      if time.time() - self.opened_at > self.open_for_s:
          self.failures = 0
          self.opened_at = None
          return True
      return False

  def on_success(self) -> None:
      self.failures = 0
      self.opened_at = None

  def on_failure(self) -> None:
      self.failures += 1
      if self.failures >= self.fail_threshold:
          self.opened_at = time.time()


class Bulkhead:
  def __init__(self, *, max_in_flight: int) -> None:
      self.max_in_flight = max_in_flight
      self.in_flight = 0

  def enter(self) -> None:
      if self.in_flight >= self.max_in_flight:
          raise RuntimeError("bulkhead full")
      self.in_flight += 1

  def exit(self) -> None:
      self.in_flight = max(0, self.in_flight - 1)


def guarded_tool_call(fn: Callable[..., Any], *, breaker: Breaker, bulkhead: Bulkhead, **kwargs) -> Any:
  if not breaker.allow():
      raise RuntimeError("circuit open (fail fast)")

  bulkhead.enter()
  try:
      out = fn(**kwargs)
      breaker.on_success()
      return out
  except Exception:
      breaker.on_failure()
      raise
  finally:
      bulkhead.exit()
JAVASCRIPT
export class Breaker {
constructor({ failThreshold = 5, openForS = 30 } = {}) {
  this.failThreshold = failThreshold;
  this.openForS = openForS;
  this.failures = 0;
  this.openedAt = null;
}

allow() {
  if (!this.openedAt) return true;
  const elapsedS = (Date.now() - this.openedAt) / 1000;
  if (elapsedS > this.openForS) {
    this.failures = 0;
    this.openedAt = null;
    return true;
  }
  return false;
}

onSuccess() {
  this.failures = 0;
  this.openedAt = null;
}

onFailure() {
  this.failures += 1;
  if (this.failures >= this.failThreshold) this.openedAt = Date.now();
}
}

export class Bulkhead {
constructor({ maxInFlight = 10 } = {}) {
  this.maxInFlight = maxInFlight;
  this.inFlight = 0;
}
enter() {
  if (this.inFlight >= this.maxInFlight) throw new Error("bulkhead full");
  this.inFlight += 1;
}
exit() {
  this.inFlight = Math.max(0, this.inFlight - 1);
}
}

export async function guardedToolCall(fn, { breaker, bulkhead, args }) {
if (!breaker.allow()) throw new Error("circuit open (fail fast)");
bulkhead.enter();
try {
  const out = await fn(args);
  breaker.onSuccess();
  return out;
} catch (e) {
  breaker.onFailure();
  throw e;
} finally {
  bulkhead.exit();
}
}

Das ist kein “enterprise resilience”. Es ist ein Seatbelt.

Echter Incident (mit Zahlen)

Ein Agent callte eine Vendor API für Enrichment. Vendor wurde flaky.

Wir hatten:

  • client retries (2)
  • tool retries (2)
  • agent loop “try again” (praktisch unlimited)

Impact:

  • Vendor ging von “flaky” zu “down”
  • Worker Pool saturierte
  • p95 Latenz anderer Endpoints stieg um ~3x
  • on-call brauchte ~2 Stunden, um Blast Radius zu isolieren

Fix:

  1. Circuit Breaker (30s fail fast)
  2. Bulkhead concurrency limit
  3. Retries nur an einem Ort, mit backoff+jitter
  4. Safe-mode: Enrichment skippen, partial results

Der Agent hat die erste Failure nicht ausgelöst. Er hat sie skaliert.

Abwägungen

  • Fail fast senkt momentane “success rate”, verhindert Full Outage.
  • Bulkheads rejecten Requests unter Load. Besser als globale Saturation.
  • Safe-mode ist weniger komplett. Hält Systeme am Leben.

Wann du es NICHT nutzen solltest

  • Wenn das Tool intern ist und robuste SLOs hat: evtl. weniger Breaker (Budgets trotzdem).
  • Wenn du kein Safe-mode definieren kannst: keine autonomen Loops während Outage.
  • Wenn du Vollständigkeit brauchst: async workflows statt sync loops.

Checkliste (Copy/Paste)

  • [ ] Timeouts auf jeden Tool Call
  • [ ] Retries an einem Ort (gateway), mit backoff+jitter
  • [ ] Circuit Breaker pro Tool
  • [ ] Bulkhead concurrency limits pro Tool
  • [ ] Budgets pro Run
  • [ ] Safe-mode fallback
  • [ ] Alerts: breaker open rate, tool error rates, tool latency

Sicheres Default-Config-Snippet (JSON/YAML)

YAML
tools:
  timeouts_s: { default: 10 }
  retries: { max_attempts: 2, backoff_ms: [250, 750], jitter: true }
  circuit_breaker:
    fail_threshold: 5
    open_for_s: 30
  bulkhead:
    max_in_flight: 10
safe_mode:
  enabled: true
  allow_partial: true

FAQ (3–5)

Sind Retries nicht gut?
Retries sind gut mit backoff + Caps. Unbounded Retries in Loops verstärken Outages.
Wo gehören Circuit Breaker hin?
Ins Tool Gateway, nicht in Prompts. Ein Choke Point.
Was ist Safe-mode?
Degraded Behavior: weniger Tools, read-only, cached data, partial results und klarer stop reason.
Brauche ich das für jedes Tool?
Starte mit flaky/teuren. Langfristig: jedes externe Dependency braucht Timeouts und Budgets.

Q: Sind Retries nicht gut?
A: Retries sind gut mit backoff + Caps. Unbounded Retries in Loops verstärken Outages.

Q: Wo gehören Circuit Breaker hin?
A: Ins Tool Gateway, nicht in Prompts. Ein Choke Point.

Q: Was ist Safe-mode?
A: Degraded Behavior: weniger Tools, read-only, cached data, partial results und klarer stop reason.

Q: Brauche ich das für jedes Tool?
A: Starte mit flaky/teuren. Langfristig: jedes externe Dependency braucht Timeouts und Budgets.

Nicht sicher, ob das dein Fall ist?

Agent gestalten ->
⏱️ 6 Min. LesezeitAktualisiert Mär, 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

Diese Dokumentation wird von Engineers kuratiert und gepflegt, die AI-Agenten in der Produktion betreiben.

Die Inhalte sind KI-gestützt, mit menschlicher redaktioneller Verantwortung für Genauigkeit, Klarheit und Produktionsrelevanz.

Patterns und Empfehlungen basieren auf Post-Mortems, Failure-Modes und operativen Incidents in produktiven Systemen, auch bei der Entwicklung und dem Betrieb von Governance-Infrastruktur für Agenten bei OnceOnly.