Normal path: execute → tool → observe.
El problema (en producción)
No es un outage total. Es peor.
Un tool está flaky:
- a veces 200
- a veces timeout
- a veces 502
Tu agente intenta “terminar la tarea”. Los usuarios retrían porque ven timeouts. Los budgets arden porque cada retry es un nuevo run.
Los outages parciales te enseñan si tu agente es ingeniero… o apostador.
Por qué esto se rompe en producción
Los outages parciales son duros porque el éxito es intermitente. Eso tienta a los loops.
1) El agente trata éxito intermitente como “sigue intentando”
Los LLM son optimistas. Si consiguen un resultado parcial, a menudo siguen para “completarlo”.
En un notebook está bien. En prod es gasto desbocado.
2) No existe concepto compartido de “salud del tool”
Si el agente no sabe “el tool X está degradado”, hará:
- seguir llamándolo
- retriarlo
- replantear alrededor y volver a llamarlo
Necesitas una señal de salud compartida:
- estado del breaker
- error rate reciente
- spikes de latencia
3) No hay safe-mode
Cuando un tool degrada, necesitas un plan que no dependa de él:
- usar cache
- devolver resultados parciales
- parar con razón y dejar que el usuario decida
4) Outputs “todo o nada” fuerzan mal comportamiento
Si tu contrato es “siempre devuelve respuesta completa”, tu agente thrash durante outages parciales.
Mejor contrato:
- resultados parciales + confianza + stop reason
- opcional: continuación async
Ejemplo de implementación (código real)
Este patrón usa un “health snapshot” al inicio del run. Si un tool crítico está degradado:
- lo deshabilitamos para el run
- cambiamos a safe-mode
- devolvemos partial + stop reason explícito
from dataclasses import dataclass
from typing import Any
@dataclass(frozen=True)
class Health:
degraded_tools: set[str]
def snapshot_health() -> Health:
# In real code: breaker states + recent error rates.
return Health(degraded_tools=set(get_degraded_tools())) # (pseudo)
def safe_tools_for_run(health: Health) -> set[str]:
allow = {"search.read", "kb.read", "http.get"}
# During outages: be conservative.
for t in health.degraded_tools:
allow.discard(t)
return allow
def run(task: str) -> dict[str, Any]:
health = snapshot_health()
allow = safe_tools_for_run(health)
if "kb.read" not in allow:
return {
"status": "degraded",
"reason": "kb.read degraded",
"partial": "I can’t reliably read the KB right now. Here’s what I can do without it…",
}
# Normal loop would run here with a tool gateway allowlist = allow.
return agent_loop(task, allow=allow) # (pseudo)export function snapshotHealth() {
// Real code: breaker states + recent error rates.
return { degradedTools: new Set(getDegradedTools()) }; // (pseudo)
}
export function safeToolsForRun(health) {
const allow = new Set(["search.read", "kb.read", "http.get"]);
for (const t of health.degradedTools) allow.delete(t);
return allow;
}
export function run(task) {
const health = snapshotHealth();
const allow = safeToolsForRun(health);
if (!allow.has("kb.read")) {
return {
status: "degraded",
reason: "kb.read degraded",
partial: "I can’t reliably read the KB right now. Here’s what I can do without it…",
};
}
return agentLoop(task, { allow }); // (pseudo)
}Esto es intencionalmente conservador. En outages parciales tu objetivo no es “triunfar a cualquier coste”. Es “no convertir un outage parcial en uno total”.
Incidente real (con números)
Teníamos un agente que respondía preguntas de soporte usando kb.read.
El servicio de KB se degradó (p95 de ~300ms → 9s, timeouts intermitentes). El agente seguía intentando porque a veces funcionaba.
Impacto:
- tiempo promedio por run: 8s → 52s
- retries del cliente duplicaron tráfico
- on-call recibió pages por “agent timeouts”, no por el problema real del KB
- gasto subió ~$180/día solo en retries + prompts más largos
Fix:
- health snapshot + degrade mode
- fail fast cuando el breaker está abierto
- resultados parciales + stop reason claro
- sugerencia de “intenta más tarde” en vez de timeouts silenciosos
Aquí aprendimos: los stop reasons visibles al usuario son una feature.
Trade-offs
- Las respuestas en degrade mode son menos completas.
- Failing fast baja el success rate en el momento.
- Las señales de salud pueden fallar (false positives). Mejor que thrash.
Cuándo NO usarlo
- Si necesitas completitud estricta, corre async y reporta progreso en vez de loop sincrónico.
- Si no puedes definir semántica de partial output, acabarás en timeouts (mal).
- Si no tienes señales de salud, empieza con budgets y breakers por defecto.
Checklist (copiar/pegar)
- [ ] Health snapshot del tool al inicio del run
- [ ] Policy de degrade mode (tools deshabilitados, read-only, cache)
- [ ] Fail fast si el breaker está abierto
- [ ] Partial results + stop reason explícito
- [ ] Budgets (tiempo/tool calls/gasto) siguen aplicando
- [ ] Alertas: runs degradados vs normales
Config segura por defecto (JSON/YAML)
degrade_mode:
enabled: true
disable_tools_when_degraded: true
allow_partial_results: true
health:
breaker_open_means_degraded: true
budgets:
max_seconds: 60
max_tool_calls: 12
FAQ (3–5)
Usado por patrones
Fallos relacionados
- AI Agent Infinite Loop (Detectar + arreglar, con código)
- Explosión de presupuesto (cuando un agente quema dinero) + fixes + código
- Tool Spam Loops (fallo del agente + fixes + código)
- Incidentes de exceso de tokens (prompt bloat) + fixes + código
- Corrupción de respuestas de tools (schema drift + truncation) + código
Gobernanza requerida
Q: ¿Por qué no retriar hasta que funcione?
A: Porque fallos intermitentes + retries amplifican outages. Tu agente se convierte en generador de carga.
Q: ¿Qué devuelvo en degrade mode?
A: Resultados parciales, cache, o un “no puedo ahora” con stop reason claro.
Q: ¿Necesito salud por tool?
A: Sí para dependencias externas. Empieza con breaker state y error rates recientes.
Q: ¿Cómo manejan los usuarios resultados parciales?
A: Mejor que timeouts. Dales stop reason y opción de reintentar luego.
Páginas relacionadas (3–6 links)
- Foundations: Agente listo para producción · Por qué fallan agentes en producción
- Failure: Fallos en cascada · Tool spam loops
- Governance: Tool permissions (allowlists)
- Production stack: Production agent stack