Facsímil 05 · Completo

Agentes y orquestación

Agentes, herramientas, memoria, SDKs, permisos, evaluación y límites operativos explicados como sistemas que toman decisiones alrededor del modelo.

Contenido disponible
11 de 11 capítulos listos
Contenido completo, pendiente de revisión editorial final.
Estado editorial
Completo
Lectura web generada desde los capítulos Markdown originales.

Sobre esta edición

Esta página se genera desde capítulos Markdown propios del facsímil. Las fórmulas se renderizan con KaTeX, los mapas con Mermaid y las notas al pie se mantienen junto al texto para leer el facsímil como una pieza autónoma, no como una exportación del taller.

Capítulo 01

Facsímil 5 · Agentes y orquestación

Capítulo 01: Agente o prompt: cuándo merece la pena actuar

La pregunta que abre el facsímil

Venimos de construir una caja de herramientas: APIs, modelos locales, embeddings, RAG, SQL, evaluación, trazas y laboratorios mínimos. Ahora aparece una tentación normal: si ya tenemos herramientas, ¿por qué no dejar que un modelo las coordine?

La respuesta corta es: a veces sí. La respuesta útil es: solo cuando la tarea necesita estado, acciones, observaciones y criterio de parada. Si lo único que necesitas es redactar, clasificar, transformar un texto o devolver JSON en una sola vuelta, un prompt bien diseñado puede ser mejor que un agente.

Este facsímil va de esa frontera. No vamos a tratar los agentes como moda ni como caja negra. Los vamos a tratar como sistemas de decisión alrededor de un modelo. Un agente puede ser muy valioso, pero también introduce más coste, más latencia, más superficie de fallo, más necesidad de trazas y más responsabilidad de diseño.

Qué no es un agente

Un agente no es un prompt largo. Puedes escribir una instrucción enorme, con muchas reglas y ejemplos, y seguir teniendo una sola llamada al modelo. Eso puede estar bien. Pero no convierte el sistema en agente.

Un agente tampoco es “autonomía total”. La autonomía se diseña por grados. Un sistema puede leer documentos sin pedir permiso, preparar una propuesta, pedir confirmación antes de enviar algo y bloquear acciones que no estén dentro de su alcance. Llamarlo agente no significa entregarle todas las llaves.

Y un agente no es una excusa para no diseñar. Si no sabes qué herramientas puede usar, qué datos ve, qué permisos tiene, cómo se mide el éxito y cuándo debe parar, el agente no está “razonando libremente”: está operando sin contrato. Ahí es donde nacen muchos fallos caros.

Qué sí es un agente

En IA clásica, Russell y Norvig definen un agente como algo que percibe su entorno y actúa sobre él para maximizar una medida de rendimiento.1 Nilsson ya conectaba búsqueda, planificación y agentes como formas de decidir acciones en un espacio de estados.2 En sistemas con LLMs, cambiamos las piezas, pero no la idea de fondo.

Un agente moderno suele tener estas partes:

PiezaPregunta que respondeEjemplo
Objetivo¿Qué intenta conseguir?“Encuentra por qué falla este test”.
Estado¿Qué sabe hasta ahora?Archivos leídos, errores vistos, plan activo.
Acciones¿Qué puede hacer?Leer fichero, ejecutar test, consultar API, pedir aclaración.
Observaciones¿Qué volvió después de actuar?Salida del test, respuesta de API, error validado.
Política de decisión¿Qué paso toma ahora?Elegir herramienta, responder, repetir o parar.
Criterio de parada¿Cuándo termina?Test pasa, falta permiso, presupuesto agotado.

La diferencia con un prompt aislado es la trayectoria. Un prompt aislado produce una salida. Un agente produce una secuencia: observa, decide, actúa, vuelve a observar y decide si seguir.

Esta idea no empieza con los LLMs. El General Problem Solver de Newell, Shaw y Simon ya formulaba resolución de problemas como búsqueda guiada por objetivos, diferencias entre estado actual y meta, y selección de operadores.3 Lo nuevo aquí no es querer decidir pasos; lo nuevo es que el modelo de lenguaje participa en interpretar el objetivo, elegir herramientas y resumir observaciones.

Memoria no significa meter más texto en el prompt

Aquí conviene frenar un segundo, porque la palabra memoria se usa para demasiadas cosas. En un chatbot sencillo, mucha gente llama “memoria” a que el modelo vea mensajes anteriores en el prompt. Eso es contexto, no memoria en sentido operativo. El modelo no recuerda por sí mismo: recibe tokens en una llamada concreta.

En agentes, necesitamos separar tres capas:

CapaQué esDónde viveQué problema resuelveQué problema crea
Contexto del promptTexto que entra en la llamada actual.Ventana de contexto del modelo.Da información inmediata para responder o decidir.Si metes todo, sube coste y baja foco.
Estado de sesiónHistorial y variables de una ejecución concreta.Base de datos, checkpoint, sesión o runtime.Permite continuar una tarea sin reconstruirla desde cero.Si no está estructurado, nadie sabe qué pasó.
Memoria persistenteHechos, decisiones, preferencias o aprendizajes reutilizables.Store externo, RAG, base vectorial, grafo o tabla.Permite recordar entre sesiones o tareas distintas.Si no se corrige o caduca, contamina decisiones futuras.

La documentación del Agents SDK distingue sesiones que mantienen historial entre ejecuciones y pueden usar distintos backends, como SQLite, Redis, SQLAlchemy, MongoDB o sesiones gestionadas por OpenAI.4 Google ADK separa Session y State, útiles para una conversación concreta, de MemoryService, pensado como conocimiento de largo plazo consultable por el agente.5 LangGraph, por su parte, habla de persistencia mediante checkpoints de estado, lo que permite memoria conversacional, reanudación, inspección humana y recuperación ante fallos.6

La lectura práctica es sencilla: la memoria nueva de un agente no debería ser un cajón de texto pegado al prompt. Debe tener operaciones: escribir, buscar, actualizar, borrar, caducar, auditar y explicar por qué se recuperó. Si no puedes editar una memoria falsa, caducar una memoria vieja o saber qué memoria influyó en una decisión, no tienes memoria fiable; tienes arrastre de contexto.

Un ejemplo cercano: si un agente de proyecto aprende “este repositorio usa npm test”, eso puede guardarse como memoria de proyecto. Pero debe tener fuente, fecha y posibilidad de corrección. Si mañana el repositorio cambia a pnpm test, la memoria vieja no puede seguir entrando en todas las decisiones como si fuera verdad permanente.

Estado del arte con fecha de corte

Fecha de corte: 10 de junio de 2026.
Fuentes consultadas ese día: referencias trabajadas en el libro sobre agentes clásicos, ReAct, Toolformer, function calling, OpenAI Agents SDK, sesiones y memoria, Google ADK Memory, LangGraph persistence, estrategias agentic en LlamaIndex, Anthropic tool use, Claude Code y principios de mínimo privilegio.

ReAct propuso combinar razonamiento y acción en LLMs para intercalar pasos de pensamiento con llamadas a entornos o herramientas.7 Toolformer exploró cómo un modelo podía aprender a usar herramientas externas mediante ejemplos generados automáticamente.8 No son recetas cerradas para producto, pero sí cambiaron la conversación: el modelo ya no solo responde; puede decidir cuándo necesita una herramienta.

En documentación práctica, OpenAI describe function calling como forma de dar herramientas al modelo mediante esquemas y recibir argumentos estructurados.9 Su Agents SDK se presenta como una capa para coordinar agentes, herramientas, handoffs, guardrails y trazas.10 LlamaIndex agrupa estrategias agentic como routing, transformaciones de consulta, motores de subpreguntas y agentes sobre datos.11

La lección estable no es una librería concreta. La lección estable es esta: cuando un sistema decide acciones, el diseño debe separar modelo, herramientas, permisos, estado, observaciones, límites y evaluación.

La fórmula mínima

Ejemplo de fórmula. Podemos escribir un agente como un bucle discreto. En cada paso tt, el sistema tiene un estado:

st=(g,ht,ot,ct,mt,bt)s_t = (g, h_t, o_t, c_t, m_t, b_t)
SímboloSignificadoEjemplo concreto
sts_tEstado del sistema en el paso tt.Lo que el agente sabe tras leer logs y ejecutar un test.
ggObjetivo de la tarea.“Diagnosticar por qué falla el login”.
hth_tHistorial útil y resumido.Archivos leídos, hipótesis descartadas, pasos hechos.
oto_tÚltima observación recibida.“El test falla por timeout en /auth/callback”.
ctc_tContexto autorizado para decidir.Instrucciones del proyecto, contrato de tool, política de permisos.
mtm_tMemorias recuperadas y vigentes para esta decisión.“Este proyecto ejecuta tests con pnpm test, confirmado ayer”.
btb_tPresupuesto restante.6 pasos, 30 segundos, 2 llamadas externas.

Desde ese estado hay un conjunto de acciones disponibles:

atA(st)a_t \in A(s_t)
SímboloSignificadoEjemplo concreto
ata_tAcción elegida en el paso tt.Ejecutar test_login, leer auth.ts, pedir aclaración.
A(st)A(s_t)Acciones permitidas desde el estado actual.Si no hay permiso de escritura, “editar archivo” no está en el conjunto.

Ejemplo de fórmula. La política de decisión elige una acción. Una forma práctica de pensarlo es maximizar utilidad esperada y restar coste y riesgo operativo:

at=argmaxaA(st)[U(a,st)C(a,st)R(a,st)]a_t = \arg\max_{a \in A(s_t)} \left[ U(a, s_t) - C(a, s_t) - R(a, s_t) \right]
SímboloSignificadoEjemplo concreto
argmax\arg\maxAcción que obtiene mejor puntuación.Elegir leer el error exacto antes que modificar código.
U(a,st)U(a, s_t)Utilidad esperada de la acción.Cuánto reduce la incertidumbre o acerca al objetivo.
C(a,st)C(a, s_t)Coste de la acción.Tokens, latencia, dinero, tiempo de herramienta.
R(a,st)R(a, s_t)Riesgo operativo de la acción.Escribir datos, enviar algo, tocar producción, usar permiso alto.

Después de actuar, llega una observación y el estado se actualiza:

st+1=T(st,at,ot+1)s_{t+1} = T(s_t, a_t, o_{t+1})
SímboloSignificadoEjemplo concreto
TTFunción de transición del sistema.Actualiza historial, presupuesto, errores y próximos pasos.
ot+1o_{t+1}Observación devuelta por la acción.Resultado de test, filas SQL, respuesta de API, error de schema.
st+1s_{t+1}Nuevo estado.El agente ya sabe que el fallo no está en el router, sino en sesión.

Esto no obliga a implementar un agente con ecuaciones explícitas. Sirve para no perder el mapa. Si una herramienta no devuelve observación estructurada, el estado se contamina. Si no hay presupuesto, el bucle puede gastar de más. Si no hay criterio de parada, el agente puede seguir aunque ya no esté aprendiendo nada.

El primer criterio: ¿una vuelta o varias?

El diagnóstico más simple es preguntar si la tarea cabe en una sola vuelta.

Si la tarea...Suele bastar con promptEmpieza a pedir agente
Necesita redactar, resumir o clasificar una entrada cerradaNo necesariamente
Necesita consultar estado realNo
Necesita elegir entre varias herramientasNo
Necesita probar, observar y corregirNo
Tiene acciones con permisosNoSí, pero con límites
Tiene éxito verificable por tests o métricasA veces
Requiere memoria operativa entre pasosNo

Un caso cercano: “resume este correo” es prompt. “Busca en el CRM los últimos pedidos del cliente, comprueba si hay una incidencia abierta, redacta una respuesta y espera aprobación” ya no es solo prompt. Ahí hay herramientas, estado, permisos y criterio de parada.

La pregunta profesional no es “¿podemos hacerlo con un agente?”. Casi siempre podremos. La pregunta es: ¿el agente reduce trabajo real más de lo que añade coste, latencia y complejidad?

Mapa visual de decisión

¿Prompt, tool call o agente? La decisión depende de estado, acciones, observaciones, permisos y criterio de parada. Tarea de entrada objetivo + datos + límites ¿qué debe quedar probado? ¿Basta una salida? resumir · clasificar redactar · transformar si sí: prompt Prompt aislado una llamada sin trayectoria operativa ¿Necesita consultar? datos · API · cálculo pero el flujo es conocido si sí: tool call Tool call schema + permisos resultado estructurado ¿Necesita bucle? decidir · actuar observar · corregir si sí: agente Agente estado + trayectoria criterio de parada Regla práctica si no puedes trazarlo, no puedes operarlo Memoria ≠ prompt se recupera y se corrige Primer diseño empieza simple y mide antes de escalar IA para gente curiosa / Facsímil 05 / Capítulo 01 / 686f6c61

La figura separa tres niveles. El prompt resuelve una transformación cerrada. La llamada a herramienta resuelve una consulta o acción conocida. El agente aparece cuando el sistema debe elegir varios pasos según lo que vaya observando.

Árbol de decisión para no complicarte demasiado pronto

El árbol siguiente sirve para una primera conversación de diseño. No sustituye la evaluación, pero evita una confusión muy frecuente: llamar agente a cualquier cosa que use un modelo.

flowchart TD
    START["Tengo una tarea con IA"]
    Q1{"¿La salida se puede producir\ncon la información que ya entra?"}
    PROMPT["Usa prompt aislado\n+ salida estructurada si hace falta"]
    Q2{"¿Necesita consultar\nun dato vivo o calcular algo?"}
    Q3{"¿La herramienta y el orden\nde pasos son conocidos?"}
    TOOL["Usa tool call o workflow fijo\nmodelo + función + validación"]
    Q4{"¿Debe decidir el siguiente paso\nsegún observaciones intermedias?"}
    Q5{"¿Hay permisos, coste,\nefectos externos o riesgo?"}
    AGENT_LIMITED["Diseña agente limitado\ncon manifest, permisos y trazas"]
    WORKFLOW["Diseña workflow orquestado\nsin autonomía dinámica"]
    NOAI["No uses IA generativa\nregla, SQL, UI o proceso"]
    Q6{"¿Hay criterio de parada\ny eval de trayectoria?"}
    HOLD["No lo despliegues aún\nfalta laboratorio y gate"]
    AGENT["Agente candidato\ncon memoria, tools y evaluación"]

    START --> Q1
    Q1 -->|"sí"| PROMPT
    Q1 -->|"no"| Q2
    Q2 -->|"no"| NOAI
    Q2 -->|"sí"| Q3
    Q3 -->|"sí"| TOOL
    Q3 -->|"no"| Q4
    Q4 -->|"no"| WORKFLOW
    Q4 -->|"sí"| Q5
    Q5 -->|"sí"| AGENT_LIMITED
    Q5 -->|"no"| Q6
    AGENT_LIMITED --> Q6
    Q6 -->|"no"| HOLD
    Q6 -->|"sí"| AGENT

IA para gente curiosa / Facsímil 05 / Capítulo 01 / 686f6c61

La lectura del árbol es sencilla:

Si respondes...Lo razonable es empezar con...Por qué
“Sí, todo está en la entrada”Prompt aisladoNo necesitas estado ni herramientas.
“Necesito un dato vivo, pero sé exactamente cuál”Tool call o workflow fijoEl modelo puede preparar argumentos; tu sistema ejecuta y valida.
“Necesito varios pasos, pero el orden es conocido”Workflow orquestadoNo hace falta que el modelo decida cada paso.
“El siguiente paso depende de lo observado”Agente limitadoYa hay bucle de decisión, acción y observación.
“Hay permisos o efectos externos”Agente con manifest y aprobaciónLa autonomía debe estar graduada por acción.
“No tengo eval ni criterio de parada”No desplegar todavíaSin trayectoria medible, no sabes si funciona de forma repetible.

Este árbol tiene una moraleja: el agente suele aparecer tarde, no al principio. Primero pregunta si basta con una llamada. Luego si basta con una herramienta. Luego si basta con un workflow. Solo cuando la tarea necesita decidir dinámicamente según observaciones empieza a merecer la palabra agente.

En el día a día

Imagina un equipo de soporte universitario. Una persona escribe: “No puedo matricularme y creo que tengo un pago pendiente”. Hay varias formas de resolverlo.

Un prompt podría redactar una respuesta amable. Una tool podría consultar si el expediente tiene pagos pendientes. Un agente podría hacer algo más amplio: leer la política de matrícula vigente, consultar pagos, comprobar si falta documentación, preparar una respuesta con evidencia y pedir aprobación antes de enviarla.

La tercera opción suena más potente, pero solo merece la pena si la tarea se repite, si la decisión depende de datos vivos y si el resultado se puede verificar. Si el equipo recibe dos casos al mes, quizá baste con una plantilla y una consulta manual. Si recibe miles, con reglas claras y trazas, el agente empieza a tener sentido.

Por qué debería importarte

Un agente cambia la unidad de evaluación. Ya no basta con leer la respuesta final. Hay que mirar la trayectoria: qué datos usó, qué herramienta eligió, qué observó, cuánto costó, qué permisos aplicó y por qué decidió parar.

Esta es la razón por la que el facsímil 04 terminó con laboratorios y trazas. Antes de coordinar herramientas, necesitamos saber medir piezas pequeñas. Un agente no elimina esa disciplina: la multiplica.

Saltzer y Schroeder formularon principios clásicos como mínimo privilegio y mediación completa: cada acceso relevante debe comprobarse y cada componente debe operar con el permiso mínimo necesario.12 En agentes, esa idea deja de ser abstracta: cada herramienta debe tener un alcance explícito, y cada acción con efecto real debe pasar por una decisión externa al modelo.

Cómo lo estructuran OpenAI y Claude

La forma exacta cambia entre proveedores, pero la arquitectura que hay debajo se parece mucho. Un sistema de agentes no es “un modelo con ganas de hacer cosas”. Es un runtime que prepara contexto, ofrece tools, ejecuta llamadas, devuelve observaciones, conserva estado, aplica permisos y deja trazas.

En OpenAI Agents SDK, un Agent se describe como un LLM configurado con instrucciones, herramientas y comportamiento opcional como handoffs, guardrails y salidas estructuradas; Agent más Runner permite que el SDK gestione turnos, tools, guardrails, handoffs y sesiones.13 Las sesiones mantienen historial entre ejecuciones y pueden apoyarse en distintos backends; eso no es “memoria humana”, sino persistencia controlada de conversación y estado.14 La trazabilidad también es explícita: el SDK registra generaciones del modelo, llamadas a herramientas, handoffs, guardrails y eventos propios como spans dentro de una traza.15

En Anthropic, la guía Building Effective Agents separa workflows y agents: un workflow sigue rutas predefinidas por código; un agent deja que el LLM dirija dinámicamente el proceso y el uso de herramientas.16 En la API de Claude, las tools se declaran en tools con nombre, descripción y esquema de entrada; el modelo puede devolver bloques tool_use y el cliente continúa la conversación con bloques tool_result.17 En Claude Code, la memoria se organiza con archivos CLAUDE.md de distintos ámbitos.18 Los subagentes se definen como Markdown con frontmatter, propósito, herramientas permitidas y ventana de contexto separada.19

La traducción a piezas queda así:

Pieza del libroOpenAI Agents SDKClaude / AnthropicQué debes mirar como ingeniero
Instruccionesinstructions, prompts y configuración del agente.System prompt, CLAUDE.md, configuración de subagente.Qué manda, con qué prioridad y dónde vive versionado.
ToolsFunction tools, hosted tools, agents as tools.tools, tool_use, tool_result, tools de Claude Code.Schema, descripción, permisos, timeout, errores y límites de salida.
Estado de sesiónSession, conversaciones, backends de persistencia.Conversación, memoria de Claude Code, contexto del subagente.Qué se guarda entre pasos y cómo se corrige.
DelegaciónHandoffs o agentes expuestos como tools.Subagentes con contexto y tools propias.Cuándo delega, qué contexto pasa y qué vuelve.
ControlesGuardrails, output types, approval gates, policies propias.Permisos de tools, límites del agente, confirmaciones de Claude Code.Qué acción se permite, se bloquea o pide revisión.
TrazasTraces y spans del SDK.Historial de tool use, logs del runtime, trazas propias.Cómo reconstruyes la trayectoria sin leer una novela.

Fíjate en la idea común: el proveedor puede darte SDK, API o entorno de código, pero el diseño sigue siendo tuyo. Tú decides qué tools existen, qué memoria entra, qué permisos se aplican, qué salida se acepta y qué traza basta para depurar.

Manos a la obra

Práctica: diseñar un manifest operativo.

Kit ejecutable de este capítulo: kit descargable.

# Descomprime el ZIP del capítulo y ejecuta estos comandos dentro de esa carpeta
python3 ops/run_f5_practices.py --chapter c01 --write --fail-on-invalid

Esta práctica sustituye al típico “clasificador de tareas” porque deja algo más útil: una plantilla mínima que podrías adaptar a un sistema real. Vamos a escribir un manifest operativo de agente y un validador pequeño.

El objetivo no es montar una integración con OpenAI o Claude todavía. El objetivo es aprender a especificar un agente antes de darle autonomía: qué tools existen, qué permiso necesita cada una, qué memoria se puede recuperar, qué eventos deben quedar en la traza y cuándo se detiene.

El caso será el mismo que venimos usando: soporte académico para matrícula y pagos pendientes. El agente podrá leer política, consultar saldo, preparar una respuesta y pedir aprobación antes de enviar nada.

import json
from datetime import datetime


AGENT_MANIFEST = {
    "name": "matricula-support-agent",
    "goal": "Resolver consultas de matrícula con evidencia y sin enviar nada sin aprobación.",
    "memory": {
        "context_prompt": ["instrucciones_del_sistema", "peticion_usuario"],
        "session_state": ["case_id", "pasos", "observaciones", "aprobaciones"],
        "persistent_memory": ["politicas_vigentes", "preferencias_de_formato"],
    },
    "stop_rules": ["done", "approval_required", "blocked", "budget_exhausted"],
    "budget": {"max_steps": 6, "max_tool_calls": 4},
    "tools": {
        "buscar_politica_matricula": {
            "purpose": "recuperar política vigente de matrícula",
            "input_schema": {"query": "string"},
            "output_schema": {"policy_id": "string", "summary": "string"},
            "risk_class": "read_scoped",
            "permission": "allow",
            "side_effect": "none",
            "timeout_ms": 1000,
            "trace_event": "tool.policy.search",
        },
        "consultar_saldo_expediente": {
            "purpose": "consultar pagos pendientes de un expediente autenticado",
            "input_schema": {"case_id": "string"},
            "output_schema": {"pending_eur": "number", "campus": "string"},
            "risk_class": "read_private",
            "permission": "authenticated_read",
            "side_effect": "none",
            "timeout_ms": 1200,
            "trace_event": "tool.balance.read",
        },
        "preparar_respuesta": {
            "purpose": "preparar una respuesta con evidencias",
            "input_schema": {"observations": "array"},
            "output_schema": {"message": "string", "evidence": "array"},
            "risk_class": "compose",
            "permission": "allow",
            "side_effect": "none",
            "timeout_ms": 800,
            "trace_event": "tool.reply.prepare",
        },
        "solicitar_envio_respuesta": {
            "purpose": "pedir aprobación para enviar la respuesta al estudiante",
            "input_schema": {"message": "string", "recipient_id": "string"},
            "output_schema": {"approval_id": "string", "status": "string"},
            "risk_class": "external_action",
            "permission": "approval_required",
            "side_effect": "external_message",
            "timeout_ms": 1000,
            "trace_event": "tool.reply.approval",
        },
    },
}

TASKS = [
    {
        "id": "caso_a",
        "request": "¿Hasta cuándo puedo modificar mi matrícula?",
        "intent": "policy_question",
        "authenticated": True,
        "wants_send": False,
    },
    {
        "id": "caso_b",
        "request": "No puedo matricularme y quizá tengo un pago pendiente. Prepara respuesta para enviar.",
        "intent": "case_resolution",
        "authenticated": True,
        "wants_send": True,
    },
]


def validate_manifest(manifest):
    errors = []
    required_tool_fields = {
        "purpose",
        "input_schema",
        "output_schema",
        "risk_class",
        "permission",
        "side_effect",
        "timeout_ms",
        "trace_event",
    }
    for layer in ["context_prompt", "session_state", "persistent_memory"]:
        if layer not in manifest["memory"]:
            errors.append(f"falta capa de memoria: {layer}")

    for name, tool in manifest["tools"].items():
        missing = sorted(required_tool_fields - set(tool))
        if missing:
            errors.append(f"{name} sin campos: {missing}")
        if tool["side_effect"] != "none" and tool["permission"] != "approval_required":
            errors.append(f"{name} cambia algo sin aprobación explícita")

    if "approval_required" not in manifest["stop_rules"]:
        errors.append("falta regla de parada approval_required")

    return errors


def trace_event(trace, event, **data):
    trace.append(
        {
            "at": datetime(2026, 5, 26, 12, 0, 0).isoformat(),
            "event": event,
            "data": data,
        }
    )


def permission_decision(tool, task):
    policy = tool["permission"]
    if policy == "allow":
        return "allow"
    if policy == "authenticated_read":
        return "allow" if task["authenticated"] else "blocked"
    if policy == "approval_required":
        return "approval_required"
    return "blocked"


def execute_tool(tool_name, observations):
    if tool_name == "buscar_politica_matricula":
        return {"policy_id": "matricula-2026", "summary": "modificación hasta el 15 de septiembre"}
    if tool_name == "consultar_saldo_expediente":
        return {"pending_eur": 180.0, "campus": "Norte"}
    if tool_name == "preparar_respuesta":
        evidence = [obs["summary"] if "summary" in obs else f"saldo pendiente: {obs['pending_eur']} EUR" for obs in observations]
        return {"message": "Respuesta preparada con evidencias.", "evidence": evidence}
    raise ValueError(f"tool desconocida: {tool_name}")


def plan_for(task):
    if task["intent"] == "policy_question":
        return ["buscar_politica_matricula", "preparar_respuesta"]
    return [
        "buscar_politica_matricula",
        "consultar_saldo_expediente",
        "preparar_respuesta",
        "solicitar_envio_respuesta",
    ]


def run_agent(task, manifest):
    trace = []
    observations = []
    tool_calls = 0

    trace_event(trace, "task.received", task_id=task["id"], request=task["request"])
    trace_event(trace, "context.build", layers=manifest["memory"]["context_prompt"])
    trace_event(trace, "memory.retrieve", stores=manifest["memory"]["persistent_memory"])

    for step, tool_name in enumerate(plan_for(task), start=1):
        if step > manifest["budget"]["max_steps"] or tool_calls >= manifest["budget"]["max_tool_calls"]:
            trace_event(trace, "final.status", status="budget_exhausted")
            return {"task_id": task["id"], "status": "budget_exhausted", "trace": trace}

        tool = manifest["tools"][tool_name]
        decision = permission_decision(tool, task)
        trace_event(trace, "permission.check", tool=tool_name, decision=decision, risk=tool["risk_class"])

        if decision != "allow":
            trace_event(trace, "final.status", status=decision, pending_tool=tool_name)
            return {"task_id": task["id"], "status": decision, "observations": observations, "trace": trace}

        tool_calls += 1
        trace_event(trace, "tool.call", tool=tool_name, tool_event=tool["trace_event"])
        output = execute_tool(tool_name, observations)
        observations.append(output)
        trace_event(trace, "tool.result", tool=tool_name, output=output)

    trace_event(trace, "final.status", status="done")
    return {"task_id": task["id"], "status": "done", "observations": observations, "trace": trace}


errors = validate_manifest(AGENT_MANIFEST)
print("manifest valido:", not errors)
print("errores:", errors)

for task in TASKS:
    result = run_agent(task, AGENT_MANIFEST)
    print(result["task_id"], result["status"])
    print([event["event"] for event in result["trace"]])

Salida esperada:

manifest valido: True
errores: []
caso_a done
['task.received', 'context.build', 'memory.retrieve', 'permission.check', 'tool.call', 'tool.result', 'permission.check', 'tool.call', 'tool.result', 'final.status']
caso_b approval_required
['task.received', 'context.build', 'memory.retrieve', 'permission.check', 'tool.call', 'tool.result', 'permission.check', 'tool.call', 'tool.result', 'permission.check', 'tool.call', 'tool.result', 'permission.check', 'final.status']

Esta práctica ya se parece más a ingeniería. No decide con una etiqueta bonita; obliga a escribir qué memoria entra, qué tools existen, qué permiso necesita cada acción y qué traza queda. Si mañana lo llevas a OpenAI Agents SDK, Claude, LangGraph o un framework propio, las piezas siguen siendo reconocibles.

El resultado más importante es caso_b approval_required. El sistema ha leído política, consultado saldo y preparado respuesta, pero se detiene antes de enviar. Eso es autonomía graduada: el agente avanza donde puede y se para donde debe.

Cómo encaja todo

flowchart TD
    subgraph "Capítulo 01: agente o prompt"
        TASK["Tarea"]
        PROMPT["Prompt aislado"]
        TOOL["Tool call"]
        AGENT["Agente"]
        PROMPTCTX["Contexto del prompt"]
        STATE["Estado"]
        MEMSTORE["Memoria persistente"]
        ACTION["Acción"]
        OBS["Observación"]
        STOP["Criterio de parada"]
        TRACE["Trayectoria"]
        MANIFEST["Manifest operativo"]
    end

    subgraph "Viene del facsímil 04"
        API["APIs y contratos (F4C2)"]
        RAG["RAG y evidencia (F4C9)"]
        EVAL["Evals y trazas (F4C10-F4C13)"]
        SQL["Text-to-SQL y herramientas (F4C12)"]
        OPENAI["OpenAI Agents SDK"]
        CLAUDE["Claude / Anthropic"]
    end

    subgraph "Sigue en el facsímil 05"
        DEF["Qué es un agente (C2)"]
        TOOLS["Contratos de tool (C3)"]
        MEMORY["Memoria y handoff (C4)"]
        ARCH["Arquitecturas (C5)"]
        HARNESS["Harness (C6)"]
        SDK["SDKs de agentes (C7)"]
        PERMS["Permisos (C8)"]
        ORCH["Orquestación (C9)"]
        AEVAL["Evaluación de agentes (C10)"]
    end

    TASK -->|"si basta una vuelta"| PROMPT
    TASK -->|"si consulta algo concreto"| TOOL
    TASK -->|"si necesita bucle"| AGENT
    AGENT -->|"mantener"| STATE
    AGENT -->|"construir"| PROMPTCTX
    MEMSTORE -->|"recuperar hacia"| PROMPTCTX
    PROMPTCTX -->|"alimentar"| STATE
    AGENT -->|"elegir"| ACTION
    ACTION -->|"producir"| OBS
    OBS -->|"actualizar"| STATE
    STATE -->|"evaluar"| STOP
    AGENT -->|"dejar"| TRACE
    MANIFEST -->|"definir"| AGENT
    MANIFEST -->|"versionar"| TOOL

    API -. "define" .-> TOOL
    RAG -. "aporta" .-> OBS
    SQL -. "ejecuta" .-> ACTION
    EVAL -. "mide" .-> TRACE
    OPENAI -. "estructura como" .-> SDK
    CLAUDE -. "estructura como" .-> SDK

    AGENT -->|"se formaliza en"| DEF
    TOOL -->|"se endurece en"| TOOLS
    STATE -->|"se gestiona en"| MEMORY
    MEMSTORE -->|"se diseña en"| MEMORY
    AGENT -->|"adopta"| ARCH
    TRACE -->|"alimenta"| HARNESS
    AGENT -->|"se implementa con"| SDK
    ACTION -->|"requiere"| PERMS
    AGENT -->|"se coordina con"| ORCH
    TRACE -->|"se juzga en"| AEVAL

IA para gente curiosa / Facsímil 05 / Capítulo 01 / 686f6c61

Vocabulario aprendido

TérminoDefinición
Prompt aisladoPetición cerrada a un modelo, sin trayectoria ni acciones externas.
AgenteSistema que decide acciones, usa herramientas, observa resultados y avanza hacia un objetivo.
EstadoMemoria operativa verificable de lo que sabe, hizo y aún puede hacer el sistema.
Contexto del promptTexto y artefactos que entran en la llamada actual al modelo.
Memoria de sesiónHistorial persistido de una conversación o ejecución concreta.
Memoria persistenteHechos, preferencias o decisiones recuperables más allá de una sesión.
AcciónPaso ejecutable o consultivo: tool, cálculo, lectura, pregunta o respuesta.
ObservaciónResultado que vuelve al sistema después de una acción.
TrayectoriaSecuencia de estados, acciones y observaciones.
Criterio de paradaRegla que decide si terminar, repetir, pedir permiso o escalar a una persona.
Presupuesto operativoLímite de pasos, tokens, coste, latencia o herramientas.
Manifest operativoEspecificación versionable de objetivo, tools, permisos, memoria, trazas y parada.

Dónde solía tropezar yo

ErrorPor qué es un errorAntídoto
Llamar agente a cualquier prompt largoUn prompt largo no observa resultados ni ejecuta acciones.Preguntar si existe trayectoria: estado, acción, observación y parada.
Añadir agente antes de medirLa complejidad puede tapar un problema que resolvía una tool simple.Probar primero prompt o tool call y medir el cuello real.
Dar herramientas genéricasUna tool demasiado amplia es difícil de auditar y controlar.Diseñar herramientas con nombre de dominio, schema, límites y errores claros.
Confundir contexto con estadoEl contexto son tokens; el estado debe ser verificable y persistente cuando importa.Guardar decisiones, permisos, artefactos y resultados fuera del prompt.
Confundir memoria con prompt largoEl sistema arrastra información sin saber si sigue siendo válida.Tratar la memoria como store: escritura, búsqueda, actualización, caducidad y auditoría.
Evaluar solo la respuesta finalUn resultado correcto puede haber llegado por una trayectoria mala.Medir outcome y trayectoria: herramientas, observaciones, coste y parada.

Antes de pasar página

  • ¿Puedo explicar por qué un agente no es simplemente un prompt largo?
  • ¿Sé distinguir prompt aislado, tool call y agente?
  • ¿Puedo escribir qué contiene sts_t en un agente sencillo?
  • ¿Entiendo qué significa elegir atA(st)a_t \in A(s_t)?
  • ¿Puedo explicar por qué una observación debe ser estructurada?
  • ¿Sé distinguir contexto del prompt, estado de sesión y memoria persistente?
  • ¿Puedo explicar por qué una memoria debe poder corregirse o caducar?
  • ¿Puedo leer un sistema tipo OpenAI Agents SDK o Claude Code y señalar instrucciones, tools, estado, memoria, permisos y trazas?
  • ¿Puedo escribir un manifest operativo mínimo antes de implementar el agente?
  • ¿Sé decir cuándo una tarea necesita varias vueltas?
  • ¿Puedo justificar por qué los permisos no los decide el modelo?
  • ¿Sé qué métrica miraría antes de complicar una solución?

En resumen

Idea fuerzaDetalle
Un agente es una trayectoria, no una respuesta.La diferencia está en estado, acciones, observaciones y criterio de parada.
No todo merece agente.Si una sola llamada o una tool conocida resuelven el problema, empezar ahí suele ser mejor.
Memoria no es contexto acumulado.La memoria útil se recupera, se versiona, se corrige y se audita fuera del prompt.
OpenAI y Claude usan piezas reconocibles.Cambia el nombre de la API, pero siguen apareciendo instrucciones, tools, estado, memoria, controles y trazas.
La autonomía se diseña por grados.Leer, redactar, escribir, enviar o modificar no tienen el mismo permiso ni el mismo coste.
Las trazas son parte del producto.Sin trayectoria observable, no puedes depurar ni evaluar bien un agente.

Para saber más

Anthropic. (2024). Building Effective Agents. Artículo técnico.

Anthropic. (2026). How to implement tool use. Documentación oficial.

Anthropic. (2026). Manage Claude's memory. Documentación oficial.

Anthropic. (2026). Subagents. Documentación oficial.

Google. (2026). Agent Development Kit: Memory. Documentación oficial.

LangChain. (2026). LangGraph persistence. Documentación oficial.

LlamaIndex. (2026). Agentic strategies. Documentación oficial.

Newell, A., Shaw, J. C. y Simon, H. A. (1959). Report on a General Problem-Solving Program. Proceedings of the International Conference on Information Processing, 256-264.

Nilsson, N. J. (1998). Artificial intelligence: a new synthesis. Morgan Kaufmann.

OpenAI. (2026). Agents SDK. Documentación oficial.

OpenAI. (2026). Agents SDK: Agents. Documentación oficial.

OpenAI. (2026). Agents SDK: Sessions. Documentación oficial.

OpenAI. (2026). Agents SDK: Tracing. Documentación oficial.

OpenAI. (2026). Function calling. Documentación oficial.

Russell, S. y Norvig, P. (2021). Artificial intelligence: a modern approach (4.ª ed.). Pearson.

Saltzer, J. H. y Schroeder, M. D. (1975). The protection of information in computer systems. Proceedings of the IEEE, 63(9), 1278-1308. https://doi.org/10.1109/PROC.1975.9939

Schick, T., Dwivedi-Yu, J., Dessì, R., Raileanu, R., Lomeli, M., Zettlemoyer, L., Cancedda, N. y Scialom, T. (2023). Toolformer: Language Models Can Teach Themselves to Use Tools. https://doi.org/10.48550/arXiv.2302.04761

Yao, S., Zhao, J., Yu, D., Du, N., Shafran, I., Narasimhan, K. y Cao, Y. (2023). ReAct: Synergizing Reasoning and Acting in Language Models. International Conference on Learning Representations. https://arxiv.org/abs/2210.03629

Notas

  1. Russell, S. y Norvig, P. (2021). Artificial intelligence: a modern approach (4.ª ed.). Pearson. Su definición de agente como sistema que percibe y actúa es el punto de partida conceptual de este facsímil.

  2. Nilsson, N. J. (1998). Artificial intelligence: a new synthesis. Morgan Kaufmann. Nilsson presenta agentes y planificación como problemas de acción, estado y objetivo.

  3. Newell, A., Shaw, J. C. y Simon, H. A. (1959). Report on a General Problem-Solving Program. Proceedings of the International Conference on Information Processing, 256-264. Es una referencia clásica para entender la idea de descomponer una tarea en estados, diferencias y operadores.

  4. OpenAI. (2026). Agents SDK: Sessions. Documentación oficial. Consultado el 10 de junio de 2026. La documentación describe sesiones como memoria de conversación entre runs y lista backends de persistencia.

  5. Google. (2026). Agent Development Kit: Memory. Documentación oficial. Consultado el 10 de junio de 2026. La documentación distingue memoria de corto plazo basada en sesión/estado y conocimiento de largo plazo mediante servicios de memoria.

  6. LangChain. (2026). LangGraph persistence. Documentación oficial. Consultado el 10 de junio de 2026.

  7. Yao, S. et al. (2023). ReAct: Synergizing Reasoning and Acting in Language Models. International Conference on Learning Representations. https://arxiv.org/abs/2210.03629

  8. Schick, T. et al. (2023). Toolformer: Language Models Can Teach Themselves to Use Tools. https://doi.org/10.48550/arXiv.2302.04761

  9. OpenAI. (2026). Function calling. Documentación oficial. Consultado el 10 de junio de 2026.

  10. OpenAI. (2026). Agents SDK. Documentación oficial. Consultado el 10 de junio de 2026.

  11. LlamaIndex. (2026). Agentic strategies. Documentación oficial. Consultado el 10 de junio de 2026.

  12. Saltzer, J. H. y Schroeder, M. D. (1975). The protection of information in computer systems. Proceedings of the IEEE, 63(9), 1278-1308. https://doi.org/10.1109/PROC.1975.9939

  13. OpenAI. (2026). Agents SDK: Agents. Documentación oficial. Consultado el 10 de junio de 2026.

  14. OpenAI. (2026). Agents SDK: Sessions. Documentación oficial. Consultado el 10 de junio de 2026.

  15. OpenAI. (2026). Agents SDK: Tracing. Documentación oficial. Consultado el 10 de junio de 2026.

  16. Anthropic. (2024). Building Effective Agents. Artículo técnico. Consultado el 10 de junio de 2026.

  17. Anthropic. (2026). How to implement tool use. Documentación oficial. Consultado el 10 de junio de 2026.

  18. Anthropic. (2026). Manage Claude's memory. Documentación oficial. Consultado el 10 de junio de 2026.

  19. Anthropic. (2026). Subagents. Documentación oficial. Consultado el 10 de junio de 2026.

Capítulo 02

Facsímil 5 · Agentes y orquestación

Capítulo 02: Qué es un agente: estado, acción y observación

El bucle que convierte un modelo en sistema

En el capítulo anterior distinguimos prompt, tool call, workflow y agente. Ahora toca abrir la caja del agente con calma.

Un agente no se define por lo impresionante que suena su respuesta. Se define por un bucle: mantiene un estado, elige una acción, recibe una observación, actualiza el estado y decide si continuar. Esa frase parece simple, pero es la frontera entre “un modelo que contesta” y “un sistema que trabaja”.

Si lo llevamos a una escena concreta: una persona pide “revisa por qué no puedo matricularme”. Un modelo puede redactar consejos generales. Un agente puede consultar la política, revisar pagos, comprobar documentación pendiente, preparar una respuesta y detenerse antes de enviar nada si falta aprobación. La diferencia está en la secuencia observable.

Qué no queremos llamar agente todavía

No llamaremos agente a una cadena de prompts sin estado verificable. Si el sistema no sabe qué ocurrió en el paso anterior salvo por texto acumulado, todavía no hay una estructura operativa sólida.

Tampoco llamaremos agente a una función que siempre ejecuta los mismos pasos. Eso puede ser un workflow magnífico, y muchas veces es justo lo que conviene. Pero si el orden está cerrado de antemano y el modelo no decide el siguiente paso a partir de observaciones, no necesitamos la palabra agente.

Y no llamaremos agente a una herramienta con nombre bonito. Una tool consulta, calcula o cambia algo. Un agente decide cuándo usarla, interpreta la observación, actualiza estado y decide si falta otra acción.

La definición útil

Russell y Norvig formulan los agentes como sistemas que reciben percepciones y ejecutan acciones sobre un entorno, guiados por una medida de rendimiento.1 Nilsson presenta la acción inteligente como selección de operadores sobre estados para alcanzar objetivos.2 Esa definición clásica sigue viva. Lo que ha cambiado es que ahora el LLM puede participar en interpretar el objetivo, elegir acciones y resumir observaciones.

Para este libro, una definición operativa será:

Un agente es un sistema que mantiene un estado verificable, elige acciones disponibles, recibe observaciones estructuradas, actualiza su estado y decide cuándo parar según un objetivo, permisos, presupuesto y evidencia.

Newell, Shaw y Simon ya planteaban resolución de problemas como selección de operadores para reducir diferencias entre estado actual y meta.3 La novedad práctica de los agentes con LLM no es que exista una secuencia de pasos. Es que el lenguaje natural, las tools y el contexto hacen que el espacio de acciones sea mucho más flexible y, por tanto, más difícil de controlar.

Agente clásico y agente moderno

La comparación ayuda a no perderse:

PiezaAgente clásicoAgente con LLMPregunta de ingeniería
PercepciónSensores o entrada formal.Mensajes, documentos, resultados de tools, memoria recuperada.¿Qué entra como dato y qué entra como instrucción?
EstadoVariables del entorno.Objetivo, historial, observaciones, permisos, presupuesto, memoria.¿Qué debe persistir fuera del prompt?
AcciónOperador definido en el dominio.Tool call, pregunta al usuario, cálculo, lectura, respuesta, handoff.¿Qué acciones están permitidas desde este estado?
PolíticaRegla, búsqueda, planificador o aprendizaje.LLM más reglas, router, validadores y límites.¿Quién decide el siguiente paso y con qué restricciones?
ObservaciónNuevo percepto o resultado del operador.Salida estructurada de tool, error, evidencia, diff, test, métrica.¿La observación permite actualizar estado sin ambigüedad?
ParadaMeta alcanzada o fallo.Done, falta evidencia, falta permiso, límite de coste, revisión humana.¿Podemos demostrar por qué terminó?

Anthropic distingue workflows y agents: los workflows siguen rutas predefinidas por código; los agents dejan que el LLM dirija dinámicamente el proceso y el uso de herramientas.4 Esa distinción encaja perfectamente con nuestra definición: si no hay decisión dinámica sobre la siguiente acción, probablemente estás diseñando un workflow, no un agente.

La anatomía formal

Ejemplo de fórmula. Vamos a escribir el agente como una tupla:

A=(G,S,A,O,π,T,Ω,B)\mathcal{A} = (G, S, A, O, \pi, T, \Omega, B)
SímboloSignificadoEjemplo concreto
A\mathcal{A}Agente completo.Agente de soporte académico.
GGObjetivos o metas verificables.Resolver consulta o pedir aprobación.
SSConjunto de estados posibles.Estado con política leída, saldo consultado y respuesta preparada.
AAConjunto de acciones disponibles.Buscar política, consultar saldo, preparar respuesta, pedir aprobación.
OOConjunto de observaciones posibles.“saldo pendiente: 180 EUR”, “política vigente encontrada”.
π\piPolítica de decisión.Regla o LLM que elige la siguiente acción.
TTFunción de transición.Actualiza el estado con la observación recibida.
Ω\OmegaCriterios de parada.done, approval_required, blocked, budget_exhausted.
BBPresupuesto operativo.Máximo de pasos, tools, tokens, coste o tiempo.

Ejemplo de fórmula. La política decide la acción:

at=π(st,At,Bt)a_t = \pi(s_t, A_t, B_t)
SímboloSignificadoEjemplo concreto
ata_tAcción elegida en el paso tt.consultar_saldo_expediente.
sts_tEstado actual.Ya se leyó política, falta saber si hay saldo pendiente.
AtA_tAcciones disponibles ahora.Solo tools permitidas por estado, permiso y presupuesto.
BtB_tPresupuesto restante.Quedan 3 pasos y 2 llamadas a tools.

Ejemplo de fórmula. Las acciones disponibles no son todas las acciones imaginables. Son las que pasan precondiciones:

At={aApre(a,st)=verdaderoperm(a,st){allow,approval}}A_t = \{a \in A \mid \operatorname{pre}(a, s_t) = \text{verdadero} \land \operatorname{perm}(a, s_t) \in \{\text{allow}, \text{approval}\}\}
SímboloSignificadoEjemplo concreto
AtA_tAcciones candidatas desde el estado actual.Buscar política y consultar saldo, pero no enviar mensaje.
pre(a,st)\operatorname{pre}(a, s_t)Precondición de la acción.Para consultar saldo hace falta case_id.
perm(a,st)\operatorname{perm}(a, s_t)Decisión de permiso para esa acción.allow, approval, deny.

Después de actuar, llega una observación:

ot+1=E(at)o_{t+1} = E(a_t)
SímboloSignificadoEjemplo concreto
ot+1o_{t+1}Observación posterior a la acción.Resultado de una consulta, error de schema, test fallido.
EEEntorno o sistema que ejecuta la acción.Backend, base de datos, terminal, navegador, API.

Y el estado se actualiza:

st+1=T(st,at,ot+1)s_{t+1} = T(s_t, a_t, o_{t+1})
SímboloSignificadoEjemplo concreto
st+1s_{t+1}Estado actualizado.Incluye política leída, saldo pendiente y respuesta preparada.
TTFunción que incorpora la observación al estado.Añade evidencia, marca pasos hechos, descuenta presupuesto.

Poole, Mackworth y Goebel explican la IA computacional como una separación entre representación, razonamiento y acción.5 En agentes con LLM, esa separación es una defensa contra la confusión: el modelo puede proponer, pero el sistema debe representar, ejecutar, validar y registrar.

La arquitectura operable de un agente

Arquitectura operable de un agente El agente no es una llamada al modelo: es un sistema con estado, política, contratos, ejecución y trazabilidad. ENTRADA Tarea recibida objetivo · usuario · canal documentos · restricciones normalizar antes de decidir PLANO DE CONTROL Estado st hechos · permisos · coste Política π modelo + reglas + objetivo Candidatas At precondición · permiso presupuesto · evidencia Selección at score = valor - coste parar si Ω se cumple PLANO DE DATOS Ledger de evidencias fuente · fecha · confianza Memoria buscar · caducar · corregir Presupuesto Bt tokens · pasos · latencia Estado st+1 T(st, at, ot+1) PLANO DE EJECUCIÓN Puerta de tool schema · permisos Contrato I/O JSON · tipos · errores Adaptadores API · DB · navegador Entorno E sistema real o simulador Observación ot+1 resultado estructurado · error · evidencia recuperada Normalizador limpia y tipa salida Validador schema · invariantes si falla: observación de error y nueva decisión, no improvisación OPERACIÓN Y EVALUACIÓN Traza evento · acción · observación Métricas calidad · coste · latencia Revisión aprobación cuando toca Reproducibilidad inputs · versiones · seeds Parada Ω done · approval · blocked · budget IA para gente curiosa / Facsímil 05 / Capítulo 02 / 686f6c61

El diagrama ya no mira solo el bucle conceptual; mira el sistema que habría que operar. Primera idea: el modelo no debería ejecutar directamente, sino proponer una acción que pasa por contrato, permisos, presupuesto y validación. Segunda idea: cada observación vuelve al estado como dato tipado, no como frase suelta. Tercera idea: sin traza, métricas y criterios de parada, no sabes si el agente resolvió, pidió aprobación, agotó presupuesto o quedó bloqueado.

En el día a día

Piensa en un agente de código. Recibe: “la página de login falla”. Si su estado solo contiene esa frase, puede hacer casi cualquier cosa. Si el estado contiene navegador abierto, error de consola, archivos relevantes, tests ejecutados, presupuesto y permisos, la siguiente acción es mucho más razonable.

El agente prudente no edita a ciegas. Primero observa: lee el error, localiza el componente, ejecuta un test pequeño. Luego actúa: cambia una función concreta. Después observa otra vez: el test pasa o falla. Ese ciclo es lo que queremos enseñar.

ReAct formalizó una manera de intercalar razonamiento y acción en modelos de lenguaje, mostrando que alternar pasos de decisión con observaciones de herramientas puede mejorar tareas que requieren información externa.6 Toolformer mostró otra dirección: modelos que aprenden a decidir cuándo usar herramientas como calculadoras, buscadores o sistemas externos.7 En ambos casos, lo importante no es la etiqueta del paper: es separar decidir, actuar y observar.

Por qué debería importarte

Si no defines estado, el agente decide con memoria borrosa. Si no defines acciones disponibles, cualquier tool parece válida. Si no defines observación, una salida textual puede parecer evidencia aunque no lo sea. Si no defines parada, el sistema puede seguir gastando o declarar éxito antes de tiempo.

Hart, Nilsson y Raphael mostraron con A* que combinar coste acumulado y estimación restante permite guiar la búsqueda de manera más disciplinada.8 En agentes modernos no siempre implementamos A*, pero la intuición sigue siendo valiosa: cada acción debe justificar qué aporta y qué cuesta.

OpenAI Agents SDK estructura agentes con instrucciones, herramientas, handoffs, guardrails, output types y trazas.9 También registra eventos como generation spans, function spans, handoff spans y guardrail spans.10 Eso confirma una idea práctica: los agentes se operan mirando trayectoria, no solo respuesta final.

Cómo lo manejan OpenAI, Claude y OpenCode

OpenAI te empuja a pensar el agente como una unidad con instrucciones, modelo, herramientas, posibles handoffs, guardrails y tipo de salida.11 Es una forma cómoda de convertir nuestra tupla A\mathcal{A} en código: las instrucciones describen GG, las tools definen AA, los esquemas de salida ayudan a tipar OO, y la traza deja visible qué ocurrió entre sts_t y st+1s_{t+1}.

Claude, visto desde la API, trabaja con un patrón más explícito de tool use: tú declaras herramientas con nombre, descripción y esquema de entrada; el modelo puede pedir un tool_use; tu aplicación ejecuta la herramienta y devuelve un tool_result en la conversación.12 En Claude Code aparece otra pieza muy útil para pensar sistemas reales: memoria mediante archivos CLAUDE.md y subagentes definidos como Markdown con frontmatter, herramientas permitidas y contexto separado.1314

OpenCode encaja bien para explicar esta idea a ingeniería porque separa agentes configurables y plugins. Los agentes se pueden definir como perfiles especializados con herramientas y permisos, y los plugins permiten añadir comportamiento o herramientas propias al entorno.1516 Dicho de forma práctica: no basta con “un modelo que sabe escribir”; queremos una mesa de trabajo donde cada especialista tenga una misión, un contrato y una traza.

EntornoUnidad principalCómo aparecen AtA_t y OtO_tQué debe poner el ingeniero
OpenAI Agents SDKAgent ejecutado por Runner.Tools, handoffs, output types y eventos de tracing.Instrucciones, esquemas, límites, validadores y observabilidad.
Claude APILoop de mensajes con tool_use y tool_result.La aplicación ejecuta tools y devuelve resultados al modelo.Estado externo, permisos, parseo de tool results y criterio de parada.
Claude CodeSubagentes Markdown y memoria CLAUDE.md.Cada subagente recibe una tarea y un conjunto de herramientas.Fronteras de responsabilidad, herramientas permitidas y contexto persistente.
OpenCodeAgentes configurables y plugins.Agentes especializados más herramientas añadidas por plugin.Ficheros de agente, plugin, contratos de entrada/salida y gates.

Crear el mismo diseño en OpenAI y Claude

Imagina un plugin editorial para este libro. Recibe un fragmento de capítulo y una lista de citas. Queremos tres agentes:

AgenteObjetivoEntradaSalida
rae_normasRevisar ortografía, puntuación, mayúsculas, comillas y usos dudosos según norma panhispánica.17Texto del capítulo.Lista de hallazgos con ubicación, explicación y propuesta.
apa_citasConvertir o revisar referencias en APA 7.18Metadatos de fuentes y citas en texto.Referencias normalizadas y errores de campo.
verificador_browserAbrir la URL citada y comprobar si el contenido respalda la afirmación.URL, afirmación y fragmento citado.supported, partial o not_found, con evidencia.

En OpenAI lo natural es crear tres Agent especializados y un cuarto agente coordinador. Los subagentes pueden exponerse como herramientas del coordinador. La idea importante no es la sintaxis exacta, sino el reparto de responsabilidad:

from pydantic import BaseModel
from agents import Agent, Runner, function_tool, trace


class HallazgoRAE(BaseModel):
    ubicacion: str
    problema: str
    propuesta: str


class RevisionAPA(BaseModel):
    referencia_normalizada: str
    errores: list[str]


class VerificacionFuente(BaseModel):
    url: str
    estado: str
    evidencia: str


@function_tool
async def browser_check(url: str, afirmacion: str) -> VerificacionFuente:
    """Abre una URL y devuelve evidencia verificable para la afirmación."""
    ...


rae_normas = Agent(
    name="rae_normas",
    instructions="Revisa el texto con criterio RAE/ASALE. Devuelve hallazgos concretos.",
    output_type=list[HallazgoRAE],
)

apa_citas = Agent(
    name="apa_citas",
    instructions="Revisa referencias en APA 7. No inventes campos ausentes.",
    output_type=list[RevisionAPA],
)

verificador_browser = Agent(
    name="verificador_browser",
    instructions="Comprueba si la URL respalda la afirmación citada.",
    tools=[browser_check],
    output_type=list[VerificacionFuente],
)

editorial_plugin = Agent(
    name="editorial_plugin",
    instructions=(
        "Coordina tres revisiones: norma lingüística, APA 7 y verificación de fuentes. "
        "No apruebes el texto si una cita clave no queda respaldada."
    ),
    tools=[
        rae_normas.as_tool("revisar_rae", "Revisa norma lingüística."),
        apa_citas.as_tool("revisar_apa", "Revisa referencias APA 7."),
        verificador_browser.as_tool("verificar_fuentes", "Comprueba URLs citadas."),
    ],
)


async def revisar(texto: str):
    with trace("plugin_editorial_tres_agentes"):
        return await Runner.run(editorial_plugin, texto)

En Claude API no tienes que imitar esa clase Agent. Puedes crear el mismo comportamiento con un loop anfitrión: declaras tools revisar_rae, revisar_apa y verificar_fuentes; Claude pide una tool con tool_use; tu aplicación ejecuta la función; devuelves tool_result; y repites hasta que el estado diga done, approval_required o blocked. En Claude Code, el mismo reparto puede vivir como tres subagentes Markdown. La diferencia técnica es clara: OpenAI te da una abstracción de agente más directa; Claude te deja muy visible el protocolo de herramientas y, en Claude Code, el patrón de subagentes por archivo.

Manos a la obra

Práctica: plugin editorial con tres agentes.

Kit ejecutable de este capítulo: kit descargable.

# Descomprime el ZIP del capítulo y ejecuta estos comandos dentro de esa carpeta
python3 ops/run_f5_practices.py --chapter c02 --write --fail-on-invalid

Una versión práctica para OpenCode podría empezar con esta estructura:

.opencode/
  agents/
    rae-normas.md
    apa-citas.md
    verificador-browser.md
  plugins/
    editorial-gate.ts

El agente rae-normas.md no necesita editar archivos. Su trabajo es devolver hallazgos:

---
description: Revisa norma lingüística, puntuación, comillas, mayúsculas y usos dudosos.
tools: []
---

Eres el agente de revisión RAE/ASALE.

Devuelve JSON con:
- ubicacion
- problema
- explicacion
- propuesta

No reescribas el capítulo entero. No cambies el tono del autor.

El agente apa-citas.md se centra en referencias:

---
description: Revisa citas y referencias en APA 7.
tools: []
---

Eres el agente de referencias APA.

Comprueba:
- autor
- año
- título
- fuente
- DOI o URL
- correspondencia entre cita en texto y referencia final

Devuelve errores accionables y una versión normalizada cuando sea posible.

El agente verificador-browser.md sí necesita una herramienta de navegador si el entorno la ofrece:

---
description: Comprueba si una URL citada respalda la afirmación del capítulo.
tools: [browser]
---

Eres el agente de verificación de fuentes.

Para cada cita:
1. abre la URL;
2. localiza la página o documento;
3. decide si la afirmación queda respaldada;
4. devuelve evidencia breve y URL final.

Estados permitidos: supported, partial, not_found.

El plugin puede añadir una puerta de calidad común. No decide por gusto; solo aplica un contrato:

export async function editorialGate(report) {
  const errors = []

  for (const item of report.rae ?? []) {
    if (!item.ubicacion || !item.propuesta) errors.push("hallazgo RAE incompleto")
  }

  for (const item of report.apa ?? []) {
    if (item.errores?.length) errors.push(`APA: ${item.errores.join("; ")}`)
  }

  for (const item of report.fuentes ?? []) {
    if (item.estado !== "supported") {
      errors.push(`fuente no cerrada: ${item.url} -> ${item.estado}`)
    }
  }

  return {
    ok: errors.length === 0,
    errors,
    next: errors.length ? "corregir antes de publicar" : "listo para revisión humana",
  }
}

Y este simulador permite ejecutar la lógica sin claves ni SDK. No sustituye a OpenAI, Claude u OpenCode; enseña el contrato mínimo que luego llevarías a cualquiera de ellos.

from dataclasses import dataclass


FUENTES = {
    "https://openai.github.io/openai-agents-python/agents/":
        "Agents have instructions, tools, handoffs, guardrails, output types, and tracing.",
    "https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/implement-tool-use":
        "Claude can request tool_use blocks and the client returns tool_result blocks.",
}


@dataclass
class Citation:
    claim: str
    url: str
    apa: str


def agente_rae(texto):
    hallazgos = []
    if "CLaude" in texto:
        hallazgos.append({
            "ubicacion": "proveedor",
            "problema": "mayúscula interna no justificada",
            "propuesta": "Claude",
        })
    if "wue" in texto:
        hallazgos.append({
            "ubicacion": "verbo",
            "problema": "errata",
            "propuesta": "que",
        })
    return hallazgos


def agente_apa(citas):
    errores = []
    for cita in citas:
        if "(" not in cita.apa or ")" not in cita.apa:
            errores.append({"url": cita.url, "error": "falta año entre paréntesis"})
        if "http" not in cita.apa:
            errores.append({"url": cita.url, "error": "falta URL o DOI"})
    return errores


def agente_browser(citas):
    resultados = []
    for cita in citas:
        pagina = FUENTES.get(cita.url, "")
        estado = "supported" if cita.claim.lower().split()[0] in pagina.lower() else "partial"
        if not pagina:
            estado = "not_found"
        resultados.append({
            "url": cita.url,
            "estado": estado,
            "evidencia": pagina[:90] if pagina else "sin contenido local",
        })
    return resultados


def editorial_gate(report):
    errores = []
    errores += [f"RAE: {h['problema']} -> {h['propuesta']}" for h in report["rae"]]
    errores += [f"APA: {e['error']} en {e['url']}" for e in report["apa"]]
    errores += [
        f"Fuente: {v['url']} queda {v['estado']}"
        for v in report["fuentes"]
        if v["estado"] != "supported"
    ]
    return {"ok": not errores, "errores": errores}


texto = "CLaude y OpenAI pueden coordinar agentes; hay que comprobar wue las citas soportan la frase."
citas = [
    Citation(
        claim="Agents have instructions",
        url="https://openai.github.io/openai-agents-python/agents/",
        apa="OpenAI. (2026). Agents SDK: Agents. https://openai.github.io/openai-agents-python/agents/",
    ),
    Citation(
        claim="Claude usa tool_use",
        url="https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/implement-tool-use",
        apa="Anthropic. (2026). How to implement tool use. https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/implement-tool-use",
    ),
]

report = {
    "rae": agente_rae(texto),
    "apa": agente_apa(citas),
    "fuentes": agente_browser(citas),
}

print(report)
print(editorial_gate(report))

La lectura de este ejemplo es importante: el agente RAE no decide si una fuente respalda una afirmación; el agente APA no corrige estilo general; el verificador no inventa una referencia perfecta. Cada uno produce una observación parcial, y el plugin decide si el conjunto pasa la puerta de calidad.

Cómo encaja todo

flowchart TD
    subgraph "Capítulo 02: estado, acción y observación"
        AG["Agente"]
        G["Objetivo G"]
        S["Estado s_t"]
        AT["Acciones A_t"]
        PI["Política π"]
        ACT["Acción a_t"]
        ENV["Entorno E"]
        OBS["Observación o_t+1"]
        T["Transición T"]
        STOP["Parada Ω"]
        TRACE["Traza"]
        PROVIDERS["OpenAI · Claude · OpenCode"]
        PLUGIN["Plugin editorial<br/>RAE · APA · browser"]
    end

    subgraph "Viene de antes"
        F2["Búsqueda y estados (F2C1-F2C4)"]
        F4["Manifest y laboratorio (F4C13-F4C14)"]
        C1["Agente o prompt (F5C1)"]
    end

    subgraph "Sigue en el facsímil 05"
        C3["Contratos de tool (C3)"]
        C4["Memoria y handoff (C4)"]
        C5["Arquitecturas ReAct/workflows (C5)"]
        C6["Harness y trazas (C6)"]
        C7["SDKs de agentes (C7)"]
        C8["Permisos y supervisión (C8)"]
        C9["MCP, A2A y ADKs (C9)"]
        C10["Evaluación de trayectoria (C10)"]
    end

    AG -->|"perseguir"| G
    AG -->|"mantener"| S
    S -->|"habilitar"| AT
    AT -->|"entrar en"| PI
    PI -->|"elegir"| ACT
    ACT -->|"ejecutarse en"| ENV
    ENV -->|"devolver"| OBS
    OBS -->|"alimentar"| T
    T -->|"actualizar"| S
    S -->|"evaluar"| STOP
    AG -->|"registrar"| TRACE
    AG -->|"implementarse sobre"| PROVIDERS
    PROVIDERS -->|"materializarse como"| PLUGIN

    F2 -. "da lenguaje de" .-> S
    F4 -. "exige" .-> TRACE
    C1 -. "decide cuándo usar" .-> AG

    ACT -->|"necesita"| C3
    S -->|"se conserva con"| C4
    PI -->|"se diseña en"| C5
    TRACE -->|"se opera en"| C6
    PROVIDERS -->|"se implementa con"| C7
    ACT -->|"se limita con"| C8
    PROVIDERS -->|"se orquesta en"| C9
    TRACE -->|"se mide en"| C10

IA para gente curiosa / Facsímil 05 / Capítulo 02 / 686f6c61

Vocabulario aprendido

TérminoDefinición
AgenteSistema que mantiene estado, elige acciones, observa resultados y avanza hacia un objetivo.
EstadoRepresentación compacta de lo que importa para decidir la siguiente acción.
AcciónOperación disponible desde un estado concreto.
ObservaciónResultado estructurado que vuelve después de actuar.
PolíticaRegla, modelo o combinación que elige la siguiente acción.
Función de transiciónActualización que produce el siguiente estado.
PrecondiciónCondición necesaria para que una acción esté disponible.
SubagenteAgente especializado que recibe una tarea parcial, herramientas concretas y contexto acotado.
PluginExtensión que añade herramientas, gates o comportamiento operativo al entorno de trabajo.
Puerta de calidadComprobación automática que decide si una salida puede seguir adelante o debe volver a revisión.
Criterio de paradaRegla que decide terminar, pedir ayuda, repetir o bloquearse.
TrazaRegistro de trayectoria suficiente para depurar y evaluar.

Dónde solía tropezar yo

ErrorPor qué es un errorAntídoto
Meter todo en el estadoEl estado grande encarece y confunde la decisión.Guardar solo lo necesario para elegir el siguiente paso.
No distinguir acción y observaciónUna tool no es útil si su salida no actualiza el estado.Diseñar cada tool con resultado estructurado y uso claro.
Tratar permiso como una frase del promptLos permisos deben aplicarse fuera del modelo.Calcular AtA_t filtrando por precondición y permiso.
Parar cuando hay textoUna respuesta redactada no siempre significa tarea terminada.Definir done, approval_required, blocked y budget_exhausted.
Hacer un agente comodínSi el mismo agente revisa lengua, referencias y fuentes, las observaciones se mezclan.Separar subagentes por responsabilidad y unificar con una puerta de calidad.
Confundir SDK con arquitecturaOpenAI, Claude y OpenCode cambian la sintaxis, pero no la necesidad de estado y trazas.Diseñar primero G,S,A,O,π,T,Ω,BG, S, A, O, \pi, T, \Omega, B; después elegir proveedor.
No registrar la trayectoriaSin eventos no sabes por qué el agente decidió lo que decidió.Registrar acción, observación, permiso, coste y parada.

Antes de pasar página

  • ¿Puedo definir un agente con G,S,A,O,π,T,Ω,BG, S, A, O, \pi, T, \Omega, B?
  • ¿Sé explicar la diferencia entre estado, contexto y memoria?
  • ¿Puedo escribir AtA_t como acciones filtradas por precondiciones y permisos?
  • ¿Entiendo por qué una observación debe ser estructurada?
  • ¿Sé distinguir workflow fijo de agente con decisión dinámica?
  • ¿Puedo explicar por qué el entorno ejecuta y el modelo propone?
  • ¿Sé diseñar un criterio de parada que no sea solo “hay respuesta”?
  • ¿Puedo leer una traza y reconstruir la trayectoria?
  • ¿Sé traducir el mismo diseño a OpenAI Agents SDK, Claude API o OpenCode?
  • ¿Puedo separar un plugin práctico en subagentes con responsabilidades verificables?

En resumen

Idea fuerzaDetalle
Un agente es un bucle observable.Estado, política, acción, entorno, observación y transición forman la unidad mínima.
El estado no es todo el contexto.Es la representación compacta que permite decidir el siguiente paso.
Las acciones disponibles se filtran.Precondiciones, permisos y presupuesto determinan AtA_t.
La observación cambia el futuro.Si no actualiza el estado, la acción no aportó evidencia útil.
La parada también se diseña.done, approval_required, blocked y budget_exhausted evitan falsa autonomía.
El proveedor no sustituye la arquitectura.OpenAI, Claude y OpenCode ofrecen caminos distintos, pero todos necesitan estado, tools, trazas y contratos.
Un plugin útil separa responsabilidades.RAE, APA y browser pueden ser tres agentes coordinados por una puerta de calidad común.

Para saber más

American Psychological Association. (2020). Publication manual of the American Psychological Association: The official guide to APA style (7.ª ed.). American Psychological Association.

Anthropic. (2024). Building Effective Agents. Artículo técnico.

Anthropic. (2026). How to implement tool use. Documentación oficial.

Anthropic. (2026). Manage Claude's memory. Documentación oficial.

Anthropic. (2026). Subagents. Documentación oficial.

Hart, P. E., Nilsson, N. J. y Raphael, B. (1968). A formal basis for the heuristic determination of minimum cost paths. IEEE Transactions on Systems Science and Cybernetics, 4(2), 100-107. https://doi.org/10.1109/TSSC.1968.300136

Newell, A., Shaw, J. C. y Simon, H. A. (1959). Report on a General Problem-Solving Program. Proceedings of the International Conference on Information Processing, 256-264.

Nilsson, N. J. (1998). Artificial intelligence: a new synthesis. Morgan Kaufmann.

OpenAI. (2026). Agents SDK. Documentación oficial.

OpenAI. (2026). Agents SDK: Agents. Documentación oficial.

OpenAI. (2026). Agents SDK: Tracing. Documentación oficial.

OpenCode. (2026). Agents. Documentación oficial.

OpenCode. (2026). Plugins. Documentación oficial.

Poole, D., Mackworth, A. y Goebel, R. (1998). Computational intelligence: a logical approach. Oxford University Press.

Real Academia Española y Asociación de Academias de la Lengua Española. (2018). Libro de estilo de la lengua española según la norma panhispánica. Espasa.

Russell, S. y Norvig, P. (2021). Artificial intelligence: a modern approach (4.ª ed.). Pearson.

Schick, T., Dwivedi-Yu, J., Dessì, R., Raileanu, R., Lomeli, M., Zettlemoyer, L., Cancedda, N. y Scialom, T. (2023). Toolformer: Language Models Can Teach Themselves to Use Tools. https://doi.org/10.48550/arXiv.2302.04761

Yao, S., Zhao, J., Yu, D., Du, N., Shafran, I., Narasimhan, K. y Cao, Y. (2023). ReAct: Synergizing Reasoning and Acting in Language Models. International Conference on Learning Representations. https://arxiv.org/abs/2210.03629

Notas

  1. Russell, S. y Norvig, P. (2021). Artificial intelligence: a modern approach (4.ª ed.). Pearson. Los autores definen agentes racionales como sistemas que perciben y actúan, y conectan esa definición con funciones de agente y medidas de rendimiento.

  2. Nilsson, N. J. (1998). Artificial intelligence: a new synthesis. Morgan Kaufmann. Nilsson trabaja la relación entre estado, operador, planificación y agente.

  3. Newell, A., Shaw, J. C. y Simon, H. A. (1959). Report on a General Problem-Solving Program. Proceedings of the International Conference on Information Processing, 256-264.

  4. Anthropic. (2024). Building Effective Agents. Artículo técnico. Consultado el 10 de junio de 2026.

  5. Poole, D., Mackworth, A. y Goebel, R. (1998). Computational intelligence: a logical approach. Oxford University Press. Su marco ayuda a separar estado, acciones, observaciones e inferencia.

  6. Yao, S. et al. (2023). ReAct: Synergizing Reasoning and Acting in Language Models. International Conference on Learning Representations. https://arxiv.org/abs/2210.03629

  7. Schick, T. et al. (2023). Toolformer: Language Models Can Teach Themselves to Use Tools. https://doi.org/10.48550/arXiv.2302.04761

  8. Hart, P. E., Nilsson, N. J. y Raphael, B. (1968). A formal basis for the heuristic determination of minimum cost paths. IEEE Transactions on Systems Science and Cybernetics, 4(2), 100-107. https://doi.org/10.1109/TSSC.1968.300136

  9. OpenAI. (2026). Agents SDK: Agents. Documentación oficial. Consultado el 10 de junio de 2026.

  10. OpenAI. (2026). Agents SDK: Tracing. Documentación oficial. Consultado el 10 de junio de 2026.

  11. OpenAI. (2026). Agents SDK. Documentación oficial. Consultado el 10 de junio de 2026.

  12. Anthropic. (2026). How to implement tool use. Documentación oficial. Consultado el 10 de junio de 2026.

  13. Anthropic. (2026). Manage Claude's memory. Documentación oficial. Consultado el 10 de junio de 2026.

  14. Anthropic. (2026). Subagents. Documentación oficial. Consultado el 10 de junio de 2026.

  15. OpenCode. (2026). Agents. Documentación oficial. Consultado el 10 de junio de 2026.

  16. OpenCode. (2026). Plugins. Documentación oficial. Consultado el 10 de junio de 2026.

  17. Real Academia Española y Asociación de Academias de la Lengua Española. (2018). Libro de estilo de la lengua española según la norma panhispánica. Espasa. https://www.rae.es/obras-academicas/obras-linguisticas/libro-de-estilo-de-la-lengua-espanola

  18. American Psychological Association. (2020). Publication manual of the American Psychological Association: The official guide to APA style (7.ª ed.). American Psychological Association.

Capítulo 03

Facsímil 5 · Agentes y orquestación

Capítulo 03: Tools y contratos operativos: function calling

Cuando el modelo deja de hablar solo

Imagina que alguien pide: “revisa si este alumno puede matricularse y dime qué falta”. Si el sistema solo genera texto, puede explicar posibilidades: quizá falta pago, quizá falta documentación, quizá hay una norma. Pero no sabe el estado real del expediente.

Una tool cambia la escena. El modelo puede proponer: “consulta el expediente”, “busca la norma aplicable”, “calcula el importe pendiente”. Aun así, hay una frase que conviene grabar desde el principio: el modelo no ejecuta la tool. El modelo propone una llamada. La aplicación valida, autoriza, ejecuta y devuelve una observación. Ahí empieza la ingeniería seria.

OpenAI describe function calling como una forma de conectar modelos con datos y acciones proporcionadas por tu aplicación mediante herramientas definidas por schema.1 Anthropic usa una idea equivalente: se definen tools con name, description e input_schema; Claude puede devolver un bloque tool_use, y la aplicación responde después con tool_result.2

Qué no es una tool

Una tool no es una función cualquiera pegada al modelo. Si pones una función enorme llamada do_everything, el modelo no tiene una interfaz clara: tiene una puerta opaca.

Tampoco es un permiso. Que el modelo pueda pedir send_email no significa que deba poder enviarlo. La autorización debe vivir fuera del modelo: usuario, rol, estado del caso, presupuesto, entorno, doble revisión si el efecto lo exige.

Y no es garantía de verdad. Una tool puede traer datos reales, pero el sistema aún puede elegir mal la tool, pasar argumentos incompletos, interpretar mal la observación o repetir una llamada sin necesidad. Function calling reduce una parte del problema: convierte intención textual en llamada estructurada. No sustituye validación, diseño de dominio ni evaluación.

La definición útil

Para este libro, una tool es:

Una interfaz operativa, validada y trazable, que permite a un agente consultar, calcular o producir un efecto fuera del texto, bajo un contrato explícito de entrada, permisos, ejecución, salida y observación.

El matiz importante está en la palabra contrato. En software clásico, una función se escribe pensando en otro programador o en otro servicio determinista. En agentes, la tool se diseña para un sistema no determinista que decide cuándo usarla.3 Por eso el contrato debe explicar más que tipos: debe explicar intención, límites, precondiciones, errores recuperables y forma de observar el efecto.

Una forma práctica de verlo:

Si tienes...Todavía no tienes una buena tool hasta que...
Una función PythonDefinas schema, permisos, errores y observación.
Un endpoint RESTSepares permisos, límites, idempotencia y salida útil para el agente.
Un conector a base de datosReduzcas alcance, evites queries libres y devuelvas evidencia mínima.
Un navegador o buscadorDecidas qué dominios, qué profundidad, qué coste y qué formato de cita aceptas.
Un comando de terminalEncierres alcance, tiempo, rutas permitidas y captura de salida.

La anatomía formal de una tool

Ejemplo de fórmula. Vamos a escribir una tool como una tupla:

T=(n,d,X,Y,pre,perm,E,obs,err,τ)\mathcal{T} = (n, d, X, Y, \operatorname{pre}, \operatorname{perm}, E, \operatorname{obs}, \operatorname{err}, \tau)
SímboloSignificadoEjemplo concreto
T\mathcal{T}Tool completa.consultar_expediente.
nnNombre inequívoco.get_student_record, no get_data.
ddDescripción operativa.Cuándo usarla, cuándo no y qué no devuelve.
XXSchema de entrada.case_id:string, include_payments:boolean.
YYSchema de salida.status, missing_documents, balance_due.
pre\operatorname{pre}Precondiciones.El case_id existe y el expediente pertenece al usuario autorizado.
perm\operatorname{perm}Política de permiso.allow, approval_required o deny.
EEEjecutor determinista.Código que consulta el sistema académico.
obs\operatorname{obs}Normalizador de observación.Convierte respuesta interna en resultado breve para el agente.
err\operatorname{err}Catálogo de errores recuperables.not_found, validation_error, timeout, permission_required.
τ\tauEvento de traza.Registro con usuario, tool, argumentos, latencia, resultado y coste.

Ejemplo de fórmula. La llamada que propone el modelo no entra directamente en EE. Primero se filtra:

valid(c,T,st)=schema(c.args,X)pre(c.args,st)perm(c,st)denybudget(c,st)Bt\operatorname{valid}(c, \mathcal{T}, s_t) = \operatorname{schema}(c.args, X) \land \operatorname{pre}(c.args, s_t) \land \operatorname{perm}(c, s_t) \neq \text{deny} \land \operatorname{budget}(c, s_t) \le B_t
SímboloSignificadoEjemplo concreto
ccTool call propuesta por el modelo.{"name":"consultar_expediente","args":{"case_id":"EXP-42"}}.
c.argsc.argsArgumentos que acompañan la llamada.case_id, include_payments.
sts_tEstado operativo en el paso actual.Usuario, objetivo, permisos, evidencias y presupuesto restante.
XXSchema de entrada permitido.JSON Schema con campos obligatorios y tipos.
BtB_tPresupuesto restante.Máximo de llamadas, tiempo, coste o filas devueltas.

Si la validación pasa, la ejecución produce una observación:

ot+1=obs(E(c.args))o_{t+1} = \operatorname{obs} \left( E(c.args) \right)
SímboloSignificadoEjemplo concreto
ot+1o_{t+1}Observación que vuelve al agente.{"ok":true,"balance_due":180,"missing":["DNI"]}.
E(c.args)E(c.args)Resultado bruto del sistema real.Respuesta del backend académico.
obs\operatorname{obs}Adaptador hacia el agente.Quita ruido, limita tamaño y conserva evidencia.

El estado del agente se actualiza así:

st+1=T(st,c,ot+1,τt+1)s_{t+1} = T(s_t, c, o_{t+1}, \tau_{t+1})
SímboloSignificadoEjemplo concreto
TTFunción de transición del sistema.Marca expediente consultado y descuenta presupuesto.
τt+1\tau_{t+1}Evento de traza de la llamada.tool.validated, tool.executed, tool.denied.
st+1s_{t+1}Estado posterior.El agente ya sabe qué falta y qué puede hacer después.

JSON Schema aporta un vocabulario estándar para validar estructura: tipos, propiedades, requisitos, enumeraciones, rangos y composición de constraints.4 Pero un schema solo valida forma. No sabe si el usuario puede consultar ese expediente, si conviene ejecutar ahora o si la llamada supera el presupuesto.

Fecha de corte de herramientas

Fecha de corte: 10 de junio de 2026.
Fuentes consultadas ese día: documentación oficial de OpenAI sobre tools, function calling y Structured Outputs; documentación oficial de Anthropic sobre tool use; artículo técnico de Anthropic sobre diseño de tools para agentes; JSON Schema Validation; RFC 9110 para idempotencia HTTP; OpenTelemetry Tracing API.

Lo estable es el patrón: el modelo propone, la aplicación valida, la aplicación ejecuta, el resultado vuelve como observación y todo queda trazado. Lo cambiante son nombres de parámetros, SDKs, modelos compatibles, tools hospedadas, límites de proveedor y formatos concretos de streaming.

El flujo completo, paso a paso

OpenAI resume el flujo en cinco pasos: enviar una petición con tools disponibles, recibir una tool call, ejecutar código en la aplicación, devolver la salida de la tool al modelo y recibir respuesta final o más llamadas.5 Esa descripción parece lineal, pero en producción conviene verla como un pipeline con puertas.

  1. El usuario pide una tarea.
  2. El sistema prepara estado: objetivo, permisos, contexto, tools disponibles y presupuesto.
  3. El modelo propone una tool call.
  4. El runtime valida schema.
  5. El runtime comprueba precondiciones.
  6. El runtime decide permiso.
  7. El runtime aplica límites de coste, tiempo, tamaño y frecuencia.
  8. El adaptador ejecuta la operación real.
  9. El resultado se normaliza como observación.
  10. La observación se devuelve al modelo y se añade a la traza.

Structured Outputs no es lo mismo que function calling. OpenAI distingue entre usar schema para llamar tools y usar schema para que la respuesta final del modelo tenga una forma concreta.6 En lenguaje llano:

Necesitas...Usa principalmente...Ejemplo
Que el modelo pida una acción externaFunction callingconsultar_expediente(case_id)
Que la respuesta final tenga JSON válido y campos fijosStructured output{"categoria":"matricula","prioridad":"alta"}
Ambas cosasTool con schema + respuesta final estructuradaConsultar datos y devolver decisión en JSON validable.

Anatomía visual de una tool bien diseñada

Tool como contrato operativo El modelo propone. El sistema valida, autoriza, ejecuta, observa y registra. MODELO Tool call name + args intención textual aún no se ejecuta CONTRATO Definición pública nombre inequívoco descripción de uso qué NO devuelve Schemas entrada X salida Y GATES ANTES DE EJECUTAR Schema tipos, enum Precondición estado válido Permiso scope y rol Budget coste, filas Idempotencia clave única Timeout retry acotado Decisión execute · ask approval · reject EJECUCIÓN Tool adapter traduce contrato a API real Sistema externo VUELTA AL AGENTE Normalizador menos ruido Observación TRAZA trace_id tool.proposed schema.checked permission.checked budget.checked tool.executed result.normalized state.updated latencia coste resultado stop reason Regla de ingeniería Una tool no termina al devolver bytes. Termina cuando hay observación estructurada, estado actualizado y traza suficiente para explicar qué ocurrió. Si una operación puede repetirse por timeout o retry, necesita clave de idempotencia o una forma clara de detectar que el efecto ya ocurrió. IA para gente curiosa / Facsímil 05 / Capítulo 03 / 686f6c61

IA para gente curiosa / Facsímil 05 / Capítulo 03 / 686f6c61

OpenAI y Anthropic: el mismo patrón con envoltorios distintos

No hace falta memorizar sintaxis todavía. El capítulo 07 destripará SDKs. Aquí queremos entender el contrato mental.

PiezaOpenAIAnthropicQué debe llevarse el lector
Declarar toolsParámetro tools en la petición; función definida con schema.Parámetro tools con name, description e input_schema.El modelo ve una lista de capacidades permitidas.
Salida del modeloTool call con nombre y argumentos.Bloque tool_use con id, name e input.La salida es una propuesta, no una ejecución.
EjecuciónLa aplicación ejecuta tu código y devuelve resultado.La aplicación ejecuta tu código y devuelve tool_result.La frontera operativa está en tu runtime.
SchemaJSON Schema para argumentos; Structured Outputs cuando toca.JSON Schema en input_schema, con descripciones detalladas.El schema reduce errores de forma, no decide permisos.
Controltool_choice, hosted tools, function tools, MCP, Agents SDK.Tools cliente, server tools, MCP, Claude Code, evaluaciones.El proveedor ayuda; la arquitectura sigue siendo tu responsabilidad.

OpenAI permite guiar el uso de tools con tool_choice: dejar que el modelo decida, exigir alguna tool, forzar una tool concreta o restringir el subconjunto disponible.7 Anthropic expone opciones equivalentes: auto, any, tool y none, además de herramientas estrictas cuando se quiere forzar llamada y schema con más control.8 Esto es más importante de lo que parece: una tool disponible no siempre debe estar disponible en este turno.

El patrón académico tampoco nace de la nada. Toolformer exploró cómo los modelos podían aprender a invocar APIs externas mediante ejemplos generados automáticamente.9 Gorilla se centró en producir llamadas de API más precisas y apoyarse en recuperación documental para adaptarse a cambios de documentación.10 ToolLLM construyó ToolBench con más de 16.000 APIs reales para entrenar y evaluar uso de herramientas.11

La lección común es sencilla y exigente: usar tools no es “darle internet al modelo”. Es enseñarle un catálogo limitado de acciones, con contrato, ejemplos, observaciones y evaluación.

La forma mental de una llamada OpenAI

No necesitamos memorizar el SDK ahora, pero sí entender la forma de los objetos. En Responses API, una tool de función se declara con type, name, description y parameters. Si el modelo decide usarla, devuelve un elemento de tipo function_call; tu aplicación ejecuta y añade un function_call_output con el mismo call_id.

A fecha de corte de este capítulo, la guía oficial de modelos recomienda empezar con gpt-5.5 para razonamiento complejo y trabajo de código, y bajar a variantes como gpt-5.4-mini o gpt-5.4-nano cuando mandan coste y latencia.12 Por eso el ejemplo usa gpt-5.5, pero en producción conviene fijar el modelo en configuración y revisarlo al actualizar la fecha de corte.

{
  "model": "gpt-5.5",
  "input": "Revisa el expediente EXP-42 y dime qué falta.",
  "tools": [
    {
      "type": "function",
      "name": "get_student_record",
      "description": "Consulta un expediente académico concreto. Úsala solo si hay case_id explícito.",
      "parameters": {
        "type": "object",
        "properties": {
          "case_id": { "type": "string" },
          "include_payments": { "type": "boolean" }
        },
        "required": ["case_id"],
        "additionalProperties": false
      }
    }
  ],
  "tool_choice": "auto"
}

La respuesta intermedia del modelo no es la respuesta final. Es una petición de ejecución:

{
  "type": "function_call",
  "call_id": "call_exp_42",
  "name": "get_student_record",
  "arguments": "{\"case_id\":\"EXP-42\",\"include_payments\":true}"
}

Tu aplicación valida, consulta el sistema real y devuelve la observación:

{
  "type": "function_call_output",
  "call_id": "call_exp_42",
  "output": "{\"ok\":true,\"missing_documents\":[\"DNI\"],\"balance_due\":180}"
}

Lo importante no es la sintaxis exacta, que cambia entre APIs y SDKs. Lo importante es que el call_id une propuesta y resultado. Sin esa unión, la traza pierde causalidad.

La forma mental de una llamada Anthropic

En Anthropic la definición se parece, pero la nomenclatura cambia: input_schema, bloque tool_use y bloque posterior tool_result.

{
  "model": "claude-opus-4-7",
  "max_tokens": 1024,
  "tools": [
    {
      "name": "get_student_record",
      "description": "Consulta un expediente académico concreto. No devuelve datos de otros expedientes ni envía mensajes.",
      "input_schema": {
        "type": "object",
        "properties": {
          "case_id": { "type": "string" },
          "include_payments": { "type": "boolean" }
        },
        "required": ["case_id"]
      }
    }
  ],
  "messages": [
    { "role": "user", "content": "Revisa el expediente EXP-42 y dime qué falta." }
  ]
}

El modelo puede responder con texto y con una petición de tool:

{
  "role": "assistant",
  "content": [
    { "type": "text", "text": "Voy a consultar el expediente indicado." },
    {
      "type": "tool_use",
      "id": "toolu_exp_42",
      "name": "get_student_record",
      "input": { "case_id": "EXP-42", "include_payments": true }
    }
  ]
}

Y la aplicación devuelve:

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_exp_42",
      "content": "{\"ok\":true,\"missing_documents\":[\"DNI\"],\"balance_due\":180}"
    }
  ]
}

En ambos proveedores se repite la misma disciplina: el modelo propone, el runtime ejecuta y el resultado vuelve con un identificador que permite continuar el bucle.

Qué debe tener una tool de producción

Una tool buena se puede revisar como revisaríamos una API interna. La diferencia es que aquí el consumidor inmediato puede ser un modelo, así que la descripción debe ser más didáctica de lo normal.

PiezaQué significaQué aporta el número o datoSeñal de que está bien diseñada
NombreVerbo específico y objeto claro.No hay número; importa semántica.get_invoice_status se entiende sin leer código.
DescripciónCuándo usarla, cuándo no y qué devuelve.Longitud suficiente para evitar ambigüedad.Incluye límites y casos en los que debe pedir aclaración.
Input schemaCampos, tipos, enums y rangos.Reduce combinaciones inválidas.Un argumento malo falla antes de tocar sistemas reales.
Output schemaForma de la observación.Facilita actualizar estado sin parsear texto libre.El agente sabe si hubo ok, error, evidence y next_allowed.
PrecondicionesHechos que deben cumplirse antes.No son tipos; son reglas del dominio.“No consultar saldo si no hay case_id validado”.
PermisosQuién puede pedir qué acción.Puede depender de rol, caso, entorno y canal.La tool puede rechazar aunque el modelo la pida bien.
Clase de efectoLectura, escritura reversible, efecto externo.Decide aprobación, idempotencia y trazabilidad.Leer no se trata igual que enviar, borrar o comprar.
IdempotenciaRepetición sin duplicar efecto.Clave única por operación.Un retry no crea dos tickets ni manda dos emails.
Timeout y retryTiempo máximo y reintentos.Limita latencia y coste.Falla con error recuperable, no cuelga el bucle.
Límite de salidaMáximo de filas, bytes o fragmentos.Protege contexto y coste.Devuelve resumen y puntero, no una base entera.
TrazaRegistro de eventos.Permite depurar y comparar versiones.Guarda tool, args saneados, permiso, latencia y resultado.

La idempotencia no es una palabra decorativa. En HTTP, RFC 9110 define métodos idempotentes como aquellos cuyo efecto pretendido sobre el servidor es el mismo si se ejecutan una o varias veces.13 En agentes esto aparece cada día: si una tool se reintenta tras un timeout, ¿sabemos si el ticket ya se creó? ¿Tenemos una clave operation_id? ¿Podemos comprobar estado antes de repetir?

Ficha completa de contrato

Esta ficha es la que me gustaría ver en un proyecto real antes de conectar una tool al modelo. Es deliberadamente más larga que la función: obliga a pensar.

tool_contract:
  name: get_student_record
  version: 1.2.0
  owner: equipo_academico
  description: >
    Consulta un expediente académico concreto a partir de un case_id.
    Usar solo cuando el usuario haya dado un identificador de expediente.
    No devuelve documentos completos ni datos de expedientes no vinculados al usuario autorizado.
  when_to_use:
    - Hay un case_id explícito.
    - La tarea necesita estado académico real.
    - El usuario tiene permiso sobre el expediente.
  when_not_to_use:
    - El usuario pregunta por normativa general.
    - Falta case_id.
    - La tarea puede resolverse con información ya observada.
  effect_class: read
  input_schema:
    type: object
    required: [case_id]
    additionalProperties: false
    properties:
      case_id:
        type: string
        pattern: "^EXP-[0-9]{2,8}$"
      include_payments:
        type: boolean
        default: false
  output_schema:
    type: object
    required: [ok, record, evidence]
    properties:
      ok:
        type: boolean
      record:
        type: object
      evidence:
        type: array
      error:
        type: string
        enum: [not_found, permission_required, timeout, partial_result]
  preconditions:
    - case_id debe existir.
    - El usuario debe estar vinculado al expediente.
    - El entorno debe ser lectura o simulación.
  permissions:
    read_scope: academic.record.read
    write_scope: none
    approval_required: false
  budgets:
    timeout_ms: 1500
    max_retries: 1
    max_rows: 1
    max_output_tokens: 900
  idempotency:
    required: false
    operation_id_field: null
  trace_fields:
    - trace_id
    - user_id_hash
    - tool_name
    - tool_version
    - case_id_hash
    - permission_decision
    - latency_ms
    - result_ok
    - error
  examples:
    valid:
      args: { case_id: "EXP-42", include_payments: true }
    invalid:
      args: { case_id: "42" }
      expected_error: validation_error

La parte que muchos equipos se saltan es when_not_to_use. Sin esa sección, el modelo aprende cuándo una tool puede servir, pero no cuándo está de más. Una buena tool no solo dice “esto puedo hacerlo”; también dice “esto no es mi trabajo”.

Prompt, structured output, tool o agente

Antes de diseñar una tool conviene preguntar si realmente hace falta. No todo problema que admite una tool mejora con una tool.

Necesidad realMecanismo suficientePor quéSeñal de que necesitas subir de nivel
Redactar, resumir o transformar texto cerradoPromptLa información ya está en la entrada.La salida debe ser validada por máquina.
Obtener JSON con campos fijosStructured outputNecesitas contrato de salida, no acción externa.El modelo necesita datos que no están en el prompt.
Consultar estado, calcular o llamar un sistemaToolHace falta una capacidad fuera del modelo.Una sola llamada no basta para completar la tarea.
Decidir varias acciones según observacionesAgenteHay bucle: estado, acción, observación, parada.Necesitas memoria, permisos, evaluación y harness.

Ejemplo: “clasifica este ticket en una de cinco categorías” puede ser structured output. “Clasifica este ticket consultando si el alumno tiene pago pendiente” pide una tool. “Consulta pago, revisa norma, prepara respuesta y detente si falta aprobación” ya pide agente o workflow con tools.

Matriz de clase de efecto

La clase de efecto define cuánto control exige una tool. No todas las tools son iguales.

ClaseQué haceEjemploControles mínimos
readLee estado sin cambiarlo.get_student_record, search_policy.Permiso de lectura, límite de salida, traza.
computeCalcula sin tocar sistemas externos.calculate_installment_plan.Validación numérica, rango, tests deterministas.
prepare_changePrepara una propuesta revisable.prepare_email_proposal, prepare_sql_migration.Evidencia, diff o preview, estado approval_required.
reversible_writeCambia estado con reversión clara.create_ticket, add_internal_note.Idempotencia, owner, rollback, traza completa.
external_effectProduce efecto fuera del sistema.Enviar mensaje, publicar, pagar, desplegar.Aprobación explícita, doble validación, operation_id, postcondición.
privileged_operationUsa permisos altos o alcance amplio.Cambiar permisos, exportar datos, tocar producción.Separación de rol, revisión humana y entorno controlado.

La frontera no está en si la tool “parece peligrosa”; está en qué cambia después. Una lectura amplia puede ser más delicada que una escritura pequeña. Por eso la clase de efecto debe decidirse mirando datos, alcance, reversibilidad y coste.

Nombres y descripciones: la semántica también es ingeniería

El modelo elige tools por lo que ve: nombre, descripción, schema y ejemplos. Anthropic insiste en que las descripciones detalladas son uno de los factores más importantes para el rendimiento de tools, especialmente cuando hay varias herramientas parecidas.14 Una descripción pobre no es estética pobre; es una fuente de llamadas equivocadas.

Tool flojaProblemaTool mejorPor qué mejora
get_dataNo dice dominio, alcance ni salida.get_student_payment_statusAcota entidad y propósito.
searchCompite con cualquier búsqueda.search_academic_policyLimita corpus y expectativa de evidencia.
send_messageMezcla preparar, enviar y canal.prepare_student_reply_for_reviewEvita efecto externo y deja revisión.
run_queryAbre demasiada libertad.get_enrollment_blockersSustituye SQL libre por intención segura.
update_caseNo dice qué campo cambia.add_case_internal_noteEfecto pequeño y auditable.

Una regla útil: si el nombre de la tool exige leer tres párrafos para saber si toca usarla, el nombre falla. Si la descripción no explica cuándo no usarla, la tool invita a llamadas innecesarias.

Para entenderlo: tres tools con distinto nivel de efecto

Miremos tres versiones de un sistema de soporte académico.

ToolContrato aceptableQué puede salir mal si se diseña floja
search_policy(query, max_results)Solo lectura, devuelve fragmentos citables, max_results <= 5, dominio documental cerrado.Devuelve demasiado texto, mezcla normas antiguas y nuevas, no trae fuente.
get_student_record(case_id)Solo lectura, exige permiso sobre el caso, devuelve campos mínimos.Consulta expedientes equivocados o expone más datos de los necesarios.
prepare_email_proposal(case_id, template_id, facts)No envía; genera propuesta trazable y deja approval_required.Confunde preparar con enviar, no registra hechos usados o no deja revisión.

Fíjate en el tercer caso. La tool útil no tiene por qué ser “enviar email”. Muchas veces la tool profesional es “preparar propuesta de email”, devolver un resumen verificable y dejar la decisión de envío a otra capa. El sistema puede ser más lento en la demo, pero mucho más gobernable en trabajo real.

Errores recuperables: la observación también se diseña

Una tool no debería devolver solo “falló”. El agente necesita una observación que le permita decidir el siguiente paso.

ErrorSignificaSiguiente paso razonable
validation_errorLos argumentos no cumplen schema.Corregir campos o pedir dato faltante.
not_foundEl recurso no existe o no está dentro del alcance.Pedir identificador correcto o cerrar con explicación.
permission_requiredLa acción necesita revisión o permiso adicional.Solicitar aprobación o proponer alternativa de solo lectura.
rate_limitedSe agotó límite temporal.Esperar, reducir llamadas o detener con evidencia.
timeoutLa tool no respondió a tiempo.Reintentar una vez si la operación es idempotente.
partial_resultHay datos, pero no todos.Explicar incertidumbre y no fingir completitud.
conflictEl estado cambió entre leer y actuar.Releer estado y replantear acción.

OpenTelemetry define las trazas como árboles de spans, donde cada span representa una operación y puede contener atributos, eventos, estado y relación con otros spans.15 Traducido al capítulo: cada tool call relevante merece un span o evento estructurado. Si luego algo no encaja, no revisas una conversación interminable: revisas la secuencia de decisiones.

Cómo se prueban las tools

Una tool no se prueba solo llamando al caso feliz. Se prueba como contrato. La pregunta no es “¿funciona mi función?”, sino “¿mi agente puede usar esta interfaz sin romper el sistema cuando falten datos, cambie el estado o haya una observación parcial?”.

TestQué compruebaEntrada mínimaResultado esperado
Caso válidoLa tool ejecuta y normaliza observación.case_id=EXP-42ok=true, evidencia y traza.
Campo obligatorio ausenteEl schema frena antes de ejecutar.{}validation_error, sin llamada externa.
Tipo incorrectoEl schema detecta forma inválida.case_id=42validation_error con detalle.
Enum fuera de catálogoNo acepta valores inventados.template_id=otrovalidation_error.
Permiso insuficienteEl modelo no se concede permiso.usuario sin scopepermission_required o deny.
Presupuesto agotadoLa tool no consume por encima del límite.cost_left=0budget_exhausted.
TimeoutLa observación explica latencia o reintento.backend lentotimeout, retry acotado.
Respuesta parcialNo se finge completitud.backend incompletopartial_result y evidencia disponible.
Retry idempotenteRepetir no duplica efecto.mismo operation_idmismo recurso o estado ya existente.
Tool innecesariaEl agente no llama si ya tiene datos.estado con observación previano hay nueva tool call.

También conviene probar la selección del modelo, no solo la función. Un dataset pequeño de evaluación puede tener pares de entrada y llamada esperada:

Entrada del usuarioTool esperadaArgumentos esperadosQué evalúa
“Mira el expediente EXP-42”get_student_recordcase_id=EXP-42Extracción de identificador.
“¿Qué dice la norma de matrícula?”search_academic_policyquery sobre matrículaElegir búsqueda documental.
“Avísale de lo que falta”prepare_student_reply_for_reviewhechos ya observadosNo enviar, solo preparar propuesta.
“No tengo número de expediente”ningunaningunoPedir dato faltante.

Ese dataset no tiene que ser enorme al principio. Veinte casos reales bien escritos valen más que doscientos ejemplos genéricos. La clave es que cubran errores de decisión: tool equivocada, argumentos incompletos, llamada innecesaria, permiso insuficiente y respuesta sin evidencia.

Versionado y compatibilidad del contrato

Las tools cambian. Se añade un campo, se renombra una enum, se reduce un límite, cambia la API interna o se decide que una operación requiere aprobación. Si no versionas el contrato, el agente y tus tests pueden quedar desalineados sin que nadie lo note.

Semantic Versioning propone separar cambios mayores, menores y parches para comunicar compatibilidad de una API pública.16 No hace falta aplicarlo de forma dogmática, pero sí adoptar la disciplina:

Cambio en la toolTipo recomendadoPor qué
Corregir descripción sin cambiar comportamientoPatchNo rompe llamadas existentes.
Añadir campo opcional con valor por defectoMinorAmplía capacidad sin exigir cambios.
Añadir enum opcionalMinorPuede mejorar selección sin romper schema anterior.
Añadir campo obligatorioMajorLas llamadas antiguas fallan.
Renombrar la toolMajorEl modelo y los tests deben reaprender el identificador.
Cambiar significado de un campoMajorEs peor que romper: parece compatible y no lo es.
Cambiar salida eliminando campos usadosMajorEl estado posterior puede quedarse sin evidencia.
Reducir límite de filas o tamañoMinor o majorMinor si sigue cumpliendo contrato; major si rompe casos aceptados.
Pasar de prepare_change a external_effectMajorCambia permisos, idempotencia y revisión.

Regla práctica: si un prompt, test o agente ya desplegado podría interpretar mal la tool después del cambio, sube versión mayor o conserva una versión antigua durante un tiempo.

Un contrato versionado debería guardar al menos:

CampoPor qué importa
tool_versionPermite saber qué contrato vio el modelo.
schema_hashDetecta cambios incluso si alguien olvida subir versión.
description_hashCambiar descripción puede cambiar selección de tool.
deprecation_dateAvisa cuándo deja de aceptarse una versión.
migration_notesExplica cómo pasar de v1 a v2.
eval_suiteIndica qué casos deben pasar antes de publicar.

El versionado también debe aparecer en la traza. Si una ejecución falló, no basta con saber “usó get_student_record”. Necesitas saber si usó get_student_record@1.1.0 o get_student_record@2.0.0, porque quizá el cambio estaba en el contrato, no en el modelo.

Manos a la obra

Práctica: un contrato de tool ejecutable.

Kit ejecutable de este capítulo: kit descargable.

# Descomprime el ZIP del capítulo y ejecuta estos comandos dentro de esa carpeta
python3 ops/run_f5_practices.py --chapter c03 --write --fail-on-invalid

Vamos a construir un mini runtime de tools con dependencias de la biblioteca estándar de Python. No llama a ningún proveedor. Justo por eso sirve: separa lo que depende del modelo de lo que debe controlar tu aplicación.

El ejercicio simula tres propuestas de tool call:

  1. Una consulta válida de expediente.
  2. Una llamada con argumentos inválidos.
  3. Una propuesta de email que no se ejecuta porque requiere aprobación.
from dataclasses import dataclass
from time import perf_counter
import json
import uuid


TYPE_MAP = {
    "string": str,
    "integer": int,
    "number": (int, float),
    "boolean": bool,
    "object": dict,
}


@dataclass
class ToolContract:
    name: str
    description: str
    input_schema: dict
    effect_class: str          # read, reversible_write, external_effect
    max_cost: float
    requires_approval: bool
    executor: object


def validate_args(args, schema):
    errors = []
    required = schema.get("required", [])
    properties = schema.get("properties", {})

    for field in required:
        if field not in args:
            errors.append(f"falta {field}")

    for field, value in args.items():
        if field not in properties:
            errors.append(f"campo extra: {field}")
            continue

        rule = properties[field]
        expected = TYPE_MAP.get(rule.get("type"))
        if expected and not isinstance(value, expected):
            errors.append(f"{field} deberia ser {rule['type']}")

        if "enum" in rule and value not in rule["enum"]:
            errors.append(f"{field} fuera de catalogo")

        if "maxLength" in rule and isinstance(value, str) and len(value) > rule["maxLength"]:
            errors.append(f"{field} demasiado largo")

    return errors


def check_permission(call, contract, state):
    if contract.requires_approval:
        return "approval_required"
    if contract.effect_class != "read" and not state["user"].get("can_write", False):
        return "permission_required"
    return "allow"


def get_student_record(case_id, include_payments=False):
    records = {
        "EXP-42": {
            "status": "incompleto",
            "missing_documents": ["DNI"],
            "balance_due": 180 if include_payments else None,
        }
    }
    if case_id not in records:
        return {"ok": False, "error": "not_found", "message": "expediente no encontrado"}
    return {"ok": True, "record": records[case_id]}


def prepare_email_proposal(case_id, template_id, facts):
    return {
        "ok": True,
        "proposal_id": f"MAIL-{case_id}-{template_id}",
        "summary": f"Propuesta preparada con {len(facts)} hechos verificados",
    }


TOOLS = {
    "get_student_record": ToolContract(
        name="get_student_record",
        description="Consulta un expediente academico concreto y devuelve solo campos operativos.",
        input_schema={
            "type": "object",
            "required": ["case_id"],
            "properties": {
                "case_id": {"type": "string", "maxLength": 20},
                "include_payments": {"type": "boolean"},
            },
        },
        effect_class="read",
        max_cost=0.01,
        requires_approval=False,
        executor=get_student_record,
    ),
    "prepare_email_proposal": ToolContract(
        name="prepare_email_proposal",
        description="Prepara una propuesta de email; no envia mensajes.",
        input_schema={
            "type": "object",
            "required": ["case_id", "template_id", "facts"],
            "properties": {
                "case_id": {"type": "string", "maxLength": 20},
                "template_id": {"type": "string", "enum": ["missing_docs", "payment_due"]},
                "facts": {"type": "object"},
            },
        },
        effect_class="external_effect",
        max_cost=0.03,
        requires_approval=True,
        executor=prepare_email_proposal,
    ),
}


def run_tool_call(call, state):
    trace = {
        "trace_id": state["trace_id"],
        "event": "tool.proposed",
        "tool": call.get("name"),
        "operation_id": call.get("operation_id") or str(uuid.uuid4()),
    }

    contract = TOOLS.get(call.get("name"))
    if not contract:
        trace["event"] = "tool.rejected"
        return {"ok": False, "error": "unknown_tool", "trace": trace}

    args = call.get("args", {})
    errors = validate_args(args, contract.input_schema)
    if errors:
        trace["event"] = "schema.rejected"
        return {"ok": False, "error": "validation_error", "details": errors, "trace": trace}

    if state["budget"]["cost_left"] < contract.max_cost:
        trace["event"] = "budget.rejected"
        return {"ok": False, "error": "budget_exhausted", "trace": trace}

    permission = check_permission(call, contract, state)
    if permission != "allow":
        trace["event"] = "permission.required"
        return {
            "ok": False,
            "error": permission,
            "next_allowed": ["ask_user_approval", "use_read_only_tool"],
            "trace": trace,
        }

    start = perf_counter()
    result = contract.executor(**args)
    elapsed_ms = round((perf_counter() - start) * 1000, 3)

    state["budget"]["cost_left"] -= contract.max_cost
    state["observations"].append(result)

    trace.update({
        "event": "tool.executed",
        "effect_class": contract.effect_class,
        "latency_ms": elapsed_ms,
        "cost": contract.max_cost,
        "result_ok": result.get("ok", False),
    })

    return {"ok": result.get("ok", False), "observation": result, "trace": trace}


state = {
    "trace_id": "run-2026-06-10-001",
    "user": {"id": "u-7", "role": "tutor", "can_write": False},
    "budget": {"cost_left": 0.05},
    "observations": [],
}

calls_from_model = [
    {
        "name": "get_student_record",
        "args": {"case_id": "EXP-42", "include_payments": True},
        "operation_id": "op-read-exp-42",
    },
    {
        "name": "get_student_record",
        "args": {"case_id": 42, "include_payments": "si"},
        "operation_id": "op-bad-args",
    },
    {
        "name": "prepare_email_proposal",
        "args": {
            "case_id": "EXP-42",
            "template_id": "missing_docs",
            "facts": {"missing_documents": ["DNI"], "balance_due": 180},
        },
        "operation_id": "op-mail-exp-42",
    },
]

results = [run_tool_call(call, state) for call in calls_from_model]

for result in results:
    print(json.dumps(result, indent=2, ensure_ascii=False))

assert results[0]["ok"] is True
assert results[0]["trace"]["event"] == "tool.executed"
assert results[1]["error"] == "validation_error"
assert results[2]["error"] == "approval_required"
assert state["budget"]["cost_left"] == 0.04

print("tests_ok: schema, permiso, ejecución y presupuesto se comportan como contrato")

Qué deberías ver:

"event": "tool.executed"
"error": "validation_error"
"error": "approval_required"
tests_ok: schema, permiso, ejecución y presupuesto se comportan como contrato

El valor del ejemplo está en la separación de responsabilidades. El modelo podría haber propuesto las tres llamadas. El runtime aceptó una, rechazó otra por schema y dejó otra esperando aprobación. Eso es exactamente lo que queremos en sistemas reales.

Cómo encaja todo

flowchart TD
    subgraph "Capítulo 03: tools y contratos"
        TOOL["Tool"]
        CALL["Tool call"]
        SCHEMA["Schema"]
        PRE["Precondiciones"]
        PERM["Permisos"]
        EXEC["Ejecutor"]
        OBS["Observación"]
        TRACE["Traza"]
        IDEMP["Idempotencia"]
        EFFECT["Clase de efecto"]
        TESTS["Tests de contrato"]
        VERSION["Versión de tool"]
    end

    subgraph "Viene de antes"
        C2["Estado, acción y observación (C2)"]
        F2C09["Planificación y efectos (F2C09)"]
        F2C08["Restricciones y guardrails (F2C08)"]
        F4C02["APIs y salidas estructuradas (F4C02)"]
    end

    subgraph "Sigue después"
        C4["Contexto y memoria (C4)"]
        C5["Arquitecturas de agentes (C5)"]
        C6["Harness engineering (C6)"]
        C7["SDKs de agentes (C7)"]
        C8["Permisos y supervisión (C8)"]
    end

    TOOL -->|"recibe"| CALL
    CALL -->|"debe cumplir"| SCHEMA
    SCHEMA -->|"no basta sin"| PRE
    PRE -->|"se combina con"| PERM
    PERM -->|"autoriza o frena"| EXEC
    EFFECT -->|"decide controles de"| PERM
    EXEC -->|"devuelve"| OBS
    OBS -->|"actualiza"| TRACE
    IDEMP -->|"protege reintentos de"| EXEC
    TESTS -->|"verifican"| SCHEMA
    TESTS -->|"verifican"| PERM
    TESTS -->|"verifican"| OBS
    VERSION -->|"etiqueta"| TOOL
    VERSION -->|"se registra en"| TRACE
    TRACE -->|"explica"| TOOL

    C2 -. "define estado y observación" .-> OBS
    F2C09 -. "aporta precondición y efecto" .-> PRE
    F2C08 -. "aporta restricciones duras" .-> SCHEMA
    F4C02 -. "aporta contratos de API" .-> CALL

    OBS -->|"alimenta"| C4
    TOOL -->|"se usa dentro de"| C5
    TRACE -->|"se opera con"| C6
    TOOL -->|"se implementa con"| C7
    PERM -->|"se profundiza en"| C8

IA para gente curiosa / Facsímil 05 / Capítulo 03 / 686f6c61

Vocabulario aprendido

TérminoDefinición
ToolInterfaz que permite al sistema consultar, calcular o producir un efecto fuera del texto.
Function callingPatrón en el que el modelo propone una llamada estructurada y la aplicación decide si ejecutarla.
Tool callObjeto con nombre de herramienta y argumentos propuestos.
SchemaContrato de forma: campos, tipos, enums, requisitos y límites.
PrecondiciónHecho verificable que debe cumplirse antes de actuar.
EfectoCambio observable producido por una tool.
ObservaciónResultado estructurado que vuelve al agente tras una acción o rechazo.
IdempotenciaGarantía de que repetir una operación no duplica el efecto pretendido.
Clase de efectoCategoría que indica cuánto cambia una tool el mundo y qué controles exige.
Tool versionVersión explícita del contrato usado por modelo, runtime, tests y trazas.
Schema hashHuella del schema para detectar cambios aunque nadie cambie el número de versión.
Trace eventEvento que registra qué ocurrió en una ejecución.
Operation IDIdentificador único de una operación para rastrear reintentos y efectos.

Dónde solía tropezar yo

ErrorPor qué es un errorAntídoto
Pensar que la tool “la ejecuta el modelo”El modelo solo propone argumentos; tu aplicación decide.Dibujar siempre modelo, runtime, validador y ejecutor separados.
Creer que schema equivale a seguridadEl schema valida forma, no permisos ni reglas de negocio.Añadir precondiciones, permisos y presupuesto.
Diseñar tools demasiado genéricasEl modelo debe inferir demasiado y la traza explica poco.Tools pequeñas, nombres específicos y salida útil.
Devolver texto libre como observaciónEl agente no actualiza estado de forma fiable.Devolver ok, error, evidence, next_allowed y datos mínimos.
No pensar en reintentosUn timeout puede duplicar efectos si repites sin control.Usar operation_id, idempotencia y comprobación de estado.
No registrar llamadas rechazadasLos rechazos enseñan tanto como las ejecuciones.Trazar schema, permiso, budget y motivo de parada.
Cambiar schemas sin versionarEl agente parece fallar, pero quizá cambió el contrato.Guardar tool_version, schema_hash y suite de evals.
Probar solo el caso felizLa primera demo funciona y producción falla en bordes.Tests de schema, permiso, timeout, retry y observación parcial.

Antes de pasar página

  • ¿Sé explicar por qué una tool no es simplemente una función?
  • ¿Puedo dibujar el flujo modelo → tool call → validación → ejecución → observación?
  • ¿Sé distinguir schema, precondición y permiso?
  • ¿Sé explicar por qué function calling y Structured Outputs no son lo mismo?
  • ¿Sé leer una tool call de OpenAI o Anthropic y ubicar dónde ejecuta mi aplicación?
  • ¿Puedo escribir T=(n,d,X,Y,pre,perm,E,obs,err,τ)\mathcal{T} = (n, d, X, Y, \operatorname{pre}, \operatorname{perm}, E, \operatorname{obs}, \operatorname{err}, \tau) y explicar cada pieza?
  • ¿Sé por qué una tool con efecto externo necesita idempotencia?
  • ¿Sé clasificar una tool por clase de efecto y elegir controles?
  • ¿Sé diseñar tests de contrato para casos válidos, inválidos, permisos, timeouts y reintentos?
  • ¿Sé cuándo un cambio de schema obliga a nueva versión mayor?
  • ¿Sé diseñar un error recuperable que ayude al agente a decidir el siguiente paso?
  • ¿He ejecutado el mini runtime y leído sus tres resultados?

En resumen

Idea fuerzaDetalle
La tool es una frontera operativa.El modelo propone; la aplicación valida, autoriza, ejecuta y observa.
El schema solo resuelve la forma.Los permisos, precondiciones, presupuesto e idempotencia viven fuera del modelo.
La observación se diseña.Una buena salida de tool permite actualizar estado y decidir el siguiente paso.
Los errores también son producto.validation_error, timeout o permission_required deben ser recuperables.
La clase de efecto decide controles.Leer, preparar, escribir y producir efectos externos no piden el mismo nivel de aprobación.
El contrato se prueba y se versiona.Tests y tool_version evitan que un cambio invisible rompa agentes ya construidos.
La traza convierte una decisión probabilística en ingeniería revisable.Sin eventos, no sabes qué tool se pidió, qué se rechazó, qué costó ni qué cambió.

Para saber más

Anthropic. (2025). Writing effective tools for agents — with agents. https://www.anthropic.com/engineering/writing-tools-for-agents

Anthropic. (2026). How to implement tool use. https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/implement-tool-use

Fielding, R., Nottingham, M. y Reschke, J. (2022). HTTP Semantics (RFC 9110). https://datatracker.ietf.org/doc/html/rfc9110

JSON Schema. (2020). JSON Schema Validation: A Vocabulary for Structural Validation of JSON. https://json-schema.org/draft/2020-12/json-schema-validation

OpenAI. (2026). Function calling. https://developers.openai.com/api/docs/guides/function-calling

OpenAI. (2026). Structured model outputs. https://developers.openai.com/api/docs/guides/structured-outputs

OpenAI. (2026). Using tools. https://developers.openai.com/api/docs/guides/tools

OpenTelemetry. (2026). Tracing API. https://opentelemetry.io/docs/specs/otel/trace/api/

Patil, S. G., Zhang, T., Wang, X. y Gonzalez, J. E. (2023). Gorilla: Large Language Model Connected with Massive APIs. https://doi.org/10.48550/arXiv.2305.15334

Preston-Werner, T. (2026). Semantic Versioning 2.0.0. https://semver.org/

Qin, Y. et al. (2023). ToolLLM: Facilitating Large Language Models to Master 16000+ Real-world APIs. https://doi.org/10.48550/arXiv.2307.16789

Schick, T., Dwivedi-Yu, J., Dessì, R., Raileanu, R., Lomeli, M., Zettlemoyer, L., Cancedda, N. y Scialom, T. (2023). Toolformer: Language Models Can Teach Themselves to Use Tools. https://doi.org/10.48550/arXiv.2302.04761

Notas

  1. OpenAI. (2026). Function calling. https://developers.openai.com/api/docs/guides/function-calling. Consultado el 10 de junio de 2026. La guía explica el flujo de tool calling como conversación en varios pasos: request con tools, tool call del modelo, ejecución en la aplicación, devolución del resultado y respuesta final.

  2. Anthropic. (2026). How to implement tool use. https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/implement-tool-use. Consultado el 10 de junio de 2026.

  3. Anthropic. (2025). Writing effective tools for agents — with agents. https://www.anthropic.com/engineering/writing-tools-for-agents. Consultado el 10 de junio de 2026. El artículo plantea que las tools para agentes son contratos entre sistemas deterministas y agentes no deterministas, y recomienda diseñarlas con evaluaciones, nombres claros, respuestas útiles y eficiencia de contexto.

  4. JSON Schema. (2020). JSON Schema Validation: A Vocabulary for Structural Validation of JSON. https://json-schema.org/draft/2020-12/json-schema-validation.

  5. OpenAI. (2026). Function calling. Consultado el 10 de junio de 2026.

  6. OpenAI. (2026). Structured model outputs. https://developers.openai.com/api/docs/guides/structured-outputs. Consultado el 10 de junio de 2026. La guía diferencia function calling cuando se conectan modelos con herramientas o datos del sistema, y response_format o text.format cuando se quiere estructurar la salida final.

  7. OpenAI. (2026). Function calling. Consultado el 10 de junio de 2026. La guía documenta tool_choice, allowed_tools y parallel_tool_calls para controlar cuándo y cuántas funciones puede llamar el modelo.

  8. Anthropic. (2026). How to implement tool use. Consultado el 10 de junio de 2026. La documentación describe tool_choice, strict, input_examples y recomendaciones de descripción.

  9. Schick, T., Dwivedi-Yu, J., Dessì, R., Raileanu, R., Lomeli, M., Zettlemoyer, L., Cancedda, N. y Scialom, T. (2023). Toolformer: Language Models Can Teach Themselves to Use Tools. https://doi.org/10.48550/arXiv.2302.04761.

  10. Patil, S. G., Zhang, T., Wang, X. y Gonzalez, J. E. (2023). Gorilla: Large Language Model Connected with Massive APIs. https://doi.org/10.48550/arXiv.2305.15334.

  11. Qin, Y. et al. (2023). ToolLLM: Facilitating Large Language Models to Master 16000+ Real-world APIs. https://doi.org/10.48550/arXiv.2307.16789.

  12. OpenAI. (2026). Models. https://developers.openai.com/api/docs/models. Consultado el 10 de junio de 2026. La página de modelos indica gpt-5.5 como punto de partida para tareas complejas y lista variantes GPT-5.4 para menor coste o latencia.

  13. Fielding, R., Nottingham, M. y Reschke, J. (2022). HTTP Semantics (RFC 9110). https://datatracker.ietf.org/doc/html/rfc9110. Consultado el 10 de junio de 2026.

  14. Anthropic. (2026). How to implement tool use. Consultado el 10 de junio de 2026. La documentación recomienda descripciones detalladas, ejemplos de entrada y respuestas de alto valor contextual.

  15. OpenTelemetry. (2026). Tracing API. https://opentelemetry.io/docs/specs/otel/trace/api/. Consultado el 10 de junio de 2026.

  16. Preston-Werner, T. (2026). Semantic Versioning 2.0.0. https://semver.org/. Consultado el 10 de junio de 2026.

Capítulo 04

Facsímil 5 · Agentes y orquestación

Capítulo 04: Contexto, memoria, compaction y handoff

El momento en que el agente empieza a olvidar

Hay una escena que aparece en casi todos los proyectos con agentes. Empiezas una tarea larga: revisar un repositorio, preparar un informe, analizar varias fuentes o resolver una incidencia de producto. Al principio el agente parece situado. Recuerda el objetivo, sabe qué herramientas ha usado y mantiene el hilo.

Después de muchos pasos, algo cambia. Vuelve a pedir una información ya consultada, confunde una decisión provisional con una decisión cerrada, mezcla preferencias personales con reglas del proyecto o propone repetir una operación que ya falló. No necesariamente porque el modelo sea peor. Muchas veces el sistema le está dando un contexto desordenado, demasiado largo o incompleto.

Este capítulo trata de esa zona gris que en ingeniería se suele nombrar mal: contexto, memoria, compaction y handoff. Si el capítulo 03 explicó cómo un agente actúa mediante tools, aquí veremos cómo sabe qué debe tener presente para actuar bien durante más de una llamada.

Qué no deberíamos llamar memoria

No deberíamos llamar memoria a “meter todo el chat otra vez”. Eso es historial, no memoria. Puede funcionar durante un rato, pero crece, cuesta, ralentiza y aumenta la probabilidad de que el modelo atienda a información vieja, contradictoria o poco relevante.

Tampoco deberíamos llamar memoria a “un resumen bonito”. Una narración agradable puede perder los detalles que permiten continuar: IDs, rutas de archivos, decisiones, errores ya probados, permisos concedidos, límites, pruebas ejecutadas y siguiente paso exacto. En agentes, una compaction mala puede sonar elegante y ser inútil.

Y no deberíamos llamar memoria a una carpeta de notas sin curación. Un vault de Obsidian, un workspace de Notion o una base documental pueden ser una fuente magnífica, pero solo si el sistema sabe seleccionar, citar, actualizar, retirar y contextualizar. Un almacén lleno no equivale a una memoria útil.

La definición útil

Para este libro, contexto es lo que el modelo ve ahora. Memoria es lo que el sistema guarda fuera de la llamada y puede recuperar después. Compaction es la reescritura controlada del historial para que quepa lo importante. Handoff es el paquete de continuidad que permite seguir trabajando sin releerlo todo.

La diferencia importa porque el modelo no recuerda como una persona. En una llamada concreta, procesa una secuencia de tokens. Si algo no está en esa secuencia, no lo puede usar directamente. Si está, pero rodeado de ruido, quizá lo use mal. Si está comprimido de forma ambigua, quizá pierda la razón por la que era importante.

Andrej Karpathy popularizó en 2025 la idea de que estamos pasando de “escribir prompts” a diseñar contextos enteros. En su charla Software Is Changing (Again) describe los LLMs como una nueva clase de ordenador programable mediante lenguaje natural, y advierte que los productos maduros se parecen menos a autonomía total y más a sistemas de autonomía parcial bien guiados.1 Lance Martin resume esa intuición como context engineering: llenar la ventana de contexto con la información justa para el siguiente paso, no con toda la información disponible.2

La versión práctica para nosotros:

Un agente no “recuerda” por voluntad propia. Un sistema de agentes construye, recupera, compacta y entrega contexto.

La anatomía formal del contexto

Ejemplo de fórmula. Podemos escribir el contexto de una llamada como una composición:

Ct=IGtStHtRtMtAtTtC_t = I \oplus G_t \oplus S_t \oplus H_t \oplus R_t \oplus M_t \oplus A_t \oplus T_t
SímboloSignificadoEjemplo concreto
CtC_tContexto total en el paso tt.Todo lo que se envía al modelo en esta llamada.
IIInstrucciones estables.Reglas del sistema, tono, límites, criterios del proyecto.
GtG_tObjetivo actual.“Revisar el capítulo 04 y dejarlo publicable”.
StS_tEstado operativo.Qué se ha hecho, qué falta, qué está bloqueado.
HtH_tHistorial seleccionado.Últimos mensajes o eventos útiles, no toda la conversación.
RtR_tRecuperación documental.Fragmentos de RAG, notas, papers, documentación oficial.
MtM_tMemoria recuperada.Preferencias, decisiones duraderas, hechos consolidados.
AtA_tReferencias a artefactos.Rutas de archivos, hashes, IDs de tickets, trazas.
TtT_tTools disponibles y contratos.Schemas, permisos, precondiciones y costes.
\oplusComposición ordenada.No basta con juntar texto: importa orden, prioridad y forma.

El tamaño del contexto no debería superar el presupuesto:

tokens(Ct)Bt\operatorname{tokens}(C_t) \le B_t
SímboloSignificadoEjemplo concreto
tokens(Ct)\operatorname{tokens}(C_t)Tokens ocupados por el contexto.46.000 tokens.
BtB_tPresupuesto máximo para la llamada.Ventana efectiva menos margen de salida y tools.

Ejemplo de fórmula. La memoria, por su parte, conviene separarla en capas:

M=(Me,Ms,Mp,Ma)\mathcal{M} = (M_e, M_s, M_p, M_a)
SímboloTipo de memoriaQué guardaEjemplo
MeM_eEpisódicaEventos ocurridos.“El 26 de mayo se decidió evitar mostrar rangos internos del taller”.
MsM_sSemánticaHechos relativamente estables.“El libro usa blanco, negro y grises”.
MpM_pProcedimentalReglas de trabajo.“Cada capítulo lleva Mermaid y la sección Dónde solía tropezar yo”.
MaM_aArtefactualReferencias a objetos.fasciculo-05-agentes-orquestacion/04-contexto...md, traza, diff, captura.

Ejemplo de fórmula. La recuperación no debe traer recuerdos por cercanía superficial. Debe estimar utilidad:

score(m,q,t)=αrel(m,q)+βvig(m,t)+γautoridad(m)δruido(m)λcoste(m)\operatorname{score}(m,q,t) = \alpha \operatorname{rel}(m,q) + \beta \operatorname{vig}(m,t) + \gamma \operatorname{autoridad}(m) - \delta \operatorname{ruido}(m) - \lambda \operatorname{coste}(m)
SímboloSignificadoEjemplo concreto
mmMemoria candidata.Una regla del proyecto sobre SVGs.
qqConsulta o tarea actual.“Escribir el capítulo 04”.
ttMomento actual.10 de junio de 2026.
rel\operatorname{rel}Relevancia para la tarea.Alta si habla de capítulos y estructura.
vig\operatorname{vig}Vigencia temporal.Alta si la regla sigue activa.
autoridad\operatorname{autoridad}Fuente o prioridad.Alta si viene de docs/plan-libro.md.
ruido\operatorname{ruido}Probabilidad de confundir.Alta si contradice reglas nuevas.
coste\operatorname{coste}Tokens, latencia o complejidad.Alta si requiere meter 20 páginas en contexto.
α,β,γ,δ,λ\alpha,\beta,\gamma,\delta,\lambdaPesos de decisión.Ajustables por producto, tarea y riesgo.

Ejemplo de fórmula. La compaction se puede ver como una función:

K:C1:thtK: C_{1:t} \rightarrow h_t
SímboloSignificadoEjemplo concreto
C1:tC_{1:t}Contexto acumulado hasta el paso actual.Mensajes, tool calls, resultados, decisiones y archivos vistos.
KKFunción de compactación.Extrae estado operativo, evidencia y pendientes.
hth_tHandoff compacto.Documento estructurado de continuidad.

Ejemplo de fórmula. Pero KK solo es válida si preserva invariantes:

ok(ht)=ODPEN\operatorname{ok}(h_t) = O \land D \land P \land E \land N
SímboloQué debe conservar el handoffEjemplo
OOObjetivo actual.Qué estamos intentando terminar.
DDDecisiones tomadas.Qué se eligió y por qué.
PPPermisos y límites.Qué se puede hacer y qué requiere confirmación.
EEEvidencia y artefactos.URLs, rutas, pruebas, capturas, resultados.
NNSiguiente paso.La acción concreta con la que continuar.

Un resumen que no preserva esos cinco elementos puede ahorrar tokens y destruir continuidad.

Fecha de corte del estado del arte

Fecha de corte: 10 de junio de 2026.
Fuentes consultadas ese día: charla de Andrej Karpathy sobre Software 3.0; texto de Lance Martin sobre context engineering para agentes; documentación oficial de OpenAI Agents SDK sobre sesiones y compaction; documentación oficial de Anthropic sobre memoria de Claude Code y ventanas de contexto; documentación de Google ADK Memory; LangGraph Persistence; LlamaIndex Memory; documentación de Obsidian sobre grafos y Bases; documentación de Zep y Mem0; Letta/MemGPT; artículos académicos sobre RAG, memoria de agentes y uso de contextos largos.

Lo estable es el mecanismo: el contexto es finito, la recuperación debe ser selectiva, la memoria debe tener ciclo de vida, las compactions deben conservar estado operativo y los handoffs deben ser verificables.

Lo cambiante son los nombres de SDK, límites exactos de ventana, APIs de sesión, formatos de memoria, herramientas comerciales, capacidades de contexto largo, precios y condiciones de proveedor.

Por qué contexto largo no resuelve la memoria

Una ventana de contexto más grande ayuda. Permite meter más documentos, más historial, más resultados de tools y más instrucciones. Pero no convierte automáticamente una conversación larga en una memoria fiable.

El paper Lost in the Middle muestra que incluso modelos con contextos largos pueden usar peor la información cuando el dato relevante aparece en posiciones intermedias del contexto. El resultado importante para ingeniería no es “los contextos largos no sirven”, sino “más contexto no significa más control”.3

RAG nació precisamente para combinar memoria paramétrica del modelo con memoria no paramétrica recuperada en tiempo de inferencia. Lewis y colaboradores lo plantearon para tareas intensivas en conocimiento: recuperar pasajes explícitos y condicionar la generación con ellos.4 En agentes, la misma idea se amplía: no solo recuperamos documentos, también reglas, estado, eventos, decisiones y artefactos.

El diseño profesional no pregunta “¿cuánto cabe?”. Pregunta:

PreguntaPor qué importa
¿Qué información necesita el siguiente paso?Evita llenar contexto con material interesante pero inútil.
¿Qué información debe salir del contexto activo?Reduce ruido y coste.
¿Qué debe guardarse como memoria duradera?Evita repetir aprendizaje entre sesiones.
¿Qué debe mantenerse solo como estado temporal?Evita convertir cada detalle en recuerdo permanente.
¿Qué debe citarse por referencia, no copiarse entero?Permite reabrir artefactos sin consumir tokens.
¿Qué memoria puede estar obsoleta?Impide que una decisión vieja mande sobre una nueva.

La lección de Karpathy: programar el entorno del modelo

La aportación útil de Karpathy para este capítulo no es una receta concreta ni una herramienta comercial. Es el cambio de foco: si el LLM es una especie de ordenador programable con lenguaje natural, entonces el contexto se parece a su memoria de trabajo. El trabajo del ingeniero deja de ser solo “redactar bien la petición” y pasa a ser “construir el entorno de ejecución que el modelo va a ver”.

Eso incluye:

Pieza del entornoQué decideEjemplo
InstruccionesCómo debe comportarse el sistema.Reglas editoriales del libro.
EstadoQué se sabe ahora mismo.Capítulo actual, cambios hechos, pruebas pendientes.
ToolsQué acciones puede proponer.Buscar referencias, validar SVG, ejecutar build.
EvidenciaEn qué se apoya.Documentación oficial, papers, trazas, capturas.
MemoriaQué debe recordar entre sesiones.Preferencias duraderas del proyecto.
HandoffCómo se continúa si cambia la sesión.Resumen operativo con próximos pasos.

La frase “context engineering” puede sonar a etiqueta de moda, pero apunta a un problema real: en sistemas con agentes, el fallo muchas veces no está en el modelo aislado, sino en el contexto que le llega. Contexto viejo, contexto duplicado, contexto contradictorio, contexto sin prioridad o contexto sin evidencia.

Un ejemplo cercano: si el sistema va a seguir este libro, no basta con decir “escribe el capítulo 04”. Debe ver que cada capítulo necesita Mermaid, SVG sobrio, fuentes, fecha de corte, fórmulas si aplica, Dónde solía tropezar yo antes de Antes de pasar página, rutas enlazables y ausencia de lenguaje provisional. Eso no es una frase decorativa: es contexto operativo.

Obsidian: memoria humana, no memoria automática

Obsidian es interesante para este capítulo porque representa muy bien una idea: una memoria útil no es una base de datos enorme, sino una red mantenible de notas, enlaces y propiedades. Obsidian trabaja sobre un vault, una carpeta local donde las notas son archivos Markdown. Sus grafos muestran relaciones entre notas; el grafo global enseña todo el vault y el grafo local muestra lo conectado con la nota activa.5 Sus Bases permiten consultar archivos y propiedades, incluyendo enlaces, etiquetas, tamaño, ruta, fecha de modificación y propiedades YAML.6

Pero Obsidian no convierte por sí mismo un agente en alguien con memoria. Sirve como fuente estructurable. Para que un agente lo use bien, el vault necesita disciplina:

Decisión en el vaultPor qué ayuda al agente
Una idea por nota cuando sea posible.Recupera conceptos concretos sin traer capítulos enteros.
Títulos descriptivos.Mejora búsqueda léxica, enlaces y lectura humana.
Propiedades YAML.Permite filtrar por tema, fecha, estado, fuente o confianza.
Enlaces bidireccionales.Explicita relaciones entre conceptos.
Notas de decisión.Conserva por qué se eligió una opción.
Extractos con fuente.Evita que el agente cite de memoria.
Fecha de actualización.Permite detectar conocimiento viejo.
Separación personal/proyecto/cliente.Evita mezclar ámbitos.

La traducción a un sistema de agentes sería esta:

En ObsidianEn un agente
Nota MarkdownUnidad recuperable de conocimiento.
BacklinkRelación explícita entre conceptos.
Propiedad YAMLMetadato filtrable.
Graph viewVista de dependencia conceptual.
CanvasMapa visual de decisiones o arquitectura.
BaseConsulta estructurada sobre notas.
Vault localFuente auditable y portable.

En el mercado hay varias familias: editores locales enlazados como Obsidian, workspaces colaborativos tipo Notion, outliners enlazados como Logseq o Roam Research, espacios personales como Anytype o Capacities, y capas de memoria para agentes como Zep, Mem0, Letta, LangGraph o LlamaIndex. No son equivalentes. Unos están pensados para que una persona piense y escriba; otros para que una aplicación guarde, recupere y evalúe memoria. La pregunta profesional no es “cuál está de moda”, sino “qué unidad de conocimiento necesito, quién la mantiene, cómo se borra, cómo se cita y cómo se evalúa”.

Mercado y estado actual de memoria para agentes

OpenAI Agents SDK ofrece sesiones para mantener historial entre ejecuciones de un agente, evitando que la aplicación tenga que reconstruir manualmente la lista de entradas en cada turno.7 En la versión JavaScript, la documentación también describe compaction manual para streaming de baja latencia: la compaction puede reescribir la sesión subyacente, y por eso conviene ejecutarla entre turnos si pesa demasiado.8

Anthropic documenta memorias de Claude Code mediante ficheros CLAUDE.md en varias capas: instrucciones gestionadas por organización, instrucciones de usuario, instrucciones de proyecto e instrucciones locales. También explica que se cargan según jerarquía y que los ficheros de subdirectorios pueden entrar bajo demanda cuando se leen archivos de esas zonas.9 Esta idea es muy práctica: memoria procedimental versionada, visible y revisable.

Google ADK separa sesiones y memoria. Su InMemoryMemoryService sirve para prototipos, permite búsquedas sencillas y documenta herramientas como load_memory o PreloadMemoryTool; también muestra un callback para extraer memorias desde una sesión mediante add_session_to_memory.10 LangGraph, por su parte, distingue checkpoints de estado por thread y un store para memorias compartibles entre threads. La documentación deja clara la diferencia: el checkpointer permite reanudar una ejecución; el store permite recordar información entre ejecuciones.11

LlamaIndex trata la memoria como componente central de sistemas agentic: permite almacenar y recuperar información pasada, combinar memoria corta con bloques de memoria larga y configurar límites de tokens.12 Zep ofrece una API de memoria donde se añaden mensajes por sesión y se construye un grafo de conocimiento a nivel de usuario a partir de la conversación.13 Mem0 se presenta como una capa gestionada de memoria para agentes, con memorias de usuario, agente y sesión, además de memoria de grafo, rerankers y controles de plataforma.14 Letta, heredera del patrón MemGPT, explica la memoria como gestión del contexto: el agente decide qué poner en contexto y qué consultar en almacenamiento externo mediante herramientas.15

La tabla honesta sería:

FamiliaQué resuelveQué no resuelve solaSeñal de buen uso
SesiónMantener conversación de una ejecución.Memoria duradera y curada.Puedes inspeccionar, limpiar y reanudar.
CheckpointRecuperar estado de una trayectoria.Saber qué recuerdos son útiles en otra tarea.Cada paso tiene estado serializable.
Store semánticoGuardar hechos o preferencias.Veracidad, caducidad y permisos.Cada memoria tiene fuente, ámbito y fecha.
Grafo temporalRelacionar entidades y cambios en el tiempo.Coste operativo y evaluación automática.Puedes explicar por qué se recuperó un hecho.
Vault humanoPensar, escribir y enlazar conocimiento.Ingesta automática fiable.Notas atómicas, enlazadas y con metadatos.
CompactionReducir contexto activo.Corregir decisiones equivocadas.Conserva objetivo, límites, evidencia y siguiente paso.
HandoffContinuar entre sesiones o personas.Ejecutar el trabajo por sí mismo.Alguien puede seguir sin preguntar “¿dónde estábamos?”.

Diseño de memoria por capas

Un sistema serio no tiene “una memoria”. Tiene capas con tiempos de vida distintos.

CapaVida útilDónde viveEjemploError típico
Contexto activoSegundos o minutosLlamada al modeloInstrucciones, tarea, tools, fragmentos recuperados.Meter demasiados tokens “por si acaso”.
Historial de sesiónMinutos u horasSession storeTurnos recientes y tool calls.Confundirlo con memoria a largo plazo.
Estado de ejecuciónDurante la tareaCheckpoint o run_statePaso actual, presupuesto, bloqueos, evidencias.Guardarlo solo en texto conversacional.
Memoria episódicaDías o mesesLog/event storeQué pasó, cuándo, con qué resultado.Guardar eventos sin consulta ni expiración.
Memoria semánticaSemanas o añosStore, grafo, DBHechos consolidados, preferencias, entidades.Aceptar cualquier frase como hecho.
Memoria procedimentalMeses o añosMarkdown versionadoAGENTS.md, CLAUDE.md, reglas de proyecto.Reglas largas, contradictorias y sin dueño.
ArtefactosSegún proyectoFilesystem, Drive, DB, GitArchivos, capturas, diffs, métricas.Copiar contenido entero en vez de citar rutas.
Índice documentalSegún corpusVector DB, search, graphPolíticas, manuales, papers, notas.Recuperar por similitud sin evaluar utilidad.

La regla es sencilla: lo que cambia rápido no debería vivir como verdad permanente; lo que debe auditarse no debería vivir solo en el contexto; lo que es grande debería entrar por referencia; lo que afecta a comportamiento debe estar versionado.

Arquitectura visual de contexto, memoria y handoff

Contexto, memoria, compaction y handoff El agente no recuerda solo: el sistema selecciona, guarda, recupera, compacta y entrega continuidad. ENTRADA Tarea actual usuario + objetivo criterios de cierre no es memoria todavía CONSTRUCTOR DE CONTEXTO Context builder Instrucciones estables Estado paso actual Retrieval docs y notas Memoria recuperada Artefactos por referencia Tools contratos VENTANA EFECTIVA C_t orden + prioridad + forma tokens disponibles margen de salida tools visibles evidencia mínima memoria relevante si todo entra, pero sin criterio, la calidad puede bajar MODELO Y EJECUCIÓN Modelo razona sobre C_t propone salida o tool no ve lo que quedó fuera Observación resultado de tool error recuperable evidencia nueva SALIDAS DE CONTINUIDAD Traza eventos coste decisiones Handoff objetivo límites siguiente paso ALMACENES FUERA DE LA LLAMADA Sesión historial corto turnos recientes Checkpoint estado durable reanudar tarea Store hechos y prefs ámbito + fecha Grafo entidades relaciones Vault Markdown enlaces Artefactos rutas e IDs no copiar todo K compaction preserva invariantes IA para gente curiosa / Facsímil 05 / Capítulo 04 / 686f6c61

El diagrama muestra la separación que conviene mantener en código. El constructor de contexto no es el modelo. El store no es el contexto activo. La compaction no es el handoff completo. La traza no es decoración: es la fuente que permite reconstruir qué pasó.

Compaction: resumir no basta

La compaction aparece cuando el historial crece demasiado o cuando queremos continuar en otra sesión. El error habitual es pedir “resume la conversación” y confiar en que eso alcanza. Para agentes, el objetivo no es producir una crónica; es producir un estado operativo.

Un handoff útil debería tener este aspecto:

handoff_version: "1.0"
objetivo_actual: "Terminar el capítulo 04 del facsímil 05"
criterios_de_cierre:
  - "Tiene fuentes actuales y papers académicos"
  - "Incluye SVG, Mermaid y práctica ejecutable"
  - "Mantiene el estilo sobrio del libro"
decisiones_tomadas:
  - decision: "Distinguir contexto, memoria, compaction y handoff"
    motivo: "Evita confundir historial con memoria duradera"
limites:
  - "No usar lenguaje provisional en el texto público"
  - "No mostrar rangos internos del taller"
artefactos:
  - ruta: "fasciculo-05-agentes-orquestacion/04-contexto-memoria-compaction-handoff.md"
    tipo: "capitulo"
fuentes_clave:
  - "OpenAI Agents SDK Sessions"
  - "Anthropic Claude Code Memory"
  - "Lost in the Middle"
errores_ya_probados:
  - "Tratar memoria como chat completo"
pendientes:
  - "Validar SVG"
  - "Ejecutar build"
siguiente_paso: "Abrir la ruta local y revisar que capítulo 04 aparece en el menú"
no_rehacer:
  - "No volver a buscar definición básica de RAG; ya está en facsímil 04"

Fíjate en tres detalles. Primero, los artefactos entran por referencia. Segundo, las fuentes importantes se nombran para poder reabrirlas. Tercero, aparece no_rehacer. Esta pieza es humilde y potentísima: evita gastar tiempo repitiendo caminos ya descartados.

Memoria en productos reales: qué guardar y qué no

La memoria tiene que tener ciclo de vida. Si un usuario dice “prefiero respuestas cortas”, quizá merece guardarse como preferencia. Si dice “hoy estoy cansado”, quizá solo importa en esa conversación. Si dice “mi dirección es...”, quizá no deberíamos guardarlo salvo que el producto lo necesite y el usuario lo controle. Si una tool devuelve un error transitorio, puede servir para la sesión pero no para siempre.

Una memoria profesional debería tener metadatos mínimos:

CampoPara qué sirveEjemplo
idTrazabilidad.mem_2026_05_26_001
scopeÁmbito.usuario, proyecto, equipo, cliente, sesion.
typeNaturaleza.episodica, semantica, procedimental, artefactual.
statementContenido atómico.“El libro usa SVGs monocromos firmados”.
source_refDe dónde sale.docs/plan-libro.md:626.
confidenceConfianza.0.92.
created_atFecha de creación.2026-06-10.
expires_atCaducidad si aplica.2026-06-26 o null.
ownerQuién la mantiene.autor, equipo, sistema.
delete_policyCómo se retira.Manual, TTL, reemplazo por memoria más nueva.

El paper de Generative Agents ya organizaba agentes con memoria, reflexión y planificación: registraban experiencias, sintetizaban reflexiones y recuperaban recuerdos para planificar comportamiento.16 MemGPT llevó la analogía más cerca de los sistemas operativos: gestión de contexto virtual, capas de memoria y movimiento de información entre memoria rápida y almacenamiento externo.17

El patrón común es este: no basta con recordar; hay que decidir qué merece volver al contexto.

Cómo se compacta una trayectoria

Una trayectoria de agente tiene eventos:

  1. Usuario pide algo.
  2. Sistema fija objetivo y límites.
  3. Modelo propone una tool.
  4. Runtime valida.
  5. Tool devuelve observación.
  6. Sistema actualiza estado.
  7. Se toman decisiones.
  8. Se crean artefactos.
  9. Aparecen errores recuperables.
  10. Se decide continuar, parar o transferir.

La compaction debería leer esa traza y construir una representación menor. No debería inventar. No debería embellecer. No debería borrar el motivo de una decisión.

Tipo de eventoQué conservarQué descartar
ObjetivoResultado esperado y criterios de cierre.Frases repetidas del usuario.
Tool callTool, argumentos importantes, permiso y resultado.Logs largos sin señal.
ObservaciónEvidencia, IDs, errores y métricas.Texto bruto si está guardado por referencia.
DecisiónQué se eligió, alternativas y motivo.Debate irrelevante.
ArtefactoRuta, hash, versión, captura o enlace.Contenido completo si puede reabrirse.
Error recuperableQué falló y qué se probó.Stacktrace entero si ya existe como archivo.
PendienteSiguiente acción concreta.Deseos vagos.

Una buena compaction debe ser verificable. Si el handoff afirma que ya se ejecutó npm run build, la traza debería tener el evento. Si dice que una fuente se consultó, debería tener URL. Si dice que una decisión está tomada, debería conservar el motivo.

La parte científica: medir memoria como un experimento

Un sistema de memoria no se valida preguntando “¿parece que recuerda?”. Se valida como cualquier pieza seria de ingeniería: con hipótesis, baseline, conjunto de pruebas, métricas, trazas y comparación.

La hipótesis debe ser falsable:

“Para tareas largas de revisión técnica, una memoria con store semántico, compaction estructurada y handoff reduce tokens al menos un 35% frente a reenviar el historial completo, manteniendo o mejorando la tasa de continuación correcta.”

Esa frase tiene algo importante: podría salir falsa. Si sale falsa, aprendemos. Si solo decimos “la memoria mejora la experiencia”, no estamos haciendo ingeniería científica; estamos contando una intención.

Sculley y colaboradores advertían que los sistemas de machine learning acumulan deuda técnica oculta cuando no se controlan dependencias, datos, configuración, evaluación y cambios entre versiones.18 Amershi y colaboradores muestran algo parecido desde la ingeniería de software para ML: los equipos necesitan procesos específicos para datos, evaluación, monitorización y mantenimiento porque el comportamiento no depende solo del código.19 En memoria de agentes ocurre igual: no basta con que el código compile; hay que medir qué recuerda, qué olvida, qué recupera mal y qué coste añade.

Un diseño de experimento mínimo:

PiezaQué fijarEjemplo
Unidad de evaluaciónQué cuenta como caso.Una tarea larga con interrupción y reanudación.
Golden setCasos representativos con respuesta esperada.40 tareas: código, documentación, citas, RAG y producto.
Baseline ASistema sin memoria.Solo prompt actual y tools.
Baseline BHistorial completo.Reenviar todo hasta llenar contexto.
Baseline CResumen libre.Pedir “resume la conversación”.
Variante DHandoff estructurado.Schema con objetivo, límites, decisiones y artefactos.
Variante EMemoria recuperable.Store con reglas, hechos, eventos y caducidad.
Variables fijasLo que no debe cambiar.Modelo, temperatura, tools, dataset, criterios y presupuesto.
TrazaQué registrar.Contexto usado, memorias recuperadas, handoff, coste, salida y decisión final.

Las ablaciones son esenciales. Una ablación consiste en quitar una pieza para ver si realmente aportaba. Si quitamos no_rehacer y el agente repite más trabajo, esa pieza vale. Si quitamos memoria semántica y no cambia nada, quizá esa memoria no estaba aportando.

AblaciónQué quitamosQué esperamos observar si era útil
Sin memoria episódicaEventos pasados.Más repetición de pasos y errores ya vistos.
Sin memoria semánticaHechos consolidados.Más preguntas repetidas y decisiones incoherentes.
Sin memoria procedimentalReglas de trabajo.Más incumplimiento de convenciones.
Sin caducidadExpiración de recuerdos.Más memorias obsoletas influyendo.
Sin referencias a artefactosRutas, IDs y enlaces.Más copia de texto y menos trazabilidad.
Sin schema de handoffCampos obligatorios.Más resúmenes bonitos pero incompletos.
Sin rerankingOrdenación final de memorias.Más recuerdos parecidos pero poco útiles en top-k.

Métricas para saber si la memoria funciona

La memoria de agentes se evalúa en varios niveles. Un RAG puede medir si recuperó documentos relevantes. Una memoria de agente debe medir además si permitió continuar, si evitó repetir trabajo, si redujo coste y si no introdujo recuerdos obsoletos.

Una primera métrica:

Precision@k={memorias relevantes en las k primeras}k\operatorname{Precision@k} = \frac{ |\{\text{memorias relevantes en las } k \text{ primeras}\}| }{k}
SímboloSignificadoEjemplo
kkNúmero de memorias recuperadas.5 memorias.
Memorias relevantesRecuerdos que ayudan a resolver la tarea actual.4 de las 5.
Precision@k\operatorname{Precision@k}Proporción útil en el top-k.4/5=0,804/5 = 0,80.

Otra métrica importante es el éxito de continuación:

continuacion_ok=Ntareas reanudadas correctamenteNtareas interrumpidas\operatorname{continuacion\_ok} = \frac{ N_{\text{tareas reanudadas correctamente}} }{ N_{\text{tareas interrumpidas}} }
SímboloSignificadoEjemplo
Ntareas reanudadas correctamenteN_{\text{tareas reanudadas correctamente}}Tareas que continúan sin perder objetivo, límites ni evidencia.31.
Ntareas interrumpidasN_{\text{tareas interrumpidas}}Tareas cortadas y retomadas.40.
continuacion_ok\operatorname{continuacion\_ok}Tasa de continuidad válida.31/40=0,77531/40 = 0,775.

Y una tercera métrica detecta memoria vieja:

tasa_obsolescencia=Nmemorias obsoletas usadasNmemorias usadas\operatorname{tasa\_obsolescencia} = \frac{ N_{\text{memorias obsoletas usadas}} }{ N_{\text{memorias usadas}} }
SímboloSignificadoEjemplo
Nmemorias obsoletas usadasN_{\text{memorias obsoletas usadas}}Recuerdos recuperados que ya no deberían influir.3.
Nmemorias usadasN_{\text{memorias usadas}}Memorias que entraron en contexto.60.
tasa_obsolescencia\operatorname{tasa\_obsolescencia}Fracción de memoria vieja usada.3/60=0,053/60 = 0,05.

La métrica de compresión dice si estamos ahorrando contexto:

ratio_compaction=tokens(ht)tokens(C1:t)\operatorname{ratio\_compaction} = \frac{ \operatorname{tokens}(h_t) }{ \operatorname{tokens}(C_{1:t}) }
SímboloSignificadoEjemplo
hth_tHandoff compacto.1.800 tokens.
C1:tC_{1:t}Historial acumulado antes de compactar.28.000 tokens.
ratio_compaction\operatorname{ratio\_compaction}Tamaño relativo tras compactar.1800/280000,0641800/28000 \approx 0,064.

Pero cuidado: un ratio bajo no siempre es bueno. Si compactamos a 300 tokens y perdemos el objetivo, el sistema ha comprimido muy bien y ha continuado fatal.

MétricaQué mideSeñal de alarma
memory_precision@kCuántas memorias recuperadas son útiles.Top-k lleno de recuerdos vagamente parecidos.
memory_recall@kCuántas memorias necesarias aparecen.Faltan reglas críticas del proyecto.
stale_memory_rateUso de memoria obsoleta.Decisiones antiguas ganan a reglas nuevas.
contradiction_rateMemorias recuperadas que se pisan.El modelo recibe instrucciones incompatibles.
handoff_completenessCampos obligatorios presentes.Falta objetivo, permisos o siguiente paso.
resume_success_rateTareas reanudadas correctamente.El agente pregunta otra vez “¿qué hacemos?”.
token_savingTokens ahorrados frente a baseline.Se ahorra poco o se pierde calidad.
human_correction_minutesMinutos de corrección humana.El sistema parece barato pero desplaza coste a revisión.
latency_p95Tiempo de respuesta en casos largos.La memoria funciona, pero vuelve el producto lento.
unsupported_claim_rateAfirmaciones sin soporte en fuente o traza.La memoria se convierte en rumor operativo.

RAGAS y LangSmith documentan métricas y flujos de evaluación para RAG, como relevancia de contexto, fidelidad y evaluación de respuestas sobre conjuntos de datos.2021 Aquí no las copiamos sin más: las extendemos a memoria de agentes, donde también importan continuidad, caducidad, permisos, artefactos y handoff.

Memoria, RAG, prompt cache y KV cache no son lo mismo

Esta confusión merece una tabla propia. Las cuatro piezas pueden aparecer en la misma aplicación y todas “suenan” a recordar, pero operan en lugares distintos.

ConceptoDónde viveCuánto duraQuién lo controlaPara qué sirveNo sirve para
Contexto activoLlamada al modeloUna inferenciaAplicaciónDar información al siguiente token.Recordar entre sesiones si no se vuelve a enviar.
RAGÍndice documental externoMientras exista el corpusEquipo de datos/productoRecuperar documentos o fragmentos relevantes.Guardar estado de una trayectoria por sí solo.
Memoria de agenteStore, sesión, grafo o ficherosVariableProducto y usuarioReusar hechos, preferencias, eventos y reglas.Sustituir verificación o fuentes.
Prompt cacheInfraestructura del proveedor o runtimeCorto o dependiente del proveedorRuntime/proveedorAhorrar coste/latencia con prefijos repetidos.Elegir qué recuerdos son relevantes.
KV cacheMemoria de inferenciaDurante generación o sesión servidaRuntimeNo recalcular atención de tokens ya procesados.Ser memoria semántica ni fuente citable.
HandoffDocumento o estado estructuradoHasta continuar o archivarSistema/equipoReanudar una tarea larga.Decidir automáticamente qué es verdad permanente.

OpenTelemetry define trazas y spans como unidades para observar trabajo distribuido.22 En agentes, esa idea ayuda a separar “lo que el modelo vio” de “lo que el sistema hizo”. Si no registramos contexto, memoria recuperada, tools y handoff como eventos, luego solo tendremos una conversación larga y pocas respuestas.

Reglas de escritura: cuándo guardar, actualizar o borrar

La memoria debe tener política de escritura. Sin política, cada conversación se convierte en una bolsa de frases.

SituaciónAcción recomendadaEjemplo
Preferencia estable del usuarioGuardar con ámbito usuario.“Prefiere explicaciones paso a paso”.
Regla del proyectoGuardar como memoria procedimental versionada.“Los SVG del libro son monocromos y firmados”.
Decisión temporal de una tareaGuardar en estado o handoff, no como verdad permanente.“Hoy revisamos solo capítulo 04”.
Dato sensible o personalGuardar solo si el producto lo necesita y el usuario lo controla.Dirección, teléfono, información privada.
Resultado de toolGuardar referencia y resumen mínimo.ticket_id, estado, fecha, URL.
Error recuperableGuardar como evento y quizá no_rehacer.“No usar parser X; falló por Y”.
Memoria contradicha por una fuente nuevaActualizar o retirar.Regla editorial reemplazada por versión nueva.
Memoria sin fuenteNo promover a memoria duradera.“Creo que el usuario prefiere...”.

Una política mínima:

memory_policy:
  write_when:
    - "es estable"
    - "tiene fuente"
    - "ayuda a tareas futuras"
    - "tiene ámbito claro"
  do_not_write_when:
    - "solo sirve para este turno"
    - "no tiene evidencia"
    - "puede caducar pronto"
    - "pertenece a otro ámbito"
  update_when:
    - "hay una fuente más nueva"
    - "otra memoria la contradice"
    - "el usuario corrige explícitamente"
  delete_when:
    - "caducó"
    - "el usuario lo pide"
    - "la fuente fue retirada"
    - "la memoria produce errores repetidos"

NIST AI RMF insiste en gobernar sistemas de IA mediante medición, gestión y documentación de riesgos y efectos a lo largo del ciclo de vida.23 En memoria, esa idea se traduce en algo muy concreto: el usuario o el equipo deben poder saber qué se guarda, por qué se recupera, cuándo caduca y cómo se elimina.

Arquitectura de referencia: proyecto, Obsidian, docs y agente

Veamos una arquitectura útil para un equipo pequeño que trabaja con un proyecto técnico, un vault de Obsidian y un agente con tools.

CapaComponenteResponsabilidadEvidencia que deja
Conocimiento humanoObsidian vaultNotas, decisiones, conceptos, enlaces.Markdown, YAML, backlinks.
Corpus técnicoDocs, repo, tickets, papersInformación verificable y artefactos.URLs, rutas, commits, IDs.
IngestaParser + normalizadorTrocear, limpiar, fechar y etiquetar.document_id, chunk_id, hash.
ÍndiceBúsqueda híbridaRecuperar por texto, vector y metadatos.Top-k, score, filtro aplicado.
MemoriaStore por ámbitoGuardar preferencias, reglas y eventos.memory_id, fuente, caducidad.
Context builderEnsambladorElegir qué entra al modelo.Context manifest.
AgenteModelo + toolsProponer pasos y usar herramientas.Tool calls y observaciones.
CompactionReescritor controladoReducir historial a handoff.Handoff validado.
EvaluaciónHarnessMedir continuidad, utilidad y coste.Métricas, baseline, ablaciones.

El context manifest es una pieza que merece nombre propio. Es el recibo de lo que vio el modelo:

{
  "run_id": "run_2026_05_26_004",
  "task": "revisar capitulo 04",
  "model": "modelo-configurado",
  "context_parts": [
    {"type": "instruction", "source": "docs/plan-libro.md", "tokens": 620},
    {"type": "memory", "source": "memory:project:svg_rules", "tokens": 80},
    {"type": "retrieval", "source": "obsidian://Agentes/Context engineering.md", "tokens": 430},
    {"type": "artifact_ref", "source": "fasciculo-05/.../04-contexto.md", "tokens": 45}
  ],
  "excluded_because": [
    {"source": "nota-antigua.md", "reason": "obsoleta"},
    {"source": "chat-completo", "reason": "supera presupuesto y duplica handoff"}
  ]
}

Sin ese manifiesto, cuando el sistema falle será difícil responder a preguntas básicas: qué vio, qué no vio, qué memoria entró, qué documento pesó demasiado y qué se dejó fuera.

Cómo estructuraría un vault de Obsidian para agentes

Si el vault va a alimentar agentes, lo diseñaría con menos romanticismo y más contrato. No hace falta convertir Obsidian en una base de datos rígida, pero sí conviene que las notas tengan forma legible para humanos y máquinas.

Una estructura razonable:

VaultIA/
  00-inbox/
  10-conceptos/
  20-decisiones/
  30-proyectos/
  40-fuentes/
  50-retrospectivas/
  90-plantillas/

Plantilla de nota de decisión:

---
type: decision
project: libro-ia-gente-curiosa
status: active
decided_at: 2026-06-10
owner: 686f6c61
source:
  - docs/plan-libro.md
expires_at:
tags: [agentes, memoria, contexto]
---

# Decisión: cada capítulo lleva handoff si hay trabajo largo

## Contexto
Qué problema resuelve esta decisión.

## Decisión
Qué se hará a partir de ahora.

## Motivo
Por qué elegimos esto frente a otras opciones.

## Cómo lo comprobará un agente
Qué regla, test o búsqueda puede verificarlo.

## Relacionado
- [[Context engineering]]
- [[Compaction]]
- [[Handoff operativo]]

Para un agente, esa nota vale más que una página larga sin estructura. Tiene tipo, estado, fecha, owner, fuente, relación y regla de verificación.

Control humano de la memoria

Un producto con memoria necesita controles visibles. Si el usuario no puede inspeccionar, corregir o borrar memoria, la memoria se convierte en comportamiento opaco.

ControlQué permitePor qué importa
Ver memoriaMostrar qué recuerda el sistema.Detecta errores y sorpresas.
Editar memoriaCorregir una preferencia o hecho.Evita que el sistema repita una mala inferencia.
Borrar memoriaRetirar recuerdos.Da control y reduce carga innecesaria.
Separar ámbitosUsuario, proyecto, equipo, sesión.Evita mezclar contextos distintos.
Ver fuenteSaber de dónde salió.Permite auditar.
Ver última recuperaciónSaber cuándo influyó.Ayuda a depurar decisiones.
Pausar escrituraImpedir guardar durante una tarea.Útil en trabajo sensible o exploratorio.
Exportar handoffContinuar con otra persona o herramienta.Reduce dependencia de una interfaz concreta.

Martin Fowler usa harness engineering para hablar del entorno que rodea al agente y permite trabajar con más control: instrucciones, pruebas, contexto, revisión y mecanismos de seguridad operativa.24 En memoria, el harness no es un extra. Es el lugar donde viven políticas, trazas, evaluaciones y controles humanos.

Manos a la obra

Práctica: construir un handoff operativo.

Kit ejecutable de este capítulo: kit descargable.

# Descomprime el ZIP del capítulo y ejecuta estos comandos dentro de esa carpeta
python3 ops/run_f5_practices.py --chapter c04 --write --fail-on-invalid

Vamos a construir una mini compaction sin dependencias externas. No pretende sustituir un SDK; sirve para entender la mecánica. Partimos de una traza de eventos y generamos un handoff con objetivo, límites, decisiones, artefactos, pendientes y memorias candidatas.

El punto importante no es el código en sí, sino el criterio: una compaction útil debe separar estado para continuar de memoria que quizá conviene guardar.

from __future__ import annotations

import json
from dataclasses import dataclass, field
from datetime import date
from typing import Any


@dataclass
class Event:
    kind: str
    payload: dict[str, Any]


@dataclass
class Handoff:
    handoff_version: str = "1.0"
    objetivo_actual: str = ""
    criterios_de_cierre: list[str] = field(default_factory=list)
    limites: list[str] = field(default_factory=list)
    decisiones_tomadas: list[dict[str, str]] = field(default_factory=list)
    artefactos: list[dict[str, str]] = field(default_factory=list)
    fuentes_clave: list[dict[str, str]] = field(default_factory=list)
    errores_ya_probados: list[str] = field(default_factory=list)
    pendientes: list[str] = field(default_factory=list)
    siguiente_paso: str = ""
    no_rehacer: list[str] = field(default_factory=list)
    memorias_candidatas: list[dict[str, Any]] = field(default_factory=list)


def stable_memory(statement: str, source_ref: str, scope: str = "proyecto") -> dict[str, Any]:
    return {
        "scope": scope,
        "type": "procedimental",
        "statement": statement,
        "source_ref": source_ref,
        "confidence": 0.92,
        "created_at": date.today().isoformat(),
        "expires_at": None,
    }


def build_handoff(events: list[Event]) -> Handoff:
    handoff = Handoff()

    for event in events:
        data = event.payload

        if event.kind == "goal":
            handoff.objetivo_actual = data["text"]
            handoff.criterios_de_cierre.extend(data.get("done_when", []))

        elif event.kind == "constraint":
            handoff.limites.append(data["text"])
            if data.get("durable"):
                handoff.memorias_candidatas.append(
                    stable_memory(data["text"], data["source_ref"])
                )

        elif event.kind == "decision":
            handoff.decisiones_tomadas.append(
                {"decision": data["decision"], "motivo": data["reason"]}
            )

        elif event.kind == "artifact":
            handoff.artefactos.append(
                {"ruta": data["path"], "tipo": data["artifact_type"]}
            )

        elif event.kind == "source":
            handoff.fuentes_clave.append(
                {"titulo": data["title"], "url": data["url"]}
            )

        elif event.kind == "recoverable_error":
            handoff.errores_ya_probados.append(data["lesson"])
            handoff.no_rehacer.append(data["do_not_repeat"])

        elif event.kind == "pending":
            handoff.pendientes.append(data["text"])
            if data.get("next"):
                handoff.siguiente_paso = data["text"]

    validate_handoff(handoff)
    return handoff


def validate_handoff(handoff: Handoff) -> None:
    missing = []
    if not handoff.objetivo_actual:
        missing.append("objetivo_actual")
    if not handoff.criterios_de_cierre:
        missing.append("criterios_de_cierre")
    if not handoff.siguiente_paso:
        missing.append("siguiente_paso")
    if not handoff.artefactos:
        missing.append("artefactos")

    if missing:
        raise ValueError(f"handoff incompleto: {', '.join(missing)}")


events = [
    Event(
        "goal",
        {
            "text": "Terminar el capítulo 04 sobre contexto, memoria, compaction y handoff",
            "done_when": [
                "incluye fuentes actuales",
                "incluye SVG, Mermaid y práctica ejecutable",
                "aparece activo en el índice",
            ],
        },
    ),
    Event(
        "constraint",
        {
            "text": "Todo capítulo debe incluir Mermaid y 'Dónde solía tropezar yo'",
            "source_ref": "docs/plan-libro.md:92",
            "durable": True,
        },
    ),
    Event(
        "decision",
        {
            "decision": "separar memoria de historial",
            "reason": "el historial completo crece y no equivale a memoria curada",
        },
    ),
    Event(
        "source",
        {
            "title": "Lost in the Middle",
            "url": "https://aclanthology.org/2024.tacl-1.9/",
        },
    ),
    Event(
        "artifact",
        {
            "path": "fasciculo-05-agentes-orquestacion/04-contexto-memoria-compaction-handoff.md",
            "artifact_type": "capitulo",
        },
    ),
    Event(
        "recoverable_error",
        {
            "lesson": "una compaction narrativa pierde IDs, rutas y próximos pasos",
            "do_not_repeat": "no pedir solo un resumen bonito de la conversación",
        },
    ),
    Event(
        "pending",
        {
            "text": "validar build de Astro y revisar el capítulo 04 en navegador",
            "next": True,
        },
    ),
]

handoff = build_handoff(events)
print(json.dumps(handoff.__dict__, indent=2, ensure_ascii=False))
print("tests_ok: handoff operativo completo")

Salida esperada, resumida:

{
  "handoff_version": "1.0",
  "objetivo_actual": "Terminar el capítulo 04...",
  "criterios_de_cierre": ["incluye fuentes actuales", "..."],
  "siguiente_paso": "validar build de Astro y revisar el capítulo 04 en navegador",
  "memorias_candidatas": [
    {
      "scope": "proyecto",
      "type": "procedimental",
      "statement": "Todo capítulo debe incluir Mermaid y 'Dónde solía tropezar yo'",
      "source_ref": "docs/plan-libro.md:92"
    }
  ]
}
tests_ok: handoff operativo completo

Preguntas para comprobar si lo entendiste:

PreguntaBuena respuesta
¿Por qué constraint puede convertirse en memoria candidata?Porque es una regla duradera del proyecto, no un detalle temporal.
¿Por qué source no se copia entera?Porque basta con URL y título para reabrir evidencia.
¿Por qué recoverable_error alimenta no_rehacer?Porque continuar bien también significa evitar repetir caminos ya descartados.
¿Qué faltaría para producción?Persistencia real, IDs, permisos, trazas firmadas, tests de compaction y política de borrado.

Cómo encaja todo

flowchart TD
  subgraph F5C04["Capítulo 04 · Contexto, memoria y continuidad"]
    Contexto["Contexto activo"]
    Memoria["Memoria recuperable"]
    Compaction["Compaction"]
    Handoff["Handoff"]
    Vault["Vault humano"]
    Sesion["Sesión"]
    Store["Store de memoria"]
    Traza["Traza de eventos"]
    Politica["Política de escritura"]
    Metricas["Métricas de memoria"]
  end

  subgraph Antes["Conceptos ya trabajados"]
    Estado["Estado, acción y observación (F5 C02)"]
    Tools["Tools y contratos (F5 C03)"]
    RAG["RAG y recuperación (F4 C09)"]
    Embeddings["Embeddings y dimensiones (F4 C07)"]
  end

  subgraph Despues["Lo que viene después"]
    Arquitecturas["Arquitecturas de agentes (F5 C05)"]
    Harness["Harness y trazas (F5 C06)"]
    SDKs["SDKs de agentes (F5 C07)"]
    Evaluacion["Evaluación de agentes (F5 C10)"]
    Operacion["Operación reproducible (F6)"]
  end

  Estado -->|"alimenta"| Contexto
  Tools -->|"añaden contratos a"| Contexto
  RAG -->|"recupera documentos para"| Contexto
  Embeddings -->|"permiten buscar"| Store
  Vault -->|"aporta conocimiento curado a"| Memoria
  Store -->|"devuelve recuerdos a"| Memoria
  Politica -->|"decide guardar o retirar"| Store
  Sesion -->|"mantiene historial corto para"| Contexto
  Memoria -->|"se selecciona dentro de"| Contexto
  Traza -->|"registra"| Estado
  Traza -->|"sirve de entrada a"| Compaction
  Compaction -->|"produce"| Handoff
  Metricas -->|"evalúan"| Memoria
  Metricas -->|"comparan"| Compaction
  Handoff -->|"reanuda"| Arquitecturas
  Contexto -->|"necesita límites de"| Harness
  Memoria -->|"se implementa en"| SDKs
  Handoff -->|"se valida con"| Evaluacion
  Traza -->|"alimenta"| Operacion

  classDef chapter fill:#ffffff,stroke:#111111,color:#111111,stroke-width:1.4px;
  classDef external fill:#f7f7f7,stroke:#777777,color:#111111,stroke-width:1.1px,stroke-dasharray: 5 4;
  class Contexto,Memoria,Compaction,Handoff,Vault,Sesion,Store,Traza,Politica,Metricas chapter;
  class Estado,Tools,RAG,Embeddings,Arquitecturas,Harness,SDKs,Evaluacion,Operacion external;

Vocabulario aprendido

TérminoDefinición útil
Contexto activoTokens que el modelo ve en una llamada concreta.
Ventana de contextoLímite máximo de tokens que puede procesar el modelo en una llamada.
MemoriaInformación guardada fuera de la llamada y recuperada cuando aporta valor.
SesiónHistorial o estado de una conversación concreta.
CheckpointFoto serializable del estado de una ejecución para poder reanudar.
StoreAlmacén consultable de memorias o hechos.
Memoria episódicaRecuerdo de eventos ocurridos.
Memoria semánticaHechos consolidados y reutilizables.
Memoria procedimentalReglas sobre cómo trabajar.
VaultCarpeta de notas enlazadas, normalmente Markdown.
CompactionReescritura estructurada del historial para conservar continuidad con menos tokens.
HandoffPaquete de continuidad para otra sesión, persona o agente.
Artifact referenceRuta, ID, hash o enlace que permite reabrir un artefacto sin copiarlo entero.
Context engineeringDiseño del conjunto de información que el modelo debe ver para el siguiente paso.
ExpiraciónRegla que indica cuándo una memoria deja de ser válida.
BaselineVariante mínima contra la que comparamos un sistema nuevo.
AblaciónPrueba que quita una pieza para medir si realmente aportaba valor.
Golden setConjunto de casos revisados que sirve como referencia de evaluación.
Prompt cacheCaché de prefijos de prompt para ahorrar coste o latencia, sin decidir relevancia.
KV cacheMemoria interna del runtime para no recalcular atención durante inferencia.
Context manifestRecibo estructurado de qué partes entraron y quedaron fuera del contexto.
Tasa de obsolescenciaProporción de memorias antiguas usadas cuando ya no deberían influir.

Dónde solía tropezar yo

TropiezoPor qué ocurreAntídoto
Confundir historial con memoriaEl chat completo parece cómodo porque no hay que decidir.Separar sesión, estado, memoria y artefactos.
Compactar como narradorEl resumen suena bien, pero pierde IDs, rutas y decisiones.Usar un schema de handoff con campos obligatorios.
Guardarlo todoParece prudente, pero llena el store de ruido.Exigir fuente, ámbito, confianza y caducidad.
Recuperar por similitud sin criterioLo parecido semánticamente no siempre es útil para la tarea.Puntuar relevancia, vigencia, autoridad, ruido y coste.
Tratar Obsidian como memoria automáticaTener notas enlazadas no significa que el agente las use bien.Diseñar notas atómicas, propiedades y reglas de recuperación.
Olvidar el borradoUna memoria vieja puede mandar sobre una decisión nueva.Añadir expiración, owner y política de sustitución.

Antes de pasar página

Antes de pasar al capítulo 05, deberías poder responder:

PreguntaSi dudas, vuelve a...
¿Cuál es la diferencia entre contexto, memoria, compaction y handoff?La definición útil.
¿Por qué una ventana larga no sustituye a una memoria bien diseñada?Por qué contexto largo no resuelve la memoria.
¿Qué debe conservar una compaction para no romper continuidad?La anatomía formal del contexto y Compaction: resumir no basta.
¿Qué capas tendría una memoria de agente en producción?Diseño de memoria por capas.
¿Por qué Obsidian puede servir como fuente, pero no como memoria automática?Obsidian: memoria humana, no memoria automática.
¿Qué campos mínimos pondrías a una memoria duradera?Memoria en productos reales: qué guardar y qué no.
¿Qué baseline usarías para demostrar que una memoria mejora algo?La parte científica: medir memoria como un experimento.
¿Qué diferencia hay entre memoria, RAG, prompt cache y KV cache?Memoria, RAG, prompt cache y KV cache no son lo mismo.
¿Qué controles humanos necesita un producto con memoria?Control humano de la memoria.
¿Qué tendría que aparecer en un handoff para que otra persona continuase mañana?Manos a la obra.

Para saber más

En resumen

IdeaQué te llevas
Contexto no es memoria.El contexto es lo que el modelo ve ahora; la memoria vive fuera y debe recuperarse con criterio.
Compaction no es resumir bonito.Una buena compaction conserva objetivo, límites, decisiones, evidencia, artefactos y siguiente paso.
El mercado ofrece piezas, no milagros.Sesiones, stores, grafos, vaults y SDKs resuelven partes distintas del problema.
Obsidian ayuda si está curado.Un vault bien enlazado puede ser una fuente excelente; sin metadatos y disciplina solo es texto acumulado.
La ingeniería está en decidir qué entra.Context engineering consiste en preparar el entorno exacto que el modelo necesita para el siguiente paso.
Una memoria se demuestra con evaluación.Baselines, ablaciones, métricas, trazas y control humano separan una demo prometedora de un sistema mantenible.

Notas

  1. Karpathy, A. (2025, 19 de junio). Software Is Changing (Again). Y Combinator AI Startup School. https://rosetta.to/u/ycombinator/andrej-karpathy-software-is-changing-again. Consultado el 10 de junio de 2026.

  2. Martin, L. (2025, 23 de junio). Context Engineering for Agents. https://rlancemartin.github.io/2025/06/23/context_engineering/. Consultado el 10 de junio de 2026.

  3. Liu, N. F., Lin, K., Hewitt, J., Paranjape, A., Bevilacqua, M., Petroni, F., & Liang, P. (2024). Lost in the Middle: How Language Models Use Long Contexts. Transactions of the Association for Computational Linguistics, 12, 157-173. https://doi.org/10.1162/tacl_a_00638.

  4. Lewis, P., Perez, E., Piktus, A., Petroni, F., Karpukhin, V., Goyal, N., Küttler, H., Lewis, M., Yih, W., Rocktäschel, T., Riedel, S., & Kiela, D. (2020). Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. Advances in Neural Information Processing Systems 33, 9459-9474. https://papers.neurips.cc/paper/2020/hash/6b493230205f780e1bc26945df7481e5-Abstract.html.

  5. Obsidian. (2026). Graph view. https://obsidian.md/help/Plugins/Graph%2Bview. Consultado el 10 de junio de 2026.

  6. Obsidian. (2026). Bases syntax. https://obsidian.md/help/bases/syntax. Consultado el 10 de junio de 2026.

  7. OpenAI. (2026). Agents SDK: Sessions. https://openai.github.io/openai-agents-python/sessions/. Consultado el 10 de junio de 2026.

  8. OpenAI. (2026). Agents SDK JS: Sessions. https://openai.github.io/openai-agents-js/guides/sessions/. Consultado el 10 de junio de 2026.

  9. Anthropic. (2026). How Claude remembers your project. https://code.claude.com/docs/en/memory. Consultado el 10 de junio de 2026.

  10. Google. (2026). Agent Development Kit: Memory. https://adk.dev/sessions/memory/. Consultado el 10 de junio de 2026.

  11. LangChain. (2026). LangGraph persistence. https://docs.langchain.com/oss/python/langgraph/persistence. Consultado el 10 de junio de 2026.

  12. LlamaIndex. (2026). Memory. https://developers.llamaindex.ai/python/framework/module_guides/deploying/agents/memory/. Consultado el 10 de junio de 2026.

  13. Zep. (2026). Memory. https://help.getzep.com/v2/memory. Consultado el 10 de junio de 2026.

  14. Mem0. (2026). Platform Overview. https://docs.mem0.ai/platform/overview. Consultado el 10 de junio de 2026.

  15. Letta. (2026). Understanding memory management. https://docs.letta.com/concepts/memory-management. Consultado el 10 de junio de 2026.

  16. Park, J. S., O'Brien, J. C., Cai, C. J., Morris, M. R., Liang, P., & Bernstein, M. S. (2023). Generative Agents: Interactive Simulacra of Human Behavior. Proceedings of UIST 2023. https://doi.org/10.1145/3586183.3606763.

  17. Packer, C., Wooders, S., Lin, K., Fang, V., Patil, S. G., Stoica, I., & Gonzalez, J. E. (2024). MemGPT: Towards LLMs as Operating Systems. arXiv. https://doi.org/10.48550/arXiv.2310.08560.

  18. Sculley, D., Holt, G., Golovin, D., Davydov, E., Phillips, T., Ebner, D., Chaudhary, V., Young, M., Crespo, J.-F., & Dennison, D. (2015). Hidden Technical Debt in Machine Learning Systems. Advances in Neural Information Processing Systems 28. https://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems.

  19. Amershi, S., Begel, A., Bird, C., DeLine, R., Gall, H., Kamar, E., Nagappan, N., Nushi, B., & Zimmermann, T. (2019). Software Engineering for Machine Learning: A Case Study. 2019 IEEE/ACM 41st International Conference on Software Engineering: Software Engineering in Practice, 291-300. https://doi.org/10.1109/ICSE-SEIP.2019.00042.

  20. RAGAS. (2026). Available metrics. https://docs.ragas.io/en/stable/concepts/metrics/available_metrics/. Consultado el 10 de junio de 2026.

  21. LangChain. (2026). Evaluate a RAG application. https://docs.langchain.com/langsmith/evaluate-rag-tutorial. Consultado el 10 de junio de 2026.

  22. OpenTelemetry. (2026). Tracing API. https://opentelemetry.io/docs/specs/otel/trace/api/. Consultado el 10 de junio de 2026.

  23. National Institute of Standards and Technology. (2023). Artificial Intelligence Risk Management Framework (AI RMF 1.0). https://doi.org/10.6028/NIST.AI.100-1.

  24. Fowler, M. (2025). Harness Engineering for Coding Agent Users. https://martinfowler.com/articles/harness-engineering.html. Consultado el 10 de junio de 2026.

Capítulo 05

Facsímil 5 · Agentes y orquestación

Capítulo 05: Arquitecturas de agentes: de ReAct a sistemas multiagente

El patrón importa más que la etiqueta

En los capítulos anteriores ya tenemos las piezas: estado, acción, observación, tools, memoria y handoff. Ahora aparece una pregunta muy de ingeniería: ¿cómo las organizo?

Una arquitectura agentic no es un nombre bonito. Es una decisión sobre el bucle: quién planifica, quién ejecuta, quién verifica, dónde vive la memoria, cuándo se consulta una tool, cuándo se pide ayuda y cómo se mide la trayectoria.

El repositorio de Fareed Khan, All Agentic Architectures, es útil precisamente porque convierte esas ideas en notebooks ejecutables. La colección declara implementaciones prácticas de arquitecturas agentic con LangChain y LangGraph, y ordena los patrones desde los más básicos hasta sistemas multiagente, memoria avanzada, simulación y metacognición.1 Lo usaremos como catálogo práctico, no como autoridad única: cada patrón hay que traducirlo a nuestro lenguaje de G,S,A,O,π,T,Ω,BG, S, A, O, \pi, T, \Omega, B.

Repositorio base de Fareed Khan: https://github.com/FareedKhan-dev/all-agentic-architectures. En este capítulo se han revisado los notebooks .ipynb del repositorio para explicar qué demuestra cada arquitectura y qué habría que añadir para llevarla a un sistema real.

La pregunta correcta

Antes de elegir una arquitectura, no preguntes “¿cuál es la más avanzada?”. Pregunta:

PreguntaQué decide
¿La tarea se puede resolver en una sola pasada?Si basta con prompt o si hace falta reflexión.
¿Necesita datos externos o cálculo exacto?Si hace falta tool use.
¿El siguiente paso depende de lo observado?Si conviene ReAct o Plan-Execute-Verify.
¿Hay varias habilidades claramente separables?Si conviene multiagente, ensemble o meta-controlador.
¿La tarea depende de memoria persistente?Si necesitas memoria episódica, semántica o grafo.
¿Actuar tiene coste alto?Si necesitas dry-run, simulador o aprobación.
¿La arquitectura debe mejorar con feedback?Si necesitas self-improvement o evaluación sistemática.

Ejemplo de fórmula. La forma formal de verlo es una función de selección:

p\*=argmaxpP[U(p,x)λcC(p)λlL(p)λvV(p)]p^\* = \arg\max_{p \in P} \left[ U(p, x) - \lambda_c C(p) - \lambda_l L(p) - \lambda_v V(p) \right]
SímboloSignificadoEjemplo
ppPatrón candidato.ReAct, planning, ensemble, graph memory.
PPConjunto de patrones disponibles.Las 17 arquitecturas del catálogo.
xxTarea concreta.“Verifica estas fuentes y corrige citas APA”.
U(p,x)U(p, x)Utilidad esperada del patrón para esa tarea.ReAct sube si hay que consultar páginas.
C(p)C(p)Coste operativo.Llamadas a modelo, tools, base vectorial, grafo.
L(p)L(p)Latencia.Un ensemble tarda más que una llamada simple.
V(p)V(p)Dificultad de verificación.Más agentes implican más trazas y más comparación.
λ\lambdaPeso de cada penalización.En producción suele subir λl\lambda_l y λv\lambda_v.

Esta fórmula no pretende automatizarlo todo. Sirve para no elegir arquitectura por entusiasmo. Una arquitectura más compleja solo compensa si aumenta utilidad más de lo que aumenta coste, latencia y dificultad de verificación.

Árbol de decisión para elegir arquitectura

Este árbol se recorre varias veces. Una tarea real puede acabar en más de una hoja: por ejemplo, una revisión académica puede necesitar multiagente para separar roles, ReAct para consultar fuentes, PEV para comprobar resultados y dry-run para enseñar cambios antes de aplicarlos. La pregunta no es “qué nombre queda bonito”, sino qué pieza cubre una necesidad que de verdad existe.

flowchart TD
    A["Tarea nueva"] --> B{"¿salida en una pasada?"}

    B -->|"sí"| C{"¿hay rúbrica clara?"}
    C -->|"sí"| L01["Reflection"]
    C -->|"no"| L02["Prompt + contrato"]

    B -->|"no"| D{"¿necesita datos externos?"}
    D -->|"sí"| E{"¿un dato puntual?"}
    E -->|"sí"| L03["Tool use + PEV"]
    E -->|"no"| F{"¿paso depende de observar?"}
    F -->|"sí"| L04["ReAct + Tool use"]
    F -->|"no"| L05["Planning + Tool use"]

    D -->|"no"| G{"¿hay muchas rutas?"}
    G -->|"sí"| H{"¿puedes puntuar ramas?"}
    H -->|"sí"| L06["Tree of Thoughts"]
    H -->|"no"| L07["Planning + Reflection"]
    G -->|"no"| I{"¿actuar cuesta caro?"}

    I -->|"sí"| J{"¿puedes simular?"}
    J -->|"sí"| L08["Simulator + Dry-run"]
    J -->|"no"| L09["Dry-run + aprobación"]
    I -->|"no"| K{"¿hay varias habilidades?"}

    K -->|"sí"| M{"¿roles fijos?"}
    M -->|"sí"| N{"¿evidencia compartida?"}
    N -->|"sí"| L10["Blackboard + Multi-agent"]
    N -->|"no"| L11["Multi-agent secuencial"]
    M -->|"no"| L12["Meta-controller"]

    K -->|"no"| O{"¿quieres comparar salidas?"}
    O -->|"sí"| L13["Ensemble"]
    O -->|"no"| P{"¿necesita memoria?"}

    P -->|"sí"| Q{"¿recuerda experiencias?"}
    Q -->|"sí"| R{"¿también hay conceptos?"}
    R -->|"sí"| L14["Episodic + Semantic"]
    R -->|"no"| L15["Episodic memory"]
    Q -->|"no"| S{"¿importan relaciones?"}
    S -->|"sí"| L16["Graph memory"]
    S -->|"no"| L17["Semantic memory"]

    P -->|"no"| T{"¿entorno espacial?"}
    T -->|"sí"| L18["Cellular automata"]
    T -->|"no"| U{"¿debe mejorar con señales?"}
    U -->|"sí"| L19["Feedback loop"]
    U -->|"no"| V{"¿debe reconocer límites?"}
    V -->|"sí"| L20["Metacognitive"]
    V -->|"no"| L21["Planning simple"]

    L03 --> Z{"¿verificación fuerte?"}
    L04 --> Z
    L05 --> Z
    L08 --> Z
    L09 --> Z
    L10 --> Z
    L11 --> Z
    L12 --> Z
    L13 --> Z
    L14 --> Z
    L16 --> Z
    L19 --> Z
    Z -->|"sí"| L22["Añadir PEV"]
    Z -->|"no"| AA{"¿traza obligatoria?"}
    AA -->|"sí"| L23["Añadir harness"]
    AA -->|"no"| L24["Mantener simple"]

    classDef question fill:#FFFFFF,stroke:#111111,color:#111111,stroke-width:1.4px;
    classDef leaf fill:#111111,stroke:#111111,color:#FFFFFF,stroke-width:1.4px;
    classDef support fill:#F6F6F6,stroke:#111111,color:#111111,stroke-width:1.2px;
    class A,B,C,D,E,F,G,H,I,J,K,M,N,O,P,Q,R,S,T,U,V,Z,AA question;
    class L01,L02,L03,L04,L05,L06,L07,L08,L09,L10,L11,L12,L13,L14,L15,L16,L17,L18,L19,L20,L21,L22,L23,L24 leaf;

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

La parte final del árbol es deliberada: aunque una rama ya te haya recomendado una arquitectura, todavía pregunta por verificación y traza. En sistemas con agentes no basta con elegir el patrón; hay que decidir cómo sabrás que funcionó.

Si el árbol llega a...Léelo así
ReflectionLa tarea cabe en una salida, pero necesitas revisión explícita.
Tool use + PEVHay una consulta externa y debes validar el resultado.
ReAct + Tool useEl siguiente paso depende de lo que observes.
Planning + Tool useSabes los pasos antes de ejecutar, pero necesitas datos externos.
Tree of ThoughtsHay varias rutas y puedes evaluar estados intermedios.
Simulator + Dry-runAntes de actuar, puedes probar consecuencias.
Blackboard + Multi-agentVarios especialistas necesitan escribir sobre la misma evidencia.
Meta-controllerNo sabes de antemano qué especialista conviene.
EnsembleQuieres comparar varias salidas antes de decidir.
Episodic + SemanticNecesitas recordar experiencias y conceptos estables.
Graph memoryLas relaciones entre entidades son parte del problema.
Cellular automataEl comportamiento global sale de muchas reglas locales.
Feedback loopLa tarea se repite y quieres conservar señales de mejora.
MetacognitiveLa calidad incluye saber cuándo responder, usar tool, pedir revisión o parar.

Mapa visual de arquitecturas agentic

Mapa de arquitecturas agentic La arquitectura decide cómo se reparten memoria, tools, planificación, verificación y coordinación. Fundacionales mejoran un agente único 01 Reflection generar · criticar · revisar 02 Tool use salir al mundo 03 ReAct razonar · actuar · observar 04 Planning plan antes de ejecutar Colaboración divide responsabilidad 05 Multi-agent especialistas 07 Blackboard memoria compartida 11 Meta-control router supervisor 13 Ensemble vistas paralelas Memoria y razonamiento explora y recuerda 08 Dual memory episódica · semántica 09 ToT ramas de pensamiento 12 Graph memory entidades y relaciones Fiabilidad mide antes de soltar 06 PEV plan · execute · verify 10 Simulator probar consecuencias 14 Dry-run simular antes de aplicar 17 Metacognitive saber cuándo parar Aprendizaje mejora y emergencia 15 Feedback guardar señales 16 Cellular reglas locales Elegir patrón = maximizar utilidad y restar coste, latencia y dificultad de verificación. IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

La figura no coloca los patrones en una escalera moral. No hay una arquitectura “mejor” en abstracto. Hay familias que resuelven problemas distintos: mejorar una respuesta, usar herramientas, coordinar especialistas, recordar, verificar, simular o aprender de señales.

Arquitecturas agentic: las 17 del catálogo

El apartado se llama así a propósito: no estamos enumerando “técnicas sueltas”, sino arquitecturas agentic. Cada una toma las piezas del capítulo 02 y decide cómo se conectan. En todas hay que preguntar lo mismo: qué estado guarda, qué acciones permite, qué observaciones acepta, qué política decide y qué criterio de parada evita que el sistema siga por inercia.

El catálogo de Khan lista 17 notebooks principales y los presenta como un recorrido progresivo: patrones fundacionales, colaboración multiagente, memoria/razonamiento, fiabilidad y aprendizaje.2 Aquí los explicamos con más lente de ingeniería.

Cada arquitectura incluye un Mermaid propio. No son adornos: son una forma rápida de ver qué entra, qué estado cambia, dónde se decide, qué se comprueba y cuándo termina el flujo.

01. Arquitectura Reflection

Reflection convierte una salida en un pequeño ciclo editorial: generar, criticar, revisar y volver a evaluar. No añade conocimiento nuevo por sí misma; añade una segunda mirada estructurada sobre lo ya producido. En código o escritura técnica, esto permite separar “crear” de “revisar”. El notebook 01_reflection.ipynb lo presenta como el paso de un generador de una sola pasada a un agente que produce, evalúa y mejora antes de entregar.

El estado mínimo contiene la primera versión, una rúbrica, los hallazgos de la crítica y la versión corregida. La política decide si basta una revisión o si hace falta otra iteración. La métrica no debería ser “suena mejor”, sino defectos corregidos, tests que pasan, citas arregladas o errores eliminados.

Reflexion formaliza una idea cercana: agentes que usan feedback verbal para mejorar decisiones futuras.3 Self-Refine estudia el ciclo generar-feedback-refinar sin entrenamiento adicional.4 La trampa: si la crítica es vaga, solo produces una segunda respuesta igual de frágil pero más cara.

flowchart LR
    A["Tarea"] --> B["Generar versión"]
    B --> C["Criticar con rúbrica"]
    C --> D["Revisar salida"]
    D --> E{"¿cumple criterio?"}
    E -->|"sí"| F["Entregar"]
    E -->|"no"| C
    C --> G["Hallazgos"]
    G --> D

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

02. Arquitectura Tool use

Tool use aparece cuando el modelo no debe inventar una respuesta desde memoria paramétrica, sino pedir ayuda a una función externa: buscar, calcular, consultar una base de datos, abrir una URL o validar un JSON. Aquí la arquitectura separa lenguaje natural de ejecución. El notebook 02_tool_use.ipynb lo plantea como el puente entre el razonamiento del LLM y datos vivos: APIs, funciones y fuentes que no caben en los pesos del modelo.

El estado mínimo contiene la intención de la tarea, el catálogo de herramientas, el esquema de entrada de cada tool, permisos, timeouts y observaciones. La salida importante no es el texto final, sino el par tool_call -> tool_result: ahí se ve si el agente usó una capacidad real o solo la mencionó.

Toolformer mostró que los modelos pueden aprender cuándo llamar herramientas, pero en sistemas de ingeniería se declara un contrato explícito.5 Las APIs modernas de agentes siguen esa línea: tools con descripción, esquema y resultado verificable.6 Cuidado: una tool sin validación es solo una nueva forma de meter ruido.

flowchart LR
    A["Pregunta"] --> B["Detectar intención"]
    B --> C{"¿requiere tool?"}
    C -->|"no"| H["Responder con contexto"]
    C -->|"sí"| D["Construir argumentos"]
    D --> E["Ejecutar tool"]
    E --> F["Validar resultado"]
    F --> G["Integrar evidencia"]
    G --> H

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

03. Arquitectura ReAct

ReAct combina razonamiento y acción en un bucle: pensar el siguiente paso, ejecutar una tool, observar, actualizar y volver a decidir. Es la arquitectura que más claramente conecta con st,at,ot+1,Ts_t, a_t, o_{t+1}, T. El notebook 03_ReAct.ipynb compara un agente de tool use de una sola llamada con un agente capaz de iterar think -> act -> observe.

Encaja cuando el siguiente paso depende de lo que se observa: investigar una librería, revisar una web, depurar un error, comparar fuentes. No encaja cuando el flujo está cerrado y siempre se ejecuta igual; ahí un workflow simple suele ser más barato y auditable.

El paper de ReAct mostró que intercalar razonamiento y acción ayuda en tareas que requieren información externa y decisiones sucesivas.7 El cuidado principal es la parada: si una observación no cambia el estado, repetir otra tool parecida rara vez mejora el sistema.

flowchart TD
    A["Objetivo"] --> B["Estado s_t"]
    B --> C["Razonar siguiente paso"]
    C --> D["Acción a_t"]
    D --> E["Tool o entorno"]
    E --> F["Observación o_t+1"]
    F --> G["Actualizar estado"]
    G --> H{"¿criterio de parada?"}
    H -->|"no"| C
    H -->|"sí"| I["Respuesta final"]

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

04. Arquitectura Planning

Planning separa planificar de ejecutar. El agente crea una descomposición de la tarea antes de tocar herramientas o producir la respuesta final. Esto da estructura, permite revisar el plan y hace visible si la solución omitió pasos. El notebook 04_planning.ipynb compara esta estrategia con ReAct: en vez de reaccionar paso a paso, crea una secuencia de subtareas antes de ejecutar.

El estado mínimo contiene objetivo, lista de subtareas, dependencias, estado de cada paso y evidencias asociadas. Si el plan no se actualiza al recibir observaciones, no es una arquitectura viva: es una lista decorativa.

La planificación automática tiene una tradición larga en IA clásica, desde lenguajes como PDDL hasta enfoques de planificación heurística.89 En agentes con LLM, el plan es útil si se puede verificar y corregir, no si solo parece ordenado.

flowchart LR
    A["Objetivo"] --> B["Planner"]
    B --> C["Subtareas"]
    C --> D["Dependencias"]
    D --> E["Ejecutor"]
    E --> F["Evidencias"]
    F --> G{"¿plan válido?"}
    G -->|"sí"| H["Síntesis"]
    G -->|"ajustar"| B

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

05. Arquitectura Multi-Agent Systems

Multi-agent divide el trabajo entre especialistas: un agente investiga, otro escribe, otro verifica, otro sintetiza. Su valor no está en tener muchos nombres, sino en separar responsabilidades que tienen criterios de calidad distintos. El notebook 05_multi_agent.ipynb usa un ejemplo de análisis de mercado con analistas especializados y un agente gestor que sintetiza.

El estado mínimo incluye roles, entradas, salidas esperadas, permisos de cada agente y un mecanismo de integración. Si todos los agentes pueden hacer lo mismo, no hay arquitectura; hay redundancia cara.

El ejemplo editorial del capítulo 02 encaja aquí: un agente RAE revisa lengua, otro APA revisa referencias y otro navegador comprueba fuentes. Cada uno produce observaciones distintas. La coordinación puede ser un workflow fijo o un agente coordinador, según si el orden depende de resultados intermedios.

flowchart TD
    A["Tarea común"] --> B["Coordinador"]
    B --> C["Agente RAE"]
    B --> D["Agente APA"]
    B --> E["Agente navegador"]
    C --> F["Observación lingüística"]
    D --> G["Observación bibliográfica"]
    E --> H["Observación de fuente"]
    F --> I["Síntesis"]
    G --> I
    H --> I
    I --> J["Salida integrada"]

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

06. Arquitectura PEV: Plan, Execute, Verify

PEV separa tres momentos: planificar, ejecutar y verificar. Parece obvio, pero cambia mucho el diseño: la verificación deja de ser una sensación final y se convierte en una fase con datos propios. El notebook 06_PEV.ipynb lo muestra como un planificador-ejecutor al que se añade un verificador para detectar fallos de herramientas y activar recuperación.

El estado mínimo contiene plan, resultado de ejecución, verificador, errores detectados y acción correctiva. Sirve cuando las herramientas fallan, las fuentes pueden no respaldar una afirmación o el código puede pasar por estados intermedios incorrectos.

Anthropic recomienda desarrollar tests para aplicaciones con LLM, precisamente porque las respuestas deben medirse en escenarios concretos y no solo leerse a ojo.10 La trampa de PEV es poner otro LLM como “verificador” sin rúbrica, tests ni evidencia externa.

flowchart LR
    A["Objetivo"] --> B["Plan"]
    B --> C["Paso ejecutable"]
    C --> D["Execute"]
    D --> E["Resultado"]
    E --> F["Verify"]
    F --> G{"¿pasa?"}
    G -->|"sí"| J{"¿terminado?"}
    G -->|"no"| I["Replanificar"]
    I --> B
    J -->|"sí"| K["Entrega"]
    J -->|"no"| C

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

07. Arquitectura Blackboard

Blackboard usa una memoria compartida donde varios especialistas escriben hallazgos parciales. El controlador observa el estado del tablero y decide qué especialista debe actuar después. El notebook 07_blackboard.ipynb lo contrapone a un multiagente secuencial: en vez de pasar siempre por A, B y C, el controlador activa al especialista que el tablero necesita en ese momento.

Esta idea viene de sistemas clásicos de IA: el modelo blackboard se usaba para resolver problemas complejos mediante fuentes de conocimiento especializadas que colaboran sobre una estructura común.11 En agentes modernos, el blackboard puede ser una tabla, un documento, una base vectorial, un grafo o un estado de LangGraph.

El estado mínimo necesita autor, fuente, timestamp, confianza, versión y relación con otras evidencias. Si el tablero no distingue “hecho”, “hipótesis” y “conclusión”, se vuelve una pared llena de notas imposibles de auditar.

flowchart TD
    A["Problema"] --> B["Blackboard"]
    B --> C["Controlador"]
    C --> D["Especialista datos"]
    C --> E["Especialista reglas"]
    C --> F["Especialista síntesis"]
    D --> G["Hechos"]
    E --> H["Hipótesis"]
    F --> I["Conclusiones"]
    G --> B
    H --> B
    I --> B
    B --> J["Respuesta auditada"]

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

08. Arquitectura Episodic + Semantic Memory

La memoria episódica guarda experiencias: qué pasó, cuándo, con quién, en qué tarea. La memoria semántica guarda conocimiento más estable: conceptos, preferencias, hechos, reglas, entidades. Combinarlas evita dos extremos: olvidar todo o recordar texto bruto sin estructura. El notebook 08_episodic_with_semantic.ipynb usa una base vectorial para episodios y Neo4j para hechos y relaciones.

Un asistente de proyecto puede guardar episodios como “la última vez el build falló por KaTeX” y conocimiento semántico como “este libro exige Mermaid en cada capítulo”. La recuperación debe explicar por qué trae una memoria concreta.

Generative Agents popularizó una arquitectura con memoria, reflexión y planificación para simular comportamiento persistente de agentes en un entorno.12 La regla práctica: toda memoria debe tener fuente, fecha, caducidad y mecanismo de corrección.

flowchart LR
    A["Nueva tarea"] --> B["Recuperar episodios"]
    A --> C["Consultar memoria semántica"]
    B --> D["Contexto vivido"]
    C --> E["Hechos y reglas"]
    D --> F["Componer contexto"]
    E --> F
    F --> G["Responder o actuar"]
    G --> H["Nuevo episodio"]
    G --> I["Nueva relación"]
    H --> B
    I --> C

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

09. Arquitectura Tree of Thoughts

Tree of Thoughts no sigue una sola cadena de razonamiento. Construye varias ramas, evalúa estados intermedios y poda caminos poco prometedores. Es búsqueda aplicada al razonamiento con LLM. El notebook 09_tree_of_thoughts.ipynb usa el problema del lobo, la cabra y la col para mostrar por qué una trayectoria lineal puede quedar atrapada y una búsqueda por ramas puede recuperar el camino.

El estado mínimo contiene nodos, puntuación de cada rama, profundidad, criterio de expansión y criterio de poda. Encaja en puzzles lógicos, planificación con restricciones, demostraciones o decisiones donde una mala primera intuición arrastra toda la respuesta.

El paper de Tree of Thoughts propone deliberar mediante búsqueda sobre unidades de pensamiento, no solo generar una secuencia lineal.13 La factura aparece rápido: ancho de búsqueda por profundidad por coste de evaluación. Sin límites, es elegante y carísimo.

flowchart TD
    A["Problema"] --> B["Generar ramas"]
    B --> C["Estado 1"]
    B --> D["Estado 2"]
    B --> E["Estado 3"]
    C --> F["Evaluar"]
    D --> F
    E --> F
    F --> G["Podar ramas débiles"]
    G --> H["Expandir mejores"]
    H --> I{"¿solución?"}
    I -->|"no"| B
    I -->|"sí"| J["Camino elegido"]

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

10. Arquitectura Mental Loop o Simulator

Mental Loop introduce un simulador interno. Antes de actuar, el agente prueba una consecuencia en un modelo del entorno: “si hago esto, ¿qué pasaría?”. Puede ser un simulador real, una función determinista, una evaluación de impacto o una réplica controlada. El notebook 10_mental_loop.ipynb usa un agente de trading que ensaya una estrategia en una copia del mercado antes de actuar.

El estado mínimo contiene acción propuesta, simulación, predicción, incertidumbre y decisión. Sirve cuando actuar tiene coste: cambiar una base de datos, modificar un archivo, ejecutar una orden, mover inventario o recomendar una decisión profesional.

La clave no es “imaginar” consecuencias, sino comparar predicción y resultado real en casos pequeños. Si el simulador no se calibra, tranquiliza sin proteger.

flowchart LR
    A["Acción candidata"] --> B["Simulador"]
    B --> C["Predicción"]
    C --> D["Evaluar impacto"]
    D --> E{"¿aceptable?"}
    E -->|"sí"| F["Ejecutar"]
    E -->|"no"| G["Ajustar acción"]
    G --> B
    F --> H["Resultado real"]
    H --> I["Calibrar simulador"]
    I --> B

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

11. Arquitectura Meta-Controller

Meta-controller es un router con criterio. Recibe una tarea, estima qué especialista o flujo conviene y delega. Es útil cuando hay muchas capacidades disponibles y elegir mal la primera acción ya encarece todo. El notebook 11_meta_controller.ipynb usa tres especialistas: generalista, investigación y código; el controlador decide quién debe responder.

El estado mínimo contiene intención clasificada, capacidades disponibles, coste esperado, restricciones y resultado del especialista. El meta-controlador debería aprender de errores de enrutamiento: cuántas veces manda una consulta técnica al agente equivocado, cuánto tarda y qué calidad final obtiene.

OpenAI Agents SDK ofrece handoffs entre agentes, una forma práctica de representar esta delegación controlada.14 La trampa es convertir el router en un “jefe” que opina de todo. Su trabajo principal es enrutar, medir y corregir routing.

flowchart TD
    A["Tarea"] --> B["Meta-controlador"]
    B --> C["Clasificar intención"]
    C --> D{"¿qué flujo conviene?"}
    D --> E["Generalista"]
    D --> F["Investigación"]
    D --> G["Código"]
    E --> H["Resultado"]
    F --> H
    G --> H
    H --> I["Medir routing"]
    I --> B

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

12. Arquitectura Graph o World-Model Memory

Graph memory guarda entidades y relaciones: autor-publicó-paper, capítulo-cita-fuente, herramienta-produce-observación, concepto-depende-de-concepto. Esto permite preguntas multi-hop que una lista de chunks recuperados no resuelve bien. El notebook 12_graph.ipynb construye un agente de inteligencia corporativa que extrae compañías, personas, productos y relaciones hacia un grafo consultable.

El estado mínimo contiene nodos, aristas, tipos, procedencia y reglas de actualización. Encaja cuando importa la estructura: ontologías, dependencias de software, investigación documental, mapas conceptuales o memoria de proyecto.

Los knowledge graphs se estudian como estructuras para representar entidades y relaciones consultables.15 En agentes, el grafo no reemplaza RAG; lo complementa cuando las relaciones importan tanto como los documentos.

flowchart LR
    A["Documentos"] --> B["Extraer entidades"]
    B --> C["Extraer relaciones"]
    C --> D["Grafo"]
    D --> E["Consulta multi-hop"]
    E --> F["Evidencia enlazada"]
    F --> G["Respuesta"]
    G --> H["Actualizar grafo"]
    H --> D

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

13. Arquitectura Ensemble

Ensemble ejecuta varias perspectivas y agrega. Puede significar varios modelos, varios prompts, varios agentes especialistas o varias trayectorias de razonamiento. Es útil cuando el error de una sola trayectoria sería demasiado frágil. El notebook 13_ensemble.ipynb usa un comité de inversión con perfiles distintos y un agregador que sintetiza consenso y discrepancias.

El estado mínimo contiene respuestas candidatas, criterios de comparación, discrepancias, agregación y decisión final. Agregar no es hacer media de frases ni votar por mayoría sin mirar evidencia.

Self-consistency mostró que muestrear varios razonamientos y agregar respuestas puede mejorar razonamiento sobre chain-of-thought.16 En un agente editorial, por ejemplo, un ensemble puede comparar tres verificaciones de una cita, pero la síntesis debe explicar por qué acepta una.

flowchart TD
    A["Pregunta"] --> B["Agente 1"]
    A --> C["Agente 2"]
    A --> D["Agente 3"]
    B --> E["Respuesta A"]
    C --> F["Respuesta B"]
    D --> G["Respuesta C"]
    E --> H["Agregador"]
    F --> H
    G --> H
    H --> I["Detectar discrepancias"]
    I --> J["Síntesis justificada"]

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

14. Arquitectura Dry-Run Harness

Dry-run harness exige simular antes de aplicar. No basta con que el agente diga “haría esto”; debe mostrar diff, efecto esperado, coste, permisos y plan de reversión antes de tocar el entorno real. El notebook 14_dry_run.ipynb usa un agente de redes sociales corporativas que primero ejecuta en modo dry_run=True, muestra la traza y solo después permite publicar.

El estado mínimo contiene acción propuesta, simulación, diff, comprobaciones, aprobación y resultado tras aplicar. Encaja muy bien con herramientas de código, operaciones, migraciones y flujos editoriales de publicación.

La documentación de tracing del Agents SDK es relevante porque un dry-run sin traza no se puede auditar después.17 En producción, el dry-run debe ser legible por una persona y comparable contra el resultado real.

flowchart LR
    A["Acción propuesta"] --> B["Modo dry-run"]
    B --> C["Diff previsto"]
    B --> D["Coste estimado"]
    B --> E["Plan de reversión"]
    C --> F["Revisión humana"]
    D --> F
    E --> F
    F --> G{"¿aprobar?"}
    G -->|"sí"| H["Aplicar cambio"]
    G -->|"no"| I["Corregir propuesta"]
    I --> B

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

15. Arquitectura RLHF / Self-Improvement

En el catálogo aparece como un bucle de feedback: una salida se revisa, se corrige y las mejores señales se guardan para mejorar futuras ejecuciones. No hay que confundirlo con entrenar un modelo desde cero; muchas veces es curar ejemplos, rúbricas y preferencias de aplicación. El notebook 15_RLHF.ipynb lo aproxima con un agente redactor y un editor que puntúa, da feedback y fuerza revisión hasta alcanzar un umbral.

El estado mínimo contiene salida, feedback, revisión, puntuación y memoria de ejemplos aceptados. Sirve cuando la tarea es repetitiva y medible: soporte, revisión editorial, generación de informes, clasificación de tickets.

RLHF se popularizó en modelos instruidos como forma de aprender de preferencias humanas.18 En una aplicación pequeña, el equivalente práctico suele ser más humilde: guardar buenas correcciones, no premiar salidas dudosas y medir si el sistema mejora en un conjunto fijo.

flowchart TD
    A["Tarea repetible"] --> B["Salida del agente"]
    B --> C["Editor o rúbrica"]
    C --> D["Puntuación"]
    C --> E["Feedback"]
    D --> F{"¿umbral?"}
    F -->|"sí"| G["Guardar ejemplo bueno"]
    F -->|"no"| H["Revisar salida"]
    E --> H
    H --> C
    G --> I["Mejorar futuras ejecuciones"]

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

16. Arquitectura Cellular Automata

Cellular Automata no es una arquitectura típica de chat. Modela muchos agentes simples con reglas locales. De esas reglas puede emerger comportamiento global: rutas, congestión, propagación, distribución de recursos. El notebook 16_cellular_automata.ipynb usa una simulación de almacén donde las celdas de una rejilla propagan información para encontrar rutas.

El estado mínimo contiene una rejilla o grafo, celdas/agentes, vecindad, regla de actualización y métrica global. Encaja en simulación espacial, logística, planificación de rutas, ocupación de salas o fenómenos donde la interacción local importa.

Su lección para agentes LLM es conceptual: no siempre necesitas un agente muy inteligente. A veces necesitas muchos componentes simples, reglas claras y una buena visualización del estado global.

flowchart LR
    A["Estado de rejilla"] --> B["Vecindad local"]
    B --> C["Regla de actualización"]
    C --> D["Actualizar celdas"]
    D --> E["Patrón global"]
    E --> F["Métrica del sistema"]
    F --> G{"¿siguiente tick?"}
    G -->|"sí"| B
    G -->|"no"| H["Estado final"]

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

17. Arquitectura Reflexive Metacognitive

Reflexive Metacognitive añade un modelo de las propias capacidades del sistema: qué sabe hacer, qué no sabe, qué herramienta necesita, cuándo debe pedir revisión y cuándo debe parar. Bien diseñada, no es modestia verbal; es control operativo. El notebook 17_reflexive_metacognitive.ipynb lo demuestra con un asistente de triaje médico-informativo que primero consulta su self-model antes de responder, usar una tool o escalar.

El estado mínimo contiene capacidad requerida, confianza calibrada, evidencia disponible, acciones permitidas y salida posible: responder, usar tool, pedir ayuda o detenerse. Encaja en asesoramiento técnico, triaje profesional, revisión de fuentes o decisiones donde reconocer límites es parte de la calidad.

La diferencia con Reflection es importante. Reflection revisa una salida. Metacognición decide si el sistema está en condiciones de actuar. Si solo añade frases tipo “podría estar equivocado” sin cambiar la política, no aporta arquitectura.

flowchart TD
    A["Tarea"] --> B["Self-model"]
    B --> C["Capacidad requerida"]
    B --> D["Evidencia disponible"]
    B --> E["Confianza calibrada"]
    C --> F{"¿puede resolver?"}
    D --> F
    E --> F
    F -->|"sí"| G["Responder"]
    F -->|"necesita datos"| H["Usar tool"]
    F -->|"necesita revisión"| I["Pedir ayuda"]
    F -->|"no conviene"| J["Detenerse"]

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

Hay una línea clara con lo visto antes. Chain-of-thought mostró que pedir razonamiento intermedio puede mejorar tareas que requieren varios pasos.19 Self-consistency añadió una idea que conecta con ensemble: muestrear varios razonamientos y agregar la respuesta puede ser más robusto que confiar en una sola trayectoria.20 ReAct formaliza la alternancia entre razonamiento y acción con observaciones de herramientas.21 Toolformer mostró que el uso de herramientas puede aprenderse como parte del comportamiento del modelo, aunque en ingeniería solemos envolverlo con schemas y validadores.22 Tree of Thoughts empuja la idea de explorar varios caminos de razonamiento antes de comprometerse con una respuesta.23

Notebooks y Colab

El repositorio está en GitHub y cada notebook se puede abrir en Colab con una URL directa. Esto es útil para clase: el alumno lee la explicación, ejecuta el patrón y luego lo traduce a su propio caso.

#PatrónNotebookColab
01Reflection01_reflection.ipynbAbrir
02Tool use02_tool_use.ipynbAbrir
03ReAct03_ReAct.ipynbAbrir
04Planning04_planning.ipynbAbrir
05Multi-agent05_multi_agent.ipynbAbrir
06PEV06_PEV.ipynbAbrir
07Blackboard07_blackboard.ipynbAbrir
08Episodic + Semantic Memory08_episodic_with_semantic.ipynbAbrir
09Tree of Thoughts09_tree_of_thoughts.ipynbAbrir
10Mental Loop10_mental_loop.ipynbAbrir
11Meta-controller11_meta_controller.ipynbAbrir
12Graph memory12_graph.ipynbAbrir
13Ensemble13_ensemble.ipynbAbrir
14Dry-run harness14_dry_run.ipynbAbrir
15Feedback loop15_RLHF.ipynbAbrir
16Cellular automata16_cellular_automata.ipynbAbrir
17Reflexive metacognitive17_reflexive_metacognitive.ipynbAbrir

Al abrir un Colab, fíjate en tres cosas antes de tocar código: qué estado se guarda, qué tools se declaran y qué métrica comprueba si el patrón funcionó. Si solo ves prompts largos y ninguna traza, todavía no tienes una arquitectura operable.

Cómo elegir entre arquitecturas

La colección se puede leer como cinco familias:

FamiliaArquitecturasDecisión práctica
Patrones fundacionalesReflection, Tool use, ReAct, Planning.Úsalos cuando todavía puedes resolver con un agente único.
ColaboraciónMulti-agent, Blackboard, Meta-controller, Ensemble.Úsalos cuando hay responsabilidades separables.
Memoria y razonamientoEpisodic + Semantic, Tree of Thoughts, Graph memory.Úsalos cuando el problema exige recordar o explorar caminos.
FiabilidadPEV, Simulator, Dry-run, Reflexive metacognitive.Úsalos cuando actuar sin verificar sale caro.
Aprendizaje y emergenciaFeedback loop, Cellular automata.Úsalos cuando necesitas adaptar comportamiento a partir de señales.

Anthropic separa workflows y agents: los workflows siguen rutas predefinidas; los agents dejan que el modelo dirija dinámicamente el proceso y el uso de herramientas.24 Esta distinción ayuda a ordenar la tabla. Planning puede ser workflow si el plan está cerrado. ReAct se acerca a agent cuando el siguiente paso depende de la observación. Multiagent puede ser workflow si los roles se ejecutan siempre igual. La arquitectura no la define el nombre: la define dónde está la decisión.

OpenAI Agents SDK ofrece una capa práctica para agentes con herramientas, handoffs, guardrails y trazas.25 La documentación de tracing del SDK refuerza algo que aquí será constante: no evalúas solo la respuesta final, evalúas la trayectoria.26 LangGraph, usado por el repositorio de Khan, encaja con estos patrones porque permite grafos con estado, ciclos y persistencia mediante checkpoints.27

Manos a la obra

Práctica: un decision record de arquitectura.

Kit ejecutable de este capítulo: kit descargable.

# Descomprime el ZIP del capítulo y ejecuta estos comandos dentro de esa carpeta
python3 ops/run_f5_practices.py --chapter c05 --write --fail-on-invalid

Una práctica útil no es imprimir “usa ReAct” y seguir. Lo útil en un proyecto real es producir una ficha que cualquiera pueda revisar: qué arquitectura propongo, por qué, qué piezas entran, qué límites tendrá, qué trazas guardará y con qué criterios aceptaré el resultado.

El siguiente script no llama a ningún modelo. Eso es deliberado. Antes de meter OpenAI, Claude, LangGraph, un navegador o una base vectorial, obliga a diseñar el arnés que rodeará al agente. Si esta ficha no convence, el sistema todavía no está listo para gastar tokens.

from dataclasses import dataclass, asdict
from datetime import datetime, timezone
import json
import uuid


@dataclass(frozen=True)
class Pattern:
    name: str
    covers: tuple[str, ...]
    cost: int
    latency: int
    verification_load: int
    why: str
    runbook_step: str


@dataclass(frozen=True)
class TaskCase:
    name: str
    goal: str
    needs: tuple[str, ...]
    max_steps: int
    max_tool_calls: int
    requires_approval: bool
    required_patterns: tuple[str, ...]
    acceptance: tuple[str, ...]


PATTERNS = [
    Pattern(
        name="Planning",
        covers=("decomposition", "audit_trail"),
        cost=2,
        latency=2,
        verification_load=2,
        why="convierte el objetivo en pasos revisables antes de ejecutar",
        runbook_step="Crear plan numerado con subtarea, entrada, salida esperada y dependencia.",
    ),
    Pattern(
        name="Tool use",
        covers=("external_data", "exact_check", "structured_output"),
        cost=2,
        latency=2,
        verification_load=3,
        why="consulta sistemas externos con contrato de entrada y salida",
        runbook_step="Declarar tools con schema, timeout, permisos y validador del resultado.",
    ),
    Pattern(
        name="ReAct",
        covers=("stepwise_observation", "external_data", "audit_trail"),
        cost=4,
        latency=4,
        verification_load=4,
        why="decide el siguiente paso a partir de cada observación",
        runbook_step="Ejecutar bucle pensar, actuar, observar y actualizar estado hasta parada.",
    ),
    Pattern(
        name="Multi-agent",
        covers=("roles", "specialized_review", "parallel_work"),
        cost=4,
        latency=3,
        verification_load=5,
        why="separa responsabilidades que tienen criterios de calidad distintos",
        runbook_step="Asignar rol, entrada, salida y límite de herramientas a cada agente.",
    ),
    Pattern(
        name="Meta-controller",
        covers=("routing", "roles", "cost_control"),
        cost=3,
        latency=2,
        verification_load=4,
        why="elige especialista o flujo sin obligar a pasar siempre por todos",
        runbook_step="Clasificar intención y enrutar solo al agente necesario.",
    ),
    Pattern(
        name="PEV",
        covers=("quality_gate", "recovery", "exact_check"),
        cost=3,
        latency=3,
        verification_load=2,
        why="separa plan, ejecución y verificación con recuperación explícita",
        runbook_step="Verificar cada salida contra criterios; si falla, corregir o replanificar.",
    ),
    Pattern(
        name="Dry-run harness",
        covers=("costly_action", "approval", "audit_trail"),
        cost=2,
        latency=2,
        verification_load=2,
        why="muestra efecto previsto antes de aplicar cambios",
        runbook_step="Generar diff, impacto esperado y plan de reversión antes de ejecutar.",
    ),
    Pattern(
        name="Reflection",
        covers=("specialized_review", "quality_gate"),
        cost=2,
        latency=2,
        verification_load=2,
        why="añade revisión crítica sobre una salida antes de entregarla",
        runbook_step="Revisar con rúbrica concreta y registrar defectos corregidos.",
    ),
]


def score(pattern, task):
    needs = set(task.needs)
    covered = needs.intersection(pattern.covers)
    missing = needs.difference(pattern.covers)

    utility = 18 * len(covered)
    penalty = 2 * pattern.cost + pattern.latency + pattern.verification_load
    missing_penalty = 3 * len(missing)

    return utility - penalty - missing_penalty


def select_patterns(task):
    by_name = {pattern.name: pattern for pattern in PATTERNS}
    selected = []
    selected_names = set()
    covered = set()
    required = set(task.needs)

    for pattern_name in task.required_patterns:
        pattern = by_name[pattern_name]
        selected.append(pattern)
        selected_names.add(pattern.name)
        covered.update(pattern.covers)

    ranked = sorted(
        PATTERNS,
        key=lambda pattern: score(pattern, task),
        reverse=True,
    )

    for pattern in ranked:
        if pattern.name in selected_names:
            continue

        gain = required.intersection(pattern.covers).difference(covered)
        if gain:
            selected.append(pattern)
            selected_names.add(pattern.name)
            covered.update(pattern.covers)

        if required.issubset(covered):
            break

    return selected, sorted(required.difference(covered))


def build_decision_record(task):
    selected, uncovered = select_patterns(task)
    run_id = f"adr-{uuid.uuid4().hex[:8]}"

    runbook = [
        {
            "order": index,
            "pattern": pattern.name,
            "step": pattern.runbook_step,
            "why": pattern.why,
        }
        for index, pattern in enumerate(selected, start=1)
    ]

    if task.requires_approval:
        runbook.append(
            {
                "order": len(runbook) + 1,
                "pattern": "Human approval",
                "step": "Revisar trazas, diff y criterios antes de publicar o modificar el sistema.",
                "why": "la tarea declara aprobación obligatoria",
            }
        )

    trace_contract = {
        "run_id": run_id,
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "events_to_log": [
            "task.received",
            "architecture.selected",
            "tool.called",
            "tool.observed",
            "verification.completed",
            "dry_run.reviewed",
            "final.delivered",
        ],
        "limits": {
            "max_steps": task.max_steps,
            "max_tool_calls": task.max_tool_calls,
            "requires_approval": task.requires_approval,
        },
    }

    return {
        "task": asdict(task),
        "selected_architecture": [pattern.name for pattern in selected],
        "uncovered_needs": uncovered,
        "decision": [
            {
                "pattern": pattern.name,
                "score": score(pattern, task),
                "covers": list(pattern.covers),
                "cost": pattern.cost,
                "latency": pattern.latency,
                "verification_load": pattern.verification_load,
                "why": pattern.why,
            }
            for pattern in selected
        ],
        "runbook": runbook,
        "acceptance": list(task.acceptance),
        "trace_contract": trace_contract,
    }


case = TaskCase(
    name="Revisión académica con fuentes consultables",
    goal=(
        "Revisar un texto técnico: lengua, citas APA, coherencia con fuentes "
        "y propuesta de cambios antes de publicar."
    ),
    needs=(
        "roles",
        "external_data",
        "stepwise_observation",
        "structured_output",
        "quality_gate",
        "costly_action",
        "approval",
        "audit_trail",
    ),
    max_steps=9,
    max_tool_calls=12,
    requires_approval=True,
    required_patterns=(
        "Multi-agent",
        "Tool use",
        "ReAct",
        "PEV",
        "Dry-run harness",
    ),
    acceptance=(
        "Cada cita queda asociada a una URL o referencia revisable.",
        "La salida final separa cambios lingüísticos, APA y verificación de fuente.",
        "No se aplica ningún cambio sin diff previo.",
        "La traza permite reconstruir qué tool produjo cada observación.",
    ),
)


record = build_decision_record(case)
print(json.dumps(record, indent=2, ensure_ascii=False))

La salida es un ADR pequeño: Architecture Decision Record. No es documentación decorativa; es el contrato mínimo antes de construir. En el caso de revisión académica debería proponerte algo parecido a:

selected_architecture:
- Multi-agent
- Tool use
- ReAct
- PEV
- Dry-run harness

runbook:
1. Asignar rol, entrada, salida y límite de herramientas a cada agente.
2. Declarar tools con schema, timeout, permisos y validador del resultado.
3. Ejecutar bucle pensar, actuar, observar y actualizar estado hasta parada.
4. Verificar cada salida contra criterios; si falla, corregir o replanificar.
5. Generar diff, impacto esperado y plan de reversión antes de ejecutar.
6. Revisar trazas, diff y criterios antes de publicar o modificar el sistema.

Lo importante es que este ejercicio deja varias preguntas difíciles encima de la mesa:

PreguntaPor qué importa
¿Qué necesidad concreta cubre cada patrón?Evita añadir arquitecturas por estética técnica.
¿Qué eventos voy a trazar?Sin eventos no puedes depurar trayectoria, solo leer la respuesta final.
¿Qué límite de pasos y tools acepto?ReAct y multiagente necesitan freno explícito.
¿Qué criterio hace fallar una ejecución?PEV necesita una rúbrica, no una opinión.
¿Qué se revisa antes de aplicar cambios?Dry-run solo vale si enseña diff, impacto y reversión.

El siguiente paso natural sería conectar cada runbook_step con código real: un agente RAE, un agente APA, una tool de navegador, un validador JSON y un registro JSONL por ejecución. Esa implementación pertenece al capítulo de harness y trazas, pero la decisión arquitectónica ya queda hecha aquí.

Cómo encaja todo

flowchart TD
    subgraph "Capítulo 05: arquitecturas de agentes"
        ARCH["Arquitectura agentic"]
        SINGLE["Agente único"]
        TOOLS["Tools"]
        LOOP["ReAct / PEV"]
        MULTI["Multiagente"]
        MEMORY["Memoria"]
        VERIFY["Verificación"]
        LEARN["Feedback"]
    end

    subgraph "Viene de antes"
        C2["Estado, acción y observación (C2)"]
        C3["Contratos de tool (C3)"]
        C4["Contexto y memoria (C4)"]
    end

    subgraph "Sigue después"
        C6["Harness y trazas (C6)"]
        C7["SDKs de agentes (C7)"]
        C8["Permisos y supervisión (C8)"]
        C9["Orquestación MCP, A2A y ADKs (C9)"]
        C10["Evaluación de trayectoria (C10)"]
    end

    ARCH -->|"puede ser"| SINGLE
    ARCH -->|"puede usar"| TOOLS
    ARCH -->|"puede iterar con"| LOOP
    ARCH -->|"puede dividirse en"| MULTI
    ARCH -->|"puede persistir con"| MEMORY
    ARCH -->|"debe medirse con"| VERIFY
    ARCH -->|"puede mejorar con"| LEARN

    C2 -. "define el estado" .-> ARCH
    C3 -. "hace operables las acciones" .-> TOOLS
    C4 -. "alimenta memoria" .-> MEMORY

    VERIFY -->|"necesita"| C6
    TOOLS -->|"se traduce a SDKs en"| C7
    MULTI -->|"necesita límites"| C8
    MULTI -->|"se coordina con"| C9
    LEARN -->|"se acepta si mejora"| C10

IA para gente curiosa / Facsímil 05 / Capítulo 05 / 686f6c61

Vocabulario aprendido

TérminoDefinición
Arquitectura agenticPatrón que organiza estado, tools, memoria, verificación y parada.
ReflectionGenerar, criticar y revisar antes de entregar.
Tool useDelegar una parte del trabajo a una función externa.
ReActIntercalar razonamiento, acción y observación.
PlanningDescomponer la tarea antes de ejecutarla.
PEVPlanificar, ejecutar y verificar.
BlackboardEspacio compartido donde varios agentes escriben evidencias.
Meta-controladorRouter que elige especialista o flujo.
EnsembleVarios agentes producen salidas y un agregador sintetiza.
Dry-runSimulación previa antes de aplicar un cambio.

Dónde solía tropezar yo

ErrorPor qué es un errorAntídoto
Elegir la arquitectura por modaUn patrón complejo puede ocultar que bastaba una tool.Empezar por la tarea y calcular coste, latencia y verificación.
Confundir multiagente con calidadMás agentes pueden producir más ruido si no hay roles claros.Definir responsabilidad, entrada y salida de cada especialista.
Usar memoria sin caducidadLa memoria vieja se vuelve una fuente falsa de certeza.Guardar fuente, fecha, versión y criterio de recuperación.
Hacer ReAct sin paradaEl bucle puede repetir observaciones sin aportar evidencia nueva.Definir límite de pasos y criterio Ω\Omega.
Simular sin comprobar el simuladorUn dry-run malo tranquiliza sin proteger.Comparar simulación con resultados reales en casos pequeños.
Copiar notebooks sin harnessEl notebook enseña el patrón, pero no opera producción.Añadir trazas, métricas, permisos, tests y rollback.

Antes de pasar página

  • ¿Puedo recorrer el árbol de decisión y justificar por qué una hoja aplica o no aplica?
  • ¿Puedo explicar qué problema resuelve cada una de las 17 arquitecturas?
  • ¿Sé diferenciar Reflection, ReAct, Planning y PEV?
  • ¿Sé cuándo usar un meta-controlador frente a un ensemble?
  • ¿Entiendo por qué memoria episódica y semántica no son lo mismo?
  • ¿Puedo justificar un dry-run con coste, evidencia y criterio de aprobación?
  • ¿Sé abrir un notebook en Colab y localizar estado, tools y métrica?
  • ¿Puedo traducir un patrón del notebook a G,S,A,O,π,T,Ω,BG, S, A, O, \pi, T, \Omega, B?
  • ¿Sé qué tendría que añadir para llevarlo a producción?

En resumen

Idea fuerzaDetalle
La arquitectura es una decisión de control.Decide cómo se reparte el bucle entre plan, tools, memoria y verificación.
No hay patrón superior en abstracto.Hay patrones más o menos adecuados para cada perfil de tarea.
Los notebooks son material de trabajo.Úsalos para aprender el patrón, no para copiar producción sin harness.
La complejidad se paga.Multiagente, memoria y simulación aumentan coste y trazabilidad necesaria.
El siguiente capítulo baja a operación.Harness, límites, sensores y trazas convierten patrones en sistemas medibles.

Para saber más

Anthropic. (2024). Building Effective Agents. Artículo técnico.

Anthropic. (2025). Develop Tests for LLM Applications. Documentación oficial.

Anthropic. (2026). How to implement tool use. Documentación oficial.

Bonet, B. y Geffner, H. (2001). Planning as heuristic search. Artificial Intelligence, 129(1-2), 5-33.

Hogan, A., Blomqvist, E., Cochez, M., d'Amato, C., de Melo, G., Gutierrez, C., Kirrane, S., Gayo, J. E. L., Navigli, R., Neumaier, S., Ngomo, A. C. N., Polleres, A., Rashid, S. M., Rula, A., Schmelzeisen, L., Sequeda, J., Staab, S. y Zimmermann, A. (2021). Knowledge graphs. ACM Computing Surveys, 54(4). https://doi.org/10.1145/3447772

Khan, F. (2026). All Agentic Architectures. Repositorio GitHub.

LangChain. (2026). LangGraph persistence. Documentación oficial.

Madaan, A., Tandon, N., Gupta, P., Hallinan, S., Gao, L., Wiegreffe, S., Alon, U., Dziri, N., Prabhumoye, S., Yang, Y., Welleck, S., Majumder, B. P., Gupta, S., Yazdanbakhsh, A. y Clark, P. (2023). Self-Refine: Iterative Refinement with Self-Feedback. https://arxiv.org/abs/2303.17651

McDermott, D., Ghallab, M., Howe, A., Knoblock, C., Ram, A., Veloso, M., Weld, D. y Wilkins, D. (1998). PDDL: The Planning Domain Definition Language Version 1.2. Technical Report CVC TR-98-003/DCS TR-1165.

Nii, H. P. (1986). Blackboard systems: The blackboard model of problem solving and the evolution of blackboard architectures. AI Magazine, 7(2), 38-53.

OpenAI. (2026). Agents SDK. Documentación oficial.

OpenAI. (2026). Agents SDK: Tracing. Documentación oficial.

Ouyang, L., Wu, J., Jiang, X., Almeida, D., Wainwright, C. L., Mishkin, P., Zhang, C., Agarwal, S., Slama, K., Ray, A., Schulman, J., Hilton, J., Kelton, F., Miller, L., Simens, M., Askell, A., Welinder, P., Christiano, P., Leike, J. y Lowe, R. (2022). Training language models to follow instructions with human feedback. Advances in Neural Information Processing Systems, 35, 27730-27744. https://arxiv.org/abs/2203.02155

Park, J. S., O'Brien, J. C., Cai, C. J., Morris, M. R., Liang, P. y Bernstein, M. S. (2023). Generative Agents: Interactive Simulacra of Human Behavior. https://arxiv.org/abs/2304.03442

Schick, T., Dwivedi-Yu, J., Dessì, R., Raileanu, R., Lomeli, M., Zettlemoyer, L., Cancedda, N. y Scialom, T. (2023). Toolformer: Language Models Can Teach Themselves to Use Tools. https://doi.org/10.48550/arXiv.2302.04761

Shinn, N., Cassano, F., Labash, A., Gopinath, A., Narasimhan, K. y Yao, S. (2023). Reflexion: Language Agents with Verbal Reinforcement Learning. https://arxiv.org/abs/2303.11366

Wang, X., Wei, J., Schuurmans, D., Le, Q., Chi, E., Narang, S., Chowdhery, A. y Zhou, D. (2023). Self-Consistency Improves Chain of Thought Reasoning in Language Models. International Conference on Learning Representations. https://arxiv.org/abs/2203.11171

Wei, J., Wang, X., Schuurmans, D., Bosma, M., Ichter, B., Xia, F., Chi, E., Le, Q. y Zhou, D. (2022). Chain-of-thought prompting elicits reasoning in large language models. Advances in Neural Information Processing Systems, 35, 24824-24837. https://arxiv.org/abs/2201.11903

Yao, S., Yu, D., Zhao, J., Shafran, I., Griffiths, T. L., Cao, Y. y Narasimhan, K. (2023). Tree of Thoughts: Deliberate Problem Solving with Large Language Models. https://arxiv.org/abs/2305.10601

Yao, S., Zhao, J., Yu, D., Du, N., Shafran, I., Narasimhan, K. y Cao, Y. (2023). ReAct: Synergizing Reasoning and Acting in Language Models. International Conference on Learning Representations. https://arxiv.org/abs/2210.03629

Notas

  1. Khan, F. (2026). All Agentic Architectures. Repositorio GitHub. https://github.com/FareedKhan-dev/all-agentic-architectures. Consultado el 10 de junio de 2026.

  2. Khan, F. (2026). All Agentic Architectures. Repositorio GitHub. https://github.com/FareedKhan-dev/all-agentic-architectures. Consultado el 10 de junio de 2026.

  3. Shinn, N. et al. (2023). Reflexion: Language Agents with Verbal Reinforcement Learning. https://arxiv.org/abs/2303.11366

  4. Madaan, A. et al. (2023). Self-Refine: Iterative Refinement with Self-Feedback. https://arxiv.org/abs/2303.17651

  5. Schick, T. et al. (2023). Toolformer: Language Models Can Teach Themselves to Use Tools. https://doi.org/10.48550/arXiv.2302.04761

  6. Anthropic. (2026). How to implement tool use. https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/implement-tool-use. Consultado el 10 de junio de 2026.

  7. Yao, S. et al. (2023). ReAct: Synergizing Reasoning and Acting in Language Models. International Conference on Learning Representations. https://arxiv.org/abs/2210.03629

  8. McDermott, D. et al. (1998). PDDL: The Planning Domain Definition Language Version 1.2. Technical Report CVC TR-98-003/DCS TR-1165.

  9. Bonet, B. y Geffner, H. (2001). Planning as heuristic search. Artificial Intelligence, 129(1-2), 5-33.

  10. Anthropic. (2025). Develop Tests for LLM Applications. https://platform.claude.com/docs/en/build-with-claude/develop-tests.

  11. Nii, H. P. (1986). Blackboard systems: The blackboard model of problem solving and the evolution of blackboard architectures. AI Magazine, 7(2), 38-53.

  12. Park, J. S. et al. (2023). Generative Agents: Interactive Simulacra of Human Behavior. https://arxiv.org/abs/2304.03442

  13. Yao, S. et al. (2023). Tree of Thoughts: Deliberate Problem Solving with Large Language Models. https://arxiv.org/abs/2305.10601

  14. OpenAI. (2026). Agents SDK. https://developers.openai.com/api/docs/guides/agents. Consultado el 10 de junio de 2026.

  15. Hogan, A. et al. (2021). Knowledge graphs. ACM Computing Surveys, 54(4). https://doi.org/10.1145/3447772

  16. Wang, X. et al. (2023). Self-Consistency Improves Chain of Thought Reasoning in Language Models. International Conference on Learning Representations. https://arxiv.org/abs/2203.11171

  17. OpenAI. (2026). Agents SDK: Tracing. https://openai.github.io/openai-agents-python/tracing/. Consultado el 10 de junio de 2026.

  18. Ouyang, L. et al. (2022). Training language models to follow instructions with human feedback. Advances in Neural Information Processing Systems, 35, 27730-27744. https://arxiv.org/abs/2203.02155

  19. Wei, J. et al. (2022). Chain-of-thought prompting elicits reasoning in large language models. Advances in Neural Information Processing Systems, 35, 24824-24837. https://arxiv.org/abs/2201.11903

  20. Wang, X. et al. (2023). Self-Consistency Improves Chain of Thought Reasoning in Language Models. International Conference on Learning Representations. https://arxiv.org/abs/2203.11171

  21. Yao, S. et al. (2023). ReAct: Synergizing Reasoning and Acting in Language Models. International Conference on Learning Representations. https://arxiv.org/abs/2210.03629

  22. Schick, T. et al. (2023). Toolformer: Language Models Can Teach Themselves to Use Tools. https://doi.org/10.48550/arXiv.2302.04761

  23. Yao, S. et al. (2023). Tree of Thoughts: Deliberate Problem Solving with Large Language Models. https://arxiv.org/abs/2305.10601

  24. Anthropic. (2024). Building Effective Agents. https://www.anthropic.com/engineering/building-effective-agents. Consultado el 10 de junio de 2026.

  25. OpenAI. (2026). Agents SDK. https://developers.openai.com/api/docs/guides/agents. Consultado el 10 de junio de 2026.

  26. OpenAI. (2026). Agents SDK: Tracing. https://openai.github.io/openai-agents-python/tracing/. Consultado el 10 de junio de 2026.

  27. LangChain. (2026). LangGraph persistence. https://docs.langchain.com/oss/python/langgraph/persistence. Consultado el 10 de junio de 2026.

Capítulo 06

Facsímil 5 · Agentes y orquestación

Capítulo 06: Harness engineering: límites, sensores y trazas

El arnés que convierte una demo en sistema

En el capítulo anterior elegimos arquitecturas: ReAct, PEV, multiagente, blackboard, dry-run, memoria, meta-controlador. Ahora toca una pregunta menos vistosa y mucho más profesional: ¿qué rodea a esa arquitectura para que no dependa de la buena suerte?

Un agente puede sonar brillante durante una demo y ser imposible de depurar cuando falla. Puede tocar demasiados archivos, repetir una tool sin información nueva, gastar más de lo previsto, declarar que terminó sin haber probado nada o perder el hilo entre sesiones. El problema no siempre es el modelo. Muchas veces falta el arnés técnico: el conjunto de límites, sensores, trazas, gates y estado operativo que convierte una ejecución en algo revisable.

Martin Fowler usa la expresión harness engineering para hablar del entorno que permite usar agentes de código con más control: instrucciones, contexto del repositorio, pruebas, evidencia, límites y revisión.1 Aquí ampliamos la idea a cualquier agente con tools: código, RAG, datos, navegador, documentos, soporte, operaciones o investigación.

Qué no es un harness

Un harness no es un prompt más largo. Puedes escribir instrucciones excelentes y aun así no tener control sobre coste, permisos, estado, herramientas o verificación.

Tampoco es un dashboard al final. Ver una gráfica después de que algo salió mal ayuda, pero el harness empieza antes: decide qué acciones están permitidas, qué datos entran, qué presupuesto hay, qué debe registrarse y qué condición permite avanzar.

Y no es solo logging. Un log puede ser una pared de texto. Una traza útil tiene estructura: quién hizo qué, con qué entrada, en qué paso, cuánto tardó, cuánto costó, qué observó, qué cambió y por qué se detuvo.

La definición útil

Para este libro, un harness es:

El arnés técnico que envuelve a un agente para limitar lo que puede hacer, medir lo que ocurre, registrar evidencia y decidir si una ejecución puede avanzar.

Anthropic distingue entre workflows predefinidos y agents que deciden dinámicamente su proceso y uso de herramientas.2 Cuanto más dinámica sea esa decisión, más necesario se vuelve el harness. Si el camino cambia durante la ejecución, necesitamos sensores para verlo y límites para acotarlo.

Ejemplo de fórmula. Formalmente podemos escribir:

H=(G,I,S,A,P,B,V,R,τ)\mathcal{H} = (G, I, S, A, P, B, V, R, \tau)
SímboloSignificadoEjemplo concreto
H\mathcal{H}Harness completo.Arnés de revisión académica con tools, trazas y gates.
GGObjetivo verificable.“Revisar citas y proponer cambios antes de publicar”.
IIInstrucciones estables.AGENTS.md, reglas editoriales, comandos permitidos.
SSEstado operativo.Paso actual, evidencias, decisiones, bloqueos, coste.
AAAcciones disponibles.Buscar referencia, validar APA, proponer diff, pedir revisión.
PPPolíticas y permisos.Qué tools puede usar, cuándo necesita aprobación.
BBPresupuesto operativo.Pasos, tokens, tools, tiempo, coste, rutas tocables.
VVVerificadores y gates.Tests, rúbricas, validadores JSON, revisión de diff.
RRRecuperación.Retry, fallback, rollback, handoff, parada controlada.
τ\tauTraza.Eventos estructurados de toda la ejecución.

Ejemplo de fórmula. El presupuesto no es una cifra decorativa. En cada paso queda:

Bt=(nt,qt,kt,ct,Δt)B_t = (n_t, q_t, k_t, c_t, \Delta_t)
SímboloSignificadoEjemplo concreto
BtB_tPresupuesto restante en el paso tt.Lo que queda antes de decidir otra acción.
ntn_tPasos restantes.4 pasos.
qtq_tLlamadas a tools restantes.2 consultas externas.
ktk_tTokens o contexto restante.18.000 tokens disponibles.
ctc_tCoste máximo restante.0,42 EUR.
Δt\Delta_tCambio máximo permitido.3 archivos, 120 líneas o solo lectura.

Ejemplo de fórmula. Y un gate mínimo puede escribirse así:

go=OokTokCCmaxPokRdef\operatorname{go} = O_\text{ok} \land T_\text{ok} \land C \le C_\text{max} \land P_\text{ok} \land R_\text{def}
SímboloSignificadoEjemplo concreto
OokO_\text{ok}Resultado final válido.La respuesta cumple el objetivo.
TokT_\text{ok}Trayectoria válida.Usó tools permitidas y dejó evidencia.
CCCoste real de ejecución.Tokens, tools, tiempo y revisión.
CmaxC_\text{max}Coste máximo aceptado.0,75 EUR por tarea.
PokP_\text{ok}Permisos respetados.No ejecutó acciones fuera de alcance.
RdefR_\text{def}Recuperación definida.Hay rollback, retry o handoff.

Ejemplo de fórmula. La métrica económica que decide producción no suele ser el precio por token, sino:

Caceptada=iCiNaceptadasC_\text{aceptada} = \frac{ \sum_i C_i }{ N_\text{aceptadas} }
SímboloSignificadoEjemplo concreto
CaceptadaC_\text{aceptada}Coste por tarea que pasa el gate.1,40 EUR por revisión aceptada.
CiC_iCoste de la ejecución ii.Modelo, tools, infraestructura y revisión.
NaceptadasN_\text{aceptadas}Ejecuciones que superan criterios.73 de 100 tareas.

Si el sistema es barato por intento, pero solo acepta 20 de cada 100 ejecuciones, quizá no es barato. Solo estaba escondiendo coste en reintentos, revisión humana o correcciones posteriores.

Fecha de corte de herramientas

Fecha de corte: 10 de junio de 2026.
Fuentes consultadas: artículo de Martin Fowler sobre harness engineering, documentación de OpenAI Agents SDK Tracing, OpenTelemetry Tracing API, W3C Trace Context, documentación de pruebas de Anthropic y NIST AI RMF.

Lo estable es el mecanismo: estado operativo, límites, tools pequeñas, trazas estructuradas, evaluaciones repetibles, gates y handoff. Lo cambiante son SDKs, nombres de herramientas, formatos de traza, integraciones de observabilidad, precios y límites de cada proveedor.

Las capas del harness

Un harness serio separa capas. Si todo vive en el prompt, no sabes qué cambiar cuando algo falla.

CapaPreguntaArtefactoFallo típico si falta
Objetivo¿Qué resultado cuenta como terminado?goal, done_when, criterios de aceptación.El agente declara éxito con una respuesta bonita.
Instrucciones¿Cómo se trabaja aquí?AGENTS.md, guía de repo, reglas editoriales.Cada ejecución interpreta el proyecto de forma distinta.
Estado¿Qué sabemos ahora?run_state.json, task board, decisiones.Se repiten pasos o se pierden bloqueos.
Scope¿Qué puede tocar?Rutas, dominios, tools, permisos, no-objetivos.Cambios fuera de alcance o consultas innecesarias.
Sensores¿Qué señales vuelven del mundo?Tests, logs, diffs, métricas, resultados de tools.El agente actúa sin feedback verificable.
Presupuesto¿Cuánto puede gastar?Máximo de pasos, tools, tokens, tiempo y coste.Bucle largo, coste sorpresa o latencia inaceptable.
Gate¿Puede avanzar?Rúbrica, tests, umbrales, revisión humana.El constructor se aprueba a sí mismo.
Handoff¿Quién puede continuar?Resumen estructurado, pendientes, evidencia, siguiente paso.La siguiente sesión empieza desde cero.

OpenAI Agents SDK documenta tracing para registrar ejecuciones de agentes y entender qué ocurrió entre modelo, tools y handoffs.3 OpenTelemetry, por su parte, define trazas y spans como unidades observables de trabajo con atributos y contexto.4 El lenguaje cambia entre herramientas; la idea no: una ejecución se entiende por sus eventos.

Anatomía visual de un harness de agentes

Harness de agentes como plano de ingeniería Separar control, ejecución, estado, tools, observabilidad y gates evita que todo dependa de una conversación larga. CONTROL PLANE todo lo que decide qué puede ocurrir antes de llamar al modelo o a una tool Intake objetivo · tenant scope · canal Context pack AGENTS.md docs · ejemplos reglas versionadas feedforward Policy engine allowlist · permisos risk class · redacción approval rules decide capacidades Budget ledger steps · tokens tools · coste latencia · diff stop reasons Run contract done_when no_goals rollback contrato que el gate puede comprobar EXECUTION PLANE el agente actúa, pero cada paso atraviesa estado, gateway de tools y entorno acotado decision bus: state + policy + budget + observation Planner subtarea precondiciones Model call prompt pack structured out Tool gateway schema · timeout idempotency key Sandbox filesystem · red comandos · browser Sensors tests · diff logs · captura Observation resultado error · métrica si hay información nueva, actualiza estado y decide otro paso MODEL BOUNDARY SIDE EFFECT BOUNDARY OBSERVATION STATE, OBSERVABILITY AND RELEASE GATES lo que permite reanudar, auditar, puntuar trazas y convertir fallos reales en evals State store thread_id checkpoint replay cursor Trace pipeline span tree redacción export OTEL Trace grading tool choice trajectory regressions Release gate outcome ok budget ok rollback ready Artifact + handoff respuesta · diff · PR pendientes · owner siguiente acción fallos reales -> evals -> reglas -> contexto Ingeniería del harness = estado durable + tools acotadas + observabilidad + gates que se pueden repetir. IA para gente curiosa / Facsímil 05 / Capítulo 06 / 686f6c61

El diagrama muestra la idea central con una lectura más de ingeniería: el agente no es una caja en medio del sistema. Hay un plano de control antes de actuar, un plano de ejecución con fronteras explícitas, un store de estado para checkpoints y replay, un gateway para tools, una tubería de observabilidad y un gate que decide con evidencia.

Sensores: cómo ve el sistema lo que hizo

En un agente, un sensor no tiene por qué ser físico. Un test fallido es un sensor. Un diff demasiado grande es un sensor. Un timeout, una captura de pantalla, un código de error, una cita encontrada, una métrica de latencia o una tool que devuelve not_found son sensores.

SensorQué mideEjemplo
TestComportamiento verificable.pytest tests/test_citas.py pasa o falla.
DiffAlcance del cambio.2 archivos, 48 líneas, ninguna ruta prohibida.
Tool resultObservación externa.URL consultada, estado HTTP, campos devueltos.
MétricaCoste, latencia, calidad o cobertura.7 pasos, 5 tool calls, 1,2 s p95.
CapturaEstado visual.Página renderizada sin solapes.
ValidadorForma de salida.JSON válido, schema completo, campos permitidos.
RevisiónJuicio estructurado.Rúbrica con criterios y evidencia.

Anthropic recomienda desarrollar tests para aplicaciones con LLM porque la calidad no puede depender de lectura manual ocasional.5 En agentes, esa idea se amplía: no solo probamos la respuesta final; probamos la trayectoria.

Límites que sí importan

Los límites buenos no están para molestar al agente. Están para hacer explícito el contrato de trabajo.

LímiteQué evitaEjemplo operativo
Pasos máximosBucle sin progreso.max_steps = 8.
Llamadas a toolsConsultas innecesarias.max_tool_calls = 5.
TiempoEsperas inaceptables.timeout_s = 30.
CosteSorpresas de factura.max_cost = 0.50.
Tokens/contextoPrompts enormes y ruido.Resumir estado cada 6 pasos.
Rutas permitidasCambios fuera de alcance.Solo docs/ y tests/.
Modo de escrituraCambios antes de revisar.dry_run obligatorio.
Filas o resultadosRespuestas gigantes de tools.limit = 20.
RedConexiones no necesarias.Allowlist de dominios.

El NIST AI RMF insiste en gestionar sistemas de IA mediante funciones de gobernanza, mapeo, medición y gestión.6 Traducido a ingeniería de agentes: no basta con que el sistema sea capaz; tiene que operar dentro de límites que alguien pueda explicar.

Trazas: lo que hay que guardar y lo que no

Una traza útil no es el pensamiento privado del modelo copiado entero. Es una estructura de eventos.

EventoCampos mínimosPor qué importa
task.receivedrun_id, objetivo, usuario/tenant, canal.Sitúa la ejecución.
architecture.selectedpatrón, motivo, versión de instrucciones.Explica por qué se eligió el flujo.
model.calledproveedor, modelo, temperatura, tokens, prompt version.Permite comparar cambios.
tool.calledtool, argumentos validados o redactados, permiso.Audita acciones.
tool.observedestado, latencia, tamaño, error, resultado resumido.Convierte acción en observación.
budget.updatedpasos, tools, coste, tiempo restante.Detecta deriva.
gate.checkedcriterio, resultado, evidencia.Explica el go/no-go.
handoff.createdestado final, pendientes, siguiente acción.Permite continuar.

W3C Trace Context define una forma estándar de propagar identificadores de traza entre sistemas distribuidos.7 No necesitamos implementar todo el estándar en este capítulo, pero sí adoptar la disciplina: cada ejecución debe tener un identificador estable y cada paso debe poder conectarse con el anterior.

La deuda técnica de los sistemas de ML ya se estudió antes del boom de agentes: Sculley y colaboradores explicaron cómo modelos, datos, dependencias y cambios ocultos pueden acumular deuda difícil de ver.8 Amershi y colaboradores mostraron que construir software con aprendizaje automático introduce retos especiales de datos, evaluación, monitorización y operación.9 Con agentes ocurre algo parecido, pero más visible: el sistema toma pasos, llama tools y deja efectos. Sin trazas, la deuda se vuelve conversación perdida.

Un ejemplo concreto: revisión académica con tools

Imagina un agente que revisa un capítulo de este libro. La tarea no es “mejora el texto” en abstracto. Queremos:

PiezaDecisión de harness
ObjetivoRevisar citas, estilo y coherencia antes de publicar.
ScopeSolo el capítulo activo y referencias.bib.
Toolsbuscar_fuente, validar_apa, proponer_diff, ejecutar_build.
Límites8 pasos, 6 tools, sin publicar, sin tocar otros facsímiles.
SensoresBuild, diff, enlaces, tabla de citas, ausencia de palabras prohibidas.
GateNo avanza si falta fuente, si el diff toca rutas no permitidas o si el build falla.
HandoffQué se cambió, qué se verificó, qué queda pendiente.

La diferencia con un prompt genérico es enorme. El modelo puede seguir siendo el mismo, pero el problema está encarrilado. Tiene menos espacio para improvisar y más señales para corregirse.

Manos a la obra

Práctica: construir un mini harness reproducible.

Kit ejecutable de este capítulo: kit descargable.

# Descomprime el ZIP del capítulo y ejecuta estos comandos dentro de esa carpeta
python3 ops/run_f5_practices.py --chapter c06 --write --fail-on-invalid

Vamos a construir un harness mínimo con Python estándar. No llama a ninguna API. Simula una revisión académica y enseña las piezas importantes: registro de eventos, validación de tools, presupuesto, gate final y salida trazable.

from dataclasses import dataclass, field
from time import perf_counter
import hashlib
import json
import uuid


@dataclass
class Budget:
    max_steps: int = 8
    max_tool_calls: int = 6
    max_cost: float = 0.50
    steps: int = 0
    tool_calls: int = 0
    cost: float = 0.0

    def spend_step(self):
        self.steps += 1
        if self.steps > self.max_steps:
            raise RuntimeError("step budget exhausted")

    def spend_tool(self, tool_name, cost):
        self.tool_calls += 1
        self.cost += cost
        if self.tool_calls > self.max_tool_calls:
            raise RuntimeError(f"tool budget exhausted after {tool_name}")
        if self.cost > self.max_cost:
            raise RuntimeError(f"cost budget exhausted after {tool_name}")


@dataclass(frozen=True)
class ToolSpec:
    name: str
    required_args: tuple[str, ...]
    cost: float
    fn: callable


@dataclass
class Trace:
    run_id: str = field(default_factory=lambda: f"run-{uuid.uuid4().hex[:8]}")
    events: list[dict] = field(default_factory=list)

    def emit(self, event, **fields):
        safe_fields = {
            key: value
            for key, value in fields.items()
            if value is not None
        }
        self.events.append({"event": event, "run_id": self.run_id, **safe_fields})


def fingerprint(value):
    raw = json.dumps(value, sort_keys=True, ensure_ascii=False)
    return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:12]


def buscar_fuente(query):
    catalog = {
        "OpenTelemetry tracing": "https://opentelemetry.io/docs/specs/otel/trace/api/",
        "NIST AI RMF": "https://doi.org/10.6028/NIST.AI.100-1",
        "Agents SDK tracing": "https://openai.github.io/openai-agents-python/tracing/",
    }
    return {"found": query in catalog, "url": catalog.get(query)}


def validar_apa(reference):
    has_year = "(" in reference and ")" in reference
    has_title = "." in reference
    return {"valid": has_year and has_title, "checks": {"year": has_year, "title": has_title}}


def proponer_diff(file, change):
    lines = change.count("\n") + 1
    return {"file": file, "lines_changed": lines, "diff_preview": change[:120]}


TOOLS = {
    "buscar_fuente": ToolSpec("buscar_fuente", ("query",), 0.05, buscar_fuente),
    "validar_apa": ToolSpec("validar_apa", ("reference",), 0.03, validar_apa),
    "proponer_diff": ToolSpec("proponer_diff", ("file", "change"), 0.02, proponer_diff),
}


def call_tool(name, args, budget, trace):
    if name not in TOOLS:
        raise ValueError(f"tool not allowed: {name}")

    spec = TOOLS[name]
    missing = [arg for arg in spec.required_args if arg not in args]
    if missing:
        raise ValueError(f"{name} missing args: {missing}")

    budget.spend_tool(name, spec.cost)
    start = perf_counter()
    result = spec.fn(**args)
    latency_ms = round((perf_counter() - start) * 1000, 3)

    trace.emit(
        "tool.observed",
        tool=name,
        args_hash=fingerprint(args),
        result=result,
        latency_ms=latency_ms,
        cost=spec.cost,
    )
    return result


def run_case(task):
    budget = Budget()
    trace = Trace()
    state = {"citations_checked": 0, "apa_valid": 0, "diffs": [], "missing_sources": []}

    trace.emit("task.received", task=task["name"], scope=task["scope"])
    trace.emit("architecture.selected", patterns=["Tool use", "PEV", "Dry-run harness"])

    for query in task["queries"]:
        budget.spend_step()
        trace.emit("step.started", step=budget.steps, intent="buscar fuente")
        result = call_tool("buscar_fuente", {"query": query}, budget, trace)
        state["citations_checked"] += 1
        if not result["found"]:
            state["missing_sources"].append(query)

    for reference in task["references"]:
        budget.spend_step()
        trace.emit("step.started", step=budget.steps, intent="validar APA")
        result = call_tool("validar_apa", {"reference": reference}, budget, trace)
        state["apa_valid"] += int(result["valid"])

    budget.spend_step()
    diff = call_tool(
        "proponer_diff",
        {
            "file": "fasciculo-05-agentes-orquestacion/06-harness-engineering-limites-sensores-trazas.md",
            "change": "Añadir tabla de trazas mínimas y gate final.",
        },
        budget,
        trace,
    )
    state["diffs"].append(diff)

    gate = {
        "enough_citations": state["citations_checked"] >= 3,
        "no_missing_sources": not state["missing_sources"],
        "apa_ok": state["apa_valid"] == len(task["references"]),
        "diff_small": sum(item["lines_changed"] for item in state["diffs"]) <= 20,
        "cost_ok": budget.cost <= budget.max_cost,
    }
    accepted = all(gate.values())
    trace.emit("gate.checked", gate=gate, accepted=accepted)
    trace.emit(
        "handoff.created",
        next_action="revisión humana si accepted=false; build y lectura visual si accepted=true",
        budget_used={"steps": budget.steps, "tool_calls": budget.tool_calls, "cost": round(budget.cost, 2)},
    )

    return {"accepted": accepted, "state": state, "gate": gate, "trace": trace.events}


task = {
    "name": "revisión académica con fuentes",
    "scope": ["capítulo 06", "referencias.bib"],
    "queries": ["OpenTelemetry tracing", "NIST AI RMF", "Agents SDK tracing"],
    "references": [
        "OpenTelemetry. (2026). Tracing API.",
        "Tabassi, E. (2023). Artificial Intelligence Risk Management Framework.",
    ],
}

result = run_case(task)
print(json.dumps(result, indent=2, ensure_ascii=False))

Qué deberías ver:

"accepted": true
"tool_calls": 6
"event": "gate.checked"
"event": "handoff.created"

El valor de este ejercicio no está en la simulación. Está en la forma. Cuando sustituyas las funciones falsas por APIs reales, ya tendrás preguntas correctas: qué args validar, qué coste registrar, qué resultado resumir, qué gate aplicar y qué handoff dejar.

Cómo encaja todo

flowchart TD
    subgraph "Capítulo 06: harness engineering"
        H["Harness"]
        LIMITS["Límites"]
        SENSORS["Sensores"]
        TRACE["Trazas"]
        GATE["Gates"]
        HANDOFF["Handoff"]
        BUDGET["Presupuesto"]
    end

    subgraph "Viene de antes"
        C2["Estado, acción y observación (C2)"]
        C3["Tools y contratos (C3)"]
        C5["Arquitecturas agentic (C5)"]
        F4C13["Evals y trazas (F4C13)"]
    end

    subgraph "Sigue después"
        C7["SDKs de agentes (C7)"]
        C8["Permisos y supervisión (C8)"]
        C9["Orquestación MCP, A2A y ADKs (C9)"]
        C10["Evaluar trayectoria y coste (C10)"]
        F6["Operar sistemas de IA (F6)"]
    end

    H -->|"define"| LIMITS
    H -->|"instala"| SENSORS
    H -->|"registra"| TRACE
    H -->|"aplica"| GATE
    H -->|"prepara"| HANDOFF
    LIMITS -->|"consumen"| BUDGET
    SENSORS -->|"alimentan"| TRACE
    TRACE -->|"da evidencia a"| GATE
    GATE -->|"decide"| HANDOFF

    C2 -. "aporta bucle" .-> H
    C3 -. "aporta actions" .-> LIMITS
    C5 -. "elige patrón" .-> H
    F4C13 -. "aporta evals" .-> GATE

    TRACE -->|"se implementa con"| C7
    LIMITS -->|"se vuelven permisos en"| C8
    TRACE -->|"viaja entre sistemas en"| C9
    GATE -->|"se mide mejor en"| C10
    HANDOFF -->|"escala a operación en"| F6

IA para gente curiosa / Facsímil 05 / Capítulo 06 / 686f6c61

Vocabulario aprendido

TérminoDefinición
HarnessArnés técnico que limita, observa, registra y verifica una ejecución.
SensorSeñal que vuelve del sistema: test, diff, log, métrica, resultado de tool.
TrazaRegistro estructurado de eventos de una ejecución.
SpanUnidad de trabajo dentro de una traza.
GateRegla que decide si una ejecución avanza, se corrige o se detiene.
Presupuesto operativoLímite de pasos, coste, tokens, tools, tiempo y alcance.
Coste por tarea aceptadaCoste real dividido entre ejecuciones que pasan el gate.
HandoffEstado resumido para que otra persona o sistema pueda continuar.
Stop reasonMotivo estructurado de parada: terminado, falta evidencia, falta permiso, presupuesto agotado.

Dónde solía tropezar yo

ErrorPor qué es un errorAntídoto
Confundir harness con prompt largoEl prompt no limita coste, permisos ni trazas.Separar instrucciones, estado, tools, sensores y gates.
Guardar logs sin estructuraLuego nadie puede comparar ejecuciones.Usar eventos con run_id, tool, latencia, coste y resultado.
Medir solo respuesta finalNo sabes si el camino fue caro, frágil o fuera de alcance.Evaluar outcome y trayectoria.
No poner límites de presupuestoEl agente puede gastar pasos y tools sin progreso.Definir máximos y stop reasons.
Dejar que el constructor se apruebe soloUna salida convincente no equivale a evidencia.Separar builder, reviewer y gate.
No diseñar handoffCada sesión futura reconstruye la historia.Guardar objetivo, decisiones, evidencia, riesgos y siguiente acción.

Antes de pasar página

  • ¿Sé explicar qué añade un harness que no añade un prompt?
  • ¿Puedo escribir H=(G,I,S,A,P,B,V,R,τ)\mathcal{H} = (G, I, S, A, P, B, V, R, \tau) y explicar cada pieza?
  • ¿Sé distinguir sensor, traza, span y gate?
  • ¿Sé definir límites de pasos, tools, coste, tiempo y alcance?
  • ¿Sé qué eventos mínimos debería guardar una ejecución agentic?
  • ¿Puedo explicar por qué coste por tarea aceptada importa más que precio por token?
  • ¿Sé construir un gate que mire resultado y trayectoria?
  • ¿Sé qué debe contener un handoff para que otra persona continúe?
  • ¿He ejecutado el mini harness y leído la traza generada?

En resumen

Idea fuerzaDetalle
Un agente sin harness es difícil de operar.Puede acertar una vez y ser imposible de depurar después.
Los límites son parte de la arquitectura.Pasos, tools, coste, tiempo y alcance definen la autonomía real.
Los sensores convierten acciones en observaciones.Tests, diffs, métricas y resultados de tools alimentan el estado.
Las trazas son memoria operativa.Permiten explicar qué ocurrió, comparar versiones y corregir fallos.
El gate protege la salida.No basta con responder: hay que pasar criterios de resultado, trayectoria y coste.
El handoff evita deuda de contexto.Otra persona o agente debe poder continuar sin reconstruir toda la conversación.

Para saber más

Amershi, S., Begel, A., Bird, C., DeLine, R., Gall, H., Kamar, E., Nagappan, N., Nushi, B. y Zimmermann, T. (2019). Software engineering for machine learning: A case study. Proceedings of the 41st International Conference on Software Engineering: Software Engineering in Practice, 291-300. https://doi.org/10.1109/ICSE-SEIP.2019.00042

Anthropic. (2024). Building Effective Agents. https://www.anthropic.com/engineering/building-effective-agents

Anthropic. (2025). Develop Tests for LLM Applications. https://platform.claude.com/docs/en/build-with-claude/develop-tests

Baylor, D. et al. (2017). TFX: A TensorFlow-Based Production-Scale Machine Learning Platform. Proceedings of the 23rd ACM SIGKDD International Conference on Knowledge Discovery and Data Mining, 1387-1395. https://doi.org/10.1145/3097983.3098021

Fowler, M. (2025). Harness Engineering for Coding Agent Users. https://martinfowler.com/articles/harness-engineering.html

NIST. (2023). Artificial Intelligence Risk Management Framework (AI RMF 1.0). https://doi.org/10.6028/NIST.AI.100-1

OpenAI. (2026). Agents SDK: Tracing. https://openai.github.io/openai-agents-python/tracing/

OpenTelemetry. (2026). Tracing API. https://opentelemetry.io/docs/specs/otel/trace/api/

OpenAI. (2026). AGENTS.md. https://github.com/openai/agents.md

Sculley, D. et al. (2015). Hidden Technical Debt in Machine Learning Systems. https://papers.nips.cc/paper_files/paper/2015/hash/86df7dcfd896fcaf2674f757a2463eba-Abstract.html

W3C. (2021). Trace Context Level 2. https://www.w3.org/TR/trace-context-2/

Notas

  1. Fowler, M. (2025). Harness Engineering for Coding Agent Users. https://martinfowler.com/articles/harness-engineering.html. Consultado el 10 de junio de 2026.

  2. Anthropic. (2024). Building Effective Agents. https://www.anthropic.com/engineering/building-effective-agents. Consultado el 10 de junio de 2026.

  3. OpenAI. (2026). Agents SDK: Tracing. https://openai.github.io/openai-agents-python/tracing/. Consultado el 10 de junio de 2026.

  4. OpenTelemetry. (2026). Tracing API. https://opentelemetry.io/docs/specs/otel/trace/api/. Consultado el 10 de junio de 2026.

  5. Anthropic. (2025). Develop Tests for LLM Applications. https://platform.claude.com/docs/en/build-with-claude/develop-tests. Consultado el 10 de junio de 2026.

  6. Tabassi, E. (2023). Artificial Intelligence Risk Management Framework (AI RMF 1.0). NIST AI 100-1. https://doi.org/10.6028/NIST.AI.100-1

  7. W3C. (2021). Trace Context Level 2. https://www.w3.org/TR/trace-context-2/. Consultado el 10 de junio de 2026.

  8. Sculley, D. et al. (2015). Hidden Technical Debt in Machine Learning Systems. Advances in Neural Information Processing Systems, 28. https://papers.nips.cc/paper_files/paper/2015/hash/86df7dcfd896fcaf2674f757a2463eba-Abstract.html

  9. Amershi, S. et al. (2019). Software engineering for machine learning: A case study. Proceedings of the 41st International Conference on Software Engineering: Software Engineering in Practice, 291-300. https://doi.org/10.1109/ICSE-SEIP.2019.00042

Capítulo 07

Facsímil 5 · Agentes y orquestación

Capítulo 07: SDKs de agentes: OpenAI, Anthropic, Google ADK y herramientas

Cuando instalar un SDK parece arquitectura

Hay una tentación muy normal: ves un SDK de agentes, copias el ejemplo mínimo, consigues que el modelo llame una tool y sientes que ya tienes arquitectura. La demo funciona. El problema empieza cuando necesitas cambiar de modelo, añadir trazas, limitar tools, guardar sesiones, evaluar trayectorias, desplegar en otro entorno o explicar por qué el agente tomó una decisión.

El SDK es importante, pero no es el sistema entero. Un SDK te da una forma concreta de hablar con una plataforma. Tu producto necesita algo más estable: contratos internos, adaptadores, estado propio, evaluación, observabilidad, permisos, política de memoria y una forma de salir de un proveedor si mañana cambia el coste, la API o la capacidad.

En el capítulo 03 vimos qué es una tool bien diseñada. En el capítulo 04 vimos contexto, memoria y handoff. En el capítulo 06 rodeamos el agente con harness, límites y trazas. Ahora conectamos esas piezas con SDKs reales: OpenAI Agents SDK, Claude Agent SDK/API, Google ADK y el ecosistema de herramientas.

Qué no es elegir un SDK

Elegir un SDK no es elegir “el mejor modelo”. El modelo puede cambiar dentro del mismo SDK.

Tampoco es elegir “el proveedor para siempre”. Si acoplas tus tools, sesiones, errores y trazas al formato exacto de una plataforma, has tomado una decisión de arquitectura aunque nadie la haya escrito.

Y no es decidir que todo debe vivir dentro del SDK. Hay estado que debería vivir en tu base de datos, permisos que deberían vivir en tu sistema, métricas que deberían vivir en observabilidad y contratos de tools que deberían poder probarse sin llamar al modelo.

La pregunta profesional no es:

“¿Qué SDK uso?”

La pregunta profesional es:

“¿Qué parte de mi sistema quiero que resuelva el SDK, qué parte controlo yo y qué coste de cambio acepto?”

La definición útil

Para este capítulo, un SDK de agentes es:

Una capa de desarrollo que facilita ejecutar bucles agentic: preparar contexto, llamar al modelo, exponer tools, gestionar sesiones, hacer handoffs, emitir eventos, aplicar guardrails y devolver resultados al producto.

El SDK puede ser muy ligero, como una librería de cliente que llama una API de mensajes. O puede ser bastante opinado, como un runtime que ya trae agentes, runners, tools, handoffs, sesiones y trazas.

La distinción clave es esta:

NivelQué te daEjemplo
API de modeloEndpoint para enviar mensajes, tools y recibir salida.Anthropic Messages API, OpenAI Responses API.
SDK de clienteTipos, métodos, streaming, errores y autenticación.openai, anthropic, @anthropic-ai/sdk.
SDK de agentesBucle agentic, tools, sesiones, handoffs, trazas.OpenAI Agents SDK, Claude Agent SDK, Google ADK.
Framework de orquestaciónGrafos, estados, workflows, persistencia, retries.LangGraph, LlamaIndex Workflows, CrewAI.
ProtocoloInteroperabilidad entre tools o agentes.MCP para tools/contexto, A2A para agentes.

La anatomía formal de una integración

Ejemplo de fórmula. Podemos representar una integración de SDK como una tupla:

Isdk=(P,M,A,T,S,K,H,G,E,τ,Ω)\mathcal{I}_{sdk} = (P, M, A, T, S, K, H, G, E, \tau, \Omega)
SímboloSignificadoEjemplo concreto
Isdk\mathcal{I}_{sdk}Integración completa con un SDK.Agente de revisión académica conectado a OpenAI o Claude.
PPProveedor o plataforma.OpenAI, Anthropic, Google ADK.
MMModelo o familia configurada.Modelo fuerte para revisión, modelo barato para clasificación.
AAAgentes definidos.Coordinador, revisor APA, verificador de fuentes.
TTTools disponibles.validar_cita, buscar_fuente, normalizar_apa.
SSSesión y estado.Historial, run_state, memoria, checkpoints.
KKConstructor de contexto.Instrucciones, documentos, memoria y artefactos.
HHHandoffs o delegaciones.Transferir de coordinador a especialista.
GGGuardrails o gates.Validar JSON, limitar tools, exigir citas.
EEEvaluación.Dataset de casos, métricas, trayectoria esperada.
τ\tauTraza normalizada.Eventos de modelo, tool, handoff, coste y latencia.
Ω\OmegaPolítica operativa.Timeouts, retries, presupuesto, permisos y fallback.

Un SDK serio reduce código repetitivo, pero no elimina estas piezas. Si no las ves en el SDK, siguen existiendo en tu producto. Si no las diseñas, aparecen como comportamiento implícito.

Ejemplo de fórmula. La portabilidad mínima se puede estimar así:

portabilidad=Ncontratos propiosNcontratos propios+Ndependencias especıˊficas\operatorname{portabilidad} = \frac{ N_{\text{contratos propios}} }{ N_{\text{contratos propios}} + N_{\text{dependencias específicas}} }
SímboloSignificadoEjemplo
Ncontratos propiosN_{\text{contratos propios}}Piezas que controlas tú.Tool schemas internos, trazas propias, tests, estado.
Ndependencias especıˊficasN_{\text{dependencias específicas}}Piezas atadas a un SDK concreto.Handoff solo disponible en una librería, session store propietario.
portabilidad\operatorname{portabilidad}Aproximación del margen de cambio.0,70 indica que gran parte del sistema no depende del proveedor.

No es una métrica científica absoluta. Sirve como disciplina: si todo depende del SDK, cambiar será caro.

Fecha de corte del estado del arte

Fecha de corte: 10 de junio de 2026.
Fuentes consultadas ese día: documentación oficial de OpenAI Agents SDK para Python y JavaScript; documentación oficial de Anthropic para Claude Agent SDK, Messages API, tool use y MCP connector; documentación oficial de Google ADK sobre agentes, tools, sesiones, memoria y evaluación; especificación pública de MCP; especificación A2A; papers sobre uso de herramientas por LLMs.

Lo estable es el patrón: agente, tools, sesiones, handoff, trazas, evaluación, permisos y adaptadores. Lo cambiante son nombres de paquetes, modelos por defecto, encabezados beta, compatibilidad de tools, límites, precios, módulos de memoria, conectores y capacidades hospedadas.

La revisión del 10 de junio confirma que conviene enseñar SDKs como capas de runtime, no como recetas cerradas. OpenAI mantiene una separación clara entre agente, runner, handoffs, guardrails y tracing. Anthropic ya presenta Claude Agent SDK como biblioteca Python/TypeScript sobre el arnés de Claude Code, con permisos, hooks, sesiones, subagentes y herramientas incluidas. Google ADK y A2A empujan la interoperabilidad entre agentes, mientras que LangGraph sigue siendo una referencia práctica para ejecución durable, interrupciones humanas y persistencia. La regla de ingeniería se mantiene: diseña tu contrato interno antes del SDK y trata cada proveedor como un adaptador observable.

OpenAI Agents SDK: runtime opinado para agentes

OpenAI Agents SDK define Agent como el bloque principal: un LLM con instrucciones, tools y comportamiento opcional como handoffs, guardrails y salidas estructuradas.1 La documentación oficial lo presenta como una forma de construir aplicaciones agentic en las que un modelo usa contexto, tools, handoffs, streaming y trazas.2

La idea importante: OpenAI te da una abstracción bastante completa. El Runner ejecuta el agente; las tools pueden ser funciones, tools hospedadas o agentes usados como tools; los handoffs permiten transferir una conversación a otro agente; las sesiones evitan reconstruir manualmente el historial; el tracing captura generaciones, tools, handoffs y guardrails.3

Pieza en OpenAI Agents SDKQué significa para ingeniería
AgentUnidad con instrucciones, modelo, tools, handoffs y configuración.
Runner / runRuntime que ejecuta el bucle del agente.
Function toolsFunciones propias expuestas como tools con contrato.
Hosted toolsTools gestionadas por OpenAI, como búsqueda, file search o code interpreter, según SDK y entorno.4
Agents as toolsUn agente especializado se expone como una tool del agente coordinador.
HandoffsUn agente transfiere la conversación a otro especialista; el handoff aparece como tool para el modelo.5
SessionsMemoria de conversación gestionada por una implementación de sesión.6
MCPIntegración con servidores MCP, incluyendo nombres prefijados por servidor para evitar colisiones.7

Cuándo encaja bien:

Encaja cuando...Cuidado con...
Quieres un runtime de agente con trazas y handoffs ya pensados.No ocultar reglas de negocio dentro de callbacks imposibles de probar.
Trabajas en Python o TypeScript y quieres moverte rápido.No depender de una feature hospedada si necesitas portabilidad.
Necesitas usar tools de OpenAI y flujos con varios agentes.Separar tool contract interno del decorador específico del SDK.
Quieres observar ejecuciones desde el principio.Normalizar trazas si comparas con otros proveedores.

Anthropic: Messages API, Claude Agent SDK y Claude Code

Anthropic tiene dos niveles que conviene no mezclar. El primer nivel es la Messages API: una API de mensajes donde tú envías historial, system prompt, tools y recibes bloques de contenido. La documentación dice explícitamente que la Messages API es stateless: debes enviar el historial conversacional completo que quieras que el modelo vea.8

El segundo nivel es el Claude Agent SDK. La documentación actual indica que el Claude Code SDK fue renombrado a Claude Agent SDK, disponible para TypeScript y Python, y construido sobre el arnés de agentes que impulsa Claude Code.9 En la quickstart, el punto central es query: la entrada que crea el bucle agentic y devuelve un iterador asíncrono para observar mensajes mientras Claude trabaja.10

Pieza AnthropicQué aportaCómo leerla
Messages APIControl bajo nivel de mensajes, tools y streaming.Tú gestionas historial, estado, reintentos y ciclo de tools.
Tool useClaude pide tool_use; tu aplicación ejecuta y devuelve tool_result.11Muy transparente para entender el protocolo.
Claude Agent SDKRuntime alto nivel para construir agentes con TypeScript o Python.Útil si quieres reutilizar el arnés de Claude Code en tu producto.
Claude CodeHerramienta de desarrollo agentic con memoria, subagentes, hooks y MCP.Buen ejemplo de harness real, aunque no todo aplica a cualquier producto.
MCP connectorPermite conectar servidores MCP remotos desde la Messages API; en la versión consultada usa beta header mcp-client-2025-11-20 y soporta tools, con limitaciones claras.12Muy útil para tools remotas, pero debes leer autenticación, retención y compatibilidad.

Anthropic brilla cuando quieres ver el protocolo con claridad. La Messages API obliga a entender tool use, historial y control externo. El Claude Agent SDK te da un arnés más completo. Claude Code enseña patrones prácticos: CLAUDE.md, subagentes, hooks, skills, permisos de tools y configuración por proyecto.

Cuándo encaja bien:

Encaja cuando...Cuidado con...
Quieres control explícito de la conversación y de las tools.La API de mensajes no guarda estado por ti.
Quieres construir agentes sobre el arnés de Claude Code.El SDK de agente tiene supuestos fuertes sobre entorno y ejecución.
Trabajas con código, repositorios o workflows de desarrollo.No confundas una herramienta de desarrollo con una arquitectura general de producto.
Quieres usar MCP remoto desde la API.Verifica transporte, autenticación, retención y tools permitidas.

Anatomía del SDK de Anthropic

Cuando alguien dice “el SDK de Anthropic” puede estar hablando de tres cosas distintas. Conviene separarlas antes de decidir arquitectura:

SuperficieQué controlas túQué resuelve AnthropicCuándo usarla
Messages APIHistorial, tools, ciclo de ejecución, estado, streaming y validación.El modelo, el protocolo de mensajes y los bloques tool_use / tool_result.Cuando quieres un loop propio y máximo control.
SDK clienteTipos, cliente HTTP, autenticación, streaming y errores.Acceso cómodo a la API desde Python, TypeScript u otro lenguaje soportado.Cuando no necesitas arnés agentic completo.
Claude Agent SDKBucle agentic, interacción con Claude Code, tools, permisos, hooks, sesiones, coste y observabilidad.Un runtime de agente ejecutado desde tu proceso, con eventos observables.Cuando quieres construir sobre el arnés de Claude Code.
Claude CodeProyecto local, CLAUDE.md, subagentes, comandos, skills, plugins, hooks y herramientas de desarrollo.Un entorno agentic de trabajo sobre archivos, terminal y repositorios.Cuando el dominio es ingeniería de software o workflows sobre workspace.

La Messages API es stateless: si quieres que Claude vea conversación anterior, debes enviar el historial que toca.13 El Claude Agent SDK, en cambio, construye un agente sobre el arnés de Claude Code; la quickstart muestra query() como punto de entrada y devuelve mensajes de manera asíncrona mientras el agente trabaja.14

Ejemplo de fórmula. Podemos escribir su anatomía así:

Aanthropic=(Q,O,C,L,T,P,H,S,R,Ω)\mathcal{A}_{anthropic} = (Q, O, C, L, T, P, H, S, R, \Omega)
SímboloPiezaQué significa en ingeniería
QQquery o ClaudeSDKClientEntrada de trabajo y canal para recibir eventos.
OOOpcionesModelo, directorio de trabajo, tools, presupuesto, permisos, MCP, hooks y sesiones.
CCContextoPrompt, historial, CLAUDE.md, subagentes, skills, documentos y estado del proyecto.
LLLoop agenticTurnos de modelo, posibles tools, resultados de tools y salida final.
TTToolsBuilt-in tools, tools MCP, tools propias y subagentes como capacidad especializada.
PPPermisosModos de permiso, allow/deny lists, callbacks y hooks antes de ejecutar tools.
HHHooksPuntos de intervención antes/después de tool, parada, subagente o notificación.
SSSesiónContinuación, reanudación, checkpointing y estado conversacional.
RRResultadoMensajes, stream, ResultMessage, coste, duración, uso y razón de finalización.
Ω\OmegaOperaciónLogs, OpenTelemetry, métricas, fallback, límites y pruebas de regresión.

Anatomía visual

Anatomía del SDK de Anthropic Del prompt inicial al resultado observable: query, opciones, loop, permisos, hooks, tools, sesión, coste y trazas. ENTRADA Y CONFIGURACIÓN Aplicación plugin, backend o CLI tarea del usuario define objetivo y contrato `query()` / Client prompt o stream de mensajes iterador asíncrono o sesión conversacional punto de entrada del SDK Options model max_turns permission_mode tools mcp_servers hooks Contexto inicial system prompt, historial `CLAUDE.md`, skills, docs working directory lo que verá el agente LOOP AGENTIC Claude Agent SDK proceso anfitrión recibe eventos Claude Code CLI proceso hijo conexión local ejecuta el arnés abstracción heredada de Claude Code Bucle de mensajes SystemMessage AssistantMessage UserMessage ResultMessage Modelo Claude Messages API streaming opcional tool_use cuando toca stateless en API base Tools built-in MCP subagentes tools propias siempre con contrato CONTROL, ESTADO Y OBSERVABILIDAD Permisos allow / deny permission callback decide cada tool Hooks PreToolUse PostToolUse, Stop intervención medible Sesión resume / continue checkpointing recuperación de runs Coste y uso tokens, duración max_budget_usd presupuesto por run Observabilidad OpenTelemetry logs y métricas traza exportable Resultado mensajes finales cost, usage, stop contrato de salida Recibo recomendado session_id · model · options_hash · tools · permisos · hook_decisions · tool_results · cost_usd · duration_ms · usage · result_subtype · trace_id IA para gente curiosa / Facsímil 05 / Capítulo 07 / 686f6c61

La figura deja una idea importante: el Claude Agent SDK no es solo una llamada HTTP. Tu aplicación llama al SDK, el SDK configura una ejecución del arnés de Claude Code, ese arnés conversa con Claude, puede pedir tools, pasa por permisos y hooks, y al final devuelve mensajes, uso, coste, duración y estado de cierre. Si usas la Messages API directamente, muchas de esas cajas siguen existiendo, pero las implementas tú.

El bucle paso a paso

El bucle del Agent SDK se entiende mejor si lo leemos como una secuencia observable:

PasoQué ocurreQué deberías registrar
1. EntradaTu app llama query() o abre un ClaudeSDKClient con prompt y opciones.run_id, session_id, versión de agente y hash de opciones.
2. InicializaciónEl SDK prepara el proceso, el contexto, el directorio de trabajo y la configuración.Directorio, modelo, tools permitidas, MCP, hooks y presupuesto.
3. Mensaje inicialLlega un SystemMessage con metadatos de sesión y estado inicial.Session id real y metadatos devueltos por el runtime.
4. Turno del modeloClaude genera texto, decide seguir, o solicita una tool.Tokens, latencia, bloques de contenido y motivo de parada si aplica.
5. Decisión de toolEl runtime comprueba permisos, listas, callbacks y hooks.Nombre de tool, input, decisión, motivo y quién la autorizó.
6. EjecuciónSe ejecuta una tool built-in, MCP, propia o de subagente.Resultado, error controlado, duración y efecto declarado.
7. Resultado de toolEl output vuelve al loop como información para Claude.Tamaño del resultado y si se recortó o resumió.
8. RepeticiónEl loop continúa hasta salida final, límite, error o parada.Número de turnos, tools usadas y coste acumulado.
9. ResultadoLlega un ResultMessage con subtipo, coste, duración y uso.Salida validada, usage, total_cost_usd, duration_ms y estado final.

La documentación del Agent SDK describe mensajes como SystemMessage, AssistantMessage, UserMessage y ResultMessage, y también subtipos de resultado como éxito, error y límite de turnos.15

Permisos: la parte que no se debe improvisar

Ejemplo de fórmula. El SDK permite combinar allowed_tools, disallowed_tools, modos de permiso, callbacks y hooks. Eso parece una lista de opciones, pero en realidad es una política de ejecución. Podemos modelarla así:

D(t,a,c)=Hpre(t,a,c)Deny(t)Mode(t)Allow(t)Callback(t,a,c)D(t, a, c) = H_{pre}(t, a, c) \rightarrow Deny(t) \rightarrow Mode(t) \rightarrow Allow(t) \rightarrow Callback(t, a, c)
PiezaQué significaQué decidiría en un proyecto serio
ttTool solicitada.Nombre estable y versión de schema.
aaArgumentos.Validación antes de ejecutar.
ccContexto de ejecución.Usuario, tenant, directorio, sesión y presupuesto.
HpreH_{pre}Hook antes de tool.Puede bloquear, modificar o registrar la solicitud.
Deny(t)Lista o regla que no permite una tool.Siempre gana sobre una allowlist.
Mode(t)Modo de permisos de la ejecución.Modo lectura, aceptar ediciones, omitir permisos o modo plan.
Allow(t)Tools explícitamente disponibles.Útil, pero no suficiente como auditoría.
Callback(t,a,c)Función propia de autorización.Donde conectas reglas de negocio.

La documentación oficial de permisos explica que hay varias capas: herramientas permitidas y no permitidas, modos de permiso y callbacks; también indica que las decisiones pueden integrarse con hooks previos a tool.16 El matiz de ingeniería: una allowlist no sustituye a una política. La allowlist dice “esta tool existe para esta ejecución”; la política decide “esta llamada concreta, con estos argumentos, en este contexto, puede hacerse”.

Hooks: instrumentación y control

Los hooks son puntos de intervención del arnés. Sirven para observar, validar, modificar contexto o detener una operación antes de que ocurra. En una integración profesional no los usaría para esconder lógica de dominio, sino para conectar el agente con el sistema operativo del producto:

Hook o momentoUso sanoSeñal de mala arquitectura
Antes de toolValidar argumentos, registrar intención, aplicar política.Reescribir medio prompt porque no hay contrato claro.
Después de toolGuardar resultado, medir latencia, normalizar errores.Parsear salidas libres imposibles de testear.
Al pararEmitir evento final, cerrar spans, persistir resumen.Depender de logs manuales para saber qué pasó.
En subagenteMedir delegación y contexto entregado.Delegar sin saber qué recibió el especialista.

Anthropic documenta hooks para personalizar el comportamiento del agente en momentos como tool use, parada, notificaciones y subagentes.17 Si el capítulo 06 hablaba de harness, aquí se ve aplicado: el hook es un punto de control.

Mensajes, streaming y resultado

En Messages API, el streaming permite recibir eventos parciales: arranque de mensaje, bloques de contenido, deltas, parada de bloque y parada de mensaje.18 En Agent SDK, el desarrollador ve mensajes de más alto nivel del loop. Esa diferencia importa:

Si usas...Lo que vesLo que te toca controlar
Messages API sin streamingRespuesta completa al final.Historial, tools, reintentos y estado.
Messages API con streamingEventos de texto y bloques parciales.Render incremental, cancelación, errores parciales.
Claude Agent SDK con query()Secuencia de mensajes del agente.Consumir eventos, persistir trazas y validar resultado.
ClaudeSDKClientConversación más interactiva.Ciclo de vida de sesión y envío de nuevas entradas.

Una salida “correcta” no debería ser solo texto. Para un producto real pediría:

CampoPor qué
result_subtypeDistingue éxito, error o límite alcanzado.
usagePermite comparar coste entre versiones.
total_cost_usdConvierte tokens y tools en presupuesto entendible.
duration_msAyuda a detectar latencia por modelo, tool o red.
trace_idUne logs, spans y eventos del producto.
final_schema_validSepara “parece correcto” de “cumple contrato”.

Sesión, checkpointing y continuidad

La sesión responde a una pregunta: “¿cómo continúa una ejecución o conversación sin reconstruir todo a mano?”. El checkpointing responde a otra: “¿puedo guardar y reanudar desde un punto fiable?”. No son la misma cosa que memoria semántica.

ConceptoQué conservaRiesgo si se confunde
Historial de conversaciónTurnos y mensajes.Creer que todo historial es conocimiento útil.
session_idIdentidad de una sesión.Mezclar sesiones de usuarios o tareas distintas.
CheckpointPunto recuperable de una ejecución.No poder depurar una run larga fallida.
Memoria externaHechos reutilizables entre sesiones.Meter recuerdos irrelevantes en cada prompt.
CLAUDE.mdInstrucciones persistentes del proyecto.Convertir normas locales en verdad universal.

Anthropic documenta continuidad de sesión y checkpointing en el Agent SDK, útiles cuando una ejecución necesita reanudarse o conservar estado operacional.19 La regla para nuestro libro: sesión es continuidad de trabajo; memoria es conocimiento recuperable; contexto es lo que entra en una llamada concreta.

Coste y observabilidad

El Agent SDK expone coste y uso en los resultados, y documenta max_budget_usd como control de presupuesto por ejecución.20 También documenta observabilidad con OpenTelemetry para exportar métricas, logs y trazas.21

Para un ingeniero, esto cambia la forma de evaluar:

MétricaQué mideUmbral útil
turn_countCuántas vueltas necesita el agente.Si crece, quizá falta tool o prompt más claro.
tool_call_countCuántas herramientas usa.Si se dispara, hay mala planificación o mala recuperación.
permission_denied_countCuántas acciones fueron rechazadas.Si es alto, el agente no entiende límites.
total_cost_usdCoste total de la run.Debe mirarse por tarea aceptada, no por demo.
duration_msTiempo real de la ejecución.Separa latencia de modelo, tools y cola.
schema_valid_ratePorcentaje de salidas válidas.Si baja, falta contrato o reparación controlada.
resume_success_rateCapacidad de recuperar sesiones/checkpoints.Clave en runs largas.

Cómo lo integraría en nuestro contrato portable

Contrato del libroAnthropic lo resuelve con...Qué dejaría fuera del SDK
AgentSpecPrompt, opciones y configuración del agente.Identidad del agente, versión y criterios de cierre.
ToolSpecTools de Messages API, tools del Agent SDK o MCP.Schema canónico, efecto e idempotencia.
PermissionPolicyallowed_tools, disallowed_tools, modo, callback y hooks.Reglas de negocio y auditoría centralizada.
SessionStoreSession id, continuidad y checkpointing.Propietario real del dato y reglas de borrado.
TraceEventMensajes del loop, OTel, logs y resultado.Formato común entre proveedores.
EvalDatasetDatos propios de evaluación.Golden traces, métricas y umbrales de aceptación.
CostEnvelopemax_budget_usd, usage y coste.Presupuesto por usuario, tenant o tarea.

Lo que me gusta de Anthropic para enseñar ingeniería es que obliga a mirar el loop. La Messages API te muestra el protocolo desnudo. El Agent SDK te da un arnés más completo, pero sigue siendo observable si consumes mensajes, coste, permisos y hooks. La trampa sería lo de siempre: usarlo como caja negra y llamar arquitectura a una demo.

Google ADK: framework de agentes, sesiones, memoria y evaluación

Google ADK se presenta como un kit de desarrollo para construir y desplegar agentes. Su documentación define un agente o LlmAgent como una unidad autocontenida que puede perseguir objetivos, interactuar con usuarios, usar tools y coordinarse con otros agentes.22

La documentación técnica destaca varias piezas relevantes: memoria para recordar información entre sesiones, evaluación integrada para crear datasets multi-turn y ejecutar evaluaciones localmente, y soporte amplio de LLMs mediante interfaces como BaseLlm, aunque esté optimizado para Gemini.23

Pieza Google ADKQué aportaCómo leerla
Agent / LlmAgentUnidad de ejecución con modelo, instrucciones y tools.Similar en idea a otros SDKs, con sabor Google/Gemini.
RunnerEjecuta el agente con servicios de sesión y memoria.Separa definición de agente y ejecución.
Session serviceHistorial, eventos y estado de una conversación.Estado corto, no memoria permanente.
Memory serviceMemoria a largo plazo; puede usarse con tools como load_memory o PreloadMemoryTool.24Requiere decidir cuándo pasar sesiones a memoria.
ToolsFunciones, herramientas de Google Cloud, MCP Toolbox, conectores y herramientas de terceros.25Muy orientado a ecosistema enterprise y Google Cloud.
EvaluationEvalúa trayectoria y respuesta con datasets y criterios.26Valioso para no quedarse en demos.
A2AIntegración con Agent2Agent para comunicación entre agentes.27Lo veremos con más detalle en el capítulo 09.

Google ADK encaja especialmente bien si quieres un framework completo con agentes, servicios de sesión/memoria, evaluación y despliegue cercano al ecosistema Google. También es interesante para enseñar arquitectura porque fuerza a separar agente, runner, sesión, memoria y evaluación.

MCP y A2A: no son lo mismo que un SDK

MCP y A2A aparecen mucho al hablar de SDKs, pero cumplen otra función.

MCP, Model Context Protocol, estandariza cómo una aplicación ofrece contexto y herramientas a modelos o agentes. Es una frontera de herramientas: servidores, tools, recursos, prompts, transportes y autorización.28

A2A, Agent2Agent Protocol, busca interoperabilidad entre sistemas agentic independientes. Es una frontera de agentes: descubrir capacidades, enviar tareas, mantener contexto de conversación y coordinar trabajo entre sistemas.29

TecnologíaFrontera principalPregunta que responde
SDK de modeloAplicación ↔ modelo¿Cómo llamo al modelo?
SDK de agentesAplicación ↔ runtime agentic¿Cómo ejecuto bucles con tools, estado y trazas?
MCPAgente ↔ tools/contexto¿Cómo expongo capacidades externas de forma estándar?
A2AAgente ↔ agente¿Cómo conversa un agente con otro sistema agentic?

Regla práctica: no uses MCP para sustituir diseño de tools; úsalo para empaquetar y exponer tools. No uses A2A para esconder falta de arquitectura interna; úsalo cuando realmente hay sistemas agentic independientes que deben coordinarse.

Mercado y criterio de elección

Además de OpenAI, Anthropic y Google, existen LangGraph, LlamaIndex, CrewAI, AutoGen, Haystack, Semantic Kernel, Vercel AI SDK, OpenCode, Codex CLI, Claude Code, Cursor y otros entornos. La lista cambia rápido. El criterio no debería ser popularidad, sino ajuste al tipo de sistema.

Necesitas...Prioriza...Pregunta incómoda
Runtime agentic completo con trazasOpenAI Agents SDK, Google ADK, LangGraph.¿Puedo exportar o normalizar las trazas?
Protocolo claro de tool useAnthropic Messages API, OpenAI Responses API.¿Dónde vive el estado entre turnos?
Agente de código con entorno de trabajoClaude Code, Codex CLI, OpenCode, Cursor.¿Qué permisos y rutas puede tocar?
Integración enterprise con Google CloudGoogle ADK + Vertex/Google Cloud tools.¿Dependo de servicios concretos del cloud?
Tools reutilizables entre clientesMCP.¿Tengo allowlist, auth y nombres únicos?
Agentes entre organizaciones o sistemasA2A.¿Necesito interoperabilidad o solo una tool?
Máxima portabilidadAdapter propio + contratos internos.¿Qué pierdo si no uso features nativas?

La respuesta madura casi nunca es “todo abstracto” ni “todo nativo”. Lo normal es una mezcla:

  1. Contrato interno propio para tools, trazas, sesiones y resultados.
  2. Adapter fino por proveedor.
  3. Uso consciente de features nativas cuando aportan mucho.
  4. Evals que comparan comportamiento, no solo compilación.

Qué le faltaba al capítulo

Antes de dibujar arquitectura, hay cuatro preguntas que un capítulo universitario sobre SDKs no debería esquivar:

Falta habitualPor qué importaQué deberíamos producir
Criterio de adopciónUn SDK puede acelerar o encerrar el diseño.Matriz: API directa, SDK de agente, framework de grafos, MCP o A2A.
Contrato de ejecuciónUna demo no describe retries, coste, estado ni salida.RunSpec con presupuesto, límites, session id, trace id y esquema final.
Plano de controlEl modelo no debe decidir credenciales, permisos, memoria y auditoría.Tool gateway, policy engine, secretos, trazas, evals y fallback fuera del modelo.
Plan de migraciónCambiar de proveedor sin plan suele implicar reescribir producto.Adaptadores finos y tests que comparan comportamiento entre proveedores.

La decisión se puede convertir en una regla práctica:

Si el sistema...Empieza por...No empieces por...
Solo necesita una respuesta estructurada y pocas tools.API de modelo + loop propio pequeño.Framework pesado de agentes.
Necesita handoffs, tools, sesiones y trazas desde el día uno.SDK de agentes.Cliente HTTP escrito a mano sin observabilidad.
Tiene workflows largos, ramas y estado persistente.LangGraph, Google ADK, LlamaIndex Workflows o framework equivalente.Un único agente con prompt gigante.
Quiere reutilizar tools entre clientes distintos.MCP.Copiar la misma tool en cada proveedor.
Necesita que sistemas agentic independientes cooperen.A2A o protocolo equivalente.Encadenar agentes como si fueran simples funciones.
Tiene exigencia fuerte de portabilidad.Contrato interno + adapters.Usar tipos del SDK como modelo de dominio.

Otra forma de verlo: el SDK se elige después de saber qué parte quieres delegar. Si el SDK decide tu estado, tus tools, tus trazas y tu evaluación, ya no es una librería: es el esqueleto del producto.

Arquitectura visual de una integración portable

SDKs de agentes: arquitectura portable de producción El producto conserva contratos, estado, trazas y evaluación; cada SDK se enchufa como adaptador medible. PLANO DE DATOS: una ejecución real Producto usuario, UI o API objetivo verificable datos de entrada no contiene lógica del proveedor Kernel agentic propio AgentSpec rol y límites RunSpec budget y estado ToolSpec schema y efecto OutputSpec JSON validable flags caps ProviderAdapter traduce, no decide dominio OpenAI Agents SDK Claude Agent/API Google ADK Local LangGraph Runtime del proveedor Modelo tokens y salida Handoff delegación Tools nativas search, files, code Streaming eventos parciales PLANO DE CONTROL: lo que no conviene esconder dentro del SDK Identidad tenant, usuario secrets, scopes antes de cualquier tool Context builder instrucciones memoria, docs context manifest Tool gateway validar entrada aprobar efecto idempotency key Session store historial corto estado de run reanudación Memory / RAG recuerdos evidencia viva no es sesión Trace bus spans comunes latencia, tokens exportable Eval gates trayectoria salida final merge o rollback Policy engine timeouts límites y cuotas MCP servers tools externas recursos, prompts A2A agente a agente capacidades Cost model tokens, tools latencia, retries Fallback proveedor B modo degradado RECIBO DE UNA RUN run_id · user_id · agent_version · context_manifest · provider · model · tool_calls · approvals · trace_id · cost · latency · final_schema · eval_status Si este recibo no existe, no podrás explicar, depurar ni comparar la ejecución cuando cambie el SDK. IA para gente curiosa / Facsímil 05 / Capítulo 07 / 686f6c61

La arquitectura separa tres planos. El plano de datos es la ejecución visible: producto, contratos, adapter y runtime. El plano de control contiene lo que no debería quedar escondido dentro del SDK: identidad, contexto, tools, sesión, memoria, trazas, evaluación, coste y fallback. El recibo final de la run es la prueba de madurez: si no puedes reconstruir qué ocurrió, no puedes comparar proveedores ni depurar un cambio.

Reglas de integración que pondría en un proyecto real

Estas reglas son deliberadamente concretas.

ReglaPor qué importaSeñal de que lo haces bien
Define AgentSpec propio antes de instanciar el SDK.Evita que tu arquitectura sea el ejemplo de la documentación.Puedes imprimir un manifiesto de agente sin proveedor.
Define ToolSpec propio.La tool pertenece a tu dominio, no al SDK.Puedes ejecutar tests de tools sin modelo.
Usa capability flags.Cada proveedor soporta piezas distintas.supports_handoffs, supports_mcp, supports_native_tracing.
Guarda trazas normalizadas.Comparar SDKs exige lenguaje común.Todos emiten model_call, tool_call, handoff, final_output.
Separa sesión de memoria.La sesión no siempre es recuerdo duradero.Hay session_store y memory_store distintos.
Versiona instrucciones y schemas.Los cambios de prompts y tools son cambios de software.Cada run guarda agent_version y tool_version.
Haz retry solo con idempotencia.Repetir una tool puede duplicar efectos.Cada tool con efecto tiene idempotency_key.
No expongas tools genéricas.Una tool demasiado amplia rompe el contrato.Tools pequeñas, con precondiciones y salida limitada.
Evalúa trayectoria, no solo respuesta.Un agente puede acertar mal.El test mira steps, tools, coste y estado final.
Escribe plan de salida del proveedor.Reduce dependencia accidental.Sabes qué se perdería al migrar y cuánto costaría.

Parámetros que debes mapear entre SDKs

Aunque los nombres cambien, casi todos los sistemas agentic tienen estas piezas.

ConceptoOpenAIAnthropicGoogle ADKContrato propio recomendado
Instruccionesinstructions del Agent.System prompt, Agent SDK prompt o configuración.instruction del agente.agent.instructions_version.
Modelomodel o settings.model en Messages/SDK.model en Agent.model_profile.
ToolsFunction tools, hosted tools, MCP.tools, MCP connector, Agent SDK tools.Function tools, built-ins, MCP/Google tools.ToolSpec[].
Handoffhandoffs.Subagentes/Agent SDK o routing propio.Multi-agent/A2A/workflows.DelegationPolicy.
SesiónSession implementation.Historial enviado o Agent SDK runtime.SessionService.SessionStore.
MemoriaSessions, custom memory, external store.CLAUDE.md, memory tools, store externo.MemoryService.MemoryStore.
TrazasTracing del Agents SDK.Eventos SDK, logs, traces propios.Evaluation/logging/Cloud observability.TraceEvent.
Salida estructuradaOutput types/schema.Structured outputs o tool/result contract.Schema/response contract.OutputSchema.
EvaluaciónEvals/trace grading/propias.Tests/evals propias.ADK evaluation.EvalDataset + rubric.
MCPSDK MCP integration.MCP connector y helpers.MCP tools / toolbox.ToolTransport.

Esta tabla es más importante que el tutorial de instalación. Si no sabes mapear estos conceptos, no estás integrando un SDK: estás pegando código.

Ingeniería de producción: la run como contrato

Ejemplo de fórmula. Una integración agentic no debería empezar en client.responses.create, query(...) o Runner.run(...). Debería empezar con una descripción completa de la ejecución:

R=(u,x,c,a,p,b,τ,o,e)R = (u, x, c, a, p, b, \tau, o, e)
SímboloQué representaPregunta de ingeniería
uuUsuario, tenant o proceso que inicia la run.¿Con qué permisos actúa?
xxEntrada original.¿Se conserva sin mezclarla con memoria o sistema?
ccContext manifest.¿Qué instrucciones, documentos y recuerdos entraron?
aaAgente o grafo elegido.¿Qué versión exacta del agente se ejecutó?
ppProvider adapter.¿Qué SDK, modelo y parámetros concretos se usaron?
bbPresupuesto.¿Cuántos pasos, tokens, tools, coste y tiempo se permiten?
τ\tauTraza.¿Puedo reconstruir cada llamada, tool y handoff?
ooSalida validada.¿Cumple el esquema o solo parece correcta?
eeEvaluación posterior.¿Pasó gates de trayectoria, calidad y coste?

Ejemplo de fórmula. El coste tampoco es una etiqueta genérica. Para una run agentic, el coste operativo se parece más a:

CR=Cinput+Coutput+iCtooli+Cretries+Cobservabilidad+ClatenciaC_R = C_{\text{input}} + C_{\text{output}} + \sum_i C_{\text{tool}_i} + C_{\text{retries}} + C_{\text{observabilidad}} + C_{\text{latencia}}
TérminoQué mideEjemplo de decisión
CinputC_{\text{input}}Tokens de instrucciones, historial, RAG y memoria.Compactar sesión o recortar contexto.
CoutputC_{\text{output}}Tokens generados y razonamiento visible/no visible según proveedor.Exigir salida corta y estructurada.
iCtooli\sum_i C_{\text{tool}_i}APIs, búsquedas, ejecución de código o consultas externas.Cachear herramientas de lectura.
CretriesC_{\text{retries}}Reintentos por timeout, JSON inválido o proveedor no disponible.Reintentar solo operaciones idempotentes.
CobservabilidadC_{\text{observabilidad}}Trazas, logs, almacenamiento y redacción de datos sensibles.Muestrear trazas en producción sin perder incidentes.
ClatenciaC_{\text{latencia}}Tiempo de espera del usuario y ocupación de workers.Streaming, colas o modo asíncrono.

Y hay fallos que conviene diseñar antes de verlos en producción:

CasoQué suele pasarDiseño que lo evita
Streaming parcialEl usuario ve media respuesta y luego falla una tool.Eventos tipados, estado partial, reanudación y mensaje final coherente.
JSON inválidoEl modelo devuelve algo cercano al esquema, pero no parseable.Validador estricto, reparación limitada y error observable.
Tool lentaLa ejecución agota timeout y el agente queda esperando.Timeout por tool, fallback y respuesta con lo que sí se sabe.
Tool repetidaUn retry duplica una acción externa.idempotency_key, efecto declarado y confirmación en tools de escritura.
Contexto excesivoEl modelo recibe demasiado ruido.Context manifest, ranking, compaction y evaluación de recuperación.
Cambio del SDKUn nombre, evento o tipo deja de encajar.Adapter con tests de contrato y versionado de provider.
Diferencia entre proveedoresUn mismo prompt no produce la misma trayectoria.Eval de trayectoria por proveedor, no solo golden answer final.

La documentación actual de OpenAI Agents SDK ya separa agentes, tools, handoffs, guardrails, output types, lifecycle hooks y tracing; además el tracing captura generaciones, tools, handoffs, guardrails y spans propios.3031 Anthropic distingue claramente la Messages API stateless, donde debes reenviar el historial que quieras que el modelo vea, del Claude Agent SDK, que ejecuta el bucle agentic en tu proceso y aporta tools, contexto y observabilidad propias de Claude Code.3233 Google ADK, por su parte, explicita agentes, tools, callbacks, sesiones, memoria, artefactos, runners, evaluación y despliegue; en evaluación distingue trayectoria y respuesta final.3435

La conclusión técnica es sencilla: si cada proveedor ya piensa en runtime, eventos y evaluación, nuestro libro no puede quedarse en “instala este SDK”. Debe enseñar a separar contrato, adapter y operación.

Caso concreto: un plugin de revisión académica con tres agentes

Imaginemos un plugin sencillo para este libro. Queremos revisar un párrafo antes de publicarlo. El sistema tiene tres especialistas:

  1. Un revisor de normas RAE: detecta problemas de ortografía, mayúsculas, tildes y estilo.
  2. Un revisor APA: revisa si las citas y referencias siguen un formato consistente.
  3. Un verificador de fuentes: abre URLs o documentos y comprueba si la afirmación citada está soportada.

El coordinador no debe “hacerlo todo”. Debe decidir qué especialista usar, reunir evidencias, devolver un informe y decir qué no puede verificar.

PiezaContrato internoEn OpenAIEn ClaudeEn Google ADK
Coordinadoragent: academic_reviewerAgent con agentes como tools o handoffs.query con subagentes o loop de tools.LlmAgent que coordina subagentes.
RAETool/agent de revisión lingüística.Agent as tool.Subagent o tool revisar_rae.AgentTool o subagente.
APATool/agent de citas.Agent as tool.Subagent o tool revisar_apa.AgentTool o subagente.
VerificadorTool con navegador/búsqueda controlada.Hosted/web tool o tool propia.MCP connector, tool propia o Agent SDK.Tool/MCP/Vertex Search según entorno.
ResultadoJSON con hallazgos, evidencia y acciones.Output type/schema.Structured output o contrato de tool.Schema de salida.
TrazaEventos por especialista.Tracing nativo + normalización.Eventos SDK/logs propios.ADK evaluation/logging.

La decisión fina:

Si necesitas...Diseña así
Que el especialista tome la conversación completaHandoff.
Que el coordinador conserve control y solo pida una tarea acotadaAgent as tool.
Que el especialista use navegador o búsquedaTool con permisos explícitos y límites.
Que el resultado sea revisableJSON con claim, evidence_url, confidence, needs_human_review.
Que se pueda migrar de SDKMantén AgentSpec, ToolSpec y TraceEvent propios.

Manos a la obra

Práctica: diseñar el contrato antes del SDK.

Kit ejecutable de este capítulo: kit descargable.

# Descomprime el ZIP del capítulo y ejecuta estos comandos dentro de esa carpeta
python3 ops/run_f5_practices.py --chapter c07 --write --fail-on-invalid

El siguiente código no llama a OpenAI, Claude ni Google. Esa es precisamente la gracia: primero construimos un contrato portable y comprobamos que el diseño tiene tools, agentes, permisos, trazas y capacidades. Después lo llevamos al SDK elegido.

from __future__ import annotations

from dataclasses import dataclass, field, asdict
from typing import Literal
import json


Effect = Literal["read", "prepare", "write"]


@dataclass(frozen=True)
class ToolSpec:
    name: str
    description: str
    effect: Effect
    input_schema: dict
    output_schema: dict
    requires_approval: bool = False


@dataclass(frozen=True)
class AgentSpec:
    name: str
    role: str
    tools: list[str]
    output_schema: dict
    max_steps: int


@dataclass(frozen=True)
class ProviderCapabilities:
    provider: str
    supports_handoffs: bool
    supports_agents_as_tools: bool
    supports_mcp: bool
    supports_sessions: bool
    supports_native_tracing: bool


@dataclass
class IntegrationManifest:
    app: str
    agents: list[AgentSpec]
    tools: list[ToolSpec]
    required_capabilities: dict[str, bool]
    trace_events: list[str] = field(default_factory=lambda: [
        "run_started",
        "model_call",
        "tool_call",
        "tool_result",
        "handoff",
        "final_output",
        "run_finished",
    ])


def validate_manifest(manifest: IntegrationManifest) -> list[str]:
    errors: list[str] = []
    tool_names = {tool.name for tool in manifest.tools}

    for agent in manifest.agents:
        if agent.max_steps < 1:
            errors.append(f"{agent.name}: max_steps debe ser >= 1")
        missing = sorted(set(agent.tools) - tool_names)
        if missing:
            errors.append(f"{agent.name}: tools no definidas: {missing}")

    for tool in manifest.tools:
        if tool.effect == "write" and not tool.requires_approval:
            errors.append(f"{tool.name}: una tool de escritura requiere aprobación")
        for field_name in ("type", "properties"):
            if field_name not in tool.input_schema:
                errors.append(f"{tool.name}: input_schema sin {field_name}")

    required = {"run_started", "tool_call", "tool_result", "final_output"}
    if not required.issubset(set(manifest.trace_events)):
        errors.append("faltan eventos mínimos de traza")

    return errors


def compatible_providers(
    manifest: IntegrationManifest,
    providers: list[ProviderCapabilities],
) -> list[str]:
    accepted = []
    for provider in providers:
        ok = True
        for capability, required in manifest.required_capabilities.items():
            if required and not getattr(provider, capability):
                ok = False
                break
        if ok:
            accepted.append(provider.provider)
    return accepted


finding_schema = {
    "type": "object",
    "properties": {
        "hallazgos": {"type": "array"},
        "evidencia": {"type": "array"},
        "requiere_revision": {"type": "boolean"},
    },
    "required": ["hallazgos", "evidencia", "requiere_revision"],
}

tools = [
    ToolSpec(
        name="revisar_rae",
        description="Revisa ortografía, tildes, mayúsculas y estilo editorial.",
        effect="read",
        input_schema={"type": "object", "properties": {"texto": {"type": "string"}}},
        output_schema=finding_schema,
    ),
    ToolSpec(
        name="revisar_apa",
        description="Comprueba citas y referencias en formato APA 7.",
        effect="read",
        input_schema={"type": "object", "properties": {"referencias": {"type": "array"}}},
        output_schema=finding_schema,
    ),
    ToolSpec(
        name="verificar_fuente",
        description="Comprueba si una URL o documento soporta una afirmación citada.",
        effect="read",
        input_schema={
            "type": "object",
            "properties": {
                "claim": {"type": "string"},
                "url": {"type": "string"},
            },
        },
        output_schema=finding_schema,
    ),
]

agents = [
    AgentSpec(
        name="coordinador_academico",
        role="Decide qué especialista usar y entrega un informe unificado.",
        tools=["revisar_rae", "revisar_apa", "verificar_fuente"],
        output_schema=finding_schema,
        max_steps=6,
    ),
    AgentSpec(
        name="revisor_rae",
        role="Detecta problemas lingüísticos y propone correcciones mínimas.",
        tools=["revisar_rae"],
        output_schema=finding_schema,
        max_steps=2,
    ),
    AgentSpec(
        name="revisor_apa",
        role="Revisa coherencia de citas y referencias.",
        tools=["revisar_apa", "verificar_fuente"],
        output_schema=finding_schema,
        max_steps=3,
    ),
]

manifest = IntegrationManifest(
    app="plugin_revision_academica",
    agents=agents,
    tools=tools,
    required_capabilities={
        "supports_handoffs": False,
        "supports_agents_as_tools": True,
        "supports_mcp": False,
        "supports_sessions": True,
        "supports_native_tracing": False,
    },
)

providers = [
    ProviderCapabilities("OpenAI Agents SDK", True, True, True, True, True),
    ProviderCapabilities("Claude Agent SDK", True, True, True, True, False),
    ProviderCapabilities("Google ADK", True, True, True, True, False),
    ProviderCapabilities("API de mensajes mínima", False, False, False, False, False),
]

errors = validate_manifest(manifest)
assert not errors, errors

print(json.dumps(asdict(manifest), indent=2, ensure_ascii=False))
print("compatibles:", ", ".join(compatible_providers(manifest, providers)))
print("tests_ok: contrato portable listo antes de elegir SDK")

Salida esperada, resumida:

compatibles: OpenAI Agents SDK, Claude Agent SDK, Google ADK
tests_ok: contrato portable listo antes de elegir SDK

Este ejercicio evita un error habitual: empezar con pip install y descubrir tarde que no tenemos contrato propio. Si el manifiesto está claro, llevarlo a OpenAI, Claude o Google ADK es trabajo de adaptador.

Cómo lo llevaría a cada SDK

La traducción conceptual sería:

Contrato propioOpenAI Agents SDKClaude Agent SDK/APIGoogle ADK
AgentSpecAgent(...)query(...) con configuración, subagente o loop propio.Agent(...) / LlmAgent(...).
ToolSpec@function_tool, hosted tool o MCP.tools en Messages API, MCP connector o Agent SDK tools.Function tools, built-ins, MCP toolbox.
SessionStoreSession implementation.Historial propio o Agent SDK runtime.SessionService.
MemoryStoreStore propio o sesión/custom.CLAUDE.md, memory tool o store externo.MemoryService.
TraceEventTracing nativo + export.Eventos SDK/logs propios.Evaluation/logging + observabilidad propia.
EvalDatasetEvals/trace grading o harness propio.Tests/evals propios.ADK evaluation.

El código de producción debería tener una carpeta parecida a esta:

agent_app/
  contracts/
    agent_spec.py
    tool_spec.py
    trace_event.py
  tools/
    revisar_rae.py
    revisar_apa.py
    verificar_fuente.py
  adapters/
    openai_agents.py
    claude_agent.py
    google_adk.py
  evals/
    revision_academica.jsonl
    rubric.yaml
  observability/
    trace_exporter.py
  config/
    agents.yaml

La carpeta contracts es el centro. Los adaptadores son reemplazables. Si un adaptador crece demasiado, probablemente estás metiendo lógica de dominio dentro del proveedor.

Lo que cada SDK esconde y lo que no deberías dejar que esconda

Un buen SDK te ahorra trabajo. También puede esconder decisiones importantes.

DecisiónPuede esconderla el SDKDebe quedar visible para ti
Cómo se forma el prompt finalSí.Context manifest o log de piezas incluidas.
Cuándo se llama una toolParcialmente.Tool call, argumentos, permiso y resultado.
Cómo se guarda sesiónSí.Qué entra, qué se compacta y cómo se borra.
Cómo se hace handoffSí.Qué historia recibe el especialista.
Cómo se trazan eventosSí.Export normalizado y retención.
Cómo se reintentaA veces.Política de idempotencia y límite.
Cómo se calcula costeA veces.Coste por tarea aceptada.
Cómo se evalúaA veces.Dataset, métrica y umbral de cambio.

La pregunta que me haría antes de desplegar:

Si mañana el SDK actual deja de funcionar, ¿qué piezas de mi sistema puedo conservar intactas?

Si la respuesta es “casi ninguna”, el SDK no está integrado: está gobernando el diseño.

Cómo encaja todo

flowchart TD
  subgraph F5C07["Capítulo 07 · SDKs de agentes"]
    SDK["SDK de agentes"]
    Adapter["Provider adapter"]
    Contract["Contratos internos"]
    ToolGateway["Tool gateway"]
    SessionStore["Session store"]
    Trace["Traza normalizada"]
    Eval["Eval de trayectoria"]
    MCP["MCP"]
    A2A["A2A"]
    AnthropicAnatomy["Anatomía Anthropic"]
  end

  subgraph Antes["Conceptos anteriores"]
    Tools["Tools y contratos (F5 C03)"]
    Memory["Memoria y handoff (F5 C04)"]
    Architectures["Arquitecturas de agentes (F5 C05)"]
    Harness["Harness y trazas (F5 C06)"]
  end

  subgraph Proveedores["Plataformas concretas"]
    OpenAI["OpenAI Agents SDK"]
    Claude["Claude Agent SDK/API"]
    Google["Google ADK"]
  end

  subgraph Despues["Continuidad"]
    Permisos["Permisos y supervisión (F5 C08)"]
    Routing["Routing, MCP y A2A (F5 C09)"]
    AgentEval["Evaluar agentes (F5 C10)"]
    Operar["Construir y operar (F6)"]
  end

  Tools -->|"define"| Contract
  Memory -->|"se implementa con"| SessionStore
  Architectures -->|"se ejecutan mediante"| SDK
  Harness -->|"exige"| Trace
  Contract -->|"se traduce por"| Adapter
  Adapter -->|"conecta con"| OpenAI
  Adapter -->|"conecta con"| Claude
  Adapter -->|"conecta con"| Google
  Claude -->|"se desgrana en"| AnthropicAnatomy
  SDK -->|"usa"| ToolGateway
  SDK -->|"mantiene"| SessionStore
  SDK -->|"emite"| Trace
  AnthropicAnatomy -->|"muestra"| ToolGateway
  AnthropicAnatomy -->|"mide"| Trace
  AnthropicAnatomy -->|"recupera"| SessionStore
  Eval -->|"compara"| OpenAI
  Eval -->|"compara"| Claude
  Eval -->|"compara"| Google
  MCP -->|"expone tools para"| SDK
  A2A -->|"coordina sistemas con"| SDK
  ToolGateway -->|"requiere"| Permisos
  MCP -->|"se amplía en"| Routing
  A2A -->|"se amplía en"| Routing
  Trace -->|"alimenta"| AgentEval
  Eval -->|"prepara"| Operar

  classDef chapter fill:#ffffff,stroke:#111111,color:#111111,stroke-width:1.4px;
  classDef external fill:#f7f7f7,stroke:#777777,color:#111111,stroke-width:1.1px,stroke-dasharray: 5 4;
  class SDK,Adapter,Contract,ToolGateway,SessionStore,Trace,Eval,MCP,A2A,AnthropicAnatomy chapter;
  class Tools,Memory,Architectures,Harness,OpenAI,Claude,Google,Permisos,Routing,AgentEval,Operar external;

Vocabulario aprendido

TérminoDefinición útil
SDKLibrería y convenciones para usar una plataforma desde código.
Runtime de agenteCapa que ejecuta el bucle entre modelo, tools, estado y resultado.
AdapterTraducción entre tu contrato interno y el formato de un proveedor.
Capability flagMarca que indica si un proveedor soporta una capacidad concreta.
Agent as toolPatrón donde un agente especializado aparece como tool de otro agente.
HandoffTransferencia de control a otro agente especialista.
Tool gatewayCapa que valida, autoriza, ejecuta y registra tools.
Session storeAlmacén de historial o estado conversacional.
Memory storeAlmacén de hechos, preferencias o recuerdos reutilizables.
Trace eventEvento observable de una ejecución agentic.
MCPProtocolo para exponer herramientas y contexto a agentes.
A2AProtocolo para comunicación entre sistemas agentic.
Vendor lock-inDependencia fuerte de una plataforma por acoplar contratos internos a ella.
Context manifestRecibo de qué piezas entraron al contexto de una llamada.
RunSpecContrato de ejecución: entrada, agente, proveedor, presupuesto, estado y salida esperada.
Idempotency keyIdentificador que permite repetir una operación sin duplicar su efecto.
Schema driftDesalineación entre el esquema que esperas y lo que el SDK, modelo o tool devuelve tras cambios.
Eval gatePrueba que decide si una versión puede avanzar porque cumple trayectoria, calidad y coste.
Eval de trayectoriaEvaluación que revisa pasos, tools, coste y resultado final.
Claude Agent SDKRuntime de Anthropic construido sobre el arnés de Claude Code para ejecutar agentes desde código.
query()Entrada simple al Agent SDK: manda una tarea y consume mensajes del agente.
Permission callbackFunción propia que decide si una tool concreta puede ejecutarse en un contexto concreto.
HookPunto de intervención antes o después de ciertos eventos del agente.
CheckpointPunto recuperable de una ejecución para continuar o depurar una run larga.

Dónde solía tropezar yo

TropiezoPor qué ocurreAntídoto
Empezar por el tutorialEl ejemplo mínimo parece suficiente.Escribir primero AgentSpec, ToolSpec y TraceEvent.
Meter lógica de negocio en el adapterEs rápido al principio.El adapter solo traduce; el dominio vive fuera.
Confundir sesión con memoriaEl SDK guarda historial y parece memoria.Separar SessionStore y MemoryStore.
No normalizar trazasCada proveedor registra distinto.Definir eventos comunes antes de comparar.
Usar handoff para todoSuena elegante delegar.Usar handoff solo si el especialista debe tomar el control.
Dejar tools demasiado ampliasEs cómodo exponer una función genérica.Tools pequeñas, efecto declarado y aprobación cuando toque.
Ignorar evaluación de trayectoriaSolo se mira la respuesta final.Medir steps, tools, coste, estado y salida.

Antes de pasar página

Antes de pasar al capítulo 08, deberías poder responder:

PreguntaSi dudas, vuelve a...
¿Qué diferencia hay entre API de modelo, SDK de cliente y SDK de agentes?La definición útil.
¿Qué piezas forman una integración de SDK completa?La anatomía formal de una integración.
¿Qué ofrece OpenAI Agents SDK que no es solo una llamada de modelo?OpenAI Agents SDK: runtime opinado para agentes.
¿Por qué Anthropic tiene dos niveles distintos: Messages API y Claude Agent SDK?Anthropic: Messages API, Claude Agent SDK y Claude Code.
¿Cómo se descompone una ejecución del SDK de Anthropic?Anatomía del SDK de Anthropic.
¿Qué diferencia hay entre permisos, hooks y tools permitidas?Permisos: la parte que no se debe improvisar.
¿Qué aporta Google ADK en sesiones, memoria y evaluación?Google ADK: framework de agentes, sesiones, memoria y evaluación.
¿Qué diferencia hay entre MCP y A2A?MCP y A2A: no son lo mismo que un SDK.
¿Cuándo usar API directa, SDK de agente, framework, MCP o A2A?Qué le faltaba al capítulo.
¿Qué debe llevar una run para ser depurable?Ingeniería de producción.
¿Por qué conviene diseñar contratos propios antes de elegir proveedor?Manos a la obra.
¿Qué debe quedar visible aunque el SDK lo automatice?Lo que cada SDK esconde y lo que no deberías dejar que esconda.

Para saber más

En resumen

IdeaQué te llevas
El SDK no es la arquitectura completa.El proveedor ejecuta una parte; tu producto debe controlar contratos, estado, tools, trazas y evaluación.
OpenAI, Anthropic y Google ADK resuelven capas distintas.OpenAI ofrece un runtime opinado, Anthropic combina API transparente y Agent SDK, Google ADK integra agentes, memoria y evaluación.
La portabilidad se diseña antes de instalar paquetes.AgentSpec, ToolSpec, TraceEvent y capability flags evitan que el SDK gobierne el dominio.
MCP y A2A son fronteras, no atajos conceptuales.MCP expone tools y contexto; A2A coordina sistemas agentic.
La integración buena se mide.Evalúa trayectoria, coste, tools, estado y salida, no solo que la demo responda.

Notas

  1. OpenAI. (2026). Agents SDK: Agents. https://openai.github.io/openai-agents-python/agents/. Consultado el 10 de junio de 2026.

  2. OpenAI. (2026). Agents SDK. https://developers.openai.com/api/docs/guides/agents. Consultado el 10 de junio de 2026.

  3. OpenAI. (2026). Agents SDK: Tracing. https://openai.github.io/openai-agents-python/tracing/. Consultado el 10 de junio de 2026.

  4. OpenAI. (2026). Agents SDK JS: Tools. https://openai.github.io/openai-agents-js/guides/tools. Consultado el 10 de junio de 2026.

  5. OpenAI. (2026). Agents SDK: Handoffs. https://openai.github.io/openai-agents-python/handoffs/. Consultado el 10 de junio de 2026.

  6. OpenAI. (2026). Agents SDK JS: Sessions. https://openai.github.io/openai-agents-js/guides/sessions/. Consultado el 10 de junio de 2026.

  7. OpenAI. (2026). Agents SDK JS: Model Context Protocol. https://openai.github.io/openai-agents-js/guides/mcp/. Consultado el 10 de junio de 2026.

  8. Anthropic. (2026). Using the Messages API. https://platform.claude.com/docs/en/build-with-claude/working-with-messages. Consultado el 10 de junio de 2026.

  9. Anthropic. (2026). Claude Agent SDK overview. https://code.claude.com/docs/en/agent-sdk/overview. Consultado el 10 de junio de 2026.

  10. Anthropic. (2026). Claude Agent SDK quickstart. https://code.claude.com/docs/en/agent-sdk/quickstart. Consultado el 10 de junio de 2026.

  11. Anthropic. (2026). How to implement tool use. https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/implement-tool-use. Consultado el 10 de junio de 2026.

  12. Anthropic. (2026). MCP connector. https://platform.claude.com/docs/en/agents-and-tools/mcp-connector. Consultado el 10 de junio de 2026.

  13. Anthropic. (2026). Using the Messages API. https://platform.claude.com/docs/en/build-with-claude/working-with-messages. Consultado el 10 de junio de 2026.

  14. Anthropic. (2026). Claude Agent SDK quickstart. https://code.claude.com/docs/en/agent-sdk/quickstart. Consultado el 10 de junio de 2026.

  15. Anthropic. (2026). Claude Agent SDK: Agent loop. https://code.claude.com/docs/en/agent-sdk/agent-loop. Consultado el 10 de junio de 2026.

  16. Anthropic. (2026). Claude Agent SDK: Permissions. https://code.claude.com/docs/en/agent-sdk/permissions. Consultado el 10 de junio de 2026.

  17. Anthropic. (2026). Claude Agent SDK: Hooks. https://code.claude.com/docs/en/agent-sdk/hooks. Consultado el 10 de junio de 2026.

  18. Anthropic. (2026). Streaming Messages. https://platform.claude.com/docs/en/build-with-claude/streaming. Consultado el 10 de junio de 2026.

  19. Anthropic. (2026). Claude Agent SDK: Checkpointing. https://code.claude.com/docs/en/agent-sdk/checkpointing. Consultado el 10 de junio de 2026.

  20. Anthropic. (2026). Claude Agent SDK: Cost tracking. https://code.claude.com/docs/en/agent-sdk/cost-tracking. Consultado el 10 de junio de 2026.

  21. Anthropic. (2026). Claude Agent SDK: Observability with OpenTelemetry. https://code.claude.com/docs/en/agent-sdk/observability. Consultado el 10 de junio de 2026.

  22. Google. (2026). Agent Development Kit: Agents. https://adk.dev/agents/. Consultado el 10 de junio de 2026.

  23. Google. (2026). Agent Development Kit: Technical overview. https://adk.dev/get-started/about/. Consultado el 10 de junio de 2026.

  24. Google. (2026). Agent Development Kit: Memory. https://adk.dev/sessions/memory/. Consultado el 10 de junio de 2026.

  25. Google. (2026). Agent Development Kit: Tools. https://adk.dev/tools/. Consultado el 10 de junio de 2026.

  26. Google. (2026). Agent Development Kit: Why Evaluate Agents. https://adk.dev/evaluate/. Consultado el 10 de junio de 2026.

  27. Google. (2026). ADK with Agent2Agent Protocol. https://adk.dev/a2a/. Consultado el 10 de junio de 2026.

  28. Model Context Protocol. (2026). Specification. https://modelcontextprotocol.io/specification. Consultado el 10 de junio de 2026.

  29. Agent2Agent Protocol. (2026). Specification. https://google-a2a.github.io/A2A/specification/. Consultado el 10 de junio de 2026.

  30. OpenAI. (2026). Agents SDK: Agents. https://openai.github.io/openai-agents-python/agents/. Consultado el 10 de junio de 2026.

  31. OpenAI. (2026). Agents SDK: Tracing. https://openai.github.io/openai-agents-python/tracing/. Consultado el 10 de junio de 2026.

  32. Anthropic. (2026). Using the Messages API. https://platform.claude.com/docs/en/build-with-claude/working-with-messages. Consultado el 10 de junio de 2026.

  33. Anthropic. (2026). Claude Agent SDK overview. https://code.claude.com/docs/en/agent-sdk/overview. Consultado el 10 de junio de 2026.

  34. Google. (2026). Agent Development Kit: Technical overview. https://adk.dev/get-started/about/. Consultado el 10 de junio de 2026.

  35. Google. (2026). Agent Development Kit: Why Evaluate Agents. https://adk.dev/evaluate/. Consultado el 10 de junio de 2026.

Capítulo 08

Facsímil 5 · Agentes y orquestación

Capítulo 08: Permisos, autonomía y supervisión humana

Autonomía no significa carta blanca

En el capítulo 01 dijimos que un agente no es “un prompt largo”, sino un sistema que puede observar, decidir y actuar. En el capítulo 03 aprendimos que una tool no es una función cualquiera: tiene contrato, permisos, errores y efectos. En el capítulo 06 pusimos harness alrededor del agente. Y en el capítulo 07 vimos cómo los SDKs exponen permisos, hooks, trazas y aprobaciones.

Ahora toca una pieza que suele decidir si un sistema agentic es publicable: quién puede hacer qué, cuándo, con qué evidencia y bajo qué revisión.

Un agente no debería vivir entre dos extremos: “no puede hacer nada” o “puede hacerlo todo”. La autonomía útil se diseña por capas. Puede leer sin preguntar, proponer cambios, preparar una acción, pedir aprobación antes de ejecutarla, ejecutar automáticamente acciones pequeñas dentro de un margen y detenerse cuando algo sale del contrato.

Qué no es supervisión humana

Supervisión humana no es poner un botón de “aceptar” al final de una pantalla. Si la persona no entiende qué va a ocurrir, qué datos se usaron, qué herramienta se ejecutará y cómo volver atrás si hace falta, no está supervisando: está firmando a ciegas.

Tampoco es revisar todas las acciones. Eso convierte el sistema en una cola lenta y enseña a la gente a pulsar “sí” sin leer. La revisión debe aparecer donde aporta juicio: acciones con efecto externo, coste alto, incertidumbre, impacto sobre otra persona, modificación persistente o falta de evidencia.

Y no es delegar responsabilidad al modelo. El modelo puede sugerir. La política decide. El harness ejecuta. La traza demuestra.

La definición útil

Para este libro, un sistema de permisos agentic es:

Una capa de decisión que evalúa cada acción propuesta por el agente y devuelve allow, approval_required o deny, dejando evidencia suficiente para explicar la decisión y reanudar la ejecución.

Ejemplo de fórmula. Podemos modelarlo así:

D(a,s,u,r,e){allow,approval,deny}D(a, s, u, r, e) \in \{\text{allow}, \text{approval}, \text{deny}\}
SímboloSignificadoEjemplo
DDDecisión de permiso.Permitir, pedir aprobación o denegar.
aaAcción propuesta.Enviar email, editar archivo, consultar CRM, crear ticket.
ssEstado de la ejecución.Paso actual, coste, intentos, evidencia acumulada.
uuUsuario o actor responsable.Alumno, profesor, operador, sistema nocturno.
rrRecurso afectado.Documento, base de datos, repositorio, cliente, factura.
eeEntorno.Desarrollo, preproducción, producción, demo, laboratorio.

La decisión no depende solo de la tool. Depende de la tool con argumentos concretos. No es lo mismo send_email(to="yo@example.com") que send_email(to="lista-completa@example.com"). No es lo mismo editar una propuesta local que publicar un cambio persistente. El permiso real vive en el cruce entre acción, recurso, usuario, entorno y momento.

Fecha de corte del estado del arte

Fecha de corte: 10 de junio de 2026.
Fuentes consultadas ese día: documentación oficial de OpenAI Agents SDK sobre human-in-the-loop, guardrails y ejecución; documentación oficial de Anthropic sobre permisos y hooks del Claude Agent SDK; documentación oficial de Google ADK sobre callbacks y controles en herramientas; documentación oficial de LangGraph sobre interrupts y persistencia; NIST AI Risk Management Framework como marco general de gobierno y gestión de riesgo.

Lo estable es el patrón: permisos explícitos, aprobación estructurada, pausa/reanudación, trazas, gates, límites de alcance y revisión humana donde aporta criterio. Lo cambiante son nombres de parámetros, APIs de SDK, modos de permiso, conectores y detalles de producto.

Niveles de autonomía

La autonomía se diseña mejor como una escala:

NivelNombreQué puede hacerEjemplo
A0ResponderSolo produce texto.Explicar una política interna.
A1LeerPuede consultar recursos permitidos.Buscar una referencia en una carpeta autorizada.
A2PrepararPuede crear una propuesta, no ejecutarla.Redactar email o diff sin enviarlo.
A3Ejecutar con aprobaciónPuede actuar tras revisión explícita.Publicar una nota, enviar email, modificar registro.
A4Ejecutar dentro de margenPuede actuar automáticamente si cumple umbrales.Clasificar tickets de baja criticidad.
A5Operar con guardiaPuede ejecutar flujos largos, pero con límites, alertas y gates.Monitorizar cola y escalar casos fuera de patrón.

La escala no se asigna al agente entero. Se asigna a cada acción.

AcciónNivel razonablePor qué
Leer documentación públicaA1No cambia estado.
Leer datos internosA1 con scopeRequiere identidad, recurso y motivo.
Redactar respuestaA2No hay efecto externo todavía.
Enviar respuesta a una personaA3 o A4Depende del canal, contenido y confianza.
Modificar una base de datosA3Persistente y difícil de corregir sin traza.
Cambiar configuración de producciónA3 con doble revisiónAlto impacto operacional.
Ejecutar una herramienta de coste altoA3Afecta presupuesto.

La regla práctica: la autonomía sube cuando el efecto es reversible, barato, acotado y bien evaluado; baja cuando el efecto es persistente, amplio, caro o incierto.

Matriz de permisos

Ejemplo de fórmula. Un permiso serio no es una lista de herramientas. Es una matriz:

P=(actor,action,resource,scope,env,budget,evidence,expiry)P = (actor, action, resource, scope, env, budget, evidence, expiry)
CampoPreguntaEjemplo
actor¿Quién responde por la acción?profesor, sistema_soporte, alumno_lab.
action¿Qué tipo de acción es?read, draft, write, send, delete, publish.
resource¿Sobre qué recurso?tickets, notas, repo, crm, email.
scope¿Con qué alcance?Solo curso actual, solo carpeta del proyecto, solo cliente asignado.
env¿En qué entorno?dev, staging, prod.
budget¿Con qué límite?3 tools, 0,20 EUR, 90 segundos, 2 ficheros.
evidence¿Qué debe demostrar antes?Cita encontrada, test pasando, diff visible.
expiry¿Cuánto dura?Esta run, 10 minutos, una sesión, una release.

Un ejemplo de permiso mal diseñado:

tool: send_email
permission: allowed

Un ejemplo más publicable:

actor: soporte_nivel_1
action: send
resource: email
scope:
  recipients: ["usuario_actual"]
  templates: ["respuesta_estado_matricula", "peticion_documentacion"]
env: prod
budget:
  max_messages: 1
  max_tokens: 900
evidence:
  required_fields: ["ticket_id", "user_id", "reason", "draft"]
decision:
  if_template_known_and_no_personal_claim: allow
  else: approval_required
expiry: run

La diferencia es enorme: el segundo permiso se puede auditar, probar y explicar.

Fórmula de riesgo operativo

Ejemplo de fórmula. No necesitamos una fórmula perfecta para tomar mejores decisiones. Necesitamos una que nos obligue a mirar variables correctas.

ρ(a)=wEE(a)+wRR(a)+wCC(a)+wUU(a)+wNN(a)wVV(a)\rho(a) = w_E E(a) + w_R R(a) + w_C C(a) + w_U U(a) + w_N N(a) - w_V V(a)
TérminoQué mideEjemplo
E(a)E(a)Efecto externo.Enviar, publicar, cobrar, modificar.
R(a)R(a)Reversibilidad.Se puede deshacer fácil o no.
C(a)C(a)Coste.Tokens, API externa, GPU, tiempo humano.
U(a)U(a)Incertidumbre.Falta evidencia o confianza.
N(a)N(a)Novedad.Acción poco probada o fuera de patrón.
V(a)V(a)Verificación disponible.Tests, schema, cita, diff, regla determinista.
wwPesos del dominio.No pesan igual en educación que en facturación.

Ejemplo de fórmula. Y decidimos con umbrales:

D(a)={allowρ(a)<θ1 approvalθ1ρ(a)<θ2 denyρ(a)θ2D(a)= \begin{cases} \text{allow} & \rho(a) < \theta_1 \ \text{approval} & \theta_1 \le \rho(a) < \theta_2 \ \text{deny} & \rho(a) \ge \theta_2 \end{cases}

Esto no sustituye al criterio. Lo documenta. Si una acción queda en approval, la persona no revisa “todo el agente”; revisa una acción concreta con evidencia concreta.

Lo que dicen los SDKs actuales

OpenAI Agents SDK documenta un flujo human-in-the-loop donde la ejecución puede pausar hasta que una persona aprueba o rechaza llamadas a tools; las interrupciones pueden aparecer en tools normales, MCP hospedado y agentes usados como tools.1 También distingue guardrails de entrada, salida y herramientas, y recomienda tool guardrails cuando hay managers, handoffs o especialistas delegados.2

Anthropic Claude Agent SDK permite controlar tools mediante modos de permiso, allow/deny lists, callbacks y hooks; además, sus hooks permiten intervenir antes o después de tool use, parada, notificaciones o subagentes.34

Google ADK coloca los callbacks como puntos de observación y control antes/después de agente, modelo y tools; los callbacks de tool permiten intervenir justo antes o después de que una herramienta se ejecute.5 Su documentación de controles en agentes insiste en diseñar herramientas defensivamente y usar callbacks como capas de validación y control.6

LangGraph usa interrupt() para pausar una ejecución, guardar estado con checkpointer y reanudar con Command; esto es importante porque la aprobación humana no debería perder el estado de la ejecución.7

La coincidencia entre ecosistemas es clara: las aprobaciones no son un modal decorativo. Son una primitiva de ejecución: pausar, mostrar evidencia, decidir, reanudar y dejar traza.

Anatomía visual de permisos y supervisión

Permisos: autonomía graduada por acción El modelo propone, la política decide, el harness ejecuta, la traza demuestra y la persona revisa donde aporta criterio. PROPUESTA DE ACCIÓN Agente observa estado propone acción no ejecuta aún Action envelope tool + argumentos recurso + entorno coste + reversibilidad todo serializable Policy engine actor scope budget evidence Decisión allow · approval · deny con motivo y trace_id nunca decisión muda RUTAS DE EJECUCIÓN ALLOW ejecuta tool dentro de scope registra resultado APPROVAL pausa ejecución muestra diff, coste, recurso persona decide approve edit DENY no ejecuta devuelve motivo propone alternativa Tool gateway valida schema aplica idempotencia ejecuta o simula sin saltarse política SUPERVISIÓN Y TRAZA Approval card qué cambia y por qué sin texto ambiguo Reviewer aprueba, edita o rechaza decisión responsable RunState pausa y reanuda estado persistente Trace log decisión, motivo, coste auditable Eval gate mide policy y UX mejora la matriz Recibo mínimo decision_id · actor · action · resource · scope · risk_score · evidence · reviewer · result · trace_id · expires_at IA para gente curiosa / Facsímil 05 / Capítulo 08 / 686f6c61

La figura separa propuesta, decisión, ejecución, revisión y traza. Esa separación es el corazón del capítulo. El agente no debería llamar una tool “porque sí”. Debe producir un sobre de acción. La política lo evalúa. Si la respuesta es approval, la ejecución se pausa y la persona recibe una tarjeta revisable. Después se reanuda con estado, no desde cero.

Qué debe llevar una tarjeta de aprobación

Una aprobación humana útil no pregunta “¿permitir?”. Pregunta algo revisable:

CampoPor qué importaEjemplo
AcciónLa persona debe saber qué se ejecutará.send_email, publish_page, update_record.
RecursoQué elemento se verá afectado.Ticket T-1042, archivo capitulo-08.md.
Cambio propuestoQué diferencia concreta habrá.Diff, email final, campos modificados.
MotivoPor qué el agente propone hacerlo.“Falta documentación solicitada”.
EvidenciaQué ha comprobado.URL, test, cita, consulta, fuente.
CosteCuánto consume continuar.Tokens, herramienta externa, tiempo.
ReversibilidadCómo se corrige si no era adecuado.Rollback, edición manual, nueva versión.
AlternativasQué pasa si se rechaza.Guardar propuesta, pedir más datos, escalar.
ExpiraciónCuándo deja de valer la decisión.Esta run, 10 minutos, versión actual.

Si la tarjeta no contiene evidencia, el revisor se convierte en oráculo. Y las personas no son oráculos: necesitan contexto, comparación y consecuencias.

Tarjeta de aprobación en Claude Agent SDK

En Anthropic, la tarjeta no debería generarla el modelo como texto libre. La tarjeta debería nacer en tu aplicación cuando el SDK llama a can_use_tool. La documentación actual explica que Claude pide entrada de usuario en dos casos: cuando necesita permiso para usar una tool y cuando llama a AskUserQuestion; ambos pasan por canUseTool / can_use_tool, y la ejecución queda pausada hasta que devuelves una respuesta.8

El orden técnico importa:

Claude pide tool
  -> hooks PreToolUse
  -> reglas deny
  -> permission_mode
  -> reglas allow
  -> can_use_tool
  -> allow / deny con mensaje

Por eso la tarjeta debe construirse con datos del runtime, no con una frase del modelo. Para un producto real, usaría esta forma:

{
  "approval_id": "appr_01J...",
  "provider": "anthropic",
  "sdk": "claude-agent-sdk",
  "session_id": "session_...",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "fasciculo-05-agentes-orquestacion/08-permisos-autonomia-supervision-humana.md"
  },
  "summary": "Claude quiere escribir cambios en el capítulo 08.",
  "why_review": "La tool modifica un archivo persistente.",
  "risk": {
    "effect": "write",
    "environment": "dev",
    "reversible": true,
    "score": 0.42
  },
  "evidence": [
    "Capítulo actual en revisión",
    "Cambio limitado al facsímil 05",
    "Build de Astro pendiente tras aprobar"
  ],
  "choices": [
    "approve_once",
    "approve_with_changes",
    "reject"
  ],
  "expires_at": "2026-06-10T10:45:00+02:00"
}

La UI visible podría mostrar algo así:

Campo en pantallaEjemplo
AcciónWrite sobre capítulo 08.
MotivoModifica un archivo persistente.
AlcanceSolo fasciculo-05-agentes-orquestacion/08...md.
EvidenciaEl cambio viene de una petición explícita del autor.
RiesgoEscritura reversible en entorno de desarrollo.
OpcionesAprobar una vez, aprobar con cambios, rechazar.

Y el código conceptual en Python quedaría así:

import asyncio
import time
from dataclasses import dataclass, asdict

from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query
from claude_agent_sdk.types import (
    HookMatcher,
    PermissionResultAllow,
    PermissionResultDeny,
    ToolPermissionContext,
)


@dataclass
class ApprovalCard:
    approval_id: str
    provider: str
    sdk: str
    tool_name: str
    tool_input: dict
    summary: str
    why_review: str
    risk_score: float
    choices: list[str]
    expires_in_seconds: int


def summarize_tool(tool_name: str, input_data: dict) -> tuple[str, str, float]:
    if tool_name in {"Write", "Edit"}:
        path = input_data.get("file_path", "archivo sin ruta")
        return (
            f"Claude quiere modificar {path}.",
            "La tool cambia un recurso persistente.",
            0.42,
        )

    if tool_name == "Bash":
        command = input_data.get("command", "")
        return (
            f"Claude quiere ejecutar: {command}",
            "La tool ejecuta un comando del sistema.",
            0.58,
        )

    return (
        f"Claude quiere usar {tool_name}.",
        "La tool no está autoaprobada por la política actual.",
        0.35,
    )


async def ask_approval_ui(card: ApprovalCard) -> dict:
    """
    En producción esto sería tu UI: web, app interna, Slack, consola,
    cola de revisión o sistema de tickets.
    """
    print("\n=== APPROVAL CARD ===")
    print(card.summary)
    print("Motivo:", card.why_review)
    print("Riesgo:", card.risk_score)
    print("Input:", card.tool_input)
    print("Opciones:", ", ".join(card.choices))

    # Simulación para el libro: una UI real devolvería también input editado.
    return {"decision": "reject", "message": "Revisar manualmente antes de ejecutar."}


async def can_use_tool(
    tool_name: str,
    input_data: dict,
    context: ToolPermissionContext,
) -> PermissionResultAllow | PermissionResultDeny:
    summary, why_review, risk = summarize_tool(tool_name, input_data)

    card = ApprovalCard(
        approval_id=f"appr-{int(time.time())}",
        provider="anthropic",
        sdk="claude-agent-sdk",
        tool_name=tool_name,
        tool_input=input_data,
        summary=summary,
        why_review=why_review,
        risk_score=risk,
        choices=["approve_once", "approve_with_changes", "reject"],
        expires_in_seconds=600,
    )

    decision = await ask_approval_ui(card)

    if decision["decision"] == "approve_once":
        return PermissionResultAllow(updated_input=input_data)

    if decision["decision"] == "approve_with_changes":
        updated_input = {**input_data, **decision.get("updated_input", {})}
        return PermissionResultAllow(updated_input=updated_input)

    return PermissionResultDeny(message=decision["message"])


async def keep_stream_open(_input_data, _tool_use_id, _context):
    return {"continue_": True}


async def prompt_stream():
    yield {
        "type": "user",
        "message": {
            "role": "user",
            "content": "Propón una mejora concreta del capítulo 08 y prepara el cambio.",
        },
    }


async def main():
    async for message in query(
        prompt=prompt_stream(),
        options=ClaudeAgentOptions(
            permission_mode="default",
            can_use_tool=can_use_tool,
            hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[keep_stream_open])]},
        ),
    ):
        if isinstance(message, ResultMessage):
            print(message.subtype, message.result)


asyncio.run(main())

Hay tres detalles finos:

DetallePor qué importa
permission_mode="default"Si todo cae en bypassPermissions, no hay tarjeta útil: el SDK aprobará demasiadas cosas.
can_use_toolEs el punto donde tu app puede construir la tarjeta y devolver PermissionResultAllow o PermissionResultDeny.
updated_inputPermite aprobar con cambios: por ejemplo, limitar ruta, cambiar comando o acotar destinatario.
Hook PreToolUseEn Python, la documentación indica que el flujo con can_use_tool requiere streaming y un hook que mantenga la sesión abierta.

Si la persona tarda demasiado, no intentaría mantener siempre vivo el proceso. Guardaría ApprovalCard, session_id, tool_name, input_data, trace_id y estado de la run en una tabla propia, y reanudaría desde sesión/checkpoint cuando llegue la decisión. Eso convierte la aprobación en arquitectura, no en un input("y/n").

ApprovalCard como entidad persistente

La tarjeta no es solo una vista. Es una entidad de dominio. Si no la persistes, no puedes auditar, reanudar ni explicar decisiones.

El ciclo de vida mínimo sería:

created
  -> pending
  -> approved | edited | rejected | expired | superseded
  -> resumed | closed
EstadoQué significaQué debe pasar
createdLa policy detectó que hace falta revisión.Crear registro con tool, input, sesión y trace id.
pendingLa tarjeta espera decisión.Mostrar UI, bloquear ejecución o devolver defer.
approvedLa persona permite la acción original.Revalidar expiración y ejecutar input original.
editedLa persona modifica argumentos.Validar schema, scope y riesgo antes de ejecutar.
rejectedLa persona no permite la acción.Devolver PermissionResultDeny con motivo útil.
expiredLa tarjeta caducó.No ejecutar; pedir nueva decisión si sigue haciendo falta.
supersededOtra tarjeta reemplaza esta.Cerrar sin ejecutar para evitar decisiones antiguas.
resumedLa run continúa con decisión aplicada.Registrar resultado de tool y estado final.
closedYa no queda nada pendiente.Conservar recibo y métricas.

Una tabla mínima podría ser:

CampoTipoPara qué sirve
approval_idstringIdentidad estable de la tarjeta.
providerstringanthropic, openai, google, local.
session_idstringReanudar o correlacionar ejecución.
trace_idstringUnir con logs y spans.
tool_namestringTool solicitada por el agente.
tool_input_originalJSONInput que pidió Claude.
tool_input_effectiveJSONInput final tras posible edición.
statusenumpending, approved, edited, rejected, etc.
reviewer_idstringQuién tomó la decisión.
decision_reasonstringMotivo visible para auditoría.
risk_scorenumberScore calculado en ese momento.
expires_attimestampEvita ejecutar decisiones viejas.
created_at / decided_attimestampLatencia de revisión.

En SQL simplificado:

CREATE TABLE approval_cards (
  approval_id TEXT PRIMARY KEY,
  provider TEXT NOT NULL,
  session_id TEXT NOT NULL,
  trace_id TEXT NOT NULL,
  tool_name TEXT NOT NULL,
  tool_input_original JSON NOT NULL,
  tool_input_effective JSON,
  status TEXT NOT NULL,
  reviewer_id TEXT,
  decision_reason TEXT,
  risk_score REAL NOT NULL,
  expires_at TEXT NOT NULL,
  created_at TEXT NOT NULL,
  decided_at TEXT
);

La regla importante: si status no está en approved o edited, no se ejecuta la tool. Y si está en edited, se ejecuta tool_input_effective, no el input original.

Aprobar con cambios

PermissionResultAllow(updated_input=...) es una pieza muy potente. Permite que la persona diga “sí, pero con este alcance”. Por ejemplo:

ToolInput originalCambio humanoValidación obligatoria
Bashpytest && npm run buildEjecutar solo npm run build.Comando permitido, cwd permitido, timeout.
WriteRuta amplia.Limitar a un archivo concreto.Ruta dentro de workspace permitido.
EditReemplazo grande.Reducir diff.Diff no toca secciones no aprobadas.
MCP toolQuery sin límite.Añadir limit=20.Schema y coste estimado.
AskUserQuestionOpciones del modelo.Respuesta libre.Mapear respuesta a input aceptado.

El flujo correcto no es:

persona edita -> ejecutar

Es:

input original
  -> edición humana
  -> validar schema
  -> validar scope
  -> recalcular riesgo si cambia el efecto
  -> ejecutar
  -> trazar input original e input efectivo

En pseudocódigo:

def apply_human_edit(original_input: dict, patch: dict, tool_schema: dict, scope: dict) -> dict:
    effective_input = {**original_input, **patch}
    validate_schema(effective_input, tool_schema)
    validate_scope(effective_input, scope)
    return effective_input

Así, la aprobación humana no abre una puerta lateral. Sigue pasando por contrato.

Variantes de tarjeta según tool

No todas las tools deben mostrar lo mismo:

ToolQué debe mostrar la tarjetaQué decisión tiene sentido
ReadRuta, límite, motivo, datos sensibles esperados.Permitir una vez, limitar ruta, rechazar.
Grep / búsquedaPatrón, carpeta, límite de resultados.Limitar scope o permitir.
WriteRuta, contenido nuevo, si crea o sobrescribe.Aprobar, editar contenido, rechazar.
EditDiff exacto, líneas tocadas, resumen de cambio.Aprobar diff, editar diff, rechazar.
BashComando, cwd, timeout, variables, efecto esperado.Ejecutar, cambiar comando, rechazar.
MCP toolServidor, tool remota, argumentos, coste y datos enviados.Permitir, reducir payload, rechazar.
AskUserQuestionPreguntas, opciones y respuesta esperada.Responder, escribir opción propia, cancelar.
SubagenteTarea, contexto entregado, tools disponibles.Delegar, acotar contexto, rechazar.

Si la tarjeta para Bash no muestra el comando completo, está incompleta. Si la tarjeta para Edit no muestra diff, está incompleta. Si la tarjeta MCP no muestra qué datos salen hacia el servidor, está incompleta.

UI sobria de una ApprovalCard

ApprovalCard · Claude Agent SDK Una decisión revisable: tool, input, evidencia, riesgo, alcance, expiración y opciones. PENDING APPROVAL Claude quiere usar una tool Tool: Write · SDK: claude-agent-sdk · Provider: anthropic approval_id: appr_01J... · session_id: session_... · trace_id: trace_... Acción Modificar archivo del capítulo 08. Ruta permitida: fasciculo-05-agentes-orquestacion/08... Motivo y evidencia La tool cambia un recurso persistente. Evidencia: petición explícita del autor. Build de Astro requerido tras aprobar. Riesgo y expiración effect: write · env: dev · reversible: sí risk_score: 0.42 Expira en 10 minutos. Input solicitado { "file_path": "fasciculo-05.../08-permisos...", "content_delta": "Añadir ApprovalCard persistente" } Si apruebas con cambios 1. Edita el input efectivo. 2. Valida schema y scope. 3. Ejecuta solo el input efectivo. Se guardan input original e input efectivo. Approve once Approve with changes Reject Nunca ejecutar sin registrar reviewer_id y decision_reason. IA para gente curiosa / Facsímil 05 / Capítulo 08 / 686f6c61

Esta figura no intenta ser un componente UI final. Es una especificación visual: si el producto no muestra al menos estas piezas, la persona no está decidiendo con suficiente contexto.

Patrones de revisión

No toda supervisión tiene el mismo diseño.

PatrónCómo funcionaCuándo usarlo
Antes de toolPausa justo antes de ejecutar una herramienta.Enviar, publicar, editar, coste alto.
Después de toolEjecuta lectura y revisa resultado antes de actuar.RAG, búsqueda, análisis de documentos.
Revisión de diffLa persona revisa cambio exacto.Código, documentos, configuraciones.
Revisión por muestreoSolo algunas ejecuciones se revisan.Acciones pequeñas con bajo impacto.
Doble revisiónDos personas o dos roles aprueban.Cambios de alto impacto.
Modo solo propuestaEl agente nunca ejecuta; solo prepara.Aprendizaje, auditoría, entornos nuevos.
Break-glassExcepción temporal y trazada.Incidencia o bloqueo operativo real.

Un error clásico es usar el mismo patrón para todo. Una tool de lectura no necesita la misma fricción que una tool de escritura. Una acción reversible no necesita el mismo proceso que una irreversible. Un entorno de laboratorio no necesita lo mismo que producción.

Permisos en tools, no solo en prompts

Las instrucciones importan, pero no son frontera suficiente. La frontera fuerte vive en código, configuración y tool gateway.

CapaQué puede hacerQué no debería hacer sola
PromptExplicar intención, estilo y normas.Decidir permisos finales.
Tool schemaAcotar campos, tipos y valores.Entender contexto completo.
Tool gatewayValidar, autorizar, ejecutar y registrar.Inventar reglas sin política versionada.
Policy engineEvaluar actor, recurso, scope y evidencia.Ejecutar herramientas directamente.
UI de aprobaciónPresentar acción y recoger decisión.Ocultar argumentos o consecuencias.
TrazasDemostrar qué ocurrió.Corregir por sí solas una mala política.

La policy no debe vivir como párrafo escondido en un prompt. Debe poder probarse con casos.

Manos a la obra

Práctica: motor de permisos con cola de revisión.

Kit ejecutable de este capítulo: kit descargable.

# Descomprime el ZIP del capítulo y ejecuta estos comandos dentro de esa carpeta
python3 ops/run_f5_practices.py --chapter c08 --write --fail-on-invalid

Vamos a construir una práctica que sí sirve: un motor pequeño que decide allow, approval_required o deny, genera tarjetas de aprobación y deja una traza. No llama a ningún proveedor. Eso es intencionado: antes de integrar OpenAI, Claude o Google ADK, tenemos que saber qué queremos permitir.

from __future__ import annotations

from dataclasses import dataclass, asdict
from typing import Literal
import json
import time


Decision = Literal["allow", "approval_required", "deny"]
Effect = Literal["read", "draft", "write", "send", "publish"]
Environment = Literal["lab", "dev", "staging", "prod"]


@dataclass(frozen=True)
class ActionEnvelope:
    actor: str
    action: str
    effect: Effect
    resource: str
    environment: Environment
    reversible: bool
    cost_eur: float
    confidence: float
    evidence_count: int
    scope: dict
    payload_preview: str


@dataclass(frozen=True)
class PermissionDecision:
    decision: Decision
    reason: str
    risk_score: float
    approval_card: dict | None


def risk_score(action: ActionEnvelope) -> float:
    external_effect = {
        "read": 0.05,
        "draft": 0.15,
        "write": 0.55,
        "send": 0.70,
        "publish": 0.80,
    }[action.effect]

    environment_weight = {
        "lab": 0.05,
        "dev": 0.15,
        "staging": 0.35,
        "prod": 0.70,
    }[action.environment]

    reversibility = 0.05 if action.reversible else 0.45
    uncertainty = max(0.0, 1.0 - action.confidence)
    missing_evidence = 0.25 if action.evidence_count == 0 else 0.10 if action.evidence_count == 1 else 0.0
    cost = min(action.cost_eur / 2.0, 0.40)

    return round(
        0.30 * external_effect
        + 0.20 * environment_weight
        + 0.20 * reversibility
        + 0.15 * uncertainty
        + 0.10 * missing_evidence
        + 0.05 * cost,
        3,
    )


def build_approval_card(action: ActionEnvelope, score: float, reason: str) -> dict:
    return {
        "approval_id": f"appr-{int(time.time())}",
        "actor": action.actor,
        "action": action.action,
        "resource": action.resource,
        "environment": action.environment,
        "risk_score": score,
        "reason": reason,
        "payload_preview": action.payload_preview,
        "scope": action.scope,
        "choices": ["approve", "edit", "reject"],
        "expires_in_seconds": 600,
    }


def decide(action: ActionEnvelope) -> PermissionDecision:
    score = risk_score(action)

    if action.effect in {"send", "publish"} and action.evidence_count == 0:
        reason = "falta evidencia antes de una acción externa"
        return PermissionDecision("deny", reason, score, None)

    if action.environment == "prod" and action.effect in {"write", "send", "publish"}:
        reason = "acción persistente en producción"
        return PermissionDecision(
            "approval_required",
            reason,
            score,
            build_approval_card(action, score, reason),
        )

    if score < 0.25:
        return PermissionDecision("allow", "riesgo operativo bajo", score, None)

    if score < 0.62:
        reason = "requiere revisión por coste, incertidumbre o alcance"
        return PermissionDecision(
            "approval_required",
            reason,
            score,
            build_approval_card(action, score, reason),
        )

    return PermissionDecision("deny", "fuera del margen permitido", score, None)


def trace_event(name: str, action: ActionEnvelope, decision: PermissionDecision) -> dict:
    return {
        "event": name,
        "actor": action.actor,
        "action": action.action,
        "resource": action.resource,
        "decision": decision.decision,
        "reason": decision.reason,
        "risk_score": decision.risk_score,
        "ts": int(time.time()),
    }


cases = [
    ActionEnvelope(
        actor="alumno_lab",
        action="buscar_referencia",
        effect="read",
        resource="biblioteca_publica",
        environment="lab",
        reversible=True,
        cost_eur=0.01,
        confidence=0.90,
        evidence_count=1,
        scope={"domains": ["arxiv.org", "docs.python.org"]},
        payload_preview="Buscar paper citado en el capítulo.",
    ),
    ActionEnvelope(
        actor="editor",
        action="publicar_capitulo",
        effect="publish",
        resource="libro/fasciculo-05",
        environment="prod",
        reversible=True,
        cost_eur=0.04,
        confidence=0.82,
        evidence_count=3,
        scope={"path": "/fasciculo-05"},
        payload_preview="Publicar capítulo 08 revisado.",
    ),
    ActionEnvelope(
        actor="soporte",
        action="enviar_email",
        effect="send",
        resource="email_usuario",
        environment="prod",
        reversible=False,
        cost_eur=0.02,
        confidence=0.51,
        evidence_count=0,
        scope={"recipient": "usuario_actual"},
        payload_preview="Enviar respuesta sin fuente comprobada.",
    ),
]

for action in cases:
    decision = decide(action)
    print(json.dumps(trace_event("permission.decision", action, decision), ensure_ascii=False))
    if decision.approval_card:
        print(json.dumps({"approval_card": decision.approval_card}, indent=2, ensure_ascii=False))

Salida esperada, resumida:

{"event": "permission.decision", "action": "buscar_referencia", "decision": "allow", ...}
{"event": "permission.decision", "action": "publicar_capitulo", "decision": "approval_required", ...}
{"approval_card": {"choices": ["approve", "edit", "reject"], ...}}
{"event": "permission.decision", "action": "enviar_email", "decision": "deny", ...}

Lo importante no es el número exacto de risk_score; es que la decisión queda separada del modelo, versionable y comprobable. El modelo puede proponer publicar_capitulo, pero la política exige aprobación porque es producción. El modelo puede proponer enviar_email, pero se deniega si falta evidencia.

Cómo lo llevaría a OpenAI, Claude, Google ADK y LangGraph

Nuestro contratoOpenAI Agents SDKClaude Agent SDKGoogle ADKLangGraph
ActionEnvelopeParámetros de tool + run context.Tool input + context del SDK.Tool input + InvocationContext.Estado del grafo + tool args.
decide()needs_approval o callback de aprobación.Permission callback o PreToolUse hook.before_tool_callback.interrupt() antes de tool.
approval_cardInterruption pendiente en RunState.Mensaje propio en UI o flujo de permisos.Resultado override o pausa propia.Payload de interrupt.
ReanudaciónAprobar/rechazar y continuar con estado.Continuar sesión o cliente.Continuar runner/estado propio.Command(resume=...).
TrazaTracing del SDK + evento propio.Mensajes, hooks, OTel.Callbacks + logging.Checkpointer + eventos del grafo.

La arquitectura portable no consiste en que todos los SDKs tengan el mismo nombre para cada cosa. Consiste en que nuestro dominio sí lo tenga: ActionEnvelope, PermissionDecision, ApprovalCard, RunState y TraceEvent.

Diseño de UX para revisión humana

La interfaz de aprobación debe reducir carga mental, no añadir teatro.

Elemento visibleBuena prácticaMala señal
Resumen de acciónUna frase concreta y verificable.“El agente quiere continuar”.
Diff o payloadMostrar el cambio exacto.Ocultar argumentos técnicos.
EvidenciaEnlace, cita, test o consulta usada.“Confía en mí”.
BotonesAprobar, editar, rechazar.Solo aceptar/cancelar.
Coste y alcanceMostrar entorno, recurso y expiración.Permiso indefinido.
Motivo de pausaExplicar por qué pide revisión.Pausas sin razón.
Resultado tras decidirConfirmar qué pasó.La pantalla desaparece sin traza.

La persona no debería tener que leer toda la conversación. Debería revisar una unidad mínima: acción, evidencia, consecuencia y alternativa.

Políticas que se prueban

Si una política de permisos no tiene tests, acabará siendo una colección de intuiciones.

TestQué comprueba
Acción de lectura en laboratorioDebe permitir.
Escritura en producciónDebe pedir aprobación.
Envío sin evidenciaDebe denegar.
Tool de coste altoDebe pedir aprobación o frenar por presupuesto.
Reintento de acción persistenteDebe exigir idempotencia.
Permiso expiradoDebe volver a pedir decisión.
Usuario sin scopeDebe denegar aunque el modelo insista.
Payload editado por reviewerDebe revalidarse antes de ejecutar.

El último punto es fácil de olvidar: si la persona edita el payload, no se ejecuta automáticamente. Se vuelve a validar. La supervisión humana no sustituye al contrato; lo completa.

Cómo encaja todo

flowchart TD
  subgraph F5C08["Capítulo 08 · Permisos y supervisión"]
    Action["ActionEnvelope"]
    Policy["Policy engine"]
    Decision["allow / approval / deny"]
    Approval["ApprovalCard"]
    Reviewer["Reviewer"]
    Gateway["Tool gateway"]
    Trace["TraceEvent"]
    Eval["Policy eval"]
  end

  subgraph Antes["Conceptos anteriores"]
    AgentState["Estado y acción (F5 C02)"]
    ToolContract["Contrato de tool (F5 C03)"]
    Harness["Harness (F5 C06)"]
    SDK["SDKs y adapters (F5 C07)"]
  end

  subgraph Despues["Continuidad"]
    Routing["Routing y MCP/A2A (F5 C09)"]
    AgentEval["Evaluar agentes (F5 C10)"]
    Operating["Operar sistemas (F6)"]
  end

  AgentState -->|"propone"| Action
  ToolContract -->|"define schema"| Action
  Harness -->|"exige"| Policy
  SDK -->|"ofrece hooks"| Policy
  Action --> Policy
  Policy --> Decision
  Decision -->|"allow"| Gateway
  Decision -->|"approval"| Approval
  Decision -->|"deny"| Trace
  Approval --> Reviewer
  Reviewer -->|"approve/edit/reject"| Gateway
  Gateway --> Trace
  Trace --> Eval
  Eval --> Policy
  Gateway --> Routing
  Trace --> AgentEval
  Eval --> Operating

  classDef chapter fill:#ffffff,stroke:#111111,color:#111111,stroke-width:1.4px;
  classDef external fill:#f7f7f7,stroke:#777777,color:#111111,stroke-width:1.1px,stroke-dasharray: 5 4;
  class Action,Policy,Decision,Approval,Reviewer,Gateway,Trace,Eval chapter;
  class AgentState,ToolContract,Harness,SDK,Routing,AgentEval,Operating external;

Vocabulario aprendido

TérminoDefinición útil
Autonomía graduadaCapacidad de actuar por niveles, según acción, recurso, entorno y evidencia.
Policy engineComponente que decide si una acción se permite, se revisa o se rechaza.
ActionEnvelopeSobre estructurado que describe acción, recurso, argumentos, coste y alcance.
ApprovalCardTarjeta revisable que muestra acción pendiente, evidencia y opciones.
Input efectivoArgumentos finales que realmente ejecuta la tool tras una posible edición humana.
Human-in-the-loopPausa de ejecución para que una persona decida o edite.
ScopeAlcance exacto donde un permiso vale.
ExpiryCaducidad de un permiso o aprobación.
ReviewerPersona o rol que toma la decisión y deja motivo trazable.
Tool gatewayCapa que valida y ejecuta tools después de la decisión de permiso.
Trace idIdentificador que permite unir tarjeta, tool, logs y resultado.
Break-glassExcepción temporal, limitada y trazada.
ReanudaciónContinuar una ejecución pausada sin perder estado.

Dónde solía tropezar yo

TropiezoPor qué ocurreAntídoto
Pensar en permisos por toolParece natural: tool permitida o no.Decidir por tool, argumentos, recurso, entorno y usuario.
Pedir aprobación para todoDa sensación de control.Revisar solo donde hay efecto, coste o incertidumbre.
Mostrar tarjetas pobresLa UI se diseña tarde.Incluir acción, evidencia, diff, coste, alcance y alternativa.
No persistir la tarjetaParece suficiente mantener el proceso esperando.Guardar estado, expiración, input original, input efectivo y trace id.
Aprobar con cambios sin validarLa edición humana da falsa sensación de seguridad.Revalidar schema, scope y riesgo antes de ejecutar.
Confundir aprobación con ejecuciónUna persona aprueba y ya se lanza todo.Revalidar después de editar o aprobar.
Guardar solo texto de conversaciónParece suficiente para depurar.Guardar decision_id, motivo, score, reviewer y trace id.
Usar prompt como fronteraEs rápido.Mover permisos a policy engine y tool gateway.
No probar la políticaSe confía en intuiciones.Crear datasets de decisiones esperadas.

Antes de pasar página

Antes del capítulo 09, deberías poder responder:

PreguntaSi dudas, vuelve a...
¿Por qué la autonomía debe asignarse por acción y no por agente entero?Niveles de autonomía.
¿Qué datos necesita una decisión de permiso?Matriz de permisos.
¿Cómo se calcula de forma aproximada el riesgo operativo?Fórmula de riesgo operativo.
¿Qué debe llevar una tarjeta de aprobación para no ser teatro?Qué debe llevar una tarjeta de aprobación.
¿Cómo sería esa tarjeta en Claude Agent SDK?Tarjeta de aprobación en Claude Agent SDK.
¿Qué estados necesita una ApprovalCard persistente?ApprovalCard como entidad persistente.
¿Qué cambia cuando apruebas con cambios?Aprobar con cambios.
¿Por qué una tarjeta de Bash no debe parecerse a una de Read?Variantes de tarjeta según tool.
¿Cómo se vería una tarjeta mínima y revisable?UI sobria de una ApprovalCard.
¿Dónde viven los permisos: prompt, tool, gateway o policy?Permisos en tools, no solo en prompts.
¿Cómo se implementa una cola de revisión mínima?Manos a la obra.
¿Cómo se traduce el patrón a OpenAI, Claude, Google ADK o LangGraph?Cómo lo llevaría a OpenAI, Claude, Google ADK y LangGraph.

Para saber más

En resumen

IdeaQué te llevas
La autonomía se gradúa por acción.Leer, redactar, enviar, publicar o modificar no tienen el mismo permiso.
La aprobación humana debe ser estructurada.Una persona necesita acción, recurso, evidencia, coste, alcance y alternativa.
El prompt no es frontera suficiente.Los permisos viven en policy engine, tool gateway, callbacks, hooks y trazas.
Pausar y reanudar es parte de la arquitectura.HITL no es un modal: es estado persistente, decisión y continuación.
Las políticas se prueban.Un agente publicable necesita datasets de decisiones, no solo buenas intenciones.

Notas

  1. OpenAI. (2026). Human-in-the-loop. https://openai.github.io/openai-agents-python/human_in_the_loop/. Consultado el 10 de junio de 2026.

  2. OpenAI. (2026). Guardrails. https://openai.github.io/openai-agents-python/guardrails/. Consultado el 10 de junio de 2026.

  3. Anthropic. (2026). Claude Agent SDK: Permissions. https://code.claude.com/docs/en/agent-sdk/permissions. Consultado el 10 de junio de 2026.

  4. Anthropic. (2026). Claude Agent SDK: Hooks. https://code.claude.com/docs/en/agent-sdk/hooks. Consultado el 10 de junio de 2026.

  5. Google. (2026). Callbacks: Observe, Customize, and Control Agent Behavior. https://adk.dev/callbacks/. Consultado el 10 de junio de 2026.

  6. Google. (2026). Safety and Security for AI Agents. https://adk.dev/safety/. Consultado el 10 de junio de 2026.

  7. LangChain. (2026). LangGraph interrupts. https://docs.langchain.com/oss/python/langgraph/human-in-the-loop. Consultado el 10 de junio de 2026.

  8. Anthropic. (2026). Claude Agent SDK: Handle approvals and user input. https://code.claude.com/docs/en/agent-sdk/user-input. Consultado el 10 de junio de 2026.

Capítulo 09

Facsímil 5 · Agentes y orquestación

Capítulo 09: Orquestación: routing, MCP, A2A y ADKs

Cuando un agente deja de estar solo

Hasta ahora hemos construido piezas: qué es un agente, cómo usa tools, cómo conserva contexto, qué arquitecturas existen, qué SDKs hay y cómo se revisan acciones con impacto. Pero un sistema real rara vez vive con un solo agente y una sola herramienta.

En el capítulo 03 definimos tools con contrato. En el capítulo 04 vimos contexto, memoria y handoff. En el capítulo 05 separamos arquitecturas de agentes. En el capítulo 07 entramos en SDKs y ADKs. Y en el capítulo 08 pusimos permisos alrededor de todo eso.

En cuanto aparece una aplicación seria, llegan preguntas nuevas: ¿uso una tool local o un servidor MCP?, ¿delego a otro agente?, ¿hago routing por coste, por latencia, por especialidad o por permisos?, ¿qué pasa si el primer proveedor falla?, ¿cómo sé qué agente sabe hacer qué?, ¿cómo evito que la lista de herramientas llene el contexto?, ¿dónde guardo la traza para comparar rutas?

Este capítulo va de esa capa: orquestar. Orquestar no es poner más agentes. Es decidir, con criterio verificable, qué pieza ejecuta cada parte del trabajo.

Qué no es orquestar

Orquestar no es encadenar diez llamadas al modelo y esperar que el resultado parezca inteligente. Eso suele producir latencia, coste y poca trazabilidad.

Tampoco es esconder todas las decisiones dentro de un prompt del tipo “elige la mejor herramienta”. A veces el modelo debe elegir. Otras veces la decisión debe ser una regla de negocio, un routing por permisos, un gate de coste, un clasificador pequeño o una tabla de capacidades mantenida por ingeniería.

Y no es confundir protocolos. MCP no convierte una herramienta en un agente completo. A2A no sustituye un contrato de tool. Un ADK no elimina la necesidad de decidir qué estado guardas, qué permiso aplicas y qué métrica vas a mirar después.

La definición útil

Para este libro, orquestación agentic es:

La capa que convierte una intención en un plan de ejecución trazable: selecciona ruta, herramienta, agente, protocolo, modelo, permisos y estrategia de reintento antes de ejecutar.

Ejemplo de fórmula. Podemos escribirlo así:

o(q,s,C,P,B)ruta,contrato,permisos,trazao(q, s, C, P, B) \rightarrow \langle ruta, contrato, permisos, traza \rangle
SímboloSignificadoEjemplo
ooFunción de orquestación.El componente que decide si usar una tool local, MCP, A2A o una cola humana.
qqPetición actual.“Revisa esta cita y actualiza la bibliografía”.
ssEstado de la ejecución.Usuario, sesión, historial, pasos previos, errores, coste acumulado.
CCCatálogo de capacidades.Qué sabe hacer cada tool, agente o servicio.
PPPolítica de permisos.Qué rutas puede usar este usuario en este entorno.
BBPresupuesto.Latencia máxima, coste máximo, tokens, número de tools, retries.
ruta,contrato,permisos,traza\langle ruta, contrato, permisos, traza \rangleSalida de orquestación.“usar mcp_biblioteca.search_paper, con aprobación si escribe, registrar trace_id”.

La definición parece formal, pero la idea es sencilla: antes de ejecutar, el sistema debe saber por qué esa ruta y no otra.

Fecha de corte del estado del arte

Fecha de corte: 10 de junio de 2026.
Fuentes consultadas ese día: especificación pública de MCP, documentación de OpenAI Agents SDK sobre MCP y handoffs, documentación de Anthropic sobre MCP connector, documentación de Google ADK sobre MCP tools, A2A, routing de agentes, routing de modelos y workflow agents, especificación A2A, y referencias clásicas de sistemas multiagente.

Lo estable es el patrón: separar capacidades, contratos, permisos, routing, ejecución y trazas. Lo cambiante son nombres de clases, transportes soportados, versiones beta, conectores hospedados, compatibilidad por proveedor y gobernanza de protocolos.

De dónde viene esta idea

Los agentes no nacieron con los LLMs. Wooldridge y Jennings ya definían agentes por propiedades como autonomía, reactividad, proactividad y habilidad social: un agente no solo calcula; actúa en un entorno y se coordina con otros.1

Jennings, Sycara y Wooldridge describieron el campo multiagente como una forma de repartir control, conocimiento y capacidad de acción entre entidades que cooperan para resolver tareas.2 Y mucho antes de hablar de LLMs, Smith propuso Contract Net Protocol: un mecanismo donde un coordinador anuncia tareas, otros componentes proponen cómo resolverlas y se asigna el trabajo según criterios.3

No copiamos esos protocolos clásicos sin más. Pero nos sirven para una idea importante: la delegación necesita contrato. Si alguien va a recibir una tarea, debe declarar qué sabe hacer, qué necesita, qué devuelve, cuánto tarda, cuánto cuesta y cómo falla.

Las tripas de la orquestación

Una arquitectura de orquestación publicable suele tener estas piezas:

PiezaQué decideQué debería registrar
RouterQué ruta intenta primero.Señales usadas, alternativas descartadas y motivo.
Capability registryQué capacidades existen.Versión, dueño, coste, latencia, permisos, contrato.
Tool gatewayCómo se ejecutan tools.Input validado, output, errores, duración, efecto.
MCP clientCómo se conectan servidores MCP.Servidor, tools listadas, auth, tool llamada, resultado.
A2A clientCómo se invocan agentes externos.AgentCard, Task, mensajes, artefactos, estado.
Policy engineQué se permite o revisa.Decisión, scope, persona revisora si aplica.
Run stateQué está pasando ahora.Paso, ruta activa, retries, presupuesto restante.
Trace storeQué ocurrió realmente.Eventos, spans, costes, latencias, decisiones.
Eval harnessQué ruta fue mejor.Tasa de acierto, coste, latencia, reintentos, calidad.

El router no debería ser un oráculo. Puede ser una función pequeña, una regla, un clasificador, un modelo barato, un grafo o una mezcla. Lo importante es que sus decisiones se puedan revisar.

Fórmula práctica para elegir ruta

Ejemplo de fórmula. Una forma útil de pensar el routing es puntuar candidatos. No para convertirlo todo en una matemática falsa, sino para obligarnos a nombrar señales.

Ri=αSi+βQi+γAiδLiϵCiζKiR_i = \alpha S_i + \beta Q_i + \gamma A_i - \delta L_i - \epsilon C_i - \zeta K_i
SímboloSignificadoEjemplo
RiR_iPuntuación de la ruta ii.Ruta mcp_biblioteca obtiene 0,74.
SiS_iEncaje semántico con la petición.La ruta sabe buscar referencias: 0,90.
QiQ_iCalidad esperada o histórica.En evaluaciones acertó 84%: 0,84.
AiA_iDisponibilidad actual.Servicio sano: 1,00.
LiL_iLatencia normalizada.1,8 s sobre máximo 5 s: 0,36.
CiC_iCoste normalizado.0,03 euros sobre máximo 0,10: 0,30.
KiK_iRiesgo operativo normalizado.Escritura externa sin revisión: 0,70.
α,β,γ,δ,ϵ,ζ\alpha,\beta,\gamma,\delta,\epsilon,\zetaPesos de decisión.Dar más peso a calidad que a coste en tareas críticas.

Ejemplo numérico:

SeñalRuta localRuta MCPRuta A2A
SiS_i0,400,920,80
QiQ_i0,700,860,88
AiA_i1,000,950,90
LiL_i0,100,350,55
CiC_i0,050,250,40
KiK_i0,100,350,45

Con pesos α=0,30\alpha=0{,}30, β=0,25\beta=0{,}25, γ=0,15\gamma=0{,}15, δ=0,10\delta=0{,}10, ϵ=0,10\epsilon=0{,}10, ζ=0,10\zeta=0{,}10:

RutaCálculoResultado
Local0,300,40+0,250,70+0,1510,100,100,100,050,100,100{,}30·0{,}40 + 0{,}25·0{,}70 + 0{,}15·1 - 0{,}10·0{,}10 - 0{,}10·0{,}05 - 0{,}10·0{,}100,42
MCP0,300,92+0,250,86+0,150,950,100,350,100,250,100,350{,}30·0{,}92 + 0{,}25·0{,}86 + 0{,}15·0{,}95 - 0{,}10·0{,}35 - 0{,}10·0{,}25 - 0{,}10·0{,}350,54
A2A0,300,80+0,250,88+0,150,900,100,550,100,400,100,450{,}30·0{,}80 + 0{,}25·0{,}88 + 0{,}15·0{,}90 - 0{,}10·0{,}55 - 0{,}10·0{,}40 - 0{,}10·0{,}450,46

La ruta MCP gana. Pero la decisión final todavía debe pasar por permisos. Si esa ruta escribe, publica o consulta datos sensibles, el score no basta.

Routing: reglas, modelo o grafo

Hay tres familias de routing que conviene distinguir.

La primera es routing determinista. Si la petición contiene un pago, va al flujo de pagos. Si pide una cita bibliográfica, va al agente de referencias. Si modifica producción, pide revisión. Es simple, barato y fácil de auditar.

La segunda es routing por clasificación. Un clasificador ligero, que puede ser un modelo pequeño o una función entrenada, decide si la tarea es simple, compleja, técnica, legal, de datos, de escritura o de soporte. Google ADK documenta RoutedAgent para elegir un agente por invocación, con fallback si el agente seleccionado falla antes de emitir eventos.4 También documenta RoutedLlm para elegir entre modelos cuando solo cambia el modelo y no cambian instrucciones, tools o subagentes.5

La tercera es routing por workflow o grafo. Aquí no elegimos un único destino, sino un recorrido: primero recuperar contexto, luego resolver, después verificar, y finalmente decidir si publicar o pedir revisión. En ADK, los workflow agents ejecutan patrones secuenciales, paralelos o de bucle con lógica predefinida; la propia documentación indica que en ADK 2.0 los workflows de grafo y dinámicos ofrecen más control y flexibilidad que las plantillas rígidas.6

Tipo de routingBuena elección cuando...Riesgo si se usa mal
Regla explícitaLa condición es clara y de negocio.Crece como una lista imposible de mantener.
ClasificadorHay muchas peticiones parecidas y categorías estables.Clasifica con seguridad aparente pero sin evidencia.
LLM routerLa intención es ambigua y necesita interpretación.Puede ser caro, lento y difícil de explicar.
GrafoHay pasos obligatorios, gates y reintentos.Se vuelve rígido si cada caso necesita excepción.
HandoffHay especialistas con contratos claros.Se usa para tapar falta de diseño interno.
A2AEl destino es otro sistema agentic independiente.Se añade protocolo cuando bastaba una tool.

MCP: tools y contexto como contrato externo

MCP, Model Context Protocol, estandariza cómo una aplicación con modelo se conecta a contexto, datos y herramientas. La especificación vigente consultada define hosts, clientes y servidores; usa JSON-RPC 2.0; y organiza capacidades como recursos, prompts y tools.7

La frase importante es esta: MCP no es “un plugin universal”; es una frontera de capacidades.

Concepto MCPQué significaEjemplo entendible
HostAplicación que usa el modelo.Un IDE, una app de chat, un panel interno.
ClientConector dentro del host.Pieza que habla con un servidor MCP.
ServerServicio que expone capacidades.Filesystem, base de datos, calendario, buscador.
ResourceDato legible o contexto.file://capitulo.md, esquema SQL, documento.
PromptPlantilla reutilizable.“Resume esta incidencia con formato técnico”.
ToolFunción ejecutable.search_docs, read_file, create_ticket.
Capability negotiationDeclaración de lo soportado.El cliente sabe si hay tools, resources o prompts.
ConsentimientoRevisión de acceso o acción.La persona autoriza leer una carpeta o llamar una tool.

OpenAI Agents SDK para JavaScript documenta varias formas de usar MCP: tools MCP hospedadas por la Responses API, servidores Streamable HTTP y servidores por stdio; también menciona aspectos de ciclo de vida, cache de listado de tools, nombres prefijados por servidor y filtrado de tools.8

Anthropic expone MCP desde la Messages API mediante mcp_servers y mcp_toolset; en la versión consultada requiere beta header mcp-client-2025-11-20, soporta tool calls, permite configuración por tool, allowlist, denylist, defer_loading y OAuth para servidores remotos.9

Google ADK documenta McpToolset como mecanismo para integrar tools de servidores MCP: conecta, lista tools mediante list_tools, adapta schemas a tools del ADK, expone esas tools al LlmAgent y proxifica llamadas mediante call_tool; además permite filtrar tools.10

El punto de ingeniería: si expones 80 tools MCP a un agente, no has “mejorado” el sistema. Has ampliado el espacio de decisión. Necesitas filtrado, nombres claros, permisos, cache de tool list, límites de coste y evaluación.

A2A: cuando el destino también decide

A2A, Agent2Agent Protocol, no va de que un modelo llame una función. Va de que un sistema agentic hable con otro sistema agentic. Google ADK lo presenta como una forma de construir sistemas multiagente donde agentes distintos colaboran mediante A2A, exponiendo y consumiendo agentes remotos.11

La especificación A2A consultada organiza el protocolo alrededor de operaciones como enviar mensajes, enviar mensajes en streaming, obtener tareas, listar tareas, cancelar tareas, suscribirse a una tarea, gestionar notificaciones y obtener una AgentCard extendida.12

Los objetos clave son:

Objeto A2AQué aportaPor qué importa
AgentCardIdentidad, capacidades, skills, interfaces, seguridad y versión.Permite descubrir qué puede hacer un agente antes de llamarlo.
AgentSkillCapacidad concreta declarada por el agente.Evita delegar tareas fuera de especialidad.
TaskUnidad durable de trabajo.Permite seguimiento, estados, streaming y recuperación.
MessageInteracción entre cliente y agente.Conserva turnos de comunicación.
PartFragmento multimodal o estructurado.Permite texto, archivos, formularios u otros modos.
ArtifactResultado producido.Separa conversación de entregables.
AgentCapabilitiesStreaming, notificaciones, extensiones.Permite validar si una operación está soportada.
SecuritySchemeRequisitos de autenticación.No todos los agentes son públicos ni equivalentes.

La diferencia con MCP:

PreguntaMCPA2A
¿Qué conecta?Un agente o host con herramientas y datos.Un agente con otro agente o sistema agentic.
¿Unidad principal?Tool, resource, prompt.AgentCard, task, message, artifact.
¿Quién decide el trabajo interno?El host o agente que llama la tool.El agente remoto puede tener su propio bucle y estado.
¿Cuándo usarlo?Para exponer capacidades concretas.Para delegar a un sistema con autonomía propia.
¿Error típico?Exponer demasiadas tools sin permisos.Usarlo cuando bastaba una API o tool simple.

Si llamas a search_contracts(query), probablemente es MCP o tool normal. Si preguntas a un agente de compras “gestiona este proceso con tus pasos, estado y outputs”, eso se parece más a A2A.

Handoffs, routing y A2A no son lo mismo

En OpenAI Agents SDK, un handoff permite que un agente delegue una tarea a otro agente especializado; se representa como una tool para el LLM, por ejemplo transfer_to_refund_agent si existe un agente de devoluciones.13

Eso no significa que todo handoff sea A2A. Un handoff dentro de un SDK puede ser interno: mismo proceso, mismo runtime, mismas trazas. A2A aparece cuando el destino es un sistema agentic independiente, con su propia AgentCard, su propio endpoint, sus propias capacidades y su propio ciclo de vida de tareas.

PatrónQuién controlaUnidad de trabajoCaso típico
Tool localTu aplicación.Función.Validar JSON, consultar tabla, calcular score.
MCP serverTu host y el servidor MCP.Tool/resource/prompt.Reutilizar herramientas entre clientes.
Handoff internoSDK o framework.Transferencia a especialista.Agente de soporte deriva a agente técnico.
A2ADos sistemas agentic.Task/mensaje/artefacto.Tu agente consulta al agente de otra unidad.
Workflow graphMotor de grafo.Nodo/estado/transición.Recuperar, resolver, verificar, publicar.

Árbol de decisión para elegir arquitectura

Cuando alguien pregunta “¿uso MCP, A2A, un handoff o una tool?”, yo intentaría que no respondiese desde la moda del momento. Respondería desde el efecto, el propietario, el estado y el contrato.

Árbol de decisión: elegir ruta sin casarte con una sigla La pregunta no es “qué protocolo mola más”, sino quién controla el estado, qué efecto tendrá la acción y qué contrato necesitas. Nueva petición intención + estado + permisos + presupuesto ¿Tiene efecto persistente o externo? publicar, enviar, escribir, cobrar, cambiar estado si dudas, trátalo como efecto persistente pasar por policy engine scope, entorno, revisión, idempotencia No optimizar por claridad y coste leer, clasificar, calcular, proponer ¿Hay pasos obligatorios? recuperar, verificar, aprobar, ejecutar orden conocido o gates fuertes ¿Otro sistema decide? tiene estado, skills y ciclo propio no es solo una función remota ApprovalCard + tool gateway si hay escritura, envío o publicación guardar input original, input efectivo y trace id Tool local si es capacidad interna y estable validar schema, permisos y salida tipada Workflow graph si el orden importa más que la libertad estado, nodos, reintentos y gates A2A si delegas a un agente independiente AgentCard, Task, Message, Artifact MCP si quieres reutilizar tools o recursos tool filtering, auth, cache, nombres únicos Handoff interno si otro especialista vive en tu runtime mismo proceso, mismas trazas, mismo dominio Router híbrido si conviven reglas, coste y modelos guardar alternativas y motivos de descarte Regla final Si no puedes explicar ruta, contrato, propietario, permiso, coste, estado y traza, todavía no tienes orquestación. IA para gente curiosa / Facsímil 05 / Capítulo 09 / 686f6c61

El árbol obliga a distinguir cuatro preguntas: efecto, control, estado y reutilización. Si una capacidad es interna, estable y de bajo impacto, empieza por tool local. Si quieres reutilizar tools entre clientes, MCP. Si delegas a un especialista dentro del mismo runtime, handoff. Si el destino es un sistema agentic independiente, A2A. Si hay pasos obligatorios, grafo. Si hay efecto persistente, aprobación o policy antes de ejecutar.

Anatomía visual de una orquestación publicable

Orquestación agentic: routing, MCP, A2A y ADKs La ruta no la decide una palabra bonita: la decide un contrato con capacidades, permisos, costes, latencias y trazas. Petición intención del usuario contexto activo estado de sesión q, s, presupuesto inicial Router reglas explícitas clasificador de intención score coste / latencia / calidad fallback antes de emitir eventos RouteDecision ruta + motivo + alternativas Capability registry skills declaradas contratos de entrada/salida latencia p50 / p95 coste, versión, owner no se delega a ciegas Policy engine scope del usuario entorno y efecto approval si hace falta allow / review / deny Ruta local tools propias base de datos interna validadores y calculadoras más control, menos interoperabilidad Ruta MCP servidores con tools resources y prompts auth, tool filter, cache ideal para capacidades reutilizables Ruta A2A AgentCard Task, Message, Artifact streaming, push, auth cuando el destino también decide Ruta humana ApprovalCard edición de input rechazo o reanudación si hay efecto persistente Tool gateway y ejecución schema validation entrada canónica auth y scope mínimo privilegio timeouts y retries sin bucles infinitos idempotencia clave de operación resultado output tipado Run state ruta activa, presupuesto, retries permite reanudar Trace store route_decision, tool_call, a2a_task explica qué ocurrió Eval harness calidad, coste, latencia, fallos mejora el routing IA para gente curiosa / Facsímil 05 / Capítulo 09 / 686f6c61

El diagrama separa algo que en demos suele estar mezclado: el router decide, el registry describe, la policy permite o detiene, el gateway ejecuta y la traza demuestra. MCP y A2A son rutas posibles, no sustitutos de esa arquitectura.

Contratos mínimos: qué viaja realmente

Si alguien solo entiende MCP y A2A como nombres, todavía no puede diseñar bien. Hay que bajar al contrato: qué identificador se envía, qué schema existe, qué estado vuelve, qué permisos intervienen y qué se guarda en la traza.

Los ejemplos siguientes son didácticos. No sustituyen la especificación ni el SDK concreto, pero muestran la forma mental que necesitas para trabajar: capacidades declaradas, argumentos tipados, resultados separados de la conversación y decisiones trazables.

MCP: una tool expuesta por un servidor

Un servidor MCP puede exponer una tool como search_docs. Lo importante no es el nombre; es el contrato de entrada y salida. Una tool sin schema obliga al modelo a adivinar.

{
  "server_id": "mcp.biblioteca",
  "transport": "streamable_http",
  "tool": {
    "name": "search_docs",
    "description": "Busca documentos normativos y devuelve fragmentos citables.",
    "inputSchema": {
      "type": "object",
      "additionalProperties": false,
      "required": ["query", "limit"],
      "properties": {
        "query": {
          "type": "string",
          "minLength": 4,
          "description": "Pregunta o término de búsqueda."
        },
        "limit": {
          "type": "integer",
          "minimum": 1,
          "maximum": 20,
          "description": "Número máximo de resultados."
        },
        "source_filter": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Colecciones permitidas para esta búsqueda."
        }
      }
    }
  }
}

Una llamada a esa tool debería dejar algo así en la traza:

{
  "event": "mcp.tool_call",
  "trace_id": "trace-2026-06-10-r1",
  "server_id": "mcp.biblioteca",
  "tool_name": "search_docs",
  "arguments": {
    "query": "normativa permanencia universidad",
    "limit": 5,
    "source_filter": ["normativa_publica"]
  },
  "policy": {
    "decision": "allow",
    "scope": ["read_public"],
    "tool_filter_version": "2026-06-10"
  },
  "result_shape": {
    "documents": "list",
    "citations": "list",
    "elapsed_ms": "number"
  }
}

El detalle útil: tool_filter_version también se guarda. Si mañana la tool deja de aparecer o cambia de schema, puedes saber con qué catálogo se tomó la decisión.

A2A: AgentCard como expediente de un agente

En A2A no solo quieres saber “hay un agente”. Quieres saber qué skills declara, qué endpoint usa, si soporta streaming, qué seguridad exige y qué versión estás invocando.

{
  "name": "Agente de becas",
  "description": "Gestiona revisión inicial de expedientes de becas.",
  "url": "https://becas.example.edu/a2a",
  "version": "1.3.0",
  "capabilities": {
    "streaming": true,
    "pushNotifications": true
  },
  "defaultInputModes": ["text", "application/json"],
  "defaultOutputModes": ["text", "application/json"],
  "skills": [
    {
      "id": "review_scholarship_case",
      "name": "Revisar expediente de beca",
      "description": "Comprueba requisitos, documentación y próximos pasos.",
      "tags": ["becas", "expediente", "revision"],
      "examples": [
        "Revisa el expediente B-1042 con la documentación adjunta."
      ]
    }
  ],
  "securitySchemes": {
    "oauth2": {
      "type": "oauth2",
      "flows": {
        "clientCredentials": {
          "tokenUrl": "https://auth.example.edu/token",
          "scopes": {
            "becas.review": "Permite solicitar revisión de expedientes."
          }
        }
      }
    }
  }
}

La AgentCard no es marketing. Es parte del contrato operativo. Si no declara capabilities, skills, seguridad y versión, el router está delegando con los ojos cerrados.

A2A: Task como unidad durable de trabajo

Cuando delegas por A2A, no quieres solo un texto de vuelta. Quieres una tarea con estado y artefactos. Eso permite consultar progreso, retomar, cancelar o guardar resultados.

{
  "jsonrpc": "2.0",
  "id": "req-2026-06-10-001",
  "method": "message/send",
  "params": {
    "message": {
      "role": "user",
      "parts": [
        {
          "kind": "text",
          "text": "Revisa el expediente B-1042 y devuelve un informe con requisitos cumplidos, dudas y siguiente paso."
        },
        {
          "kind": "data",
          "data": {
            "case_id": "B-1042",
            "student_scope": "scoped-token-abc",
            "requested_artifact": "decision_report"
          }
        }
      ]
    },
    "metadata": {
      "trace_id": "trace-2026-06-10-r2",
      "caller": "agente-orquestador",
      "budget": {
        "max_latency_ms": 5000,
        "max_cost_eur": 0.10
      }
    }
  }
}

Y un resultado razonable debería separar estado, mensaje y artefacto:

{
  "task": {
    "id": "task-becas-7781",
    "status": {
      "state": "completed",
      "message": {
        "role": "agent",
        "parts": [
          {
            "kind": "text",
            "text": "Expediente revisado. Falta justificante de residencia."
          }
        ]
      }
    },
    "artifacts": [
      {
        "artifactId": "decision-report-7781",
        "name": "Informe de revisión",
        "parts": [
          {
            "kind": "data",
            "data": {
              "case_id": "B-1042",
              "requirements_ok": ["matricula_activa", "renta_declarada"],
              "missing": ["residencia"],
              "next_step": "Solicitar justificante de residencia antes de continuar."
            }
          }
        ]
      }
    ]
  }
}

La separación importa. El mensaje sirve para conversar. El artefacto sirve para operar. Si mezclas ambos, luego no sabes qué parte debe leer una persona y qué parte puede consumir un sistema.

Fallos de producción que conviene ensayar

La orquestación falla de formas bastante previsibles. Lo sensato es probarlas antes de publicar.

FalloSeñal visibleCómo lo diseñaría
Tool list desactualizadaEl agente intenta llamar una tool que ya no existe.Cache con versión, invalidación explícita y test de catálogo.
Colisión de nombresDos servidores exponen search y el modelo elige mal.Prefijos por servidor y nombres semánticos: biblioteca.search_docs.
Schema driftLa tool acepta otros campos o deja de aceptar uno.Contract tests y validación additionalProperties: false.
Permiso heredado de másUna ruta puede acceder a datos que no necesita.Scope por tool, usuario, entorno y operación.
Fallback peligrosoAl fallar una ruta, el sistema prueba otra con más impacto.Fallback solo hacia rutas de igual o menor efecto.
Timeout ambiguoNo sabes si la acción se ejecutó o no.Idempotency key y consulta de estado antes de reintentar.
Task parcial en A2ALa tarea queda working o input-required.Guardar task id, estado y próximo paso; no inventar final.
Coste crecienteCada ruta añade llamadas de modelo y tool.Presupuesto por run, cortes por p95 y trazas de coste.
Artefacto perdidoEl agente responde, pero no queda entregable usable.Separar message de artifact y validar artifact schema.
Ruta no explicableEl resultado es bueno, pero nadie sabe por qué se eligió.Registrar top candidatos, score, señales y motivo final.

Una prueba sencilla: fuerza cada fallo en entorno de desarrollo. Quita una tool del catálogo. Cambia un schema. Devuelve un timeout. Haz que A2A responda con tarea en progreso. Si el sistema no sabe parar, reintentar o pedir revisión con claridad, todavía no está listo.

Caso para entenderlo: una universidad con sistemas distintos

Imagina una universidad con tres necesidades:

  1. Un alumno pregunta por una norma de matrícula.
  2. Secretaría necesita consultar expedientes.
  3. Un departamento externo tiene su propio agente para becas.

La solución torpe sería dar al agente principal todas las herramientas posibles: calendario, expedientes, normativa, becas, correo, editor, base de datos, CRM y formularios. La solución profesional separa rutas.

PeticiónRuta probableMotivo
“¿Qué dice la normativa sobre permanencia?”MCP documental o RAG interno.Es consulta de conocimiento cambiante.
“Comprueba si tengo pago pendiente”Tool interna con permisos.Afecta datos personales y necesita scope.
“Inicia revisión de beca externa”A2A con agente de becas.Otro sistema mantiene estado y proceso propio.
“Redacta respuesta al alumno”Tool local o agente interno.Es una propuesta textual sin efecto externo.
“Envía la respuesta oficial”ApprovalCard antes de tool.Hay efecto comunicativo y registro institucional.

El mismo usuario puede pasar por varias rutas en una sola tarea. La orquestación no elige “el agente ganador”. Elige el recorrido verificable.

Arquitecturas de orquestación

No existe una única arquitectura correcta. Sí existen patrones reconocibles.

ArquitecturaCómo funcionaCuándo encaja
Router centralUn componente decide cada destino.Productos con pocas rutas críticas y reglas claras.
Supervisor y especialistasUn agente supervisor delega a agentes especializados.Tareas ambiguas donde el modelo puede elegir especialistas.
Grafo de workflowNodos y transiciones controlan el recorrido.Procesos con pasos obligatorios, gates y reintentos.
Tool gateway con MCPTools internas y externas se exponen por contratos.Muchas capacidades reutilizables entre clientes.
A2A federadoSistemas agentic independientes coordinan tareas.Varias unidades, proveedores o dominios con autonomía propia.
Router híbridoReglas, clasificador, coste, permisos y fallback.Sistemas en producción con variedad de casos.

Mi recomendación práctica: empezar con router explícito y registry pequeño. Añadir MCP cuando la herramienta deba reutilizarse fuera de un solo agente. Añadir A2A cuando el destino tenga ciclo de vida propio. Añadir grafo cuando hay pasos que no deben quedar a improvisación del modelo.

Decisiones de ingeniería que no se ven en la demo

Una demo puede vivir sin estas piezas. Un sistema serio, no.

DecisiónPregunta que debes responder
Versionado de capacidades¿Qué pasa si search_docs cambia su schema mañana?
Nombres únicos¿Qué ocurre si dos servidores MCP exponen search?
Tool filtering¿Expones todo el servidor o solo tres tools?
Cache de tool list¿Listas tools en cada run y pagas latencia siempre?
Presupuesto¿Quién corta una ruta que consume demasiado?
Idempotencia¿Qué pasa si se reintenta una acción con efecto persistente?
Fallback¿Cuándo intentas otra ruta y cuándo paras?
Estado parcial¿Qué haces si A2A devuelve una task en progreso?
Artefactos¿Dónde guardas archivos, diffs o resultados largos?
Trazas¿Puedes reconstruir por qué se eligió una ruta?
Evaluación¿Qué métrica demuestra que el router mejora?

La orquestación es, sobre todo, disciplina de interfaces.

Manos a la obra

Práctica: construir un router interoperable.

Kit ejecutable de este capítulo: kit descargable.

# Descomprime el ZIP del capítulo y ejecuta estos comandos dentro de esa carpeta
python3 ops/run_f5_practices.py --chapter c09 --write --fail-on-invalid

Vamos a construir un router mínimo, ejecutable sin dependencias externas. No llama a OpenAI, Anthropic ni Google. Esa es la gracia: antes de usar un SDK, tenemos que diseñar nuestro contrato interno.

El ejemplo modela cuatro rutas: tool local, servidor MCP, agente A2A y revisión humana. Para cada petición calcula un score, aplica permisos, elige ruta y deja traza.

from __future__ import annotations

from dataclasses import dataclass, asdict
from typing import Literal
import json
import time


RouteKind = Literal["local_tool", "mcp_tool", "a2a_agent", "human_review"]
Effect = Literal["read", "draft", "write", "send", "publish"]


@dataclass(frozen=True)
class Request:
    request_id: str
    text: str
    required_tags: set[str]
    effect: Effect
    environment: str
    max_latency_ms: int
    max_cost_eur: float
    user_scope: set[str]


@dataclass(frozen=True)
class Capability:
    route_id: str
    kind: RouteKind
    tags: set[str]
    owner: str
    input_contract: str
    output_contract: str
    p95_latency_ms: int
    cost_eur: float
    quality: float
    availability: float
    required_scope: set[str]
    effect: Effect
    version: str


@dataclass(frozen=True)
class RouteDecision:
    request_id: str
    decision: Literal["run", "review", "stop"]
    selected_route: str | None
    selected_kind: RouteKind | None
    score: float | None
    reason: str
    alternatives: list[dict]
    trace_id: str


def normalize(value: float, maximum: float) -> float:
    if maximum <= 0:
        return 1.0
    return min(value / maximum, 1.0)


def overlap_score(required: set[str], offered: set[str]) -> float:
    if not required:
        return 0.5
    return len(required & offered) / len(required)


def effect_risk(effect: Effect) -> float:
    return {
        "read": 0.05,
        "draft": 0.15,
        "write": 0.45,
        "send": 0.65,
        "publish": 0.80,
    }[effect]


def effect_rank(effect: Effect) -> int:
    return {
        "read": 1,
        "draft": 2,
        "write": 3,
        "send": 4,
        "publish": 5,
    }[effect]


def score_route(request: Request, capability: Capability) -> tuple[float, list[str]]:
    reasons: list[str] = []

    semantic = overlap_score(request.required_tags, capability.tags)
    if semantic < 0.50:
        reasons.append("poco encaje semántico")

    if effect_rank(capability.effect) < effect_rank(request.effect):
        reasons.append("efecto insuficiente")

    latency_penalty = normalize(capability.p95_latency_ms, request.max_latency_ms)
    cost_penalty = normalize(capability.cost_eur, request.max_cost_eur)
    risk_penalty = effect_risk(capability.effect)

    missing_scope = capability.required_scope - request.user_scope
    if missing_scope:
        reasons.append(f"faltan permisos: {sorted(missing_scope)}")

    score = (
        0.32 * semantic
        + 0.25 * capability.quality
        + 0.16 * capability.availability
        - 0.10 * latency_penalty
        - 0.08 * cost_penalty
        - 0.09 * risk_penalty
    )

    if capability.p95_latency_ms > request.max_latency_ms:
        reasons.append("supera latencia p95")
    if capability.cost_eur > request.max_cost_eur:
        reasons.append("supera coste permitido")

    return round(score, 3), reasons


def permission_gate(request: Request, capability: Capability, reasons: list[str]) -> Literal["run", "review", "stop"]:
    if "efecto insuficiente" in reasons:
        return "stop"
    if capability.required_scope - request.user_scope:
        return "stop"
    if "supera coste permitido" in reasons:
        return "stop"
    if request.environment == "prod" and request.effect in {"write", "send", "publish"}:
        return "review"
    if capability.kind == "a2a_agent" and request.effect in {"send", "publish"}:
        return "review"
    return "run"


def route_request(request: Request, capabilities: list[Capability]) -> RouteDecision:
    trace_id = f"trace-{int(time.time())}-{request.request_id}"
    ranked: list[dict] = []

    for capability in capabilities:
        score, reasons = score_route(request, capability)
        gate = permission_gate(request, capability, reasons)
        ranked.append(
            {
                "route_id": capability.route_id,
                "kind": capability.kind,
                "score": score,
                "gate": gate,
                "reasons": reasons or ["candidato válido"],
            }
        )

    ranked.sort(key=lambda item: item["score"], reverse=True)

    for item in ranked:
        if item["gate"] == "run":
            return RouteDecision(
                request_id=request.request_id,
                decision="run",
                selected_route=item["route_id"],
                selected_kind=item["kind"],
                score=item["score"],
                reason="mejor ruta ejecutable dentro de permisos y presupuesto",
                alternatives=ranked[:3],
                trace_id=trace_id,
            )
        if item["gate"] == "review":
            return RouteDecision(
                request_id=request.request_id,
                decision="review",
                selected_route=item["route_id"],
                selected_kind=item["kind"],
                score=item["score"],
                reason="mejor ruta, pero requiere aprobación por efecto o entorno",
                alternatives=ranked[:3],
                trace_id=trace_id,
            )

    return RouteDecision(
        request_id=request.request_id,
        decision="stop",
        selected_route=None,
        selected_kind=None,
        score=None,
        reason="ninguna ruta cumple permisos, coste y contrato",
        alternatives=ranked[:3],
        trace_id=trace_id,
    )


capabilities = [
    Capability(
        route_id="local.normativa_cache",
        kind="local_tool",
        tags={"normativa", "lectura", "universidad"},
        owner="equipo-libro",
        input_contract="query:string",
        output_contract="citas:list",
        p95_latency_ms=120,
        cost_eur=0.001,
        quality=0.62,
        availability=1.00,
        required_scope={"read_public"},
        effect="read",
        version="1.0.0",
    ),
    Capability(
        route_id="mcp.biblioteca.search_docs",
        kind="mcp_tool",
        tags={"normativa", "referencias", "biblioteca", "lectura"},
        owner="biblioteca",
        input_contract="query:string, limit:int",
        output_contract="documents:list",
        p95_latency_ms=850,
        cost_eur=0.015,
        quality=0.88,
        availability=0.96,
        required_scope={"read_public"},
        effect="read",
        version="2026-06-10",
    ),
    Capability(
        route_id="a2a.becas.review_case",
        kind="a2a_agent",
        tags={"becas", "expediente", "revision", "workflow"},
        owner="unidad-becas",
        input_contract="task:object",
        output_contract="artifact:decision_report",
        p95_latency_ms=2500,
        cost_eur=0.060,
        quality=0.91,
        availability=0.90,
        required_scope={"read_student", "delegate_becas"},
        effect="write",
        version="1.0.0",
    ),
    Capability(
        route_id="human.editorial_approval",
        kind="human_review",
        tags={"publicar", "enviar", "revision", "aprobacion"},
        owner="editor",
        input_contract="approval_card:object",
        output_contract="approved|edited|rejected",
        p95_latency_ms=300000,
        cost_eur=0.0,
        quality=0.99,
        availability=0.70,
        required_scope={"editor"},
        effect="publish",
        version="1.0.0",
    ),
]

requests = [
    Request(
        request_id="r1",
        text="Busca la normativa de permanencia y cita la fuente.",
        required_tags={"normativa", "referencias"},
        effect="read",
        environment="dev",
        max_latency_ms=3000,
        max_cost_eur=0.05,
        user_scope={"read_public"},
    ),
    Request(
        request_id="r2",
        text="Pide al sistema de becas que revise este expediente.",
        required_tags={"becas", "expediente", "workflow"},
        effect="write",
        environment="prod",
        max_latency_ms=5000,
        max_cost_eur=0.10,
        user_scope={"read_public", "read_student", "delegate_becas"},
    ),
    Request(
        request_id="r3",
        text="Publica el resultado final en el portal.",
        required_tags={"publicar", "revision"},
        effect="publish",
        environment="prod",
        max_latency_ms=600000,
        max_cost_eur=0.02,
        user_scope={"read_public"},
    ),
]

for request in requests:
    decision = route_request(request, capabilities)
    print(json.dumps(asdict(decision), ensure_ascii=False, indent=2))

Salida esperada, resumida:

r1 -> run    -> mcp.biblioteca.search_docs
r2 -> review -> a2a.becas.review_case
r3 -> stop   -> ninguna ruta cumple permisos, coste y contrato

Fíjate en el tercer caso. El sistema no intenta publicar porque la ruta humana exige scope de editor y el usuario no lo tiene. Esto es lo que queremos: el router no solo elige capacidades; también respeta permisos.

Cómo encaja todo

flowchart TD
  subgraph F5C09["Capítulo 09 · Orquestación"]
    Request["Petición"]
    Router["Router"]
    Registry["Capability registry"]
    Policy["Policy engine"]
    Local["Tool local"]
    MCP["MCP server"]
    A2A["A2A agent"]
    Gateway["Tool gateway"]
    State["Run state"]
    Trace["Trace store"]
    Eval["Eval harness"]
  end

  subgraph Antes["Capítulos anteriores"]
    Tools["Contratos de tools (F5 C03)"]
    Memory["Contexto y handoff (F5 C04)"]
    Patterns["Arquitecturas de agentes (F5 C05)"]
    SDKs["SDKs y ADKs (F5 C07)"]
    Permissions["Permisos y HITL (F5 C08)"]
  end

  subgraph Despues["Lo que viene"]
    AgentEval["Evaluar agentes (F5 C10)"]
    Recap["Recapitulación y laboratorio (F5 C11)"]
    Ops["Operación de sistemas (F6)"]
  end

  Tools -->|"definir schemas para"| Registry
  Memory -->|"aportar estado a"| Router
  Patterns -->|"ofrecer formas de"| Router
  SDKs -->|"ejecutar mediante"| Gateway
  Permissions -->|"limitar"| Policy
  Request --> Router
  Registry --> Router
  Router --> Policy
  Policy -->|"permitir ruta"| Gateway
  Policy -->|"pedir revisión"| State
  Gateway --> Local
  Gateway --> MCP
  Gateway --> A2A
  Local --> Trace
  MCP --> Trace
  A2A --> Trace
  State --> Trace
  Trace --> Eval
  Eval -->|"ajustar pesos"| Router
  Trace --> AgentEval
  Eval --> Recap
  Gateway --> Ops

  classDef chapter fill:#ffffff,stroke:#111111,color:#111111,stroke-width:1.4px;
  classDef external fill:#f7f7f7,stroke:#777777,color:#111111,stroke-width:1.1px,stroke-dasharray: 5 4;
  class Request,Router,Registry,Policy,Local,MCP,A2A,Gateway,State,Trace,Eval chapter;
  class Tools,Memory,Patterns,SDKs,Permissions,AgentEval,Recap,Ops external;

Vocabulario aprendido

TérminoDefinición útil
OrquestaciónCapa que decide ruta, agente, tool, permisos y estrategia de ejecución.
RouterComponente que selecciona una ruta usando señales observables.
Capability registryCatálogo de capacidades con contrato, coste, latencia, versión y owner.
MCPProtocolo para conectar hosts con herramientas, recursos y prompts.
MCP serverServicio que expone capacidades mediante MCP.
ResourceDato o contexto que un servidor MCP puede ofrecer.
Tool filteringExponer solo un subconjunto de tools a un agente.
Tool listLista de tools disponibles para un cliente o agente en un momento concreto.
Schema driftCambio de contrato que rompe supuestos de entrada o salida.
A2AProtocolo para coordinar sistemas agentic independientes.
AgentCardManifiesto que describe identidad, capacidades, skills, interfaces y seguridad.
TaskUnidad de trabajo durable en A2A.
ArtifactResultado producido por una tarea, separado de los mensajes.
HandoffTransferencia de una tarea a otro agente, normalmente dentro de un runtime.
FallbackRuta alternativa cuando la primera falla antes de producir resultado útil.
IdempotenciaPropiedad que permite repetir una operación sin duplicar efectos.
RouteDecisionRecibo estructurado con ruta elegida, motivo, score y alternativas.

Dónde solía tropezar yo

TropiezoPor qué ocurreAntídoto
Llamar orquestación a cualquier cadenaVarias llamadas parecen arquitectura.Exigir router, contrato, estado, permisos y traza.
Usar MCP para todoEs cómodo exponer tools estándar.Usar MCP cuando haya reutilización real y controlar tool filtering.
Usar A2A demasiado prontoSuena moderno delegar a otro agente.Usarlo solo si el destino tiene autonomía, estado y capacidades propias.
Dejar que el modelo elija siempreReduce código al principio.Separar reglas de negocio, policy, routing y decisión del modelo.
No registrar alternativasSolo guardas la ruta ganadora.Guardar top candidatos y motivos de descarte.
Ignorar latencia de descubrimientoListar tools parece gratis.Cachear tool list, versionar capacidades y medir p95.
Mezclar output conversacional y artefactosTodo termina como texto.Separar mensaje, task, artifact, diff, archivo y traza.
No ensayar fallosLa demo solo prueba el camino feliz.Simular tool ausente, schema drift, timeout y task parcial.
Permitir fallback hacia más impactoParece una forma de “resolver como sea”.Fallback solo hacia rutas de igual o menor efecto operativo.

Antes de pasar página

Antes del capítulo 10, deberías poder responder:

PreguntaSi dudas, vuelve a...
¿Qué diferencia hay entre orquestar y encadenar llamadas?Qué no es orquestar.
¿Qué entra y qué sale de una función de orquestación?La definición útil.
¿Por qué el routing debe registrar alternativas descartadas?Las tripas de la orquestación.
¿Cómo se puntúa una ruta sin fingir precisión absoluta?Fórmula práctica para elegir ruta.
¿Cuándo usarías regla, clasificador, LLM router o grafo?Routing: reglas, modelo o grafo.
¿Qué problema resuelve MCP y qué no resuelve?MCP: tools y contexto como contrato externo.
¿Qué cambia cuando usas A2A en lugar de una tool?A2A: cuando el destino también decide.
¿Por qué un handoff interno no siempre es A2A?Handoffs, routing y A2A no son lo mismo.
¿Qué árbol usarías para elegir entre tool local, MCP, handoff, A2A o workflow?Árbol de decisión para elegir arquitectura.
¿Qué aspecto mínimo tienen una tool MCP, una AgentCard y una Task A2A?Contratos mínimos: qué viaja realmente.
¿Qué fallos deberías ensayar antes de publicar?Fallos de producción que conviene ensayar.
¿Qué decisiones de ingeniería hacen publicable la orquestación?Decisiones de ingeniería que no se ven en la demo.
¿Cómo implementarías un router mínimo sin casarte con proveedor?Manos a la obra.

Para saber más

En resumen

IdeaQué te llevas
Orquestar es decidir rutas con contrato.No basta con encadenar agentes: hay que registrar por qué se eligió cada ruta.
MCP y A2A resuelven problemas distintos.MCP conecta tools y contexto; A2A coordina sistemas agentic independientes.
El router necesita señales, no intuición.Encaje, calidad, disponibilidad, latencia, coste, riesgo y permisos deben verse en la decisión.
Los ADKs ayudan, pero no sustituyen arquitectura.Puedes usar OpenAI, Claude, Google ADK o LangGraph, pero tu dominio debe mantener contratos propios.
La evaluación del capítulo 10 empieza aquí.Sin trazas y alternativas descartadas no podremos medir si la orquestación funciona mejor.

Notas

  1. Wooldridge, M., & Jennings, N. R. (1995). Intelligent agents: Theory and practice. The Knowledge Engineering Review, 10(2), 115-152. https://doi.org/10.1017/S0269888900008122. Consultado el 10 de junio de 2026.

  2. Jennings, N. R., Sycara, K., & Wooldridge, M. (1998). A roadmap of agent research and development. Autonomous Agents and Multi-Agent Systems, 1(1), 7-38. https://doi.org/10.1023/A:1010090405266. Consultado el 10 de junio de 2026.

  3. Smith, R. G. (1980). The Contract Net Protocol: High-Level Communication and Control in a Distributed Problem Solver. IEEE Transactions on Computers, C-29(12), 1104-1113. https://doi.org/10.1109/TC.1980.1675516. Consultado el 10 de junio de 2026.

  4. Google. (2026). Agent Development Kit: Route Between Agents. https://adk.dev/agents/routing/. Consultado el 10 de junio de 2026.

  5. Google. (2026). Agent Development Kit: Route Between Models. https://adk.dev/agents/models/routing/. Consultado el 10 de junio de 2026.

  6. Google. (2026). Agent Development Kit: Template Agent Workflows. https://adk.dev/agents/workflow-agents/. Consultado el 10 de junio de 2026.

  7. Model Context Protocol. (2026). Specification. https://modelcontextprotocol.io/specification. Consultado el 10 de junio de 2026.

  8. OpenAI. (2026). Agents SDK: Model Context Protocol. https://openai.github.io/openai-agents-js/guides/mcp/. Consultado el 10 de junio de 2026.

  9. Anthropic. (2026). MCP Connector. https://platform.claude.com/docs/en/agents-and-tools/mcp-connector. Consultado el 10 de junio de 2026.

  10. Google. (2026). Agent Development Kit: MCP Tools. https://adk.dev/tools-custom/mcp-tools/. Consultado el 10 de junio de 2026.

  11. Google. (2026). ADK with Agent2Agent Protocol. https://adk.dev/a2a/. Consultado el 10 de junio de 2026.

  12. Agent2Agent Protocol. (2026). Specification. https://google-a2a.github.io/A2A/specification/. Consultado el 10 de junio de 2026.

  13. OpenAI. (2026). Agents SDK: Handoffs. https://openai.github.io/openai-agents-python/handoffs/. Consultado el 10 de junio de 2026.

Capítulo 10

Facsímil 5 · Agentes y orquestación

Capítulo 10: Evaluar agentes: trayectoria, coste y gates

Un agente puede acertar por el camino equivocado

En una aplicación clásica, muchas veces basta con comprobar la salida: esta función recibe x y devuelve y. En un agente, eso se queda corto. Un agente puede dar una respuesta final aceptable después de usar la tool equivocada, consultar demasiadas fuentes, saltarse una aprobación, repetir un paso, gastar demasiado o llegar a una conclusión que no puede reconstruirse.

En el capítulo 06 pusimos harness, límites, sensores y trazas. En el capítulo 08 diseñamos permisos y aprobación humana. En el capítulo 09 construimos routing entre tool local, MCP, A2A y workflows. Ahora toca una pregunta incómoda y necesaria: ¿cómo sabemos que todo eso funciona mejor, y no solo que parece funcionar?

La evaluación de agentes tiene que mirar tres cosas a la vez: el resultado final, la trayectoria y el coste operativo. Si falta una, podemos engañarnos. Para ingeniería del software, además, hay una cuarta capa: el cambio. Una evaluación seria no solo responde “¿funciona esta demo?”, sino “¿puedo cambiar el prompt, el modelo, el router, una tool o el dataset y saber si he mejorado o he roto algo?”.

Qué le pediría a una clase de ingeniería del software

Si este capítulo se convirtiera en práctica universitaria, no lo plantearía como “mide si responde bien”. Lo plantearía como un sistema evaluable, versionado y desplegable.

CompetenciaPregunta de ingenieríaEvidencia que debería producir el alumno
Requisitos observables¿Qué significa “bien” sin depender de una opinión suelta?Rúbrica, criterios de aceptación y casos con why_it_exists.
Diseño de pruebas¿Qué capas se prueban por separado y cuáles integradas?Tests de schema, tool contract, trayectoria, escenario y gate.
Trazabilidad¿Puedes reconstruir por qué el agente decidió algo?trace_id, spans, eventos, argumentos, resultados y versiones.
Reproducibilidad¿Otra persona puede repetir la evaluación?Dataset versionado, modelo fijado, prompt versionado, seed si aplica y fixtures.
Control de regresiones¿Lo que corregiste ayer queda protegido mañana?Caso nuevo en el dataset y comparación baseline contra candidate.
Estadística mínima¿La mejora es señal o ruido?Tamaño de muestra, intervalo, repetición de runs y tolerancia de cambio.
Operación¿Qué ocurre si esto llega a producción?Coste por tarea aceptada, p95 de latencia, rate limits y alertas.
Integración continua¿Dónde se bloquea un cambio?Gate de PR, gate nocturno, gate de prepublicación y canary.

Lo importante para el alumno: un agente no se evalúa como una función pura, pero tampoco como una caja negra. Se evalúa como software no determinista con efectos, dependencias externas y trazas.

Qué no es evaluar un agente

Evaluar un agente no es leer diez conversaciones bonitas. Eso sirve para intuición inicial, pero no para publicar ni comparar versiones.

Tampoco es pedirle a otro modelo “ponle nota” sin definir criterios. Un evaluador puede ayudar, pero necesita rúbrica, ejemplos, calibración y casos donde sepamos la respuesta. Si no, cambiaremos una caja negra por otra.

Y no es medir solo exactitud. Un sistema que acierta un 90% pero cuesta el triple, tarda 40 segundos, exige revisión manual constante o falla justo en tareas críticas no está listo para un producto serio.

La definición útil

Para este libro, evaluar un agente es:

Ejecutar tareas representativas, capturar la traza completa, puntuar resultado y trayectoria, aplicar gates de coste y permisos, y comparar versiones con criterios repetibles.

Ejemplo de fórmula. Podemos modelar una ejecución como:

r=(x,y,τ,c,l,g)r = (x, y, \tau, c, l, g)
SímboloSignificadoEjemplo
rrRun o ejecución evaluada.Una petición de alumno resuelta por el agente.
xxEntrada del caso.“Comprueba una cita y genera referencia APA”.
yySalida final.Respuesta, informe, diff, JSON o artefacto.
τ\tauTrayectoria.Secuencia de modelo, tool, observación, decisión y parada.
ccCoste.Euros, tokens, llamadas a tools, revisión humana.
llLatencia.Tiempo total y p95 por paso.
ggGates aplicados.quality_gate, budget_gate, policy_gate.

Ejemplo de fórmula. Y una puntuación útil podría escribirse así:

S(r)=wySy+wτSτ+wpSp+woSoλCnμLnS(r) = w_y S_y + w_\tau S_\tau + w_p S_p + w_o S_o - \lambda C_n - \mu L_n
SímboloSignificadoEjemplo
S(r)S(r)Puntuación total de la ejecución.0,82 sobre 1.
SyS_yCalidad de salida final.Respuesta correcta y bien citada.
SτS_\tauCalidad de trayectoria.Usó tools esperadas, orden razonable y argumentos correctos.
SpS_pCumplimiento de permisos y gates.Pidió revisión antes de publicar.
SoS_oSalud operativa.Sin retries innecesarios ni loops.
CnC_nCoste normalizado.Coste real dividido por coste máximo.
LnL_nLatencia normalizada.Latencia real dividida por latencia máxima.
wy,wτ,wp,wow_y,w_\tau,w_p,w_oPesos de calidad.En soporte quizá pesa más SyS_y; en operaciones pesa más SpS_p.
λ,μ\lambda,\muPenalizaciones.Penalizar coste y latencia.

La fórmula no pretende esconder juicio humano. Pretende hacerlo explícito. Si tu producto valora trazabilidad, dale peso a SτS_\tau. Si el coste manda, sube λ\lambda. Si el riesgo operativo manda, ningún score debería saltarse SpS_p.

Fecha de corte del estado del arte

Fecha de corte: 10 de junio de 2026.
Fuentes consultadas ese día: documentación oficial de OpenAI sobre agent evals, trace grading y tracing del Agents SDK; documentación oficial de Google ADK sobre evaluación de agentes; documentación de LangChain/LangSmith sobre Agent Evals y evaluación de trayectorias; documentación de OpenTelemetry sobre trazas; documentación de Phoenix y Promptfoo sobre evals de agentes y agentes de código; benchmarks académicos como AgentBench, SWE-bench y ToolBench; referencias de ingeniería de ML sobre evaluación y deuda técnica.

Lo estable es el método: dataset, replay, traza, métricas, gates, comparación de versiones y análisis de regresiones. Lo cambiante son productos, nombres de métricas, dashboards, APIs de eval, modelos evaluadores, precios y benchmarks de moda.

Qué mirar: salida, trayectoria y operación

Google ADK lo formula de forma clara: en agentes no basta con evaluar la respuesta final; también hay que evaluar la trayectoria, es decir, la secuencia de pasos y tools usadas antes de responder.1 La misma idea aparece en OpenAI: las trazas permiten evaluar llamadas de modelo, tool calls, guardrails y handoffs, y trace grading puntúa esas trazas con criterios estructurados.23

Podemos organizarlo así:

CapaPreguntaMétrica típica
Salida final¿Respondió lo correcto?answer_score, json_valid, citation_match, artifact_valid.
Trayectoria¿Llegó por un camino aceptable?tool_order_score, arg_match, extra_tools, missing_steps.
Permisos¿Pidió revisión cuando tocaba?policy_gate_pass, approval_required_match.
Coste¿Compensa económicamente?cost_per_run, cost_per_accepted_task, token_budget_pass.
Latencia¿Es usable?p50, p95, timeout_rate.
Robustez¿Se recupera de fallos esperables?retry_success, fallback_correct, partial_task_handled.
Trazabilidad¿Podemos explicar qué pasó?trace_completeness, missing_span_rate.

Si solo evalúas salida final, no verás que el agente usó cuatro tools cuando bastaba una. Si solo evalúas coste, no verás que dejó una cita falsa. Si solo evalúas trayectoria, no verás que el texto final no ayuda a nadie.

Pirámide de pruebas para agentes

La forma más útil de pensar esto para ingeniería del software es una pirámide, pero no exactamente la pirámide clásica de unit tests. En agentes hay pruebas de contrato, de trayectoria y de operación.

CapaQué pruebaEjemploFrecuencia
UnitFunciones puras del harness.Normalizar una URL, calcular coste, validar JSON.En cada cambio.
ContractQue una tool respeta schema, permisos y errores esperados.search_source(query: str) devuelve documentos con url, title, snippet.En cada PR.
ComponentUna pieza del agente aislada.Router elige biblioteca y no publicacion en modo lectura.En cada PR.
TrajectorySecuencia de pasos contra una referencia o rúbrica.Buscar fuente antes de validar APA.En PR y nightly.
ScenarioCaso completo multi-turn con tools y estado.Alumno aporta cita incompleta, agente pregunta, busca, valida y responde.Nightly o prepublicación.
RegressionFallos ya corregidos convertidos en casos permanentes.La versión anterior omitía validate_apa.Siempre.
ShadowTráfico real copiado a una versión candidata sin afectar al usuario.Comparar agent-v2 y agent-v3 con el mismo input.Antes de publicar.
Online sampled evalMuestra de producción revisada automáticamente y, si hace falta, por personas.2% de runs con trazas puntuadas.Continuo.

OpenAI recomienda empezar por trazas cuando aún estás depurando comportamiento y pasar a datasets/eval runs cuando necesitas repetibilidad.4 Promptfoo lo aterriza muy bien para agentes de código: un agente no transforma X en Y una sola vez, decide, actúa, observa y repite; por eso hay que evaluar sistema, no solo modelo.5

El problema del oráculo

En testing clásico, a veces sabemos exactamente la salida esperada. En agentes, muchas veces no. Dos respuestas pueden ser correctas con redacciones distintas; dos trayectorias pueden ser aceptables con orden diferente; una tool puede devolver datos equivalentes con otro ranking. A eso lo llamamos problema del oráculo: no siempre existe una respuesta única y fácil de comparar.

Tipo de oráculoCuándo sirveRiesgo si lo usas mal
Exact matchJSON, IDs, cálculos, rutas, permisos.Castiga respuestas válidas con formato distinto.
Schema/property checkSalida estructurada, artefactos, contratos.Puede pasar contenido pobre si el schema es débil.
Golden referenceCasos donde hay respuesta conocida.Se queda corto para problemas abiertos.
Rúbrica humanaCalidad, utilidad, explicación, criterio profesional.Cara y menos escalable.
LLM-as-judgeEscalar revisión cualitativa con criterios.Necesita calibración, ejemplos y control de sesgo.
PairwiseComparar baseline contra candidate.No dice si ambos son malos.
Metamorphic testingPropiedades que deben mantenerse al cambiar el input.Requiere pensar invariantes útiles.

Ejemplo de metamorphic testing: si pido “cita en APA” y luego “cita en APA en castellano”, el idioma puede cambiar, pero la URL, el año y el autor no deberían desaparecer. No busco una frase idéntica; busco una propiedad que debe conservarse.

El dataset: pequeño, vivo y con intención

Un dataset de evaluación de agentes no empieza siendo enorme. Empieza siendo representativo.

Tipo de casoQué cubreEjemplo
Golden setCasos que siempre deben pasar.Buscar fuente, citar, validar y responder.
RegresiónFallos ya observados.Antes omitía revisar permisos al publicar.
Casos límiteEntradas raras pero posibles.Tool devuelve respuesta vacía o incompleta.
Casos de costePeticiones que podrían disparar pasos.Consulta amplia que invita a llamar varias tools.
Casos de permisosAcciones con scope distinto.Alumno puede leer, editor puede publicar.
Casos multi-turnConversaciones con información gradual.Usuario aporta datos en dos turnos.
Casos de recuperaciónError recuperable de tool o timeout.Primera ruta falla y debe usar fallback válido.

Cada caso debería guardar:

{
  "case_id": "f5c10-001",
  "input": "Comprueba esta cita y genera referencia APA.",
  "expected": {
    "final_must_contain": ["autor", "año", "URL"],
    "required_tools": ["search_source", "validate_apa"],
    "forbidden_tools": ["publish_page"],
    "max_cost_eur": 0.05,
    "max_latency_ms": 5000,
    "policy": "read_only"
  },
  "tags": ["referencias", "tool-use", "read-only"],
  "why_it_exists": "Evita publicar una cita sin fuente comprobada."
}

El campo why_it_exists es más importante de lo que parece. Cuando el dataset crece, ayuda a no borrar casos “raros” que en realidad protegen aprendizaje del equipo.

En una asignatura o proyecto profesional, el dataset debería vivir como código. No basta con una hoja suelta llamada evals_final_v3.xlsx.

suite_id: f5-agentes-referencias
dataset_version: 2026-06-10.1
owner: equipo-ia
baseline_agent: agent-v2
candidate_agent: agent-v3
default_budget:
  max_cost_eur: 0.08
  max_latency_ms: 6000
cases:
  - case_id: f5c10-001
    tags: [referencias, tool-use, read-only]
    input_file: cases/f5c10-001/input.md
    expected_file: cases/f5c10-001/expected.json
    rubric_file: rubrics/reference_check.yaml
    min_scores:
      final: 0.85
      trajectory: 0.90
      trace: 0.95
    run:
      repeat: 3
      temperature: 0
      sandbox: read_only

Esto permite revisar cambios como cualquier otro cambio de software: diff, PR, revisión, historial y rollback. Si el dataset no se versiona, una mejora puede ser solo que hemos cambiado el examen.

Trazas: el material que se evalúa

OpenAI Agents SDK representa una traza como una operación completa de workflow, compuesta por spans; esos spans pueden envolver agentes, generaciones, function tools, guardrails y handoffs.6 OpenTelemetry describe las trazas como el camino de una petición por una aplicación, formado por spans con nombre, tiempos, atributos, eventos, estado y relaciones padre-hijo.7

Para evaluar agentes, una traza mínima debería contener:

EventoCampos mínimos
run.startedcase_id, agent_version, model, prompt_version, dataset_version.
route.decisionRuta elegida, alternativas, motivo, score.
model.callModelo, tokens, latencia, input_hash, output_hash.
tool.callTool, argumentos, schema_version, efecto, timeout.
tool.resultEstado, resumen, bytes, filas, error recuperable si aplica.
approval.requestAcción, recurso, scope, score de riesgo.
approval.resultDecisión, input efectivo, persona o rol revisor.
gate.resultGate, umbral, valor medido, pass/fail.
run.completedSalida final, coste total, latencia total, estado final.

No hace falta guardar todo el texto en claro si hay datos sensibles. Puedes guardar hashes, resúmenes, IDs y muestras controladas. Pero si la traza no permite reconstruir la decisión, la evaluación será decorativa.

Para que un alumno de ingeniería lo implemente bien, conviene pensar en términos de OpenTelemetry:

CampoQué aportaError típico
trace_idUne toda la ejecución.Generar uno distinto por cada tool y perder la historia completa.
span_idIdentifica una operación concreta.Mezclar llamada de modelo, tool y gate en un mismo evento enorme.
parent_span_idReconstruye jerarquía.No saber qué tool nació de qué decisión.
nameNombra la operación.Usar nombres genéricos como call o step.
start_time, end_timeCalcula latencia por tramo.Medir solo latencia total.
attributesGuarda versión, modelo, tool, coste, tokens, policy, dataset.Esconder datos críticos dentro de texto libre.
eventsMarca momentos dentro del span.No distinguir “se pidió aprobación” de “se aprobó”.
statusResultado de la operación.No separar error recuperado de final correcto.
linksRelaciona trazas asíncronas.Perder trabajos en cola o handoffs entre agentes.

OpenTelemetry recalca que los spans comparten trace_id, que parent_id permite construir jerarquía, que los exporters envían trazas a un backend y que la propagación de contexto permite correlacionar spans generados en servicios distintos.8 En agentes esto es oro: si un router llama a un subagente y ese subagente llama a MCP, la evaluación debe poder seguir el hilo.

Métricas de trayectoria

Una trayectoria no es “buena” solo porque termine. Hay que mirar pasos y argumentos.

MétricaQué mideCuándo usarla
tool_sequence_matchSi las tools aparecen en el orden esperado.Flujos con orden obligatorio.
tool_set_matchSi se usaron las tools esperadas, sin importar orden.Recuperación de varias fuentes.
required_tool_recallProporción de tools obligatorias usadas.Evitar omisiones críticas.
extra_tool_rateTools no esperadas por caso.Controlar coste y ruido.
argument_matchCoincidencia de argumentos relevantes.APIs, búsquedas, acciones con scope.
observation_useSi la respuesta final usa observaciones reales.RAG, navegador, bases de datos.
stop_qualitySi paró por condición correcta.Evitar loops o cierres prematuros.

LangChain documenta evaluadores de trayectoria con modos como strict, unordered, subset y superset, además de evaluación con evaluador cuando la trayectoria correcta no es única.9 La idea práctica es muy buena: no todos los casos necesitan el mismo tipo de comparación.

ModoQué exigeEjemplo
strictMismo orden y mismas tools.lookup_policy antes de create_ticket.
unorderedMismas tools, orden libre.Buscar normativa y calendario.
subsetNo llamar tools fuera de la referencia.Caso de solo lectura.
supersetAl menos llamar las tools obligatorias.Puede consultar fuente extra si no publica.
RúbricaEvaluación cualitativa con criterios.“La trayectoria usa evidencia antes de concluir”.

Evaluar argumentos, no solo nombres de tools

Un error muy común: “ha llamado a la tool correcta, entonces bien”. No necesariamente. La tool correcta con argumentos malos puede ser peor que no llamarla.

CasoLlamada aparenteQué hay que evaluar
Búsquedasearch_source(query="paper")Query demasiado genérica, fecha, idioma, dominio, número de resultados.
RAGretrieve(k=20)k, filtro, namespace, score mínimo, diversidad, documento usado en la respuesta.
Base de datossql_query("SELECT *")Proyección, filtros, límites, coste, permisos, explain plan.
Códigorun_tests(command="npm test")Directorio, timeout, salida, cobertura del test ejecutado.
Escrituracreate_ticket(...)Campos obligatorios, idempotencia, recurso, owner y efecto persistente.

Para agentes, un contrato de tool debería tener cuatro capas:

CapaQué declara
SchemaTipos, campos obligatorios, enums y límites.
SemánticaQué significa cada campo y qué invariantes debe respetar.
EfectoSi lee, escribe, ejecuta, llama red, modifica estado o requiere aprobación.
ObservabilidadQué span, atributos y eventos debe emitir.

Esto conecta directamente con el capítulo 03: function calling no es solo “pasar JSON”. Es diseñar contratos que luego se puedan evaluar.

Calibrar evaluadores y rúbricas

Un evaluador automático puede ayudar, pero no debería entrar en producción sin control. Phoenix documenta evaluadores con salida estructurada mediante tool calling: el evaluador no devuelve texto libre, sino una etiqueta y una explicación parseables.10 Esa idea es muy importante: si el evaluador también improvisa formato, la evaluación se vuelve frágil.

ControlCómo se haceQué evita
Calibration set30-100 ejemplos puntuados por personas.Evaluador demasiado generoso o demasiado duro.
Rubric anchorsEjemplos de 0, 0.5 y 1 para cada criterio.Escalas ambiguas.
AgreementComparar evaluador contra criterio humano.Confiar en un evaluador que no replica el estándar.
Explanation requiredPedir motivo breve y estructurado.Scores sin diagnóstico.
Blind comparisonOcultar qué versión es baseline o candidate.Preferencias por nombre de modelo o versión.
Drift checkRepetir calibración al cambiar evaluador o modelo.Que el evaluador cambie sin que nos demos cuenta.

No todos los criterios necesitan evaluador. Si puedes validar con parser, test, schema, diff o cálculo, hazlo. El evaluador queda para lo semántico: utilidad, coherencia, suficiencia de evidencia o claridad.

Gates: pasar o no pasar

Un gate es una condición de paso. En agentes, conviene tener gates antes de publicar cambios, antes de activar una versión y durante ejecución.

Ejemplo de fórmula. Podemos escribir un gate de release así:

G=1[Syθy]1[Sτθτ]1[CCmax]1[L95Lmax]1[P=1]G = \mathbf{1}[S_y \ge \theta_y]\cdot \mathbf{1}[S_\tau \ge \theta_\tau]\cdot \mathbf{1}[C \le C_{\max}]\cdot \mathbf{1}[L_{95} \le L_{\max}]\cdot \mathbf{1}[P = 1]
SímboloSignificadoEjemplo
GGResultado del gate.1 pasa, 0 no pasa.
SyS_yScore de salida final.Al menos 0,85.
SτS_\tauScore de trayectoria.Al menos 0,90 en tools obligatorias.
CCCoste medio o p95.Menor o igual a 0,08 EUR por run.
L95L_{95}Latencia p95.Menor o igual a 8 segundos.
PPCumplimiento de permisos.1 si no hubo violación de policy.
θy,θτ\theta_y,\theta_\tauUmbrales mínimos.Decididos por el producto.

Si cualquier factor vale 0, el gate no pasa. Esto parece duro, pero es sano: no queremos compensar una violación de permisos con una respuesta bonita.

Coste por tarea aceptada

Ejemplo de fórmula. El coste más honesto no es coste por llamada. Es coste por tarea que realmente aceptarías.

CPA=i=1Nci+hii=1N1[Gi=1]CPA = \frac{\sum_{i=1}^{N} c_i + h_i}{\sum_{i=1}^{N} \mathbf{1}[G_i = 1]}
SímboloSignificadoEjemplo
CPACPACoste por tarea aceptada.0,19 EUR por caso que pasa.
NNNúmero de ejecuciones.200 runs.
cic_iCoste técnico de la ejecución ii.Modelo, tools, infraestructura.
hih_iCoste de revisión humana o corrección.Minutos convertidos a euros.
GiG_iGate de la ejecución ii.1 si pasa, 0 si no.

Un agente barato que falla mucho puede salir caro. Si 100 ejecuciones cuestan 5 EUR pero solo 20 pasan, el coste por aceptada es 0,25 EUR. Si otro sistema cuesta 8 EUR y pasan 80, el coste por aceptada baja a 0,10 EUR.

Incertidumbre: no confundas mejora con ruido

Los agentes son sistemas no deterministas. Aunque fijes temperatura, cambian dependencias, tools, latencia, documentos recuperados y, a veces, pequeñas decisiones internas. Por eso una suite de evaluación necesita repetición e intervalo, no solo un porcentaje bonito.

La tasa de paso básica es:

p^=kn\hat{p} = \frac{k}{n}
SímboloSignificado
kkRuns que pasan el gate.
nnRuns totales.
p^\hat{p}Estimación de tasa de paso.

Pero p^=0,90\hat{p}=0{,}90 no significa lo mismo con 10 casos que con 1.000. Para una estimación más honesta se puede usar un intervalo de Wilson:

IC=p^+z22n±zp^(1p^)n+z24n21+z2nIC = \frac{\hat{p}+\frac{z^2}{2n} \pm z\sqrt{\frac{\hat{p}(1-\hat{p})}{n}+\frac{z^2}{4n^2}}} {1+\frac{z^2}{n}}

No hace falta memorizar la fórmula. La idea práctica es esta: si la versión nueva pasa 19 de 20 y la antigua 18 de 20, quizá no has descubierto una mejora; quizá solo has visto variación. Si la nueva pasa 860 de 1.000 y la antigua 780 de 1.000, ya tienes más señal.

Ejemplo de fórmula. Para comparar versiones, escribe el gate como diferencia:

ΔS=ScandidateSbaseline\Delta S = S_{candidate} - S_{baseline}

Y decide tolerancias:

Gate de comparaciónEjemplo
CalidadΔSy0,01\Delta S_y \ge -0{,}01: no acepto perder más de 1 punto.
TrayectoriaΔSτ0\Delta S_\tau \ge 0: no acepto empeorar tool use.
CosteΔCp950,02\Delta C_{p95} \le 0{,}02: no acepto subir más de 2 céntimos en p95.
LatenciaΔLp95500ms\Delta L_{p95} \le 500ms: no acepto medio segundo extra sin mejora.
Estabilidadflake_rate <= 0.03: no acepto más de 3% de casos inestables.

Esto enseña una lección clave: evaluar agentes se parece más a hacer ingeniería experimental que a corregir un examen de opción única.

Benchmarks y por qué no bastan

Los benchmarks públicos son útiles para orientarse, pero no sustituyen tu dataset. AgentBench evalúa LLMs como agentes en varios entornos interactivos y muestra que actuar en entornos largos exige razonamiento, decisión y seguimiento de instrucciones más allá de responder texto.11 ToolLLM/ToolBench se centra en uso de APIs reales y construcción de datos para evaluar llamadas a herramientas.12 SWE-bench convirtió issues reales de GitHub en tareas de edición de repositorios, mostrando que resolver trabajo de software real exige entender contexto largo, modificar varios archivos y pasar tests.13

La lección: usa benchmarks para comparar familias de modelos o enfoques, pero decide con tu tráfico, tus tools, tus permisos y tus costes.

BenchmarkQué enseñaQué no decide por ti
AgentBenchAgentes en entornos interactivos.Tu policy, coste y UX.
ToolBenchUso de APIs y selección de tools.Tus schemas, permisos y datos.
SWE-benchTrabajo de código con repos reales.Tu producto si no es software engineering.
Evals propiasTu flujo, tus rutas y tus gates.Comparación global con el mercado.

Herramientas que conviene conocer

No hay una única herramienta definitiva. Para un alumno, lo valioso es entender qué pieza del sistema cubre cada una.

Herramienta o enfoqueQué aportaQué no sustituye
OpenAI Evals y trace gradingDatasets, graders, runs y evaluación de trazas de workflows.Tu diseño de casos y rúbricas.
Google ADK EvaluateTests de sesión, trayectoria, tool use y respuesta final en agentes ADK.Observabilidad general si tu sistema vive fuera de ADK.
LangChain/LangSmith evalsComparación de trayectorias, datasets y seguimiento de runs.Decisiones de producto sobre coste y riesgo.
PhoenixTrazas, evaluación con salidas estructuradas y análisis de comportamiento.Versionado completo del dataset si no lo diseñas.
PromptfooEvals configurables, assertions y casos para agentes de código.Arquitectura de permisos del agente.
OpenTelemetryModelo estándar de trazas, spans, exporters y propagación de contexto.Rúbricas semánticas o criterios de negocio.
pytest + scripts propiosControl total, CI sencillo y aprendizaje profundo.Dashboards y colaboración si el equipo crece.

Mi recomendación didáctica: empezar con scripts propios para entender las piezas, luego conectar una herramienta de trazas/evals cuando el volumen haga incómodo revisar a mano.

Anatomía visual de una suite de evaluación

Suite de evaluación de agentes como sistema de software No se mide solo la respuesta: se versiona el experimento, se reproduce la ejecución, se captura traza, se puntúa y se decide con gates. 1 · ARTEFACTOS VERSIONADOS Dataset dataset_version case_id + tags Prompt prompt_hash system + tools Modelo model_id params + seed Tools schemas timeouts + effects Policy scope approval rules Baseline agent-v2 candidate agent-v3 2 · REPLAY CONTROLADO Y AGENTE BAJO PRUEBA Replay harness misma entrada · fixtures · reloj fijo mocks · presupuestos · repeat N Sandbox FS aislado · red controlada credenciales ficticias · límites Agente bajo prueba planner router modelo tools Efectos observables salida final · artefacto tool calls · aprobaciones 3 · TRAZAS Y CONTRATO DE OBSERVABILIDAD Trace envelope trace_id · dataset_version agent_version · model_id Spans jerárquicos span_id · parent_span_id model.call · tool.call approval.request · gate.result status · attributes · events Trace store backend OTel o proveedor sampling · retention · privacy hashes para datos sensibles material para grader y depuración Replayable evidence mismo caso · misma versión · misma traza comparar baseline contra candidate 4 · EVALUADORES: SALIDA, TRAYECTORIA, CONTRATOS Y OPERACIÓN Final output exact · schema · rubric citations · artifact tests S_y Trajectory strict · subset · superset orden · argumentos · retries S_tau Contracts tool schema · policy idempotencia · efectos S_p Ops coste · tokens · p95 timeouts · flake rate C_n · L_n · S_o Judge check calibración · anchors agreement · explicación rubric_score Scores S = wS - cost CPA = cost/pass Delta vs baseline 5 · GATES DE CI/CD Y DECISIÓN OPERATIVA PR gate Nightly suite Prepublicación Canary Online sampled eval Publicar solo si pasan calidad, trayectoria, contratos, coste, latencia, trazabilidad e incertidumbre. feedback: todo fallo importante vuelve al dataset IA para gente curiosa / Facsímil 05 / Capítulo 10 / 686f6c61

La figura ya no enseña una cadena bonita, sino una arquitectura: artefactos versionados, replay aislado, agente bajo prueba, trazas, evaluadores separados, estadística, gates y bucle de regresión. Si mezclamos todo en una nota única, no sabremos si falló la respuesta, la tool, el permiso, el coste, la ruta, la observabilidad o la comparación contra baseline.

Cómo diseñaría gates por entorno

No todos los gates viven en el mismo sitio. Un gate de PR debe ser rápido; uno nocturno puede ser más caro; uno de canary mira tráfico real; uno de runtime protege la ejecución concreta.

MomentoEntradaGateUmbral típicoQué bloquea
DesarrolloUnit y contract tests.contract_gate.100% schemas y tools críticas.Cambios que rompen contratos.
Pull requestGolden set corto.pr_eval_gate.Sin regresiones P0/P1.Prompts o routers que rompen casos básicos.
NightlySuite completa repetida.stability_gate.flake_rate <= 3%.Versiones inestables o dependientes del azar.
PrepublicaciónBaseline contra candidate.release_gate.Mejora o empate dentro de tolerancia.Versiones más caras, lentas o peores.
CanaryMuestra pequeña de tráfico real.canary_gate.p95, coste y fallos dentro de SLO.Despliegue amplio si sube el fallo.
RuntimeCada ejecución.policy_budget_gate.Scope y presupuesto válidos.Acciones fuera de permiso o presupuesto.
Revisión humanaAcciones persistentes.approval_gate.Decisión explícita y trazable.Efectos persistentes sin revisión.

La cultura sana no es “bloquear por bloquear”. Es saber qué evidencia falta para avanzar. En un equipo maduro, cada gate tiene dueño, umbral, razón, caducidad y plan de actuación cuando falla.

Manos a la obra

Práctica: evaluar trazas de agentes.

Kit ejecutable de este capítulo: kit descargable.

# Descomprime el ZIP del capítulo y ejecuta estos comandos dentro de esa carpeta
python3 ops/run_f5_practices.py --chapter c10 --write --fail-on-invalid

Vamos a construir un evaluador pequeño, sin dependencias externas. El objetivo no es competir con OpenAI Evals, ADK, LangSmith o Phoenix. Es entender la mecánica: caso esperado, run real, métricas de salida, trayectoria, permisos, coste, latencia y gate final.

Para que esto sea útil de verdad en ingeniería, el evaluador debería generar un artefacto que pueda leer una CI: JSON estable, exit code, diff contra baseline y lista de casos que pasan a regresión.

from __future__ import annotations

from dataclasses import dataclass, asdict
from typing import Literal
import json


EventType = Literal[
    "route.decision",
    "model.call",
    "tool.call",
    "tool.result",
    "approval.request",
    "approval.result",
    "gate.result",
    "run.completed",
]


@dataclass(frozen=True)
class Event:
    type: EventType
    name: str
    data: dict


@dataclass(frozen=True)
class EvalCase:
    case_id: str
    input_text: str
    final_must_contain: list[str]
    required_tools: list[str]
    forbidden_tools: list[str]
    required_approval: bool
    max_cost_eur: float
    max_latency_ms: int
    min_final_score: float = 0.80
    min_trajectory_score: float = 0.90


@dataclass(frozen=True)
class AgentRun:
    case_id: str
    agent_version: str
    final_output: str
    events: list[Event]
    total_cost_eur: float
    latency_ms: int


def tool_calls(run: AgentRun) -> list[Event]:
    return [event for event in run.events if event.type == "tool.call"]


def tool_names(run: AgentRun) -> list[str]:
    return [event.name for event in tool_calls(run)]


def ordered_subsequence_score(expected: list[str], actual: list[str]) -> float:
    if not expected:
        return 1.0

    cursor = 0
    matched = 0
    for tool in actual:
        if cursor < len(expected) and tool == expected[cursor]:
            cursor += 1
            matched += 1
    return matched / len(expected)


def final_answer_score(case: EvalCase, run: AgentRun) -> float:
    output = run.final_output.lower()
    hits = sum(1 for fragment in case.final_must_contain if fragment.lower() in output)
    return hits / max(len(case.final_must_contain), 1)


def trajectory_score(case: EvalCase, run: AgentRun) -> tuple[float, list[str]]:
    actual = tool_names(run)
    notes: list[str] = []

    required_score = ordered_subsequence_score(case.required_tools, actual)
    extra_forbidden = sorted(set(actual) & set(case.forbidden_tools))
    if extra_forbidden:
        notes.append(f"tools no permitidas: {extra_forbidden}")

    missing = [tool for tool in case.required_tools if tool not in actual]
    if missing:
        notes.append(f"faltan tools: {missing}")

    extra_penalty = min(
        len([tool for tool in actual if tool not in case.required_tools]) * 0.10,
        0.30,
    )
    forbidden_penalty = 0.50 if extra_forbidden else 0.0
    score = max(0.0, required_score - extra_penalty - forbidden_penalty)
    return round(score, 3), notes


def approval_score(case: EvalCase, run: AgentRun) -> tuple[float, list[str]]:
    requested = any(event.type == "approval.request" for event in run.events)
    resolved = any(event.type == "approval.result" for event in run.events)

    if case.required_approval and not requested:
        return 0.0, ["faltó approval.request"]
    if case.required_approval and requested and not resolved:
        return 0.5, ["approval sin resolución"]
    if not case.required_approval and requested:
        return 0.8, ["pidió aprobación innecesaria"]
    return 1.0, []


def trace_completeness(run: AgentRun) -> tuple[float, list[str]]:
    required = {"route.decision", "run.completed"}
    present = {event.type for event in run.events}

    notes: list[str] = []
    for missing in sorted(required - present):
        notes.append(f"falta evento {missing}")

    for call in tool_calls(run):
        has_result = any(
            event.type == "tool.result" and event.name == call.name
            for event in run.events
        )
        if not has_result:
            notes.append(f"tool sin resultado: {call.name}")

    if not notes:
        return 1.0, []
    return max(0.0, 1.0 - 0.20 * len(notes)), notes


def evaluate(case: EvalCase, run: AgentRun) -> dict:
    final_score = final_answer_score(case, run)
    traj_score, traj_notes = trajectory_score(case, run)
    approval, approval_notes = approval_score(case, run)
    trace_score, trace_notes = trace_completeness(run)

    cost_pass = run.total_cost_eur <= case.max_cost_eur
    latency_pass = run.latency_ms <= case.max_latency_ms
    final_pass = final_score >= case.min_final_score
    trajectory_pass = traj_score >= case.min_trajectory_score
    approval_pass = approval == 1.0
    trace_pass = trace_score >= 0.95

    gate_pass = all([
        final_pass,
        trajectory_pass,
        approval_pass,
        trace_pass,
        cost_pass,
        latency_pass,
    ])

    return {
        "case_id": case.case_id,
        "agent_version": run.agent_version,
        "scores": {
            "final": round(final_score, 3),
            "trajectory": traj_score,
            "approval": approval,
            "trace": round(trace_score, 3),
        },
        "budget": {
            "cost_eur": run.total_cost_eur,
            "cost_pass": cost_pass,
            "latency_ms": run.latency_ms,
            "latency_pass": latency_pass,
        },
        "gate_pass": gate_pass,
        "notes": traj_notes + approval_notes + trace_notes,
        "actual_tools": tool_names(run),
    }


case = EvalCase(
    case_id="f5c10-001",
    input_text="Comprueba una cita, valida formato APA y prepara respuesta.",
    final_must_contain=["autor", "año", "url"],
    required_tools=["search_source", "validate_apa"],
    forbidden_tools=["publish_page"],
    required_approval=False,
    max_cost_eur=0.08,
    max_latency_ms=6000,
)

good_run = AgentRun(
    case_id="f5c10-001",
    agent_version="agent-v2",
    final_output="Referencia revisada: incluye autor, año y URL verificada.",
    total_cost_eur=0.041,
    latency_ms=4200,
    events=[
        Event("route.decision", "reference_flow", {"route": "mcp.biblioteca"}),
        Event("tool.call", "search_source", {"query": "paper citado"}),
        Event("tool.result", "search_source", {"documents": 2}),
        Event("tool.call", "validate_apa", {"style": "APA7"}),
        Event("tool.result", "validate_apa", {"valid": True}),
        Event("run.completed", "final", {"status": "ok"}),
    ],
)

bad_run = AgentRun(
    case_id="f5c10-001",
    agent_version="agent-v3",
    final_output="La referencia parece correcta.",
    total_cost_eur=0.132,
    latency_ms=9100,
    events=[
        Event("route.decision", "reference_flow", {"route": "freeform"}),
        Event("tool.call", "search_source", {"query": "paper citado"}),
        Event("tool.result", "search_source", {"documents": 1}),
        Event("tool.call", "publish_page", {"path": "/fasciculo-05"}),
        Event("run.completed", "final", {"status": "ok"}),
    ],
)

for run in [good_run, bad_run]:
    print(json.dumps(evaluate(case, run), indent=2, ensure_ascii=False))

Salida esperada, resumida:

agent-v2 -> gate_pass: true
agent-v3 -> gate_pass: false
  notas: faltan tools, tools no permitidas, coste y latencia fuera de presupuesto

Fíjate en algo importante: agent-v3 podría sonar aceptable si solo leemos la frase final. Pero falla al mirar trayectoria, tool no permitida, coste y latencia. Esa es la diferencia entre revisar una respuesta y evaluar un agente.

En una entrega universitaria yo pediría que este script evolucionara a:

EntregaQué debe demostrar
eval_cases/*.jsonCasos versionados con why_it_exists, tags y umbrales.
traces/*.jsonlUna línea por evento o span, con trace_id y parent_span_id.
run_eval.pyEjecuta baseline y candidate con el mismo dataset.
report.jsonScores, gates, CPA, p95, casos inestables y regresiones.
pytestLa CI falla si aparece una regresión P0/P1.
README.mdExplica qué mide la suite y qué no mide.

La nota no debería premiar solo que el agente “conteste”. Debería premiar que el alumno pueda explicar qué se midió, por qué, con qué límites y qué decisión tomaría con esos datos.

Cómo encaja todo

flowchart TD
  subgraph F5C10["Capítulo 10 · Evaluar agentes"]
    Dataset["Dataset de evaluación"]
    Replay["Replay harness"]
    Trace["Traza completa"]
    FinalEval["Evaluación de salida"]
    TrajEval["Evaluación de trayectoria"]
    PolicyEval["Evaluación de permisos"]
    OpsEval["Evaluación operativa"]
    Gate["Gate de release"]
    Regression["Casos de regresión"]
  end

  subgraph Antes["Capítulos anteriores"]
    State["Estado, acción y observación (F5 C02)"]
    Tools["Contratos de tools (F5 C03)"]
    Harness["Harness y trazas (F5 C06)"]
    Permissions["Permisos y aprobación (F5 C08)"]
    Routing["Routing, MCP y A2A (F5 C09)"]
  end

  subgraph Despues["Cierre"]
    Recap["Recapitulación (F5 C11)"]
    Lab["Laboratorio de agentes (F5 C11)"]
    Ops["Construir y operar (F6)"]
  end

  State -->|"define eventos de"| Trace
  Tools -->|"define tool calls para"| TrajEval
  Harness -->|"captura"| Trace
  Permissions -->|"alimenta"| PolicyEval
  Routing -->|"aporta route decisions"| Trace
  Dataset --> Replay
  Replay --> Trace
  Trace --> FinalEval
  Trace --> TrajEval
  Trace --> PolicyEval
  Trace --> OpsEval
  FinalEval --> Gate
  TrajEval --> Gate
  PolicyEval --> Gate
  OpsEval --> Gate
  Gate -->|"si falla"| Regression
  Gate -->|"si pasa"| Recap
  Regression --> Dataset
  Gate --> Lab
  OpsEval --> Ops

  classDef chapter fill:#ffffff,stroke:#111111,color:#111111,stroke-width:1.4px;
  classDef external fill:#f7f7f7,stroke:#777777,color:#111111,stroke-width:1.1px,stroke-dasharray: 5 4;
  class Dataset,Replay,Trace,FinalEval,TrajEval,PolicyEval,OpsEval,Gate,Regression chapter;
  class State,Tools,Harness,Permissions,Routing,Recap,Lab,Ops external;

Vocabulario aprendido

TérminoDefinición útil
RunEjecución concreta de un caso por una versión de agente.
Trace gradingEvaluación estructurada de una traza completa.
Golden setCasos pequeños y estables que protegen lo esencial.
RegresiónAlgo que antes pasaba y ahora falla.
Trajectory matchComparación entre pasos reales y pasos esperados.
RúbricaLista de criterios observables para puntuar una respuesta o trayectoria.
GateCondición que permite o detiene una versión, acción o ejecución.
Coste por tarea aceptadaCoste total dividido por runs que pasan criterios.
p95Percentil 95: valor que deja por debajo al 95% de ejecuciones.
Replay harnessSistema que reproduce casos con versiones controladas.
Dataset versionIdentificador del conjunto de casos usado en una evaluación.
Trace completenessGrado en que la traza contiene los eventos necesarios para explicar la run.
Oracle problemDificultad de saber cuál es la salida correcta cuando hay varias respuestas válidas.
Flake rateProporción de casos que pasan unas veces y fallan otras sin cambio de código.
BaselineVersión de referencia contra la que comparas una candidata.
CandidateVersión nueva que quieres aceptar o descartar.
Calibration setCasos puntuados por personas para comprobar si un evaluador automático se comporta bien.
Metamorphic testingPruebas basadas en propiedades que deben mantenerse al transformar la entrada.

Dónde solía tropezar yo

TropiezoPor qué ocurreAntídoto
Evaluar solo la respuesta finalEs lo más rápido de leer.Puntuar salida, trayectoria, permisos, coste y latencia.
No guardar dataset versionadoLos casos cambian y ya no comparas lo mismo.Versionar dataset, prompt, modelo, tools y policy.
Convertir el evaluador en verdad absolutaUna nota generada parece objetiva.Usar rúbrica, calibración y casos con respuesta conocida.
Ignorar coste humanoEl modelo parece barato pero exige mucha corrección.Medir coste por tarea aceptada, incluyendo revisión.
No mirar argumentos de toolsLa tool correcta puede llamarse con parámetros malos.Evaluar tool, orden y argumentos relevantes.
Usar un benchmark como decisión finalDa sensación de rigor externo.Combinar benchmark público con eval propia del producto.
No meter fallos corregidos en regresiónSe repiten problemas viejos.Cada fallo importante crea un caso nuevo.
No repetir runsUna ejecución aislada parece concluyente.Medir repeat, flake_rate e intervalos.
No separar contrato de semánticaEl JSON válido parece suficiente.Validar schema, significado, efectos y observabilidad.
No definir baselineLa versión nueva se evalúa en el vacío.Comparar siempre contra una referencia estable.

Antes de pasar página

Antes del cierre del facsímil, deberías poder responder:

PreguntaSi dudas, vuelve a...
¿Por qué un agente puede acertar por el camino equivocado?Un agente puede acertar por el camino equivocado.
¿Qué contiene una run evaluable?La definición útil.
¿Qué diferencia hay entre salida final, trayectoria y operación?Qué mirar: salida, trayectoria y operación.
¿Cómo se parece una suite de agentes a una suite de ingeniería del software?Pirámide de pruebas para agentes.
¿Qué haces cuando no hay una salida única esperada?El problema del oráculo.
¿Qué casos debería tener un dataset inicial?El dataset: pequeño, vivo y con intención.
¿Qué eventos mínimos necesita una traza?Trazas: el material que se evalúa.
¿Qué métricas usarías para evaluar tool calls?Métricas de trayectoria.
¿Por qué hay que mirar argumentos de tools?Evaluar argumentos, no solo nombres de tools.
¿Cómo controlas que un evaluador automático no sea una caja negra nueva?Calibrar evaluadores y rúbricas.
¿Por qué un gate no debería compensar permisos con buena respuesta?Gates: pasar o no pasar.
¿Cómo calculas coste por tarea aceptada?Coste por tarea aceptada.
¿Cómo evitas confundir una mejora real con variación?Incertidumbre: no confundas mejora con ruido.
¿Qué aportan benchmarks como AgentBench o SWE-bench?Benchmarks y por qué no bastan.
¿Qué herramienta usarías según la pieza que quieras evaluar?Herramientas que conviene conocer.
¿Cómo construirías un evaluador mínimo sin depender de proveedor?Manos a la obra.

Para saber más

En resumen

IdeaQué te llevas
Un agente se evalúa por más que su respuesta.Hay que mirar salida final, trayectoria, permisos, coste, latencia y trazabilidad.
Una suite de evaluación es software.Dataset, prompt, modelo, tools, policy y baseline deben estar versionados.
Las trazas son el material de evaluación.Sin eventos estructurados no puedes saber dónde falló ni comparar versiones.
Los gates convierten métricas en decisión.Una versión pasa solo si cumple calidad, trayectoria, coste y policy.
El oráculo no siempre es exacto.Combina exact match, schemas, propiedades, rúbricas, evaluadores calibrados y revisión humana.
El coste real se mide por tarea aceptada.Un sistema barato por llamada puede salir caro si falla mucho o exige corrección.
La estadística importa.Repite runs, mide inestabilidad y compara candidate contra baseline con tolerancias.
Cada fallo importante alimenta el dataset.La evaluación mejora cuando las regresiones se convierten en casos permanentes.

Notas

  1. Google. (2026). Agent Development Kit: Why Evaluate Agents. https://adk.dev/evaluate/. Consultado el 10 de junio de 2026.

  2. OpenAI. (2026). Evaluate agent workflows. https://developers.openai.com/api/docs/guides/agent-evals. Consultado el 10 de junio de 2026.

  3. OpenAI. (2026). Trace grading. https://developers.openai.com/api/docs/guides/trace-grading. Consultado el 10 de junio de 2026.

  4. OpenAI. (2026). Evaluate agent workflows. https://developers.openai.com/api/docs/guides/agent-evals. Consultado el 10 de junio de 2026.

  5. Promptfoo. (2026). Evaluate Coding Agents. https://www.promptfoo.dev/docs/guides/evaluate-coding-agents/. Consultado el 10 de junio de 2026.

  6. OpenAI. (2026). Agents SDK: Tracing. https://openai.github.io/openai-agents-python/tracing/. Consultado el 10 de junio de 2026.

  7. OpenTelemetry. (2026). Traces. https://opentelemetry.io/docs/concepts/signals/traces/. Consultado el 10 de junio de 2026.

  8. OpenTelemetry. (2026). Traces. https://opentelemetry.io/docs/concepts/signals/traces/. Consultado el 10 de junio de 2026.

  9. LangChain. (2026). Agent Evals. https://docs.langchain.com/oss/python/langchain/test/evals. Consultado el 10 de junio de 2026.

  10. Arize Phoenix. (2026). LLM Evals. https://arize.com/docs/phoenix/evaluation/llm-evals. Consultado el 10 de junio de 2026.

  11. Liu, X. et al. (2024). AgentBench: Evaluating LLMs as Agents. International Conference on Learning Representations. https://doi.org/10.48550/arXiv.2308.03688. Consultado el 10 de junio de 2026.

  12. Qin, Y. et al. (2023). ToolLLM: Facilitating Large Language Models to Master 16000+ Real-World APIs. https://doi.org/10.48550/arXiv.2307.16789. Consultado el 10 de junio de 2026.

  13. Jimenez, C. E. et al. (2024). SWE-bench: Can Language Models Resolve Real-World GitHub Issues?. International Conference on Learning Representations. https://proceedings.iclr.cc/paper_files/paper/2024/hash/edac78c3e300629acfe6cbe9ca88fb84-Abstract-Conference.html. Consultado el 10 de junio de 2026.

Capítulo 11

Facsímil 5 · Agentes y orquestación

Capítulo 11: Lo que deberías saber: agentes y orquestación

Cerrar el facsímil sin cerrar la pregunta

Este facsímil empezó con una duda práctica: ¿cuándo merece la pena llamar agente a un sistema y cuándo basta con un prompt, una función o una interfaz más clara?

La respuesta no era una etiqueta. Era una arquitectura. Un agente útil no es “un modelo con herramientas”. Es un sistema que mantiene estado, decide acciones, observa resultados, respeta permisos, registra trazas, puede pedir revisión, se integra con otros sistemas y se evalúa con casos repetibles.

Si has entendido el facsímil, deberías poder mirar una demo de agente y preguntar: qué estado conserva, qué tools puede llamar, qué efecto tienen, qué permisos gobiernan esas acciones, cómo se recupera si algo falla, qué traza deja, qué coste tiene y qué gate decide si una versión avanza.

Fecha de corte y alcance

Fecha de corte: 10 de junio de 2026.
Alcance: este cierre resume conceptos estables del facsímil: agente como bucle estado-acción-observación, tools como contratos, memoria como sistema separado del prompt, harness, permisos, SDKs, MCP, A2A, routing y evaluación de trayectorias.

Los nombres de SDKs, modelos, APIs, precios y protocolos se moverán. Lo que queremos conservar es más estable: un agente es software con decisiones observables. Por tanto, se diseña, se versiona, se prueba, se mide y se opera.

La frase que resume el facsímil

Ejemplo de fórmula. La forma más compacta de decirlo sería:

A=M+S+T+P+H+O+EA = M + S + T + P + H + O + E
SímboloSignificadoEjemplo
AAAgente operable.Asistente que revisa una cita, consulta fuente, valida APA y pide aprobación si publica.
MMModelo.LLM que interpreta instrucciones y genera decisiones.
SSEstado.Conversación, memoria, plan, tareas pendientes, contexto compacto.
TTTools.Buscar fuente, validar formato, crear ticket, leer base de datos.
PPPermisos.Qué puede leer, escribir, ejecutar o pedir a una persona.
HHHarness.Entorno que limita, ejecuta, observa y captura trazas.
OOOrquestación.Routing, handoffs, MCP, A2A, workflows y subagentes.
EEEvaluación.Dataset, trayectorias, coste, latencia, gates y regresiones.

La fórmula no pretende ser matemática profunda. Sirve como recordatorio: si falta una pieza, la palabra “agente” puede estar escondiendo una demo frágil.

Lo que ya no deberías confundir

ConfusiónForma precisa de decirlo
“Un agente es un LLM que responde”.Un agente observa, decide, actúa, registra y vuelve a decidir.
“Memoria es meter más contexto”.Memoria implica qué se guarda, cuándo, dónde, con qué permisos y cómo se recupera.
“Una tool es una función cualquiera”.Una tool tiene schema, semántica, efecto, permisos, errores y observabilidad.
“El SDK es la arquitectura”.El SDK implementa patrones; la arquitectura la decides tú.
“Si pasa la demo, funciona”.Funciona si pasa casos, trazas, coste, permisos, latencia y regresiones.
“Orquestar es llamar muchos agentes”.Orquestar es decidir ruta, contrato, handoff, responsabilidad y evaluación.

Wooldridge y Jennings definían los agentes como sistemas situados en un entorno, capaces de actuar de forma autónoma para cumplir objetivos.1 Ese vocabulario clásico sigue siendo útil, pero ahora lo aterrizamos en modelos, tools, trazas y sistemas distribuidos.

Lo que faltaría si lo revisa un ingeniero informático

Para una persona de ingeniería informática, el facsímil no debería cerrar con “ya sé qué es un agente”. Debería cerrar con una lista de condiciones para llevarlo a un sistema mantenible. Un agente productivo se parece menos a una conversación y más a un servicio distribuido que llama otros servicios, mantiene estado, falla parcialmente, consume presupuesto y deja evidencia.

Tema que añadiría al cierrePregunta que debe responderPor qué importa
Máquina de estados¿En qué estados puede estar una run y qué transiciones son válidas?Evita flujos implícitos imposibles de depurar.
Idempotencia¿Qué pasa si llega dos veces la misma petición?Evita duplicar tickets, publicaciones, cobros o acciones persistentes.
Semántica de reintentos¿Qué errores se reintentan, cuántas veces y con qué espera?Un retry mal diseñado multiplica coste y efectos.
Timeouts y cancelación¿Cuándo se aborta una tool o una run completa?Sin límites, el sistema se atasca y consume recursos.
Colas y backpressure¿Qué ocurre si llegan más tareas de las que podemos atender?Protege latencia y evita saturar tools externas.
Versionado de contratos¿Qué versión de prompt, tool, schema, policy y dataset produjo esta salida?Sin versión no hay rollback ni comparación honesta.
Consistencia del estado¿Qué se guarda antes y después de cada acción?Evita que una traza diga una cosa y la base de datos otra.
Observabilidad estándar¿Hay trace_id, span_id, atributos, eventos y propagación de contexto?Permite seguir una run aunque atraviese varios servicios.
SLO y presupuesto¿Cuál es el p95 aceptable, coste máximo y tasa de fallo tolerable?Sin objetivos no hay operación, solo impresiones.
CI/CD de agentes¿Qué evals bloquean un PR, una nightly o una publicación?Convierte “parece mejor” en decisión revisable.

OpenTelemetry define APIs para crear spans y trazas, y W3C Trace Context estandariza cómo propagar contexto entre servicios.23 En un agente, esto significa que una decisión del router, una llamada al modelo, una tool MCP, una cola de revisión y un gate de evaluación pueden formar parte de la misma historia técnica.

El versionado semántico ayuda a distinguir cambios compatibles de cambios que rompen contrato.4 En agentes, no solo versionamos librerías: versionamos prompts, tools, policies, datasets, modelos y formatos de traza.

La ingeniería de ML ya avisaba de una deuda técnica específica: sistemas con modelos pueden esconder dependencias, configuraciones, datos y comportamiento de difícil mantenimiento.5 Amershi y colaboradores muestran que desarrollar sistemas de ML exige prácticas de ingeniería distintas a las de software clásico, especialmente por datos, experimentación y evaluación continua.6 TFX es un buen ejemplo histórico de plataforma pensada para producción: no basta entrenar o ejecutar; hay que validar datos, modelos y despliegues.7

Recapitulación activa por capítulos

Esta tabla no es un índice. Es una prueba rápida de criterio. Si no puedes explicar la columna derecha, vuelve al capítulo correspondiente.

CapítuloQué deberías poder defenderPregunta de control
01Elegir entre prompt, workflow y agente.¿Hay bucle, estado, tools y objetivo o solo una respuesta?
02Describir estado, acción, observación y política.¿Qué cambia después de cada paso?
03Diseñar tools con contratos operativos.¿Qué argumentos, errores, efectos y permisos tiene la tool?
04Separar contexto, memoria, compaction y handoff.¿Qué se guarda y qué se vuelve a pasar al modelo?
05Elegir arquitectura agentic según tarea.¿ReAct, planificador, workflow, multiagente o grafo?
06Construir harness con límites, sensores y trazas.¿Cómo se observa y reproduce una ejecución?
07Integrar SDKs sin delegar el diseño.¿Qué es portable y qué es específico del proveedor?
08Diseñar permisos y revisión humana.¿Qué acciones requieren aprobación y por qué?
09Orquestar routing, MCP, A2A y workflows.¿Quién decide, quién actúa y qué contrato viaja?
10Evaluar trayectoria, coste y gates.¿Cómo sabes que la versión nueva mejora de verdad?

ReAct popularizó el patrón de intercalar razonamiento y acciones observables con tools, en vez de producir una respuesta de una sola vez.8 ToolLLM mostró la importancia de enseñar y evaluar uso de APIs reales en modelos de lenguaje.9

Mapa visual del facsímil

Sistema de agentes visto por ingeniería informática Un agente publicable se diseña como servicio: contratos, estado, colas, permisos, trazas, evaluación y operación. 1 · CONTRATO DE PRODUCTO Y ARQUITECTURA Requisitos tarea · usuario · efecto ADR por qué agente y no workflow SLO p95 · coste · tasa de fallo Contratos schema · SemVer · policy Dataset de aceptación golden · regresión · trazas 2 · RUNTIME AGENTIC COMO MÁQUINA DE ESTADOS API boundary request_id idempotency_key Ingress queue prioridad · backpressure rate limit y cancelación Run state machine CREATED → PLANNING → TOOL_CALLING → WAITING_APPROVAL → COMPLETED → FAILED / CANCELLED estado persistido transiciones válidas Planner / Router workflow · ReAct · grafo elige ruta y presupuesto Policy engine scope · permisos · aprobación gate antes del efecto Memory / context working · episodic · semantic compaction y recuperación Tool dispatcher timeouts · retries · circuit adaptadores y contratos Sistemas externos MCP tools A2A agents DB / SQL RAG / vector store tickets / docs todo efecto debe trazarse Output contract respuesta estructurada · artefacto · decisión · next_state · trace_id · coste 3 · OBSERVABILIDAD Y EVALUACIÓN Trace context trace_id · span_id parent_span_id propagado entre servicios Telemetry store logs · metrics · traces coste · tokens · latencia retención y muestreo Eval runner baseline vs candidate trayectoria y contrato repeat y flake rate Release gates quality · policy · budget latencia · trazabilidad PR · nightly · canary Regression loop fallo → caso nuevo dataset versionado rollback si empeora 4 · OPERACIÓN Y GOBIERNO DEL CAMBIO CI/CD tests · evals · gates Runtime SLO p95 · coste · errores Config registry prompt · model · policy Audit log quién · qué · cuándo Criterio final: si no puedes reproducirlo, limitarlo, observarlo y evaluarlo, todavía no es ingeniería. IA para gente curiosa / Facsímil 05 / Capítulo 11 / 686f6c61

La decisión técnica: de tarea a arquitectura

Cuando alguien pide “un agente”, la respuesta profesional no es sí o no. Es ordenar la decisión.

PreguntaSi la respuesta es síSi la respuesta es no
¿La tarea requiere varios pasos?Puede tener sentido un workflow o agente.Empieza por prompt, función o interfaz.
¿Hay tools con efectos reales?Diseña contrato, permiso y traza.No vendas autonomía que no existe.
¿El sistema necesita recordar algo?Separa memoria, contexto y almacenamiento.No metas historial infinito en prompt.
¿Hay varias rutas posibles?Añade routing explícito y métrica de ruta.Mantén camino simple y evaluable.
¿Puede afectar a datos o personas?Añade approval y gate de runtime.Aun así registra la decisión.
¿Puedes evaluar la trayectoria?Versiona dataset y traces.Todavía estás en fase exploratoria.

Smith ya describió el Contract Net Protocol como coordinación distribuida entre participantes que anuncian tareas, reciben propuestas y asignan trabajo.10 La idea resuena con sistemas modernos: incluso cuando usamos LLMs, coordinar trabajo exige contratos y responsabilidad.

Jennings, Sycara y Wooldridge insistían en que la investigación de agentes no iba solo de piezas aisladas, sino de coordinación, interacción, entornos y metodologías de desarrollo.11 Esa advertencia encaja perfectamente con este facsímil: si un agente moderno no deja claro cómo coordina, cómo observa y cómo se evalúa, todavía no está bien diseñado.

Cómo encaja todo

flowchart TD
  subgraph F5["Facsímil 05 · Agentes y orquestación"]
    Decision["Decidir si hace falta agente"]
    Loop["Bucle estado-acción-observación"]
    Tools["Tools con contrato operativo"]
    Memory["Contexto, memoria y handoff"]
    Architecture["Arquitectura agentic"]
    Harness["Harness y trazas"]
    SDK["SDKs y proveedores"]
    Permissions["Permisos y aprobación"]
    Orchestration["Routing, MCP y A2A"]
    Evaluation["Evaluación y gates"]
    Lab["Laboratorio final"]
  end

  subgraph Antes["Facsímiles anteriores"]
    LLM["LLMs y arquitectura (F3)"]
    API["APIs, RAG y herramientas (F4)"]
    Search["Búsqueda y planificación (F2)"]
  end

  subgraph Despues["Lo que viene"]
    Ops["Construir y operar (F6)"]
    Metrics["Evaluar y calibrar (F7)"]
    Governance["Privacidad y gobernanza (F9)"]
  end

  LLM -->|"aportar modelo a"| Decision
  API -->|"aportar tools y RAG a"| Tools
  Search -->|"aportar planificación a"| Architecture
  Decision -->|"si procede, activar"| Loop
  Loop -->|"invocar"| Tools
  Loop -->|"actualizar"| Memory
  Tools -->|"exigir"| Permissions
  Memory -->|"habilitar"| Architecture
  Architecture -->|"ejecutarse dentro de"| Harness
  SDK -->|"implementar"| Harness
  Permissions -->|"limitar"| Orchestration
  Orchestration -->|"producir trazas para"| Evaluation
  Harness -->|"capturar evidencia para"| Evaluation
  Evaluation -->|"alimentar"| Lab
  Evaluation -->|"preparar"| Ops
  Evaluation -->|"profundizar en"| Metrics
  Permissions -->|"conectar con"| Governance

  classDef chapter fill:#ffffff,stroke:#111111,color:#111111,stroke-width:1.4px;
  classDef external fill:#f7f7f7,stroke:#777777,color:#111111,stroke-width:1.1px,stroke-dasharray: 5 4;
  class Decision,Loop,Tools,Memory,Architecture,Harness,SDK,Permissions,Orchestration,Evaluation,Lab chapter;
  class LLM,API,Search,Ops,Metrics,Governance external;

El mapa ya no resume capítulos: muestra una arquitectura de sistema. La parte superior obliga a justificar requisitos, ADR, SLO, contratos y dataset de aceptación. La zona central modela el agente como runtime con estado, cola, idempotencia, router, policy engine, memoria, dispatcher y sistemas externos. La parte inferior aterriza lo que un equipo de ingeniería necesita para operar: trazas, métricas, evals, gates, CI/CD, configuración versionada y auditoría.

Vocabulario aprendido

TérminoDefinición útil
Agente operableSistema agentic que se puede limitar, observar, reproducir y evaluar.
Tool contractDescripción verificable de argumentos, semántica, efecto, errores y permisos.
HarnessEntorno que ejecuta el agente con límites, sensores, trazas y fixtures.
HandoffPaso explícito de contexto y responsabilidad entre componentes o agentes.
MCPProtocolo para exponer tools y contexto a modelos o agentes mediante contrato externo.
A2APatrón/protocolo para que un agente o sistema delegue trabajo en otro que también decide.
Approval gatePunto donde una persona debe revisar una acción antes de que tenga efecto.
Trace gradingEvaluación estructurada de una traza, no solo de una respuesta final.
BaselineVersión de referencia contra la que comparas una candidata.
RegresiónEmpeoramiento de un comportamiento que antes funcionaba.
IdempotenciaPropiedad por la que repetir una petición no duplica el efecto.
BackpressureMecanismo para frenar entrada cuando el sistema no puede procesar más sin degradarse.
SLOObjetivo operativo medible, como p95 de latencia o coste máximo por run aceptada.
ADRRegistro breve de una decisión de arquitectura y sus razones.
Trace contextInformación propagada para correlacionar spans de una misma ejecución.

El Model Context Protocol define una forma estándar de conectar aplicaciones de IA con tools y datos externos.12 A2A, por su parte, se orienta a comunicación entre agentes o sistemas agentic con tareas, mensajes y artefactos.13

La evaluación completa cierra el círculo. OpenAI Agents SDK organiza las ejecuciones en trazas y spans que permiten inspeccionar llamadas de modelo, tools, guardrails y handoffs.14 Google ADK separa evaluación de trayectoria/tool use y respuesta final.15 Promptfoo plantea los agentes de código como sistemas que deciden, actúan, observan y repiten, por lo que recomienda assertions de ruta, coste, latencia, permisos y repetición.16 AgentBench refuerza la misma idea desde benchmark académico: evaluar agentes exige entornos interactivos y no solo preguntas de texto.17

Dónde solía tropezar yo

TropiezoPor qué pasaAntídoto
Llamar agente a cualquier chatLa palabra suena avanzada y vende bien.Preguntar por estado, tools, acciones, observaciones y evaluación.
Diseñar tools sin pensar efectosEl schema parece suficiente.Añadir semántica, permisos, idempotencia, errores y traza.
Guardar memoria sin políticaParece útil recordarlo todo.Definir qué se guarda, por cuánto tiempo, quién lo ve y cómo se borra.
Elegir SDK antes de diseñar arquitecturaEl proveedor da una sensación de camino hecho.Escribir primero el contrato de run, tools, permisos y evaluación.
Añadir subagentes demasiado prontoMultiplica rutas y fallos antes de medir.Empezar con workflow simple y subir complejidad solo si hay evidencia.
Evaluar solo la respuesta finalEs lo que se ve en pantalla.Medir trayectoria, coste, latencia, permisos y trazas.
No cerrar el bucle de regresionesEl mismo fallo vuelve con otro nombre.Cada fallo importante crea caso permanente en el dataset.
No modelar estadosEl agente parece flexible, pero nadie sabe dónde puede quedarse atascado.Dibujar estados, transiciones válidas y salidas de error.
Olvidar idempotenciaUn reintento puede duplicar una acción persistente.Usar idempotency_key y registrar efectos antes de repetir.
No definir SLOTodo parece aceptable hasta que hay usuarios reales.Fijar p95, coste, tasa de fallo y presupuesto por ruta.

Antes de pasar página

Responde estas preguntas sin mirar el texto. Si alguna se te escapa, vuelve al capítulo indicado.

PreguntaVuelve a
¿Cuándo basta un prompt y cuándo aparece un agente?Capítulo 01.
¿Puedes explicar estado, acción, observación y política con un ejemplo?Capítulo 02.
¿Qué debe contener una tool para ser operable?Capítulo 03.
¿Qué diferencia hay entre contexto, memoria, compaction y handoff?Capítulo 04.
¿Qué arquitectura agentic elegirías para una tarea multi-paso y por qué?Capítulo 05.
¿Cómo harías reproducible una ejecución?Capítulo 06.
¿Qué no deberías delegar al SDK?Capítulo 07.
¿Qué acciones requieren aprobación humana?Capítulo 08.
¿Cuándo usarías MCP, A2A o un workflow local?Capítulo 09.
¿Cómo decidirías si una versión nueva puede publicarse?Capítulo 10.
¿Puedes dibujar la máquina de estados de una run?Lo que faltaría si lo revisa un ingeniero informático.
¿Qué harías para que una petición repetida no duplique efectos?Lo que faltaría si lo revisa un ingeniero informático.
¿Qué SLO usarías para operar el agente?Lo que faltaría si lo revisa un ingeniero informático.

En resumen

Idea fuerzaQué te llevas
Un agente es arquitectura, no etiqueta.Debe tener bucle, estado, acciones, observaciones y control.
Las tools son contratos, no simples funciones.Importan argumentos, errores, efectos, permisos y trazas.
La memoria exige gobierno.Guardar contexto sin política puede empeorar calidad, privacidad y coste.
La orquestación reparte responsabilidad.Routing, MCP, A2A y handoffs deben decir quién decide y qué viaja.
La autonomía se gradúa.No todo debe ejecutarse sin aprobación ni todo necesita revisión manual.
La evaluación mira trayectoria.Una respuesta final buena no basta si el camino fue caro, opaco o fuera de contrato.
La ingeniería aparece en los bordes.Idempotencia, colas, timeouts, SLO, versionado y trazas separan producto de demo.
El laboratorio convierte criterio en práctica.Construir, medir y justificar es la forma de comprobar que entendimos.

Para saber más

Google. (2026). Agent Development Kit: Evaluate. https://google.github.io/adk-docs/evaluate/

Google. (2026). Agent2Agent Protocol Specification. https://a2a-protocol.org/latest/specification/

Amershi, S. et al. (2019). Software Engineering for Machine Learning: A Case Study. International Conference on Software Engineering: Software Engineering in Practice, 291-300. https://doi.org/10.1109/ICSE-SEIP.2019.00042

Baylor, D. et al. (2017). TFX: A TensorFlow-Based Production-Scale Machine Learning Platform. Proceedings of KDD, 1387-1395. https://doi.org/10.1145/3097983.3098021

Jennings, N. R., Sycara, K. y Wooldridge, M. (1998). A Roadmap of Agent Research and Development. Autonomous Agents and Multi-Agent Systems, 1(1), 7-38. https://doi.org/10.1023/A:1010090405266

Liu, X. et al. (2024). AgentBench: Evaluating LLMs as Agents. International Conference on Learning Representations. https://doi.org/10.48550/arXiv.2308.03688

Model Context Protocol. (2026). Specification. https://modelcontextprotocol.io/specification

OpenAI. (2026). Agents SDK: Tracing. https://openai.github.io/openai-agents-python/tracing/

OpenTelemetry. (2026). Tracing API. https://opentelemetry.io/docs/specs/otel/trace/api/

Promptfoo. (2026). Evaluate Coding Agents. https://www.promptfoo.dev/docs/guides/evaluate-coding-agents/

Preston-Werner, T. (2026). Semantic Versioning 2.0.0. https://semver.org/

Qin, Y. et al. (2023). ToolLLM: Facilitating Large Language Models to Master 16000+ Real-World APIs. https://doi.org/10.48550/arXiv.2307.16789

Sculley, D. et al. (2015). Hidden Technical Debt in Machine Learning Systems. Advances in Neural Information Processing Systems. https://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems

Smith, R. G. (1980). The Contract Net Protocol: High-Level Communication and Control in a Distributed Problem Solver. IEEE Transactions on Computers, C-29(12), 1104-1113. https://doi.org/10.1109/TC.1980.1675516

W3C. (2021). Trace Context. https://www.w3.org/TR/trace-context/

Wooldridge, M. y Jennings, N. R. (1995). Intelligent Agents: Theory and Practice. The Knowledge Engineering Review, 10(2), 115-152. https://doi.org/10.1017/S0269888900008122

Yao, S. et al. (2023). ReAct: Synergizing Reasoning and Acting in Language Models. International Conference on Learning Representations. https://arxiv.org/abs/2210.03629

Laboratorio

Un laboratorio, dentro de este libro, es un espacio de práctica guiada. Aquí no buscamos una respuesta bonita. Buscamos construir algo pequeño que se pueda ejecutar, medir, explicar, corregir y defender.

En este laboratorio vamos a tocar los temas centrales del facsímil:

  • Del capítulo 01 al 03: decidir si hace falta agente y diseñar tools con contrato.
  • Del capítulo 04 al 06: gestionar estado, límites y trazas.
  • Del capítulo 07 al 09: pensar en SDKs, permisos, routing, MCP y A2A sin depender de una marca.
  • Del capítulo 10: evaluar trayectoria, coste, latencia y gates.

Los dos retos incluyen solución completa. No se trata de esconder la respuesta: se trata de enseñar cómo piensa alguien que quiere llevar una idea desde “parece que funciona” hasta “puedo explicarlo y medirlo”.

El kit real del laboratorio está en:

kit/

El texto del capítulo explica las piezas. El kit las convierte en entrega ejecutable: runtime con idempotencia, ledger de efectos, trazas, evaluación de routing, gate de CI y checker.

Reto 1: construir un runtime mínimo con estado e idempotencia

Contexto

Una escuela quiere un agente que reciba solicitudes internas. Algunas solo consultan información; otras preparan cambios con efecto persistente, como abrir un ticket de soporte o proponer una actualización de una guía. El problema no es que el modelo “entienda” la frase. El problema de ingeniería es otro: ¿qué ocurre si la petición llega dos veces, si una tool tarda demasiado, si hay que pedir aprobación o si queremos reconstruir lo ocurrido?

Este reto no busca un agente inteligente. Busca un runtime pequeño con piezas que un ingeniero informático reconocería: estado, clave de idempotencia, policy gate, tool dispatcher, traza y registro de efectos.

Objetivo

Construir un ejecutor mínimo que:

PiezaQué debe hacerCapítulo que sostiene la decisión
Máquina de estadosControlar transiciones de una run.Capítulo 02: estado, acción y observación.
IdempotenciaEvitar duplicar efectos si se repite una petición.Capítulo 06: harness y límites.
Policy gatePedir aprobación antes de una acción persistente.Capítulo 08: permisos y revisión humana.
Tool dispatcherLlamar tools con contrato y registrar resultado.Capítulo 03: tools.
TrazaDejar evidencia reproducible.Capítulo 10: evaluación de trayectoria.

Enunciado

  1. Define una petición con request_id, idempotency_key, texto y si tiene aprobación.
  2. Modela estados: CREATED, PLANNING, WAITING_APPROVAL, TOOL_CALLING, COMPLETED, DUPLICATE.
  3. Si la petición ya se procesó con la misma clave, no repitas la tool persistente.
  4. Si falta aprobación para una acción persistente, detén la run en WAITING_APPROVAL.
  5. Registra trazas con trace_id, spans y atributos.

En el kit se ejecuta así:

# Descomprime el ZIP del capítulo y ejecuta estos comandos dentro de esa carpeta
python3 ops/run_agent_runtime.py --write
python3 -m json.tool output/effect_ledger.json
cat output/runtime_decision.md

La salida esperada contiene una run completada, una petición duplicada que no repite efecto, una run detenida por aprobación pendiente y consultas de lectura con trazas reconstruibles.

Resolución paso a paso

Ejemplo de fórmula. Primero definimos la transición de estado:

st+1=δ(st,et,pt)s_{t+1} = \delta(s_t, e_t, p_t)
SímboloSignificadoEjemplo
sts_tEstado actual de la run.PLANNING.
ete_tEvento observado.approval.present o tool.completed.
ptp_tPolítica activa.“crear ticket requiere aprobación”.
δ\deltaFunción de transición.Pasa de PLANNING a WAITING_APPROVAL.
st+1s_{t+1}Estado siguiente.TOOL_CALLING si hay aprobación.

Ejemplo de fórmula. Después fijamos la propiedad de idempotencia:

effects(idempotency_key)1\operatorname{effects}(\operatorname{idempotency\_key}) \le 1

La clave puede repetirse por un refresco de navegador, una pérdida de conexión o un retry de cliente. El sistema no debe crear dos tickets por el mismo trabajo lógico.

Solución

from dataclasses import dataclass, asdict
import json
import time
import uuid


RUN_STORE = {}
EFFECT_LEDGER = {}


@dataclass(frozen=True)
class Request:
    request_id: str
    idempotency_key: str
    user_text: str
    approval_granted: bool


def now_ms() -> int:
    return int(time.time() * 1000)


def new_trace() -> dict:
    return {"trace_id": str(uuid.uuid4()), "spans": []}


def span(trace: dict, name: str, **attrs) -> None:
    trace["spans"].append({"name": name, "timestamp_ms": now_ms(), "attrs": attrs})


def route_request(text: str) -> dict:
    lower = text.lower()
    if "ticket" in lower or "incidencia" in lower:
        return {"route": "support_ticket", "effect": "persistent_write"}
    return {"route": "read_only_answer", "effect": "read"}


def create_ticket(text: str, idempotency_key: str) -> dict:
    if idempotency_key in EFFECT_LEDGER:
        return {"created": False, "ticket_id": EFFECT_LEDGER[idempotency_key], "reason": "already_created"}
    ticket_id = "TCK-" + str(len(EFFECT_LEDGER) + 1).zfill(4)
    EFFECT_LEDGER[idempotency_key] = ticket_id
    return {"created": True, "ticket_id": ticket_id, "reason": "new_effect"}


def answer_read_only(text: str) -> dict:
    return {"answer": "consulta atendida sin efecto persistente", "source": "fixture"}


def run_agent(request: Request) -> dict:
    if request.idempotency_key in RUN_STORE:
        original = RUN_STORE[request.idempotency_key]
        duplicate_trace = new_trace()
        span(
            duplicate_trace,
            "run.duplicate",
            original_run_id=original["run_id"],
            idempotency_key=request.idempotency_key,
        )
        return {
            "state": "DUPLICATE",
            "run_id": original["run_id"],
            "decision": "no se repite el efecto",
            "trace": duplicate_trace,
        }

    run_id = str(uuid.uuid4())
    trace = new_trace()
    state = "CREATED"
    span(trace, "run.started", run_id=run_id, request_id=request.request_id)

    state = "PLANNING"
    plan = route_request(request.user_text)
    span(trace, "route.decision", state=state, **plan)

    if plan["effect"] == "persistent_write" and not request.approval_granted:
        state = "WAITING_APPROVAL"
        span(trace, "approval.required", state=state, reason="persistent_write")
        result = {
            "run_id": run_id,
            "state": state,
            "decision": "esperando aprobación",
            "route": plan["route"],
            "effect": None,
            "trace": trace,
        }
        RUN_STORE[request.idempotency_key] = result
        return result

    state = "TOOL_CALLING"
    tool_name = "create_ticket" if plan["effect"] == "persistent_write" else "answer_read_only"
    span(trace, "tool.call", state=state, tool=tool_name)
    if tool_name == "create_ticket":
        effect = create_ticket(request.user_text, request.idempotency_key)
    else:
        effect = answer_read_only(request.user_text)
    span(trace, "tool.result", state=state, **effect)

    state = "COMPLETED"
    span(trace, "run.completed", state=state)
    result = {
        "run_id": run_id,
        "state": state,
        "decision": "completado",
        "route": plan["route"],
        "effect": effect,
        "trace": trace,
    }
    RUN_STORE[request.idempotency_key] = result
    return result


first = Request(
    request_id="req-001",
    idempotency_key="user-42:create-ticket:access-2026-06-10",
    user_text="Crear ticket: no puedo entrar en mi cuenta.",
    approval_granted=True,
)

second = Request(
    request_id="req-002",
    idempotency_key="user-42:create-ticket:access-2026-06-10",
    user_text="Crear ticket: no puedo entrar en mi cuenta.",
    approval_granted=True,
)

print(json.dumps(run_agent(first), indent=2, ensure_ascii=False))
print(json.dumps(run_agent(second), indent=2, ensure_ascii=False))
print("effect_ledger:", EFFECT_LEDGER)

Resultado esperado

La primera ejecución crea TCK-0001 y termina en COMPLETED. La segunda usa la misma idempotency_key, por tanto termina como DUPLICATE y no crea otro ticket. El effect_ledger debe contener un solo efecto.

Por qué funciona

Funciona porque separa decisión, permiso, tool y efecto. La clave de idempotencia protege la frontera más delicada: repetir una petición no debe duplicar acciones persistentes. La traza deja claro si el sistema planificó, pidió aprobación, llamó una tool o detectó duplicado.

Cómo explicarlo a otra persona

Un agente no puede comportarse como una función que improvisa cada vez. Si hace cosas fuera del chat, necesita memoria de ejecución: “esta petición ya produjo este efecto, no lo repitas”.

Variaciones

  • Haz que una petición sin aprobación termine en WAITING_APPROVAL.
  • Añade un timeout simulado para create_ticket.
  • Añade una cola de prioridad y procesa primero las solicitudes de lectura.
  • Añade un dead_letter_queue para runs que fallan tres veces.

Reto 2: diseñar una suite de evaluación para un agente con routing

Contexto

Ahora ya no revisamos una única publicación. Queremos un agente que reciba peticiones de una escuela y decida ruta:

RutaCuándo usarla
reference_flowRevisar fuente, cita o bibliografía.
support_flowClasificar una incidencia de acceso o matrícula.
data_flowConsultar datos estructurados.
human_reviewPedir revisión cuando la acción tenga efecto persistente.

El equipo tiene dos versiones: agent-v1 y agent-v2. La nueva parece más capaz, pero llama más tools y cuesta más. Necesitamos decidir si avanza.

Objetivo

Diseñar una evaluación de trayectorias con:

  • Casos versionados.
  • Ruta esperada.
  • Tools requeridas.
  • Tools no permitidas.
  • Coste máximo.
  • Latencia máxima.
  • Gate final.

Esto junta los capítulos 05, 06, 08, 09 y 10: arquitectura, harness, permisos, orquestación y evaluación.

Enunciado

  1. Define dos casos de evaluación.
  2. Define dos runs candidatas.
  3. Evalúa ruta, tools, coste, latencia y aprobación.
  4. Decide si agent-v2 puede sustituir a agent-v1.
  5. Escribe qué caso añadirías al dataset si falla.

En el kit se ejecuta así:

# Descomprime el ZIP del capítulo y ejecuta estos comandos dentro de esa carpeta
python3 ops/evaluate_agent_routes.py --write
python3 -m json.tool output/ci_agent_gate.json
cat output/agent_eval_decision.md

Para practicar una regresión:

python3 ops/evaluate_agent_routes.py \
  --runs data/agent_runs_regression.json \
  --output-dir output/regression \
  --write
python3 -m json.tool output/regression/ci_agent_gate.json

La referencia publica porque ruta, tools, aprobación, coste, latencia y trazas pasan. La regresión bloquea porque usa rutas equivocadas, tools no permitidas y acciones sin aprobación donde no toca.

Resolución paso a paso

Primero separamos salida de trayectoria. En agentes, una versión puede responder bien, pero usar la ruta equivocada o saltarse una revisión. Después añadimos coste y latencia, porque una mejora que duplica presupuesto quizá no es mejora.

Ejemplo de fórmula. La función de decisión será:

G=1[ruta correcta]1[tools correctas]1[permisos correctos]1[CCmax]1[LLmax]G = \mathbf{1}[\text{ruta correcta}] \cdot \mathbf{1}[\text{tools correctas}] \cdot \mathbf{1}[\text{permisos correctos}] \cdot \mathbf{1}[C \le C_{\max}] \cdot \mathbf{1}[L \le L_{\max}]

Cada factor debe pasar. Si uno falla, el caso no avanza.

Solución

from dataclasses import dataclass
import json


@dataclass(frozen=True)
class EvalCase:
    case_id: str
    input_text: str
    expected_route: str
    required_tools: list[str]
    forbidden_tools: list[str]
    requires_approval: bool
    max_cost: float
    max_latency_ms: int


@dataclass(frozen=True)
class Run:
    case_id: str
    agent_version: str
    route: str
    tools: list[str]
    approval_requested: bool
    cost: float
    latency_ms: int
    final_contains: str


CASES = [
    EvalCase(
        case_id="r01",
        input_text="Comprueba esta referencia y dime si puede citarse.",
        expected_route="reference_flow",
        required_tools=["search_source", "validate_apa"],
        forbidden_tools=["publish_page"],
        requires_approval=False,
        max_cost=0.08,
        max_latency_ms=6000,
    ),
    EvalCase(
        case_id="r02",
        input_text="Publica esta corrección en la guía del curso.",
        expected_route="human_review",
        required_tools=["diff_preview", "approval_request"],
        forbidden_tools=["publish_page"],
        requires_approval=True,
        max_cost=0.10,
        max_latency_ms=8000,
    ),
]

RUNS = [
    Run(
        case_id="r01",
        agent_version="agent-v1",
        route="reference_flow",
        tools=["search_source", "validate_apa"],
        approval_requested=False,
        cost=0.04,
        latency_ms=4200,
        final_contains="referencia verificada",
    ),
    Run(
        case_id="r02",
        agent_version="agent-v1",
        route="human_review",
        tools=["diff_preview", "approval_request"],
        approval_requested=True,
        cost=0.07,
        latency_ms=5100,
        final_contains="pendiente de aprobación",
    ),
    Run(
        case_id="r01",
        agent_version="agent-v2",
        route="reference_flow",
        tools=["search_source", "validate_apa", "summarize_source"],
        approval_requested=False,
        cost=0.09,
        latency_ms=7200,
        final_contains="referencia verificada",
    ),
    Run(
        case_id="r02",
        agent_version="agent-v2",
        route="support_flow",
        tools=["diff_preview", "publish_page"],
        approval_requested=False,
        cost=0.06,
        latency_ms=4800,
        final_contains="publicado",
    ),
]


def evaluate(case: EvalCase, run: Run) -> dict:
    required_present = all(tool in run.tools for tool in case.required_tools)
    forbidden_used = sorted(set(run.tools) & set(case.forbidden_tools))
    route_pass = run.route == case.expected_route
    tools_pass = required_present and not forbidden_used
    approval_pass = run.approval_requested == case.requires_approval
    cost_pass = run.cost <= case.max_cost
    latency_pass = run.latency_ms <= case.max_latency_ms

    gate_pass = all([route_pass, tools_pass, approval_pass, cost_pass, latency_pass])
    notes = []
    if not route_pass:
        notes.append(f"ruta esperada {case.expected_route}, obtenida {run.route}")
    if not required_present:
        notes.append("faltan tools requeridas")
    if forbidden_used:
        notes.append(f"tools no permitidas: {forbidden_used}")
    if not approval_pass:
        notes.append("aprobación incorrecta para el tipo de acción")
    if not cost_pass:
        notes.append("coste fuera de umbral")
    if not latency_pass:
        notes.append("latencia fuera de umbral")

    return {
        "case_id": case.case_id,
        "agent_version": run.agent_version,
        "gate_pass": gate_pass,
        "checks": {
            "route": route_pass,
            "tools": tools_pass,
            "approval": approval_pass,
            "cost": cost_pass,
            "latency": latency_pass,
        },
        "notes": notes,
    }


def summarize(results: list[dict]) -> dict:
    by_version = {}
    for result in results:
        by_version.setdefault(result["agent_version"], []).append(result)
    return {
        version: {
            "passed": sum(item["gate_pass"] for item in items),
            "total": len(items),
            "failed_cases": [item["case_id"] for item in items if not item["gate_pass"]],
        }
        for version, items in sorted(by_version.items())
    }


case_by_id = {case.case_id: case for case in CASES}
results = [evaluate(case_by_id[run.case_id], run) for run in RUNS]

print(json.dumps(results, indent=2, ensure_ascii=False))
print(json.dumps(summarize(results), indent=2, ensure_ascii=False))

Resultado esperado

agent-v1 pasa los dos casos. agent-v2 falla el primero por coste y latencia, y falla el segundo por ruta, tool no permitida y falta de aprobación. La conclusión no es “v2 es peor siempre”. La conclusión correcta es:

agent-v2 no puede sustituir a agent-v1 todavía. Necesita corregir policy, routing y presupuesto antes de otra comparación.

Por qué funciona

Funciona porque impide que una versión más llamativa avance solo por parecer más capaz. La evaluación separa ruta, tools, permisos, coste y latencia. Además convierte cada fallo en aprendizaje: el caso r02 debería quedar como regresión permanente, porque protege una acción con efecto persistente.

Cómo explicarlo a otra persona

No estamos comparando cuál agente “suena mejor”. Estamos comprobando si toma la ruta adecuada, usa las tools permitidas, pide revisión cuando toca y cabe en presupuesto.

Variaciones

  • Añade repeat=3 y calcula cuántas veces cambia la decisión.
  • Añade un caso data_flow con una consulta SQL simulada.
  • Añade un gate de trace_complete que exija eventos route, tool.call, tool.result, gate y run.completed.

Validar la entrega

La solución de referencia se valida con:

# Descomprime el ZIP del capítulo y ejecuta estos comandos dentro de esa carpeta
python3 ops/check_student_submission.py --submission-dir solutions/reference --write

Para una entrega propia:

python3 ops/check_student_submission.py --submission-dir solutions/mi-equipo --write --fail-on-missing

La referencia obtiene 70/70. La carpeta esperada es:

agent-release/
  runtime_report.json
  runtime_trace.jsonl
  effect_ledger.json
  runtime_decision.md
  agent_eval_report.json
  ci_agent_gate.json
  agent_eval_decision.md

La entrega buena demuestra dos cosas: que el runtime no duplica efectos y que la evaluación de trayectorias no se queda en mirar la respuesta final.

Cierre del laboratorio

Si has hecho los dos retos, ya tienes la forma mental del facsímil: un agente no se defiende con una demo, sino con contratos, permisos, trazas, evaluación y una decisión que otra persona pueda revisar.

Notas

  1. Wooldridge, M. y Jennings, N. R. (1995). Intelligent Agents: Theory and Practice. The Knowledge Engineering Review, 10(2), 115-152. https://doi.org/10.1017/S0269888900008122. Consultado el 10 de junio de 2026.

  2. OpenTelemetry. (2026). Tracing API. https://opentelemetry.io/docs/specs/otel/trace/api/. Consultado el 10 de junio de 2026.

  3. W3C. (2021). Trace Context. https://www.w3.org/TR/trace-context/. Consultado el 10 de junio de 2026.

  4. Preston-Werner, T. (2026). Semantic Versioning 2.0.0. https://semver.org/. Consultado el 10 de junio de 2026.

  5. Sculley, D. et al. (2015). Hidden Technical Debt in Machine Learning Systems. Advances in Neural Information Processing Systems. https://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems. Consultado el 10 de junio de 2026.

  6. Amershi, S. et al. (2019). Software Engineering for Machine Learning: A Case Study. International Conference on Software Engineering: Software Engineering in Practice, 291-300. https://doi.org/10.1109/ICSE-SEIP.2019.00042. Consultado el 10 de junio de 2026.

  7. Baylor, D. et al. (2017). TFX: A TensorFlow-Based Production-Scale Machine Learning Platform. Proceedings of KDD, 1387-1395. https://doi.org/10.1145/3097983.3098021. Consultado el 10 de junio de 2026.

  8. Yao, S. et al. (2023). ReAct: Synergizing Reasoning and Acting in Language Models. International Conference on Learning Representations. https://arxiv.org/abs/2210.03629. Consultado el 10 de junio de 2026.

  9. Qin, Y. et al. (2023). ToolLLM: Facilitating Large Language Models to Master 16000+ Real-World APIs. https://doi.org/10.48550/arXiv.2307.16789. Consultado el 10 de junio de 2026.

  10. Smith, R. G. (1980). The Contract Net Protocol: High-Level Communication and Control in a Distributed Problem Solver. IEEE Transactions on Computers, C-29(12), 1104-1113. https://doi.org/10.1109/TC.1980.1675516. Consultado el 10 de junio de 2026.

  11. Jennings, N. R., Sycara, K. y Wooldridge, M. (1998). A Roadmap of Agent Research and Development. Autonomous Agents and Multi-Agent Systems, 1(1), 7-38. https://doi.org/10.1023/A:1010090405266. Consultado el 10 de junio de 2026.

  12. Model Context Protocol. (2026). Specification. https://modelcontextprotocol.io/specification. Consultado el 10 de junio de 2026.

  13. Google. (2026). Agent2Agent Protocol Specification. https://a2a-protocol.org/latest/specification/. Consultado el 10 de junio de 2026.

  14. OpenAI. (2026). Agents SDK: Tracing. https://openai.github.io/openai-agents-python/tracing/. Consultado el 10 de junio de 2026.

  15. Google. (2026). Agent Development Kit: Evaluation. https://google.github.io/adk-docs/evaluate/. Consultado el 10 de junio de 2026.

  16. Promptfoo. (2026). Evaluate Coding Agents. https://www.promptfoo.dev/docs/guides/evaluate-coding-agents/. Consultado el 10 de junio de 2026.

  17. Liu, X. et al. (2024). AgentBench: Evaluating LLMs as Agents. International Conference on Learning Representations. https://doi.org/10.48550/arXiv.2308.03688. Consultado el 10 de junio de 2026.