El problema
La solicitud parece normal: construir un perfil de cliente y preparar una respuesta breve.
En trazas se ve otra cosa: un tool externo empezó a devolver timeout,
el agente pasó a retries, sobrecargó el worker pool en 4 minutos,
y 7 minutos después ya degradaban workflows y servicios no relacionados.
El fallo inicial era local. Pero, a través del loop del agente, se volvió sistémico.
El sistema no cae de inmediato.
Poco a poco arrastra cada vez más dependencias.
Analogía: imagina atasco en un solo carril de un puente. Al principio, se frena solo ese carril. Luego la ola de frenado llega a todos los accesos del puente. Un fallo en cascada en un agente funciona igual: un problema local sin límites se convierte rápido en problema compartido del sistema.
Por qué pasa
Un fallo en cascada no aparece por una sola respuesta "mala" del tool, sino porque el error se amplifica en varias capas al mismo tiempo.
En producción, normalmente pasa así:
- un
toolse degrada (5xx,429,timeout); - retries arrancan en varios puntos a la vez (SDK, gateway, agente);
- la queue crece y los workers quedan bloqueados esperando;
- sube la latency también en otros runs, incluso sin ese
tool; - sin fail-fast y safe-mode, el sistema sigue multiplicando llamadas.
El problema no es solo un servicio inestable. Runtime no detiene la ola mientras todavía es local.
Fallos más frecuentes
En producción se ven cuatro patrones de cascading failures con más frecuencia.
Amplificación de retries entre capas (Retry amplification)
Un fallo se repite en cliente HTTP, tool gateway y loop de reasoning del agente.
El número de llamadas crece de forma geométrica.
Mini ejemplo: 1 failure -> 3 retries en SDK -> 3 retries en gateway -> 3 retries en agent loop = 27 llamadas.
Causa típica: retry policy repartida en varios sitios.
Saturación de pool compartido (Shared pool saturation)
Un tool degradado ocupa la mayoría de workers.
Otros runs quedan en queue aunque sus dependencias estén sanas.
Causa típica: no hay per-tool bulkhead limits.
Dominó de timeouts en servicios cercanos (Timeout domino)
Cuando crece la queue, aumenta wait time.
Por eso, servicios upstream/downstream entran más en timeout.
Causa típica: no hay max_seconds rígido ni fail-fast ante degradación de dependencia.
Cascada de costos sobre fallo técnico (Cost cascade)
La cascada también aumenta costo del run: más retries, más tokens, ciclo más largo. Hasta los finales "exitosos" salen demasiado caros.
Causa típica: faltan execution budgets (max_tool_calls, max_retries, max_usd).
Cómo detectar estos problemas
Los fallos en cascada se detectan mejor combinando métricas de gateway, runtime y queue.
| Métrica | Señal de cascading failure | Qué hacer |
|---|---|---|
retry_amplification_rate | un fallo produce muchos retries duplicados | centralizar retries en un solo gateway |
circuit_open_rate | breaker abre con frecuencia en un tool | activar safe-mode y bajar fan-out |
queue_backlog | la queue crece con tráfico normal de entrada | introducir bulkhead limits y timeout de run |
cross_service_timeout_rate | timeouts aparecen en servicios no relacionados | aislar tool degradado y limitar concurrencia |
cascading_stop_reason_rate | cascade:* stop reasons frecuentes | revisar breaker/bulkhead y estrategia fallback |
Cómo distinguir fallo en cascada de error local del tool
No todo tool_timeout significa cascade.
La pregunta clave: el fallo queda local o ya afecta otras partes del sistema.
Normal si:
- el fallo queda aislado en un solo tool;
- queue y latency de otros runs se mantienen estables;
- tras un cooldown corto, el sistema vuelve a baseline.
Peligroso si:
- error de un
toolsubequeue_backlogglobalmente; - aparecen timeouts en workflows no relacionados;
- costo y duración de runs suben incluso donde ese
toolno se usa.
Cómo frenar estos fallos
En la práctica:
- mantener retries en un solo choke point (tool gateway);
- poner per-tool circuit breaker + cooldown + bulkhead limits;
- definir execution budgets para retries, tool calls, tiempo y costo;
- con degradación, cambiar el run a safe-mode (partial/fallback), no "seguir empujando".
Guard mínimo contra 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
Este es un guard base.
En esta versión, tool_calls cuenta intentos de llamada, no solo llamadas admitidas con éxito.
En producción suele ampliarse con priorización de requests,
límites separados para tool críticos y ruta safe-mode explícita.
before_tool_call(...) se llama antes de la llamada externa,
y after_tool_call(...) justo después de la respuesta para apagar la cascade lo antes posible.
Dónde se implementa en la arquitectura
En producción, el control de cascading failures suele repartirse entre tres capas del sistema.
Tool Execution Layer es la primera barrera: retry policy, circuit breaker, bulkhead, timeout y normalización de errores. Si esta capa es débil, un fallo local se vuelve ola rápidamente.
Agent Runtime gestiona budgets,
stop reasons (cascade:*) y transiciones de safe-mode.
Aquí hay que frenar el run antes de saturación sistémica.
Orchestration Topologies define cómo aislar ramas de workflow degradadas y evitar que un camino degradado bloquee todo el workflow.
Autoevaluación
Verificación rápida antes del release. Marca los puntos y mira el estado abajo.
Este es un sanity-check corto, no una auditoría formal.
Progreso: 0/8
⚠ Hay señales de riesgo
Faltan controles básicos. Cierra los puntos clave del checklist antes del release.
FAQ
Q: Los retries son útiles. ¿Por qué pueden romper el sistema?
A: Útiles solo con backoff, caps y un punto único de control. Si se duplican entre capas, multiplican carga más rápido que la recuperación.
Q: ¿Por qué sistemas de agentes son más propensos a cascade que APIs normales?
A: Porque el agente tiene reasoning loop y puede repetir el mismo tool_call muchas veces. El fallo de dependencia se multiplica en cada paso del run.
Q: ¿Timeout no alcanza? ¿Para qué breaker y bulkhead también?
A: Timeout solo limita una llamada. Breaker detiene la ola de repeticiones, y bulkhead evita que un tool tome todos los workers.
Q: ¿Safe-mode no baja la calidad de respuesta?
A: Parcialmente sí, pero es degradación controlada. Mejor devolver resultado parcial correcto que esperar outage completo.
Cascading failure casi nunca parece un único gran error. Más bien es una cadena de fallos pequeños que el sistema amplifica por sí mismo. Principio clave: agent loops amplify failures (los agentes amplifican fallos). Por eso los agentes de producción necesitan no solo buenos modelos, sino límites estrictos a nivel runtime y gateway.
Páginas relacionadas
Si este problema aparece en producción, también conviene revisar:
- Por qué fallan los agentes de IA - mapa general de fallos en producción.
- Tool failure - cómo un fallo local de tool pasa a cascade.
- Tool spam - cómo llamadas repetidas aceleran la degradación.
- Budget explosion - cómo cascade se convierte en incidente financiero.
- Partial outage - cómo operar con degradación parcial de dependencias.
- Agent Runtime - dónde implementar budgets, stop reasons y safe-mode.
- Tool Execution Layer - dónde mantener retries, breaker y bulkhead.