Agentic Loop en TypeScript: cómo evitar el bucle infinito
Un viernes a las 11 de la noche dejé corriendo un script experimental con un agente para refactorizar unos modelos de datos.
A la mañana siguiente abrí la consola de Anthropic y vi que el agente había ejecutado más de 300 llamadas a herramientas seguidas. ¿El motivo? Había intentado leer un archivo que no existía, la herramienta devolvió un error genérico, el modelo interpretó que la ruta estaba mal escrita, probó otra ruta inexistente, y así entró en un bucle infinito durante horas.
La factura de tokens dolió, pero la lección técnica fue mucho más valiosa: un agente de IA no es un modelo inteligente; es un Agentic Loop, un bucle while que tú controlas (o que te controla a ti).
La mayoría de tutoriales te enseñan a pasarle tres herramientas a un LLM y llamar a generateText. Nadie te explica qué pasa cuando el modelo entra en pánico lógico, se niega a terminar o repite la misma llamada una y otra vez.
Aquí está cómo funciona de verdad el Agentic Loop en producción y cómo implementarlo en TypeScript sin caer en trampas de novato.
Qué es realmente un Agentic Loop
La diferencia entre un chatbot clásico y un agente autónomo no es el modelo de lenguaje. Ambos pueden usar Claude Sonnet 5, Opus 5 o el GPT vigente. La diferencia es el patrón de ejecución.
Un chatbot recibe un mensaje y devuelve texto. Fin del ciclo.
Un agente recibe una instrucción, razona qué herramienta necesita, la ejecuta, observa el resultado y vuelve a razonar con esa nueva información hasta que considera que la tarea está terminada.
El flujo básico sigue siempre este ciclo:
- Reasoning (Razonamiento): El modelo evalúa el historial y decide si responder o invocar una herramienta (Tool Call).
- Action (Acción): Tu servidor ejecuta la función requerida (leer un archivo, consultar una base de datos, llamar a una API).
- Observation (Observación): Inyectas el resultado de la función de vuelta al contexto como un mensaje de rol
tool. - Evaluation (Evaluación): El modelo analiza el nuevo estado. Si el objetivo está cumplido, entrega la respuesta final; si no, repite desde el paso 1.
Parece sencillo en un diagrama. En producción, si dejas este bucle sin defensas activas, tienes una bomba de tiempo.
Los 3 errores fatales que rompen tus agentes en producción
1. No tener un límite de pasos rígido
Si no defines un tope máximo de iteraciones, un fallo imprevisto en una API externa convertirá a tu agente en un generador infinito de facturación. En cualquier sistema agéntico serio, cada tarea debe tener un presupuesto máximo de pasos (usualmente entre 5 y 15 para tareas estándar).
2. Tratar los errores de las herramientas como excepciones no controladas
Si una tool lanza un throw new Error("File not found") y tu código revienta el proceso de Node/Bun, tu agente se cae. Pero si capturas el error y le devuelves un string vacío, el agente asumirá que el archivo está en blanco y tomará decisiones erróneas.
Devuelve los errores de herramientas formateados como datos descriptivos para que el modelo sepa exactamente qué falló y pueda autocorregirse.
3. Falta de detección de estancamiento (Loop Fingerprinting)
El modelo a veces se obsesiona. Ejecuta readFile({ path: "config.json" }), falla, y en el siguiente paso vuelve a llamar exactamente a readFile({ path: "config.json" }) esperando un milagro.
Si no comparas el hash de la llamada actual con las 2 anteriores, tu agente se quedará atascado en un ciclo ciego.
Implementando un Agentic Loop robusto en TypeScript
Para evitar dependencias pesadas que oscurezcan lo que pasa por debajo, el estándar más limpio hoy es usar el Vercel AI SDK con schemas tipados mediante Zod.
Aquí tienes una implementación defensiva lista para producción:
import { generateText, tool, isStepCount } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
// 1. Definición estricta de herramientas con Zod
const tools = {
readFile: tool({
description: "Lee el contenido de un archivo del proyecto",
inputSchema: z.object({
filePath: z.string().describe("Ruta relativa del archivo a leer"),
}),
execute: async ({ filePath }) => {
try {
const content = await Bun.file(filePath).text();
return { success: true, data: content };
} catch (err: any) {
// Devolvemos el error como información útil para el agente
return {
success: false,
error: `No se pudo leer el archivo "${filePath}": ${err.message}`
};
}
},
}),
};
// 2. Ejecutor agéntico con defensas
export async function runAgentTask(prompt: string, maxIterations = 10) {
const callHistory: string[] = [];
const result = await generateText({
model: anthropic("claude-sonnet-5"),
system: `Eres un asistente de desarrollo autónomo.
Usa las herramientas disponibles para inspeccionar el entorno.
Si una herramienta falla, analiza el motivo antes de reintentar.
Cuando termines la tarea, responde con el resumen final sin invocar más tools.`,
prompt,
tools,
stopWhen: isStepCount(maxIterations), // Límite estricto de seguridad
onStepFinish: ({ toolCalls }) => {
if (!toolCalls || toolCalls.length === 0) return;
for (const call of toolCalls) {
const fingerprint = `${call.toolName}:${JSON.stringify(call.input)}`;
// Detectar si el agente repite la misma llamada idéntica
const repetitions = callHistory.filter((f) => f === fingerprint).length;
if (repetitions >= 2) {
console.warn(`[LOOP DETECTED] El agente está repitiendo: ${fingerprint}`);
}
callHistory.push(fingerprint);
}
},
});
return result.text;
}
Fíjate en lo que hace este patrón:
- Schemas Zod estrictos: Si el LLM intenta inventarse un parámetro que no existe, la librería lo rechaza antes de tocar el sistema de archivos. Si quieres profundizar en cómo validar estructuras complejas, en nuestro curso de Zod para TypeScript vemos cómo blindar estas entradas.
- Resultados estructurados: Las herramientas devuelven
{ success: boolean, data?: string, error?: string }. El modelo sabe interpretar este formato a la primera. - Trazabilidad paso a paso: El callback
onStepFinishte permite auditar en tiempo real qué decide el agente y abortar si detectas bucles repetitivos.
La regla de oro: Antes de escribir el loop, define el Spec
El mejor código de loop no compensa una instrucción ambigua. Si le dices a tu agente "arregla el bug del login", gastará 8 iteraciones solo descubriendo dónde está el archivo de login.
Esta es la razón por la que en Dominicode insistimos tanto en la metodología Spec-First. Antes de soltar a un agente a modificar código, generamos una especificación técnica clara que acota el alcance exacto de la tarea. Es exactamente el flujo que enseñamos en el curso Construye con IA: de la idea al producto y en el libro de Spec-Driven Development.
Qué puedes cambiar hoy en tu arquitectura
Si ya tienes agentes corriendo en tus proyectos locales o en servidores de staging, haz esta revisión técnica hoy mismo:
- Revisa tu límite de pasos (
stopWhen): Ninguna llamada agéntica debería correr sin un límite superior finito. - Audita el retorno de tus tools: Asegúrate de que las excepciones devuelvan mensajes de error útiles en lugar de romper el runtime o devolver respuestas vacías.
- Implementa fingerprinting: Guarda un hash de las últimas 3 tool calls. Si hay coincidencia exacta consecutiva, inyecta un mensaje del sistema advirtiendo al modelo que cambie de estrategia.
En Dominicode Labs estamos probando patrones avanzados de orquestación agéntica con subagentes independientes y guardrails de seguridad en proyectos reales.
Construir con IA no consiste en maravillarse con lo que genera el modelo en el primer prompt, sino en diseñar la arquitectura que hace que el sistema sea predecible y seguro cuando nadie está mirando la pantalla.
