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:
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()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:
- Circuit Breaker (30s fail fast)
- Bulkhead concurrency limit
- Retries nur an einem Ort, mit backoff+jitter
- 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)
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)
Von Patterns genutzt
Verwandte Failures
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.
Verwandte Seiten (3–6 Links)
- Foundations: Wie Agents Tools nutzen · Production-ready Agent
- Failure: Partial Outage Handling · Tool-Spam Loops
- Governance: Tool Permissions
- Production stack: Production Stack