El problema
La solicitud parece simple: verificar estado de pago y dar una respuesta breve al cliente.
En trazas se ve otra cosa: en 11 minutos un run hizo 33 llamadas,
10 devolvieron 200, 14 devolvieron timeout, y 9 devolvieron 502/503.
Para una tarea de esta clase puede ser alrededor de ~$1.90 en lugar de ~$0.14 habitual.
El servicio está formalmente "vivo": parte de las llamadas funciona, no hay caída total. Pero la cola de runs crece, la latency salta y los usuarios reciben resultados inestables.
El sistema no cae.
Solo se atasca lentamente entre éxitos raros y fallos repetidos.
Analogía: imagina una caja donde el terminal a veces acepta tarjeta y a veces se cuelga. La tienda no está cerrada, pero la fila crece cada minuto. Una caída parcial en sistemas de agentes funciona igual: la infraestructura parece disponible, pero ya no existe un camino estable hacia la respuesta.
Por qué pasa
En producción normalmente sucede así:
- una dependencia empieza a comportarse de forma inestable (
timeout,5xx, a veces200); - retries arrancan en varias capas a la vez;
- el run retiene workers más tiempo, la cola crece;
- otros workflows también se ralentizan por recursos compartidos;
- sin fail-fast y safe-mode, el sistema multiplica costo en vez de aislar el fallo.
En trace esto aparece como patrón mixto: tool_2xx_rate todavía existe,
pero al mismo tiempo suben timeout_rate, retry_attempts_per_run y queue_backlog.
El problema no es un solo timeout.
Runtime no pasa la dependencia inestable a degraded mode mientras el fallo todavía es local.
Fallos más frecuentes
En producción se ven cuatro patrones de partial outage con más frecuencia.
Trampa de éxito intermitente (Intermittent success trap)
El tool a veces devuelve 200, y eso enmascara la degradación.
El agente sigue insistiendo por el mismo canal en vez de cambiar de forma controlada.
Causa típica: no hay umbral de "salud" de dependencia a nivel run.
Retry amplification entre capas
Cliente HTTP, gateway y runtime ejecutan retries por separado. Incluso un pico pequeño de errores se vuelve rápido una ola de llamadas innecesarias.
Causa típica: retry policy no centralizada.
La cola se bloquea por runs "ruidosos" (Queue starvation)
Los runs problemáticos quedan colgados mucho tiempo, ocupan worker pool y desplazan tareas sanas.
Causa típica: faltan límites de duración de run y budget-gates.
Esperar respuesta "perfecta" sin degrade path
El sistema intenta esperar un resultado "ideal", aunque la dependencia está claramente degradada.
Causa típica: no existe contrato partial/fallback para usuario.
Cómo detectar estos problemas
La caída parcial se ve bien con combinación de métricas de health, runtime y queue.
| Métrica | Señal de partial outage | Qué hacer |
|---|---|---|
degraded_dependency_rate | una dependencia da timeout/5xx con frecuencia | activar degraded mode y reducir fan-out |
tool_2xx_with_high_timeout_rate | aparecen juntos 200 y alta tasa de timeout | agregar umbral de health, no mirar solo 200 |
retry_attempts_per_run | demasiados reintentos para un run | centralizar retries y limitar retry budget |
run_duration_p95 | runs largos "colgados" | introducir fail-fast timeout y stop reasons |
queue_backlog | la cola crece con tráfico normal | aislar ruta degradada y activar fallback |
Cómo distinguir partial outage de full outage
No toda degradación es caída total. La pregunta clave: existe camino estable de ejecución o solo "éxitos" aleatorios.
Normal para full outage si:
- casi todas las llamadas fallan de forma uniforme (
5xxo indisponibilidad total); - el sistema pasa rápido a fail-fast;
- no existe ilusión de "a veces funciona".
Peligroso para partial outage si:
- en el mismo run se mezclan
200,timeouty5xx; - el agente repite llamadas porque ve éxitos raros;
- queue/latency suben incluso sin global incident claro.
Cómo frenar estos fallos
En práctica:
- fijar health snapshot de dependencias al inicio del run;
- al romper umbral, pasar workflow a degraded mode de inmediato;
- mantener retries en un solo tool gateway con budget estricto;
- devolver partial/fallback y stop reason explícito en vez de esperar "eternamente".
Guard mínimo para partial outage:
from dataclasses import dataclass
import time
RETRYABLE = {408, 429, 500, 502, 503, 504}
@dataclass(frozen=True)
class OutageLimits:
max_retry_per_call: int = 2
max_retry_total: int = 6
max_run_seconds: int = 45
max_tool_calls: int = 14
degraded_error_threshold: float = 0.35
min_sample_size: int = 5
class PartialOutageGuard:
def __init__(self, limits: OutageLimits = OutageLimits()):
self.limits = limits
self.started_at = time.time()
self.tool_calls = 0
self.retry_count = 0
self.total_calls = 0
self.error_calls = 0
def before_tool_call(self) -> str | None:
self.tool_calls += 1
if self.tool_calls > self.limits.max_tool_calls:
return "partial_outage:tool_call_budget"
if (time.time() - self.started_at) > self.limits.max_run_seconds:
return "partial_outage:run_timeout"
return None
def on_tool_result(self, status_code: int, attempt: int) -> str | None:
self.total_calls += 1
if status_code in RETRYABLE:
self.error_calls += 1
error_rate = self.error_calls / max(1, self.total_calls)
if (
self.total_calls >= self.limits.min_sample_size
and error_rate >= self.limits.degraded_error_threshold
):
return "partial_outage:degraded_mode"
self.retry_count += 1
if self.retry_count > self.limits.max_retry_total:
return "partial_outage:retry_budget"
if attempt >= self.limits.max_retry_per_call:
return "partial_outage:retry_exhausted"
return "partial_outage:retry_allowed"
error_rate = self.error_calls / max(1, self.total_calls)
if (
self.total_calls >= self.limits.min_sample_size
and error_rate >= self.limits.degraded_error_threshold
):
return "partial_outage:degraded_mode"
return None
En esta versión, retry_count cuenta todas las respuestas retryable dentro del run,
y attempt es la cantidad de reintentos de una llamada concreta.
Este es un guard base.
En producción normalmente se amplía con per-tool health probes,
circuit breaker y ruta safe-mode separada para runs degradados.
before_tool_call(...) y on_tool_result(...) se llaman en tool gateway,
para que la decisión de degradación sea centralizada y no por capa.
Dónde se implementa en la arquitectura
En producción, el control de partial outage casi siempre se reparte entre tres capas del sistema.
Tool Execution Layer aporta health signals:
error rate, timeout patterns, retry budget y circuit breaker.
Ahí se ve que una dependencia ya está inestable, incluso si parte de llamadas aún devuelve 200.
Agent Runtime toma decisiones por run: paso a degraded mode, stop reasons y cierre controlado con fallback. Sin esta capa, el sistema sigue esperando "una llamada exitosa más".
Orchestration Topologies define cómo aislar workflow degradado del resto del sistema (bulkheads, colas, prioridades). Esto evita que degradación local se vuelva incidente compartido.
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: ¿Por qué partial outage suele ser peor que full outage?
A: Porque se enmascara como "a veces funciona".
El sistema no se detiene y sigue quemando tiempo, tokens y worker pool.
Q: ¿Hay que desactivar de inmediato el tool degradado?
A: No siempre. Lo normal es activar degraded mode: limitar retries, bajar fan-out y pasar a ruta partial/fallback.
Q: ¿Dónde conviene decidir retries y degradación?
A: En un solo tool gateway. Si no, cada capa hace sus retries y partial outage escala rápido.
Q: ¿Qué mostrar al usuario cuando la dependencia degrada?
A: Stop reason explícito, qué falló exactamente y siguiente paso controlado: respuesta parcial o nuevo intento tras recuperación.
Partial outage casi nunca parece un choque ruidoso. Es degradación silenciosa: el sistema aún se mueve, pero ya no sostiene calidad ni ritmo. Por eso los agentes en producción necesitan no solo retries, sino modo de degradación estricto y aislamiento de dependencias.
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 error local de tool se vuelve incidente.
- Deadlocks — cómo crecen estados de espera durante degradación de dependencias.
- Cascading failures — cómo partial outage se propaga a otros workflows.
- Agent Runtime — dónde gestionar degraded mode, stop reasons y fallback.
- Tool Execution Layer — dónde mantener retries, health signals y circuit breaker.