Skip to content

English · Español

05 — Arquitectura del bucle del agente: observation → reasoning → tool call → … → answer

🇪🇸 Un agente es un bucle: observa, razona, llama a una herramienta, vuelve a observar, hasta terminar. Esta sección formaliza el ciclo del tutor de gramática §A13 — qué estados existen, qué transiciones son legales, cuándo el bucle debe parar y cómo falla. Cruzamos con el extension-track X3 (RLHF/DPO) para la dimensión de alineamiento — no la reimplementamos aquí; sólo apuntamos dónde el agente toca esa frontera.

Referencias: theory/01-react-and-planning.md, theory/02-memory.md, theory/03-sandboxing.md, Fase 31 theory/05-mcp-wire-and-100-line-server.md, extension-track docs/extension-track/X3-rlhf-dpo/README.md.


La máquina de estados mínima del agente

El agente tutor de gramática §A13 es un autómata de 5 estados:

    ┌──────────────┐
    │  observe     │  ◀── input: user sentence, e.g., "He goed to school"
    └──────────────┘
    ┌──────────────┐
    │  reason      │  ◀── LLM planning: "this looks like an irregular verb error"
    └──────────────┘
    ┌──────────────┐
    │  tool_call   │  ──▶ MCP: conjugate(verb="go", tense="past simple", person="3rd singular")
    └──────────────┘
    ┌──────────────┐
    │  observe     │  ◀── tool response: "went"
    └──────────────┘
    ┌──────────────┐                    ┌──────────────┐
    │  reason      │  ──── confident ──▶│  answer      │  ──▶ output: "He went to school."
    └──────────────┘                    └──────────────┘
            └── unsure ──▶ tool_call (loop)

Cinco estados, cuatro transiciones. El bucle está entre reason y tool_call vía pasos intermedios observe. La terminación es el arco reason → answer.

Semántica de los estados

Estado Entradas Salida Coste
observe delta de entorno (input de usuario o resultado de herramienta) observación estructurada añadida al scratchpad ≈ 0 ms
reason scratchpad siguiente acción: tool_call (con qué herramienta, qué args) o answer un forward pass del LLM
tool_call nombre de herramienta + args resultado de herramienta, añadido al siguiente observe un round-trip MCP
answer scratchpad respuesta final visible al usuario un forward pass del LLM (generación)
terminate scratchpad registro estructurado de fallo (se rindió) solo logs

El estado terminate es el terminal de fallo. El estado answer es el terminal de éxito.

Reglas de transición

Las transiciones no son de forma libre — están restringidas por un decoder enmascarado por JSON-Schema (Fase 30):

NEXT_ACTION_SCHEMA = {
    "type": "object",
    "required": ["action"],
    "oneOf": [
        {"properties": {"action": {"const": "tool_call"},
                        "tool":   {"enum": ["conjugate", "lookup_rule"]},
                        "args":   {"type": "object"}}},
        {"properties": {"action": {"const": "answer"},
                        "text":   {"type": "string", "maxLength": 200}}},
    ],
}

El modelo solo puede emitir una de dos siguientes acciones estructuralmente válidas. La prosa de forma libre es mecánicamente imposible. Esta es la dependencia Fase 30 → Fase 32 hecha concreta.

El pseudocódigo Python completo

def grammar_tutor(user_input: str,
                  max_turns: int = 6,
                  max_tool_calls: int = 4,
                  mcp_client: MCPClient) -> str:
    scratchpad: list[Observation] = [Observation(role="user", text=user_input)]
    turns = 0
    tool_calls = 0

    while turns < max_turns:
        turns += 1

        # reason: ask the LLM to pick the next action.
        action = llm_reason(scratchpad, schema=NEXT_ACTION_SCHEMA)

        if action.kind == "answer":
            return action.text

        # action is tool_call
        if tool_calls >= max_tool_calls:
            return "I tried but could not resolve the grammar question in time."
        tool_calls += 1

        try:
            result = mcp_client.call(action.tool, action.args, timeout=2.0)
            scratchpad.append(Observation(role="tool", text=result.text))
        except MCPError as e:
            scratchpad.append(Observation(role="tool", text=f"[error] {e}"))
            # the next reason step will see the error and either retry or give up

    # fell through the turn budget
    return "I tried but could not resolve the grammar question in time."

Veintidós líneas. El bucle es deliberadamente corto — la complejidad vive en el decoder enmascarado (llm_reason) y el cliente MCP, ambos componentes de la Fase 30 y la Fase 31.

Condiciones de terminación — tres capas

Un agente correcto nunca itera para siempre. Hay tres puertas de terminación independientes:

1. Cap de turnos (max_turns)

El while turns < max_turns externo es la red de seguridad. Si el LLM sigue emitiendo tool_call para siempre (porque está confuso, o porque el prompt es adversario), el cap de turnos detiene el bucle. Default: 6 turnos para §A13 (suficiente para 2–3 reintentos con una o dos tool calls cada uno).

2. Cap de tool calls (max_tool_calls)

Independiente de max_turns. La razón: un agente podría emitir answer entre tool calls y resetear la noción de "turno" de forma engañosa. Un presupuesto separado de tool_call acota el uso de recursos específicamente.

3. Detector de no-progreso (avanzado, diferido)

Si el scratchpad crece pero tool calls idénticos se repiten con resultados idénticos, el bucle no avanza. Los agentes de producción detectan esto y rompen. Para la Fase 32 lo omitimos — el cap max_turns es suficiente a nuestra escala.

El agente sin estos caps es el ejercicio /break de la Fase 32. Itera para siempre sobre entrada adversaria (o sobre una herramienta mal configurada que devuelve repetidamente el mismo "no sé").

Modos de fallo (los cuatro bugs canónicos)

Fallo Síntoma Causa raíz Mitigación
Bucle infinito El agente nunca retorna; CPU al 100 % Sin cap de turnos, o LLM atascado en el ciclo tool_call → observe → tool_call Cap duro max_turns / max_tool_calls
Herramienta alucinada El agente emite tool_call con tool: "translate" cuando solo existe conjugate Decoder no enmascarado contra el enum de herramientas Máscara JSON-Schema con tool: enum: [...]
Ceguera ante errores de herramienta El agente recibe un error de MCP, reintenta con los mismos args, vuelve a errar El LLM no está viendo el error de la herramienta en el scratchpad Anteponer marcadores [error]; ajustar el prompt de razonamiento
Respuesta errónea con citas El agente responde desde sus propios pesos aunque la herramienta devolvió un valor diferente El prompt del lector no se restringe a la salida de la herramienta El prompt de respuesta final debe referenciar el resultado de la herramienta por [#tool_call_id]

El lab 03-failure-mode-tour.md de la Fase 32 reproduce los cuatro sobre el tutor de gramática. El /break (siguiente archivo de la serie) apunta al primero — el bucle infinito.

Dónde este bucle toca el alineamiento (referencia cruzada a X3)

El módulo extension-track docs/extension-track/X3-rlhf-dpo/ cubre la dimensión de alineamiento que este bucle de agente no resuelve por sí solo. Específicamente:

  • La calidad del paso reason es función del LLM detrás de él. Si Mini-GPT se entrenó solo con el corpus §A13, sus salidas reason son mediocres en casos límite. RLHF/DPO (módulos X3) mejoran el razonamiento del agente entrenando al LLM sobre pares de preferencia sobre sus propias propuestas de acción.
  • La decisión de "rendirse" es en sí misma una cuestión de calibración. Un modelo entrenado con RLHF es mejor diciendo "no sé" que un modelo base (que a menudo bullshittea con confianza). La pérdida DPO sobre la tarea del tutor de gramática (X3 lab 01-dpo-on-grammar-tutor.md) entrena exactamente esta distinción.
  • Revisión constitucional (X3 lab 02) envuelve el bucle del agente con un paso de auto-crítica entre reason y tool_call. La crítica pregunta "¿es esta acción consistente con las reglas?"; el agente reintenta si no. Esta es una capa por encima de lo que implementa la Fase 32.

El agente de la Fase 32 es el sustrato sobre el que operan las técnicas de alineamiento de X3. Puedes ejecutar el tutor de gramática §A13 sin ningún módulo X3 — será ~85 % correcto en el conjunto de eval con un Mini-GPT LoRA-finetuned vanilla. Con el paso DPO de X3 encima, el mismo agente alcanza ~93 %. La arquitectura del bucle del agente no cambia; la calidad del reason del LLM sí.

Borja debería leer docs/extension-track/X3-rlhf-dpo/theory/04-dpo-and-direct-methods.md después de cerrar esta fase para ver cómo se enchufa el alineamiento.

Una nota sobre selección de herramientas

En nuestra configuración mínima §A13, el agente tiene dos herramientas: conjugate (Fase 31) y lookup_rule (un recuperador estilo RAG de la Fase 29 que envuelve la tabla de verbos irregulares). La elección entre ellas es en sí misma una decisión de reason: las preguntas estructurales ("¿cuál es el past simple de X?") prefieren conjugate; las preguntas explicativas ("¿por qué es X irregular?") prefieren lookup_rule. Los agentes de producción a menudo tienen 10–100 herramientas; el problema de selección de herramientas se convierte en su propio objetivo de optimización.

Mantenemos dos herramientas para la Fase 32. Añadir más es una preocupación de la Fase 33 (serving), no de la Fase 32 (semántica del bucle).

Por qué un agente

Un desafío razonable: "¿por qué no llamar al LLM una vez con la entrada del usuario y dejar que conjugue? ¿Por qué todo el bucle?"

Tres razones:

  1. Guardrails. La máscara enum §A13 (Fase 30) confina al modelo al alcance de 20 verbos. Sin el bucle, el modelo emite una frase; con el bucle, la herramienta de conjugación devuelve un resultado tipado que el agente puede verificar estructuralmente.
  2. Composabilidad. Mañana, un aprendiz pregunta "¿es correcta esta frase italiana?". Una nueva herramienta (italian_conjugate) se enchufa al bucle; el código del agente no cambia. La generación monolítica no puede hacer eso.
  3. Trazas de razonamiento. El scratchpad es el rastro de auditoría. Cuando el agente da la respuesta equivocada, puedes reproducir la traza y ver si el modelo eligió la herramienta equivocada, parseó mal el resultado o fabricó la respuesta a pesar de una respuesta correcta de la herramienta. Esta es la diferencia entre "el modelo mintió" (no testeable) y "el modelo mintió en el paso 3 después de que la herramienta conjugate devolviera went" (debuggable).

El agente de la Fase 32 es el bucle más pequeño que te compra las tres propiedades. Los agentes de producción (Claude Code, la API de Assistants de OpenAI) son este bucle con más herramientas, más memoria y más guardrails.

Citas

  • Yao, Zhao, Yu, Du, Shafran, Narasimhan, Cao. ReAct: Synergizing Reasoning and Acting in Language Models. ICLR 2023. arXiv:2210.03629.
  • Schick et al. Toolformer: Language Models Can Teach Themselves to Use Tools. NeurIPS 2023. arXiv:2302.04761.
  • Anthropic. Building effective agents (engineering blog, 2024-12-20). Guía pragmática sobre diseño de bucles de agente; argumenta contra la complejidad prematura.

Recapitulación en un párrafo

El agente tutor de gramática §A13 es un bucle de 5 estados: observe → reason → tool_call → observe → … → answer. Las transiciones están enmascaradas por JSON-Schema para que el modelo solo pueda emitir una de dos siguientes acciones estructuralmente válidas. Tres puertas de terminación independientes (cap de turnos, cap de tool calls, detector de no-progreso) evitan bucles infinitos; el ejercicio /break (siguiente archivo) las elimina a propósito. Cuatro modos de fallo canónicos — bucle infinito, herramienta alucinada, ceguera ante errores de herramienta, respuesta-errónea-con-citas — son cada uno el resultado de un guardrail ausente. El bucle de la Fase 32 es el sustrato; la pista de extensión X3 se sienta encima para alinear la calidad del paso reason. La arquitectura completa son ~30 líneas de Python más el decoder enmascarado y el cliente MCP construidos en las Fases 30 y 31.

Siguiente: lab/01-tutor-end-to-end.md para cablear todas las fases juntas y ejecutar el agente sobre 30 frases §A13 canónicas.