Error Recovery en AI Agents 2026: El 95% se Rompe en el Primer Error Real — y No, un Try-Catch No Va a Salvarlos
Aprende a construir AI Agents resilientes con error recovery real. Framework de 4 capas para detectar, clasificar y recuperar fallos en producción sin detener tu agent.
El 95% de los AI Agents Que Construyes Hoy se Van a Romper en Producción
No es culpa del modelo. Es culpa de tu try-catch.
La mayoría de los desarrolladores tratáis los AI Agents como software determinista. Escribís código que asume que el LLM va a devolver JSON válido. Que la tool call va a funcionar. Que el contexto va a caber en la ventana.
Y cuando falla, ponéis un try-catch.
*El problema no es que los agents fallen. Es que los tratáis como funciones puras cuando son sistemas probabilísticos. *
Un AI Agent no es una API REST. Es un bucle que toma decisiones. Y en cada iteración de ese bucle, hay al menos cinco puntos donde todo puede explotar:
1. El LLM alucina un tool name que no existe.
2. La tool recibe parámetros que no esperaba.
3. La respuesta de la tool no es parseable.
4. El contexto excede el límite de tokens.
5. El LLM decide "no hacer nada" y entra en un bucle infinito.
Si tu estrategia de error recovery es un try-catch genérico que logea el error y reintenta, *no tienes un sistema de recuperación. Tienes una bomba de relojería. *
Vamos a construir algo que sí funcione.
---
❌ El Enfoque Que Mata a tus Agents: el Try-Catch Generalista
```typescript
// Lo que el 90% de los desarrolladores escribe:
try {
const result = await agent.execute(userInput);
return result;
} catch (error) {
console.error("Agent failed:", error);
return "Lo siento, hubo un error.";
}
```
Esto no es error recovery. Es *tirar la toalla con estilo. *
El problema es conceptual: estás modelando un agent como una función pura. Pero un agent no es `(input) => output`. Un agent es un bucle:
```
(bucle) => [
percibe_input(),
decide_accion(),
ejecuta_tool(),
evalua_resultado(),
repite()
]
```
Cada paso del bucle tiene tipos de error radicalmente diferentes. Y cada tipo requiere una estrategia de recuperación distinta.
✅ El enfoque correcto: no un try-catch, sino una máquina de estados con handlers específicos por fase.
---
El Framework de 4 Capas para Error Recovery en AI Agents
Basado en lo que he aprendido desplegando agents en producción para gestoriascercademi.com y Juridica Integral, aquí tenéis la arquitectura que sí funciona.
1. Capa de Detección: Clasificar el Error Antes de Actuar
No todos los errores son iguales. Y tratarlos igual es el error más caro que podéis cometer.
Clasificad los fallos en tres categorías:
| Tipo | Ejemplo | Estrategia |
|------|---------|------------|
| Recoverable | Tool timeout, JSON malformado | Reintentar con parámetros corregidos |
| Degradable | Contexto excede límite, modelo responde en inglés | Degradar comportamiento, no detener |
| Fatal | API key inválida, schema roto | Parar. No hay recuperación posible |
```typescript
type ErrorSeverity = "recoverable" | "degradable" | "fatal";
function classifyError(error: unknown, context: AgentContext): ErrorSeverity {
if (error instanceof ToolTimeoutError && context.retryCount < 3) {
return "recoverable";
}
if (error instanceof ContextOverflowError) {
return "degradable"; // Podemos truncar y seguir
}
if (error instanceof AuthError) {
return "fatal"; // No hay vuelta atrás
}
return "recoverable"; // Por defecto, intentamos recuperar
}
```
2. Capa de Estrategia: Cómo Recuperar Cada Tipo
Aquí es donde la mayoría se deja la productividad. Tener un plan de recuperación para cada tipo de error.
Para errores recoverable (tool timeout, JSON malformado):
```typescript
async function recoverableHandler(
error: ToolError,
context: AgentContext
): Promise<RecoveryAction> {
const retryCount = context.getRetryCount(error.toolName);
if (retryCount < 2) {
// Primer intento: reintentar con backoff exponencial
const delay = Math.min(1000 * Math.pow(2, retryCount), 8000);
await sleep(delay);
return { action: "retry", delay };
}
if (retryCount === 2) {
// Segundo intento: simplificar los parámetros
const simplifiedParams = simplifyParameters(error.params);
return { action: "retry_with_modifications", params: simplifiedParams };
}
// Tercer intento: fallback a una respuesta predefinida
return { action: "fallback", response: "No he podido completar la operación. He registrado el error para revisión." };
}
```
Para errores degradable (contexto excedido, modelo impreciso):
El agent no se detiene. Simplemente opera con menos capacidad.
```typescript
function degradableHandler(error: ContextOverflowError): DegradationPlan {
return {
degradation: "summary_mode",
actions: [
"truncate_oldest_messages(keep_last_10)",
"summarize_history(before_timestamp)",
"disable_tools(['web_search', 'file_read'])"
],
notify_user: true,
message: "Estoy operando en modo resumido. Algunas herramientas están desactivadas."
};
}
```
3. Capa de Circuit Breaker: No Dejar que un Error lo Contamine Todo
Esto es lo que diferencia un agent "de demo" de un agent "de producción".
Un circuit breaker monitoriza la tasa de fallos de cada tool y, cuando supera un umbral, la desactiva temporalmente.
```typescript
class ToolCircuitBreaker {
private failures: Map<string, number> = new Map();
private readonly threshold = 3;
private readonly resetTimeout = 60_000; // 1 minuto
recordFailure(toolName: string): void {
const count = (this.failures.get(toolName) || 0) + 1;
this.failures.set(toolName, count);
if (count >= this.threshold) {
this.openCircuit(toolName);
}
}
isOpen(toolName: string): boolean {
return (this.failures.get(toolName) || 0) >= this.threshold;
}
private openCircuit(toolName: string): void {
console.warn(`[Circuit Breaker] Tool ${toolName} desactivada por ${this.resetTimeout}ms`);
setTimeout(() => {
this.failures.delete(toolName);
console.info(`[Circuit Breaker] Tool ${toolName} reactivada`);
}, this.resetTimeout);
}
}
```
Cuando el circuito está abierto, el agent no intenta llamar a esa tool. Simplemente salta al siguiente paso o responde con un mensaje de degradación.
4. Capa de Auditoría: Lo Que no Registras, No lo Puedes Mejorar
El error recovery no termina cuando el agent responde. Termina cuando tú analizas el fallo y mejoras el sistema.
```typescript
interface ErrorEvent {
timestamp: Date;
agentId: string;
userId: string;
phase: "perception" | "decision" | "execution" | "evaluation";
toolName: string;
errorType: ErrorSeverity;
errorMessage: string;
recoveryStrategy: string;
recoveryOutcome: "success" | "partial" | "failed";
contextSize: number;
latency: number;
}
```
Cada error recovery debe generar un evento. Cada evento debe ir a un log estructurado que puedas interrogar.
```sql
-- Consulta semanal para detectar tools problemáticas
SELECT
tool_name,
COUNT(*) as total_errors,
COUNT(CASE WHEN recovery_outcome = 'failed' THEN 1 END) as unrecovered_errors,
AVG(latency) as avg_recovery_latency
FROM error_events
WHERE timestamp > NOW() - INTERVAL '7 days'
GROUP BY tool_name
ORDER BY total_errors DESC;
```
Si no tienes esto, estás operando a ciegas.
---
Implementación Completa: El Bucle del Agent con Error Recovery
Aquí tenéis el código que une todo. Es el patrón que uso en producción para Juridica Integral (procesamiento de documentos legales con agents autónomos):
```typescript
async function resilientAgentLoop(context: AgentContext): Promise<AgentResponse> {
const breaker = new ToolCircuitBreaker();
let currentError: Error | null = null;
while (context.shouldContinue()) {
try {
// Fase 1: Percibir
const perception = await context.perceive();
// Fase 2: Decidir
const decision = await context.decide(perception);
// Comprobar si la tool está disponible
if (decision.action === "tool_call" && breaker.isOpen(decision.toolName)) {
// Circuito abierto — usar fallback
decision.action = "respond";
decision.response = `No puedo acceder a ${decision.toolName} en este momento. ¿Quieres que lo intente más tarde?`;
}
// Fase 3: Ejecutar
const result = await context.executeTool(decision);
// Fase 4: Recuperar si falla
if (result.error) {
const severity = classifyError(result.error, context);
if (severity === "fatal") {
throw result.error; // Propagar hacia arriba
}
if (severity === "degradable") {
const plan = degradableHandler(result.error);
context.applyDegradation(plan);
continue; // Seguir con capacidades reducidas
}
// Recoverable
breaker.recordFailure(decision.toolName);
const recovery = await recoverableHandler(result.error, context);
if (recovery.action === "fallback") {
return { response: recovery.response, degraded: true };
}
// Reintentar con la acción de recovery
context.modifyParams(recovery.params);
continue;
}
// Éxito — resetear contador de fallos
breaker.recordFailure.calledWith && breaker.recordFailure.reset(decision.toolName);
// Fase 5: Evaluar y continuar si es necesario
context.addToHistory(perception, decision, result);
} catch (fatalError) {
// Esto solo se alcanza si el error es fatal
console.error("[Agent] Error fatal:", fatalError);
await context.notifyAdmin(fatalError);
return {
response: "He encontrado un error del que no puedo recuperarme. Lo he notificado al equipo.",
fatal: true,
errorId: crypto.randomUUID()
};
}
}
return context.buildFinalResponse();
}
```
---
Lo Que la Mayoría Sigue Haciendo Mal
Tres errores que veo cada semana revisando codebases de agents:
1. Reintentar sin backoff. Si una tool falla por timeout, reintentar inmediatamente es inútil. Usad backoff exponencial con jitter. Si no sabéis qué es eso, buscad "exponential backoff jitter" antes de desplegar nada.
2. No diferenciar entre error del LLM y error de la tool. Que el LLM devuelva JSON inválido no es lo mismo que una API externa caída. El primero se arregla con un mejor prompt o un parser robusto. El segundo requiere circuit breaker.
3. Ignorar el estado. Un error recovery que no actualiza el estado interno del agent produce respuestas inconsistentes. Si el agent dice "no puedo buscar en la web", pero su estado interno sigue asumiendo que puede, las siguientes decisiones serán incorrectas.
---
El Patrón que Debéis Robar: Supervisor Agent con Fallback Planning
La mejor arquitectura que he visto para error recovery no es un try-catch. Es tener un supervisor agent separado que monitoriza al worker agent y decide la estrategia de recuperación.
```typescript
class SupervisorAgent {
async monitor(worker: WorkerAgent): Promise<void> {
const errorPattern = await this.detectPattern(worker.recentErrors);
if (errorPattern === "tool_degradation") {
// El supervisor decide cambiar de herramienta
await worker.switchTool(errorPattern.failingTool, errorPattern.alternativeTool);
}
if (errorPattern === "context_fragmentation") {
// El supervisor solicita un resumen antes de continuar
await worker.summarizeAndCompress();
}
if (errorPattern === "loop_detected") {
// El supervisor interrumpe el bucle y redirige
await worker.interrupt("Has repetido la misma acción 3 veces. Cambia de estrategia.");
}
}
}
```
Este patrón duplica el coste de ejecución (dos LLM calls en lugar de una), pero multiplica la fiabilidad. Para agents en producción que manejan datos de clientes reales, es la única opción sensata.
---
La Regla de Oro del Error Recovery
*Un AI Agent no es bueno porque nunca falle. Es bueno porque sabe qué hacer cuando falla. *
La diferencia entre un agent de demo y un agent de producción no es la calidad del LLM. Es la calidad del sistema de error recovery.
Podéis tener el modelo más avanzado del mundo. Si vuestro agent se cuelga en un bucle infinito porque la tool devolvió `undefined` en lugar de `null`, ese modelo no vale nada.
En 2026, si estáis aprendiendo how to build ai agents 2026, lo primero que tenéis que dominar no es prompting ni RAG. Es error recovery. Todo lo demás viene después.
Construid el circuito. Clasificad los errores. Recuperad con estrategia.
Y dejad el try-catch genérico para los tutoriales de YouTube.
Lee el artículo completo en brianmenagomez.com
Más sobre mis servicios en brianmenagomez.com
Herramientas: Conversor IAE CNAE · Gestorias cerca de ti · Calculadora IRPF

