Fallos en cascada de tools (cómo un agente amplifica outages) + código

  • Detecta el fallo temprano antes de que suba el gasto.
  • Entiende qué se rompe en producción y por qué.
  • Copia guardrails: budgets, stop reasons, validación.
  • Sabe cuándo esto no es la causa raíz.
Señales de detección
  • Tool calls por run suben (o repiten mismo args hash).
  • Gasto/tokens suben sin mejorar el resultado.
  • Retries pasan de raros a constantes (429/5xx).
Cuando los tools degradan, retries ingenuos y loops amplifican outages. Usa circuit breakers, bulkheads y safe-mode para que el agente no DDoSee tus dependencias.
En esta página
  1. El problema (en producción)
  2. Por qué esto se rompe en producción
  3. 1) Retries ingenuos
  4. 2) El agente retría *y* el tool retría
  5. 3) No hay circuit breaker
  6. 4) No hay bulkheads (límites de concurrencia)
  7. 5) No hay safe-mode / fallback
  8. Ejemplo de implementación (código real)
  9. Incidente real (con números)
  10. Trade-offs
  11. Cuándo NO usarlo
  12. Checklist (copiar/pegar)
  13. Config segura por defecto (JSON/YAML)
  14. FAQ (3–5)
  15. Páginas relacionadas (3–6 links)
Flujo interactivo
Escenario:
Paso 1/2: Execution

Normal path: execute → tool → observe.

El problema (en producción)

Una dependencia se vuelve flaky.

Tu agente reacciona llamándola más.

Ahora la dependencia está más flaky.

Ahora el agente la llama todavía más.

Ese es el cuento completo de fallos en cascada en sistemas con agentes: amplifican.

En producción el daño no es solo “el agente falló”. Es:

  • saltan rate limits en servicios no relacionados
  • se atascan colas
  • on-call pierde la capacidad de distinguir incidentes reales de “ruido del agente”
  • y tu agente se convierte en un load test que nadie pidió

Por qué esto se rompe en producción

Los agentes son loops. Los loops amplifican feedback. Eso no es “AI”. Eso es control de sistemas.

1) Retries ingenuos

Retries son necesarios. Retries sin backoff/jitter son una estampida.

Si 1.000 runs retrían a la vez, acabas de crear un segundo outage.

2) El agente retría y el tool retría

Es común tener:

  • retries en el cliente HTTP
  • retries en el wrapper del tool
  • comportamiento del loop: “intenta otra vez”

Multiplica eso y obtienes tormentas.

3) No hay circuit breaker

Cuando un tool está claramente degradado (timeouts, 5xx), necesitas dejar de llamarlo por un rato. Sin breaker, sigues pegándole a una dependencia caida y la empeoras.

4) No hay bulkheads (límites de concurrencia)

Si un tool va lento, no quieres que se coma todos los workers. Límites por tool evitan que una dependencia consuma todo.

5) No hay safe-mode / fallback

A veces el comportamiento correcto es:

  • devolver resultados parciales
  • parar temprano con razón clara
  • cambiar a cache / last-known-good

Los agentes que “tienen que triunfar” tienden a thrash.

Ejemplo de implementación (código real)

Un patrón pequeño de circuit breaker + bulkhead que puedes poner delante de un 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:
          # half-open: reset and try again
          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();
}
}

Esto no es “resiliencia enterprise”. Es un cinturón de seguridad. Sin eso, los agentes convierten dependencias flaky en incidentes del sistema entero.

Incidente real (con números)

Teníamos un agente que llamaba un API de vendor para enrichment. El vendor empezó a timeoutear de forma intermitente.

Nuestro sistema tenía:

  • retries de cliente (2)
  • retries del wrapper del tool (2)
  • el loop del agente “intenta otra vez” (prácticamente ilimitado)

Impacto:

  • el API del vendor pasó de “flaky” a “down”
  • nuestro pool de workers se saturó
  • p95 latencia en endpoints no relacionados subió ~3x
  • on-call gastó ~2 horas aislando el blast radius

Fix:

  1. circuit breaker (fail fast 30s tras umbral)
  2. bulkhead de concurrencia por tool
  3. retries en un solo sitio, con backoff + jitter
  4. safe-mode: saltar enrichment y devolver resultado parcial

El agente no causó el fallo inicial. Lo escaló.

Trade-offs

  • Failing fast baja el “success rate” durante outages parciales. Previene outages completos.
  • Bulkheads pueden rechazar requests bajo carga. Mejor que saturación global.
  • Safe-mode devuelve respuestas menos completas. Mantiene el sistema vivo.

Cuándo NO usarlo

  • Si el tool es interno y ya tiene SLOs robustos, quizá no necesitas breakers por tool (igual mantén budgets).
  • Si no puedes definir safe-mode, no corras loops autónomos durante outages.
  • Si necesitas completitud estricta, usa workflows async, no agentes sincrónicos.

Checklist (copiar/pegar)

  • [ ] Timeouts en cada tool call
  • [ ] Retries en un solo sitio (gateway), con backoff + jitter
  • [ ] Circuit breaker por tool (fail fast)
  • [ ] Bulkhead de concurrencia por tool
  • [ ] Budgets por run (tiempo/tool calls/gasto)
  • [ ] Safe-mode (resultados parciales)
  • [ ] Alertas: breaker open rate, error rates, latencia por tool

Config segura por defecto (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)

¿Los retries no son buenos?
Son buenos con backoff y límites. Retries sin límite en loops es cómo amplificas outages.
¿Dónde van los circuit breakers?
En el tool gateway, no en prompts. Un choke point.
¿Qué es safe-mode?
Comportamiento degradado: menos tools, read-only, cache, resultados parciales y stop reason claro.
¿Necesito esto para cada tool?
Empieza por los externos/flaky/caros. Con el tiempo, sí: toda dependencia externa necesita timeouts y budgets.

Q: ¿Los retries no son buenos?
A: Son buenos con backoff y límites. Retries sin límite en loops es cómo amplificas outages.

Q: ¿Dónde van los circuit breakers?
A: En el tool gateway, no en prompts. Un choke point.

Q: ¿Qué es safe-mode?
A: Comportamiento degradado: menos tools, read-only, cache, resultados parciales y stop reason claro.

Q: ¿Necesito esto para cada tool?
A: Empieza por los externos/flaky/caros. Con el tiempo, sí: toda dependencia externa necesita timeouts y budgets.

No sabes si este es tu caso?

Disena tu agente ->
⏱️ 7 min de lecturaActualizado Mar, 2026Dificultad: ★★☆
Implementar en OnceOnly
Guardrails for loops, retries, and spend escalation.
Usar en OnceOnly
# 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 }
Integrado: control en producciónOnceOnly
Guardrails para agentes con tool-calling
Lleva este patrón a producción con gobernanza:
  • Presupuestos (pasos / topes de gasto)
  • Kill switch y parada por incidente
  • Audit logs y trazabilidad
  • Idempotencia y dedupe
  • Permisos de herramientas (allowlist / blocklist)
Mención integrada: OnceOnly es una capa de control para sistemas de agentes en producción.
Ejemplo de policy (concepto)
# Example (Python — conceptual)
policy = {
  "budgets": {"steps": 20, "seconds": 60, "usd": 1.0},
  "controls": {"kill_switch": True, "audit": True},
}
Autor

Esta documentación está curada y mantenida por ingenieros que despliegan agentes de IA en producción.

El contenido es asistido por IA, con responsabilidad editorial humana sobre la exactitud, la claridad y la relevancia en producción.

Los patrones y las recomendaciones se basan en post-mortems, modos de fallo e incidentes operativos en sistemas desplegados, incluido durante el desarrollo y la operación de infraestructura de gobernanza para agentes en OnceOnly.