Agentic Loop en TypeScript: cómo evitar el bucle infinito

Agentic Loop en TypeScript — Dominicode

Written by

in

,

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 había ejecutado más de 300 llamadas a herramientas seguidas. ¿El motivo? Intentó 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í durante horas.

Un agente de IA no es un modelo inteligente: es un Agentic Loop, un bucle while en TypeScript que tú controlas (o que te controla a ti). La factura de tokens dolió, pero la lección técnica valió cada céntimo.

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:

  1. Reasoning (Razonamiento): El modelo evalúa el historial y decide si responder o invocar una herramienta (Tool Call).
  2. Action (Acción): Tu servidor ejecuta la función requerida (leer un archivo, consultar una base de datos, llamar a una API).
  3. Observation (Observación): Inyectas el resultado de la función de vuelta al contexto como un mensaje de rol tool.
  4. 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.

Si este ciclo te suena, es porque es la formalización del patrón ReAct: Thought → Action → Observation. Ahí explico el razonamiento; aquí vamos a lo que nadie cuenta: qué defensas necesita ese bucle para sobrevivir a producción.

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 la huella de la llamada actual con las anteriores, tu agente se quedará atascado en un ciclo ciego. Y ojo: un límite de pasos no te salva de esto, solo te acota el gasto. El agente sigue quemando el presupuesto entero sin avanzar un milímetro.


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.

La clave está en stopWhen: acepta un array de condiciones y detiene el bucle cuando cualquiera de ellas se cumple. Eso te permite combinar el techo de gasto con tu propio detector de bucles.

Aquí tienes una implementación defensiva lista para producción:

import { generateText, tool, isStepCount, type StopCondition } 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. Huella de cada tool call: nombre + argumentos exactos
const fingerprint = (call: { toolName: string; input: unknown }) =>
  `${call.toolName}:${JSON.stringify(call.input)}`;

// 3. Condición de parada propia: corta a la tercera llamada idéntica
//    (la segunda es un reintento legítimo; la tercera ya es obsesión)
const noRepeatedCalls: StopCondition<typeof tools> = ({ steps }) => {
  const calls = steps.flatMap((step) => step.toolCalls.map(fingerprint));

  return calls.some(
    (f) => calls.filter((other) => other === f).length >= 3
  );
};

// 4. Ejecutor agéntico con las dos defensas activas
export async function runAgentTask(prompt: string, maxIterations = 10) {
  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,
    // Para en cuanto se cumpla una: presupuesto agotado o bucle detectado
    stopWhen: [isStepCount(maxIterations), noRepeatedCalls],
    onStepFinish: ({ toolCalls }) => {
      for (const call of toolCalls) {
        console.log(`[STEP] ${fingerprint(call)}`);
      }
    },
  });

  return { text: result.text, steps: result.steps.length };
}

Fíjate en lo que hace este patrón:

  1. 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.
  2. Resultados estructurados: Las herramientas devuelven { success: boolean, data?: string, error?: string }. El modelo sabe interpretar este formato a la primera.
  3. Dos condiciones de parada independientes: isStepCount es el techo de gasto; noRepeatedCalls es el que de verdad mata el bucle infinito, porque detecta el estancamiento aunque queden pasos de presupuesto.
  4. Trazabilidad paso a paso: onStepFinish te da el log de cada decisión del agente. No puede abortar el bucle —eso es trabajo de stopWhen—, pero es lo que te permite reconstruir después por qué se atascó.

Un detalle que se pasa por alto: la parada devuelve el control a tu código, no lanza una excepción. Comprueba steps al recibir el resultado — si el agente terminó en el paso 10 de 10, probablemente no completó la tarea y toca escalarla, no darla por buena.

Es el mismo cambio de mentalidad del que hablo en Loop Engineering: el valor ya no está en el prompt, está en el bucle que lo envuelve.


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:

  1. Revisa tu límite de pasos (stopWhen): Ninguna llamada agéntica debería correr sin un límite superior finito.
  2. 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.
  3. Implementa fingerprinting: Añade una condición de parada propia que compare la huella de cada tool call con las anteriores y corte a la tercera repetición exacta.
  4. Comprueba en qué paso terminó: Si el agente agota el presupuesto, trátalo como fallo y escala la tarea. Un resultado entregado en el último paso casi nunca es un resultado bueno.

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.


Preguntas frecuentes

¿Cuántos pasos como máximo debe tener un Agentic Loop?

Entre 5 y 15 para tareas estándar. Con menos de 5 el agente no llega a explorar el entorno; por encima de 15 el problema casi nunca es el presupuesto, sino que la instrucción era ambigua. Si necesitas 30 pasos, parte la tarea en dos.

¿Qué diferencia hay entre un Agentic Loop y el patrón ReAct?

ReAct describe el razonamiento del modelo: pensar, actuar, observar. El Agentic Loop es la implementación real de ese ciclo en tu código, con sus límites, su manejo de errores y sus condiciones de parada. ReAct es el qué; el Agentic Loop es el cómo lo ejecutas sin que se te vaya la factura.

¿Debo lanzar una excepción cuando falla una tool?

No. Captura el error dentro de execute y devuélvelo como dato estructurado ({ success: false, error: "..." }). Si lanzas la excepción, rompes el proceso; si devuelves vacío, el modelo asume que el resultado era vacío y decide mal. El error descriptivo es lo único que le permite autocorregirse.

¿No basta con poner un límite de pasos para evitar el bucle infinito?

El límite evita que el gasto sea infinito, pero no evita el bucle. Un agente atascado consumirá los 10 pasos repitiendo la misma llamada y te devolverá un resultado inútil habiendo pagado el presupuesto completo. El límite de pasos es el airbag; el fingerprinting es el freno.

¿Necesito un framework de orquestación para implementar esto?

No para un bucle de una sola tarea: el Vercel AI SDK con stopWhen te da control de pasos y parada personalizada en unas 40 líneas. Los frameworks de grafos empiezan a compensar cuando necesitas ramificaciones, estado persistente entre sesiones o varios agentes coordinados.


Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *