Inicio / Artículos

Por qué los agentes de ejecución prolongada necesitan una máquina de estados: los roles de Líder, Trabajador y Verificador

Cuatro formas en que un bucle de agente único falla en tareas prolongadas, y cómo un equipo de Líder, Trabajador y Verificador impulsado por un motor determinista las soluciona, además de cuándo omitirlo.

Editor: REACTAPP.TOP11 min de lectura2547 palabras

Fuentes: MiniMax Agent Team Best Practice: An AI Agent Is a While Loop. A Reliable One Is a State Machine.

pasar de un chatbot a un agente requiere un bucle while, no un avance revolucionario en la investigación. Eso es cierto, y también es donde terminan la mayoría de las explicaciones y comienza el problema en la producción. La pregunta más útil es por qué ese bucle falla silenciosamente en tareas que duran más de diez minutos, y qué estructura puede repararlo. MiniMax ha publicado dos referencias detalladas sobre exactamente esto: el tutorial de código abierto Mini-Agent, que muestra el bucle básico, y un informe técnico de su Equipo de Agentes, que explica qué falló a gran escala y el sistema de ejecución desarrollado como respuesta. Ambos sirven como base aquí, y este patrón se puede aplicar a cualquier stack.

Un solo bucle, de principio a fin

Si se descompone un agente, quedan cuatro componentes: un modelo, una lista en tiempo real de mensajes, un registro de herramientas y un bucle que las gestiona. La referencia de Mini-Agent funciona de la siguiente manera: en cada iteración primero se verifica si el historial está cerca del límite de contexto; si es así, se condensa. Luego se envían los mensajes y las definiciones de herramientas al modelo. Si el modelo no solicita ninguna herramienta, el bucle finaliza. De lo contrario, se ejecutan las herramientas solicitadas, su salida se agrega a los mensajes y el proceso se repite. Aquí hay una versión completa para cualquier endpoint compatible con OpenAI, con dos herramientas reales y la función de resumen:

# naive_loop.py -- a single-agent tool loop,
# Mini-Agent style: summarize if needed ->
# llm.generate(messages, tools) -> execute
# tool_calls -> append results -> repeat.
import json
import subprocess
from openai import OpenAI

client = OpenAI()
MODEL = "gpt-4o-mini"
MAX_HISTORY = 40  # crude context budget


def read_file(path: str) -> str:
    with open(path) as f:
        return f.read()[:4000]


def run_cmd(cmd: str) -> str:
    p = subprocess.run(
        cmd, shell=True, timeout=30,
        capture_output=True, text=True)
    out = p.stdout + p.stderr
    return f"exit={p.returncode}\n{out[-2000:]}"


TOOLS = {"read_file": read_file,
         "run_cmd": run_cmd}


def schema(name, desc, arg):
    return {
        "type": "function",
        "function": {
            "name": name,
            "description": desc,
            "parameters": {
                "type": "object",
                "properties": {
                    arg: {"type": "string"},
                },
                "required": [arg],
            },
        },
    }


SCHEMAS = [
    schema("read_file",
           "Read a local text file.", "path"),
    schema("run_cmd",
           "Run a shell command.", "cmd"),
]



def summarize(msgs):
    # Mini-Agent compresses old turns with an
    # LLM summary call when the context nears
    # its limit; hard truncation is the v0.
    if len(msgs) <= MAX_HISTORY:
        return msgs
    return [msgs[0]] + msgs[-MAX_HISTORY:]


def run(task: str, max_steps: int = 20):
    msgs = [{"role": "user", "content": task}]
    for _ in range(max_steps):
        msgs = summarize(msgs)
        r = client.chat.completions.create(
            model=MODEL,
            messages=msgs,
            tools=SCHEMAS,
        )
        msg = r.choices[0].message
        msgs.append(msg)
        if not msg.tool_calls:
            return msg.content  # model says done
        for call in msg.tool_calls:
            fn = TOOLS[call.function.name]
            args = json.loads(
                call.function.arguments)
            try:
                out = fn(**args)
            except Exception as e:
                out = f"tool error: {e}"
            msgs.append({
                "role": "tool",
                "tool_call_id": call.id,
                "content": out,
            })
    return "hit max_steps -- gave up"


if __name__ == "__main__":
    print(run("Count the .py files here, then "
              "summarize naive_loop.py."))

El bucle tiene dos formas de finalizar, y ambas son importantes. El éxito lo determina el propio modelo, expresado mediante la ausencia de llamadas a herramientas adicionales. La seguridad se mide mediante un contador simple, el límite de pasos. Esa asimetría, en la que solo el modelo decide cuándo se ha completado el trabajo, es precisamente lo que falla primero.

Donde un único bucle falla en tareas largas

Si se ejecuta el mismo bucle en una tarea de dos horas en lugar de una de dos minutos, comienzan los fallos. El equipo de MiniMax catalogó aquellos con los que seguían topando. Ninguno es un error de programación; son características propias de un contexto específico, por lo que ni una ventana de tiempo más amplia ni una instrucción mejor resuelven ninguno de ellos.

Primero aparece la ansiedad por el contexto: el agente se detiene cuando nadie se lo pide. Después de realizar siete subtareas, termina tres y dice que ha completado las 1, 2 y 3, preguntando si debe continuar. Los modelos tienen una evaluación imprecisa sobre cuándo puede finalizar una tarea larga, y entrenar directamente esa capacidad de juicio resulta extremadamente costoso en términos económicos y de esfuerzo algorítmico. Un bucle simple deja la decisión de detenerse en su parte menos fiable.

Luego viene la degradación: el agente se vuelve más torpe a medida que el contexto crece. A los cinco minutos funciona con precisión, pero a los sesenta y cinco se comporta como un pasante distraído. Un error menor, como que una tarea de investigación se convierta en texto publicitario, contamina todo lo que sigue, ya que cada generación posterior se basa en esa historia “contaminada”.

En tercer lugar, nada verifica el trabajo, ya que el juez es también el concursante. Un agente solitario que genera el resultado final se califica a sí mismo dentro del mismo contexto en el que cometió sus errores. Eso no es deshonestidad; es examinar una escena que el propio modelo acaba de crear.

En cuarto lugar está el silencio. Las interfaces de estilo chat hacen que los usuarios esperen una respuesta en cuestión de segundos, incluso para tareas que duran una hora, pero un bucle oculto dentro de una llamada al modelo no puede indicar que recibió la solicitud ni prometer una respuesta. Según el informe de MiniMax, la pregunta “¿por qué mi agente no responde?” generó más comentarios de los usuarios que cualquier otra cosa.

La solución: tres roles y un motor determinista

MiniMax responde con estructura. Tres tipos de roles son coordinados por código determinista ordinario, no por un prompt ingenioso, y ese código se denomina Motor del Equipo. Un ciclo de vida de tarea corresponde a una Sesión, y el motor hace pasar cada Sesión por tres estados: producción, verificación y finalización, despertando nuevamente al generador cada vez que falla la verificación.

  • El Líder divide la solicitud del usuario en un plan estructurado, elige qué partes se ejecutan simultáneamente, responde al usuario en cuestión de segundos mientras el resto continúa de forma invisible, y finalmente fusiona los resultados.
  • Los Trabajadores se encargan de subtareas, cada uno en un contexto separado equipado con sus propias herramientas, memoria y formato de salida requerido. Esa separación es importante porque un Trabajador solo ve una subtarea a la vez, nunca dos horas de desviaciones acumuladas.
  • Los verificadores convierten “está terminado” en “puede ser entregado”. La relación entre ellos es intencionadamente adversarial: un trabajador que completa una tarea inicia al verificador, y un verificador que rechaza la tarea reinicia al trabajador, al igual que ocurre entre I+D y control de calidad, donde la calidad surge de las rondas de iteración y no de la microgestión humana.
  • El núcleo del motor es lo suficientemente pequeño como para poder escribirlo por completo. Esta versión no necesita clave API, ya que los roles de stub sustituyen a las llamadas al LLM y pueden intercambiarse sin afectar la máquina de estados:

    # team_engine.py -- the loop promoted to a
    # state machine. One task lifecycle is one
    # Session; deterministic code owns every
    # transition: producing -> verifying -> done.
    from dataclasses import dataclass, field
    
    PRODUCING = "producing"
    VERIFYING = "verifying"
    DONE = "done"
    FAILED = "failed"
    
    
    @dataclass
    class Session:
        goal: str
        state: str = PRODUCING
        attempts: int = 0
        artifact: str = ""
        reviews: list = field(default_factory=list)
    
    
    def run_session(s, worker, verifier,
                    max_retries: int = 3):
        while s.state not in (DONE, FAILED):
            if s.state == PRODUCING:
                s.attempts += 1
                s.artifact = worker(
                    s.goal, s.reviews)
                s.state = VERIFYING
            elif s.state == VERIFYING:
                ok, report = verifier(
                    s.goal, s.artifact)
                s.reviews.append(report)
                if ok:
                    s.state = DONE
                elif s.attempts >= max_retries:
                    s.state = FAILED
                else:
                    # Reject: wake the worker with
                    # the review log in context.
                    s.state = PRODUCING
        return s
    
    
    def run_team(goals, worker, verifier):
        # Leader's job: split the goal, run the
        # sessions (a thread pool in real use),
        # then merge N artifacts into 1 result.
        done, failed = [], []
        for g in goals:
            s = run_session(Session(g),
                            worker, verifier)
            if s.state == DONE:
                done.append(s.artifact)
            else:
                failed.append(s.goal)
        return done, failed
    
    
    if __name__ == "__main__":
        # Stub roles so the engine runs anywhere;
        # swap in LLM calls for the real thing.
        def worker(goal, reviews):
            v = len(reviews) + 1
            return f"draft v{v}: {goal}"
    
        def verifier(goal, artifact):
            ok = "v2" in artifact
            return ok, f"review {artifact!r}: {ok}"
    
        done, failed = run_team(
            ["intro", "benchmarks", "faq"],
            worker, verifier)
        print("delivered:", done)
        print("failed:", failed)
    

    Al ejecutarlo, la primera versión de cada sesión es rechazada; el Worker se activa con el registro de revisiones en su contexto, y la segunda versión es aceptada. Si se compara con el bucle simple, se destacan algunas diferencias.

    • Detención: el bucle único finaliza cuando el modelo deja de emitir llamadas a herramientas (método difuso) o alcanza el límite de pasos, mientras que el motor utiliza estados explícitos de “completado” y “fallido”, con un presupuesto de intentos definido en el código.
    • Verificación: en el bucle, el mismo contexto que generó el trabajo lo verifica, mientras que el equipo emplea un verificador adversarial en un contexto separado, basado en comandos.
    • Paralelismo: el bucle funciona en un único contexto secuencial, mientras que el equipo ejecuta Workers en sesiones paralelas, con el líder que fusiona varias en una sola.
    • Persistencia: el bucle mantiene una lista de mensajes hasta el momento de la resumen, mientras que el equipo conserva el estado de la sesión, los registros de revisión, los artefactos y la memoria de cada agente.

    El Líder tampoco está limitado a llamadas puntuales que generan una subtarea y devuelven texto. En el diseño de MiniMax, este lee el estado en tiempo real del motor y puede enviar instrucciones adicionales a una Sesión que está en proceso de generación o verificación, lo que permite tanto envíos proactivos como consultas a demanda. Esto también resuelve el problema del silencio: el Líder responde en cuestión de segundos, y el usuario recibe informes en puntos clave como iniciado, bloqueado, se necesita una decisión o completado.

    Base para el Verificador

    El defecto de autoevaluación desaparece únicamente cuando el veredicto proviene de fuera de todos los modelos. En el caso del código, MiniMax rodea la ejecución con un marco de control. El trabajo se realiza en ramas, se ejecuta en entornos aislados, las ediciones se presentan como diferencias, las pruebas se pueden repetir, las revisiones dejan registros y los fallos se pueden reproducir. La frase más importante es esta: una condición de parada debe estar vinculada a algo determinista y observable desde el exterior, como las pruebas, los analizadores de código y el proceso de compilación, y nunca al sentido que tenga el modelo de haber terminado.

    # grounded_verifier.py -- evidence over vibes.
    # The verdict comes from exit codes and logs,
    # never from the model's self-assessment.
    import subprocess
    import sys
    
    PY = sys.executable
    
    # pytest exit codes: 0 = all passed,
    # 1 = failures, 5 = no tests collected
    # (acceptable for a docs-only change).
    PYTEST_OK = (0, 5)
    
    CHECKS = [
        ("diff", ["git", "diff", "--stat"], (0,)),
        ("build", [PY, "-m", "compileall",
                   "-q", "."], (0,)),
        ("tests", [PY, "-m", "pytest", "-q",
                   "--maxfail", "1"], PYTEST_OK),
    ]
    
    
    def run_check(name, cmd, ok_codes, cwd):
        p = subprocess.run(
            cmd, cwd=cwd,
            capture_output=True, text=True)
        tail = (p.stdout + p.stderr)[-2000:]
        return {
            "check": name,
            "cmd": " ".join(cmd),
            "exit_code": p.returncode,
            "ok": p.returncode in ok_codes,
            "output_tail": tail,
        }
    
    
    def verify(repo: str):
        evidence = []
        for name, cmd, ok_codes in CHECKS:
            e = run_check(name, cmd, ok_codes, repo)
            evidence.append(e)
            if not e["ok"]:
                # Reject with logs attached: the
                # worker retries against real
                # errors, not "please improve".
                return False, evidence
        return True, evidence
    
    
    
    if __name__ == "__main__":
        ok, ev = verify(".")
        for e in ev:
            print(f"{e['check']:>6} exit "
                  f"{e['exit_code']} ok={e['ok']}")
        print("verdict:", "pass" if ok else "fail")
    

    La lista de pruebas funciona también como registro de revisión, formando parte de un rastro reutilizable y verificable que distingue lo que realmente se puede entregar de lo que es solo plausible. MiniMax divide aún más el proceso de revisión. Un probador genera pruebas externas al ejecutar las pruebas, comprimir los registros de fallos y añadir una reproducción mínima. Un revisor plantea una pregunta diferente: no si la prueba pasó o no, sino si el código debería modificarse de esa manera, analizando los límites de abstracción, las nuevas dependencias, los permisos ampliados y los secretos que se filtran en los registros. Los revisores pueden trabajar en paralelo con especialidades como general, seguridad y dominio.

    Qué cuesta un equipo y cuándo prescindir de él

    Un equipo no es paralelismo gratuito. Aparecen tres costos tan pronto como los agentes colaboran, y una ventana de contexto más amplia no elimina ninguno de ellos.

    Costo de transferencia: la misma información debe ser reorganizada entre los agentes, por lo que las páginas de investigación se convierten en un resumen para el escritor, el cual sirve como entrada para una verificación de formato. La solución es pasar archivos legibles mediante rutas específicas junto con resúmenes breves, en lugar de volcados de contexto sin procesar.

    Costo de compartición: cada token compartido a nivel global es costeado por todos los trabajadores en cada ronda. Un diseño funcional mantiene la memoria privada para cada agente, permite que estos se comuniquen entre sí mediante interrupciones por CLI y ofrece una pizarra blanca compartida a la que los agentes acceden solo cuando lo necesitan.

    El tercer costo es el de agregación. Recopilar diez borradores parciales es sencillo. Unirlos en un único archivo con datos, citas y tono coherentes es la tarea más difícil para el líder, y requiere un paso de fusión, no más personal.

    El consenso también tiene un costo. El debate libre entre los agentes puede consumir varias veces más tokens que la autocorrección aislada y aún así no mejorar la precisión. El valor radica en la estructura: sesiones delimitadas, artefactos explícitos y un verificador autorizado para rechazar.

    Omite el trabajo en equipo cuando la tarea es pequeña, local y económica de verificar, ya que un solo ciclo cuesta menos. Úsalo cuando el trabajo se divide claramente, los errores son costosos y una verificación independiente puede proporcionar pruebas de que el modelo no puede engañarse a sí mismo. Una buena prueba: añade un rol solo si cubre un modo de fallo que ningún otro rol abarca; de lo contrario, la sobrecarga de la colaboración no aporta nada.

    Qué construir primero

    Comience con la máquina de estados más pequeña en la que pueda confiar. Cada sesión cuenta con un objetivo, un artefacto, un registro de revisiones y un presupuesto para intentos adicionales, y cada transición está detallada claramente. Coloque al verificador detrás de un sistema cuyas respuestas provengan de pruebas, análisis de código, compilaciones o verificaciones mecánicas similares. Los trabajadores en paralelo se añaden posteriormente: el paralelismo es una optimización, mientras que los límites de corrección forman parte de la arquitectura. Deje visible el paso de fusión del líder, mantenga los artefactos de transferencia abiertos para inspección y registre por qué terminó cada sesión.

    Lo que resulta no es una mente autónoma. Se trata de un flujo de trabajo controlado: los modelos proponen, las herramientas ejecutan las tareas y el código ordinario determina si el trabajo está completado. Construya el bucle básico a mano una vez antes de adoptar un framework. Espere que las tareas largas dañen su estructura debido a la ansiedad, la deriva, la autoevaluación y el silencio, algo que ninguna cantidad de instrucciones puede solucionar. Considere a los sistemas multiagente como un entorno de ejecución con un plano de control que gestiona el estado, los eventos, los permisos, la observabilidad y los artefactos recuperables, y no como un juego de roles en una instrucción. Además, asigne recursos para la transferencia de responsabilidades, el intercambio de información y la agregación de datos antes de formar un equipo, y nunca forme uno solo por un error tipográfico.

    Leer más

  • Contratos de delegación para equipos de agentes de IA: alcance, aceptación y gestión de fallos — Por qué los títulos de los trabajos de los agentes no evitan transferencias defectuosas, y cómo se puede probar un contrato de delegación con políticas obligatorias, reglas de aceptación e infracción independientes.
  • Ejecutar agentes de programación locales en una Mac con oMLX: explicación del caché KV basado en disco — Por qué los agentes locales se ralentizan en sesiones largas, cómo oMLX almacena prefijos en SSD, y cómo instalarlo y conectar Claude Code, Codex, Cursor y OpenCode en Apple Silicon.
  • Permitir que la IA consulte Postgres de forma segura: servidores MCP, configuración y buenas prácticas — Cómo un servidor MCP de PostgreSQL permite que Claude Code o Cursor lean esquemas en tiempo real, además de una comparación entre CrystalDBA, Supabase y el servidor de referencia archivado, y hábitos seguros.
  • Un agente es un bucle while: create_agent de LangChain y Fit de LangGraph — Un agente que llama a herramientas en veinte líneas, los cuatro problemas de producción que surgen con los bucles, y cuándo pasar de create_agent de LangChain a los gráficos de estado de LangGraph.
  • Notas prácticas: Ingeniería de bucles de agentes: Un blog completo hasta la IA en producción — Guía paso a paso de las Notas prácticas: Ingeniería de bucles de agentes: Un blog completo hasta la IA en producción: contratos, verificaciones y espacios para código listo para usar para los equipos que implementan este patrón.