Diseñar un harness de agente: de la tarea a la arquitectura
Traducción automática
Este artículo se tradujo automáticamente a partir de la versión original en inglés.
Un harness de agente es el código de aplicación que convierte las decisiones de un modelo en trabajo: aporta contexto, ejecuta las acciones permitidas, mantiene el estado y comprueba cuándo la tarea está completa. Si construyes un agente para tu propia aplicación, diseñar ese código significa decidir cuánta libertad necesita la tarea y qué debe garantizar la aplicación sea cual sea la respuesta del modelo.
Esas decisiones van antes que el framework. Un asistente de soporte, un coding agent y un clasificador de documentos necesitan formas distintas de actuar, de recuperarse y de demostrar que han tenido éxito. Dar a los tres el mismo bucle, el mismo almacén de memoria y la misma colección de herramientas ocultaría los requisitos que los diferencian.
Este artículo sigue esas decisiones desde la descripción de una tarea hasta una primera implementación. Amplía Harness Engineering for AI Agents, que presenta las responsabilidades de un harness. Aquí veremos cómo elegirlas para un proyecto concreto.
Por qué el modelo necesita un harness
Un modelo puede proponer un reembolso. Otra cosa tiene que identificar la cuenta, cargar el pedido, decidir si la operación propuesta está permitida, llamar al servicio de pagos y registrar lo que ha pasado. Si ese servicio agota el tiempo de espera, la aplicación también tiene que decidir si es seguro volver a intentarlo. Estas responsabilidades existen aunque el modelo elija muy bien el siguiente paso.
LangChain usa una definición amplia de harness que incluye el código, la configuración y la lógica de ejecución que rodean al modelo. En la práctica, parte de eso puede pertenecer ya a tu aplicación: la autenticación, el acceso a la base de datos, una cola de trabajos o un sistema de aprobaciones. Diseñar un harness incluye decidir cómo usa el agente esos recursos. No exige reconstruirlos dentro de un framework de agentes.
La versión más pequeña podría hacer una sola llamada al modelo, validar su salida y devolver un resultado. Una más grande podría dejar que el modelo inspeccione archivos, ejecute programas y revise su trabajo a lo largo de muchos turnos. Ambas necesitan responder a la misma pregunta: ¿qué partes de esta tarea podemos delegar y cómo sabremos que se han hecho correctamente?
Para concretarlo, pensemos en un asistente de atención al cliente que gestiona reembolsos, preguntas sobre entregas y cambios en la cuenta. Lo usaremos como ejemplo de diseño. Sus requisitos nos llevarán hacia una arquitectura concreta; una tarea distinta debería llevar a un conjunto de decisiones distinto.
Describe el resultado antes que el agente
«Gestionar solicitudes de reembolso» deja sin especificar la mayor parte de la ingeniería. Supongamos que un cliente pide el reembolso de un pedido entregado. Un resultado correcto exige un reembolso del pedido y el importe correctos, una explicación al cliente y ningún cambio ajeno en la cuenta. Si la solicitud queda fuera de la política, una negativa útil puede ser el resultado correcto. Si no se puede identificar el pedido, el asistente debe pedir la información que falta.
Son resultados distintos. Tratar como éxito toda conversación que termina sin error los reduciría a una sola señal engañosa.
También necesitamos saber quién lo pide. Un identificador de pedido en un mensaje no demuestra que ese pedido sea suyo. La cuenta autenticada debe venir de la aplicación, y el servicio que hace el reembolso debe comprobar que la operación pertenece a esa cuenta. El modelo puede ayudar a interpretar la solicitud; no puede establecer la autoridad de quien llama generando un identificador verosímil.
Esto nos da el comienzo de un contrato de tarea: los cambios que cuentan como éxito, los cambios prohibidos y las situaciones que exigen una pregunta o un traspaso a una persona. También deja ver requisitos que un prompt no puede cumplir por sí solo. Un importe máximo de reembolso necesita un servicio que lo haga cumplir. Un objetivo de tiempo de respuesta necesita un plazo. El requisito de reanudar mañana necesita un estado duradero.
Llegados a este punto, sabemos lo suficiente para preguntarnos si un agent loop sirve de algo.
¿Cuánto del procedimiento conocemos ya?
Si todas las solicitudes siguen la misma secuencia —extraer un número de pedido, obtener el pedido, calcular si procede y explicar el resultado—, podemos escribir esa secuencia directamente. El modelo puede encargarse del lenguaje al principio y al final. No necesita otra llamada para decidir si obtiene un pedido que el procedimiento siempre exige.
Un workflow explícito resulta útil cuando el procedimiento tiene ramas obligatorias o estados de espera. Un reembolso podría necesitar aprobación por encima de un umbral. Un cambio de dirección podría permitirse solo antes del envío. Podemos representar estas etapas como una máquina de estados o un grafo, y dejar que el código de la aplicación decida qué transiciones están permitidas.
Un agent loop da más margen de decisión al modelo. En una conversación de soporte mixta, el cliente puede empezar quejándose de una entrega, revelar que llegó el artículo equivocado y luego pedir un cambio. La siguiente consulta útil depende de lo que el asistente descubra. Un bucle le permite elegir una acción, inspeccionar el resultado y volver a elegir. Esa flexibilidad trae más caminos posibles que revisar, incluidas llamadas innecesarias, consultas repetidas y finalizaciones prematuras.
La guía de workflows y agentes de LangGraph plantea esta distinción como rutas de ejecución predeterminadas frente a pasos elegidos dinámicamente. Un grafo puede expresar cualquiera de las dos. «Basado en grafos» y «agentic» no son arquitecturas mutuamente excluyentes.
Un compromiso útil para nuestro asistente de soporte es dejar que un agente reúna información y prepare un cambio propuesto, y después pasar esa propuesta a un paso fijo de validación y ejecución. El modelo puede explorar dentro de la conversación mientras el código ordinario controla las condiciones de una escritura. Si ya sabemos que esta separación es necesaria, podemos construir primero el workflow exterior.
Otra opción es empezar con un agent loop acotado cuyas herramientas hagan cumplir esas condiciones. Así la orquestación se mantiene pequeña mientras aprendemos qué caminos necesita realmente la tarea. La elección depende de si las etapas explícitas, las esperas de aprobación y las reglas de recuperación ya son requisitos.
No hace falta introducir varios agentes solo para dibujar más cajas. Los agentes separados resultan útiles cuando las subtareas necesitan un contexto o unos permisos distintos, o pueden avanzar de forma independiente. También necesitan una manera de combinar resultados y resolver acciones en conflicto. Dos workers que pueden reembolsar el mismo pedido crean un problema de coordinación que un solo worker no tenía.
¿Qué lenguaje debe usar el agente para actuar?
Después de elegir quién controla el siguiente paso, todavía tenemos que elegir cómo se expresa una acción. Son decisiones independientes: un workflow puede contener un agente que escribe código, y un bucle abierto puede usar solo unas pocas herramientas estrechas.
Para el asistente de soporte, una operación con nombre como issue_refund(order_id, amount_cents) es una interfaz natural. Su esquema hace explícita la acción solicitada. La herramienta puede devolver un identificador de reembolso si tiene éxito o una explicación estructurada de por qué se rechazó la solicitud. Podemos inspeccionar y probar este contrato sin pedir al modelo que genere la implementación de un reembolso.
Algunas tareas no necesitan herramientas. Un clasificador que tiene en su prompt toda la entrada relevante puede devolver una etiqueta validada. La recuperación de información resulta útil cuando la tarea necesita datos que no están en esa entrada. Las escrituras resultan útiles solo cuando completar la tarea exige cambiar algo. Cada capacidad debería tener un motivo para existir.
El código generado ofrece otro lenguaje de acción. Imagina que pides a un asistente que inspeccione una tabla, agrupe las filas por cliente y calcule varios totales. Python puede expresar ese trabajo en un solo programa y mantener los valores intermedios dentro del entorno de ejecución. Expresar cada operación pequeña como un tool call separado podría exigir más intercambios con el modelo.
smolagents, de Hugging Face, ilustra ambos enfoques. ToolCallingAgent produce tool calls estructurados. CodeAgent produce Python que puede llamar a las herramientas proporcionadas, guardar valores y componer operaciones. Un agente de código sigue usando herramientas; expresa su composición como un programa.
Esa expresividad cambia lo que tenemos que operar. Ahora hay que gestionar errores de sintaxis, límites de ejecución, dependencias y el acceso a archivos o redes. Que la reducción de intercambios con el modelo compense esos costes depende de la tarea y del modelo. Es una comparación que hay que hacer, no una ventaja automática del código.
Varias plataformas admiten un enfoque híbrido: herramientas de negocio estrechas más una herramienta de ejecución de código en sandbox. La Responses API de OpenAI, la Claude API, la Gemini API y AWS Bedrock AgentCore ofrecen la ejecución de código como una herramienta integrada que se ejecuta junto a tus propias funciones. El programmatic tool calling de Anthropic va un paso más allá: el código del sandbox puede llamar a las herramientas que permitas, y solo los resultados seleccionados vuelven al modelo. La guía de Anthropic mantiene los tool calls directos como opción por defecto y usa la vía del código para resultados grandes y cadenas de llamadas dependientes. También existen diseños solo con código, como CodeAgent de smolagents y el Code Mode de Cloudflare.
Para nuestro asistente de reembolsos, yo empezaría con operaciones de negocio tipadas. Si una tarea posterior necesita cálculos importantes, podemos añadir computación restringida sin dar a ese código autoridad directa sobre las cuentas de los clientes.
MCP responde a otra pregunta distinta: ¿cómo debe conectarse la aplicación a las herramientas? Una función local basta cuando una sola aplicación es dueña de la implementación. MCP puede ayudar cuando las herramientas se sirven por separado o se comparten entre clientes. Aporta un protocolo de integración; la validación y el control de acceso siguen siendo responsabilidad de la aplicación y del servidor que participan, como describe la especificación de herramientas de MCP.
Pon las restricciones donde ocurren las acciones
La descripción de una herramienta le dice al modelo cuándo es útil una operación. La implementación debe decidir si esta llamada concreta puede ejecutarse. Para un reembolso, eso significa comprobar la cuenta autenticada, el pedido, el importe, la política aplicable y cualquier aprobación necesaria en el servicio que hace la escritura.
Algunas comprobaciones necesitan interpretación
Supongamos que el cliente pregunta «¿Puedo devolver este pedido?» y el agente propone un reembolso. El pedido pertenece al cliente, el importe es válido y el plazo de devolución está abierto. Todas esas comprobaciones pueden pasar mientras la acción pretendida sigue sin estar clara: puede que el cliente pregunte si cumple los requisitos y no esté pidiendo un reembolso ahora. Tenemos que interpretar el mensaje antes de decidir qué hacer a continuación.
Este es un buen lugar para separar tres responsabilidades. El código ordinario comprueba la titularidad, los importes y las fechas. El modelo de lenguaje principal lleva la conversación y explica el resultado. Un modelo de decisión separado puede evaluar una pregunta semántica acotada, como si el cliente pidió realmente el cambio propuesto. Esta separación nos da una decisión que podemos probar de forma independiente del resto de la conversación.
Jev, un modelo de decisión de TypeSafe, es un candidato para ese papel. Recibe un estado y unas preguntas tipadas, y devuelve respuestas estructuradas. En este ejemplo, su tipo de pregunta Noul devuelve una probabilidad para una proposición de sí o no. Yo preguntaría por separado si el cliente pidió explícitamente el reembolso y si un mensaje posterior retiró esa petición. TypeSafe recomienda que cada pregunta sea específica y que las respuestas se combinen en código; varias preguntas independientes pueden compartir una sola llamada a la API. Mantener separados los dos juicios hace más fácil localizar una interpretación errónea.
Después, la aplicación combina esos resultados con sus comprobaciones obligatorias. Las respuestas inciertas o contradictorias pueden llevar a una aclaración o a una revisión humana; un timeout del clasificador también necesita una ruta de error explícita. Un juicio semántico positivo no puede anular una comprobación de titularidad ni sustituir una confirmación obligatoria. La probabilidad de Jev es una salida de un modelo, no un registro de autorización ni una comprobación independiente de que todo es correcto.
Para una comprobación así, yo proporcionaría la acción propuesta, los mensajes relevantes del cliente en orden y los tool results necesarios para la decisión, manteniendo distinguibles sus fuentes. Pasar solo el último mensaje podría perder una cancelación; pasar todo el historial de la cuenta podría enterrar la solicitud relevante. La pregunta, el contexto y el comportamiento de respaldo forman parte del diseño. Un modelo pequeño de propósito general con structured output es otro candidato. Hay que comparar su utilidad y su coste sobre las mismas decisiones.
Restringe el acceso con independencia de los juicios del modelo
El origen de una instrucción importa. Un mensaje del cliente o un documento recuperado pueden contener instrucciones. Ese texto no debe poder conceder nuevos permisos ni sustituir la política de referencia de la aplicación. Si un documento le dice al agente que exporte todos los registros de clientes, el sistema no debería tener permiso para hacerlo, por muy convincente que sea la forma en que el documento lo pide.
Aquí también se decide el sandbox. Si el modelo solo puede llamar a funciones revisadas con permisos estrechos, un sandbox de código de propósito general puede aportar poco. Las funciones siguen necesitando autorización, validación, timeouts y operaciones seguras sobre la base de datos.
En cuanto permitimos Python generado, comandos de shell o ediciones de archivos ejecutables, tenemos que decidir a qué puede acceder esa ejecución antes de habilitarla. ¿Qué directorios se pueden escribir? ¿Hace falta acceso a la red? ¿Qué credenciales hay disponibles? ¿Cuánto tiempo de CPU, memoria y salida puede consumir una ejecución? Un proceso separado no responde por sí solo a esas preguntas.
La guía de ejecución segura de Hugging Face describe la ejecución local restringida y alternativas más aisladas. Sus restricciones son distintas. Un contenedor con montajes amplios del host y credenciales potentes puede seguir exponiendo justo los recursos que queríamos proteger. Prueba un intento de lectura, de escritura y de petición de red prohibidos junto con un programa que funciona.
Podemos posponer un ejecutor de código mientras la tarea no lo necesite. Su aislamiento tiene que estar listo cuando concedamos la ejecución de código. Y el aislamiento no puede impedir el mal uso de una API que ponemos a disposición a propósito: el servicio de reembolsos tiene que seguir haciendo cumplir sus propias reglas.
¿Qué sobrevive cuando una ejecución se detiene a medias?
Supongamos ahora que el asistente envía un reembolso, el servicio lo crea y la respuesta se pierde. El modelo ve un timeout. Repetir la llamada puede crear otro reembolso; declarar un fallo puede decirle al cliente algo que ya no es cierto.
Este problema conecta el estado, los reintentos y la recuperación. Necesitamos un identificador de operación que pertenezca a la aplicación, que se guarde antes del envío y se reutilice al recuperar la misma acción pretendida. El servicio debe reconocer ese identificador y devolver el resultado existente en lugar de repetir la acción. La guía de AWS sobre APIs idempotentes explica este patrón. Si el servicio no ofrece esa garantía, la recuperación necesita una forma de comprobar qué ocurrió o de pedir una decisión humana antes de otro intento.
Guardar la conversación no basta. Puede registrar que el modelo pidió un reembolso sin decir nada sobre si el servicio externo lo completó. Un checkpoint ayuda a reanudar la ejecución; no revierte los efectos externos ni hace seguro repetirlos.
Para este asistente, tres tipos de estado tienen funciones distintas. La conversación contiene la solicitud del cliente y los hechos reunidos hasta el momento. Los registros de pedidos y reembolsos describen el estado del negocio. Un registro de ejecución duradero sigue las operaciones y aprobaciones pendientes para que un worker reiniciado pueda continuar de forma segura. Pueden compartir infraestructura, pero no deben sustituirse entre sí.
La memoria entre sesiones es otra decisión. Si el asistente debe recordar una preferencia la semana que viene, necesitamos reglas sobre de quién es esa preferencia, quién puede actualizarla y cómo se puede borrar. Si cada tarea es independiente, puede que un servicio de memoria sea innecesario. Más texto retenido también significa más ocasiones de reutilizar información obsoleta o irrelevante.
El mismo razonamiento se aplica a los resúmenes. Cuando una conversación supera su contexto útil, puede que necesitemos recuperación o resumen. Un resumen que pierde un identificador de pedido, una condición de aprobación o una operación sin resolver puede romper el procedimiento. Guarda el estado operativo esencial en campos explícitos y prueba qué sobrevive a la compresión antes de confiar en ella.
Da al bucle una forma de parar
Un bucle flexible puede seguir encontrando algo más que inspeccionar. Por eso necesitamos una política de parada que cubra el éxito, la información que falta, los recursos agotados y los fallos. Alcanzar un límite debería producir un estado veraz: el reembolso puede estar completo, sin intentar o todavía incierto. «El agente se detuvo» no es información suficiente para el cliente ni para el siguiente worker.
Un límite de llamadas al modelo acota solo una parte de la ejecución. Una herramienta puede quedarse colgada. Un reintento puede repetir trabajo. Un resumidor o un guard pueden hacer llamadas adicionales al modelo. La aplicación necesita límites de tiempo transcurrido, intentos, tamaño de salida y gasto que incluyan los componentes que realmente invoca. La cancelación también tiene que tener en cuenta el trabajo ya enviado a un servicio externo.
La elección del modelo forma parte de este diseño porque afecta a lo que el bucle puede hacer dentro de esos límites. Prueba el formato de acción necesario con tareas representativas. Ten en cuenta el contexto utilizable, la latencia, el coste y adónde se pueden enviar los datos de la tarea. Un modelo servido localmente y una API alojada pueden implementar la misma interfaz e imponer restricciones de capacidad y de operación distintas.
Elige un modelo y un endpoint iniciales, registra su configuración y mantenla fija mientras evalúas un cambio en el harness. De lo contrario, una ejecución más rápida puede deberse a otro proveedor u otro modelo y no al cambio que queríamos probar.
Evalúa la finalización, incluidos los casos que no deben hacer nada
Ya podemos ejecutar una solicitud, pero todavía tenemos que decidir si el resultado es útil. En el ejemplo del reembolso hay al menos tres observaciones: lo que dijo el asistente, qué acciones intentó y qué cambió en el servicio. Cada una detecta fallos que las otras pueden pasar por alto.
«He reembolsado tu pedido» puede ir acompañado de una base de datos sin cambios. El reembolso correcto puede ir acompañado de un cambio de dirección que nadie pidió. Una base de datos sin cambios puede significar una negativa correcta, una pregunta de aclaración útil, un fallo del programa o un asistente que no hizo nada. El estado final por sí solo no distingue esos cuatro últimos casos.
La simulación de soporte que acompaña a este artículo hace concreta esa limitación. De sus doce tareas propias, siete esperan que la base de datos no cambie. Por eso, pasar a cada checker la base de datos inicial sin cambios supera 7 de 12 comprobaciones de estado. Es una propiedad de las comprobaciones, no una tasa de éxito de soporte medida. Las definiciones de las tareas inspeccionan las diferencias en la base de datos final; no califican la explicación ni las acciones intermedias. Una escritura que se revierte más tarde también puede desaparecer de esa comparación.
Para tu primer conjunto de evaluación, incluye una acción correcta, una negativa válida, una solicitud a la que le falta información esencial y un fallo durante la ejecución. Define la respuesta esperada y las acciones permitidas además del estado final. Un caso de aclaración debe exigir la pregunta adecuada; un caso de negativa debe exigir una explicación útil. Estos casos muestran si el agente entiende la tarea y si la aplicación la hace cumplir.
La comprobación de intención con Jev también necesita sus propios casos. Dale acciones propuestas con decisiones esperadas asignadas de forma independiente: una petición explícita de reembolso, una pregunta sobre si el pedido cumple los requisitos, una petición retirada y una instrucción incrustada en un tool result. Ese último caso también prueba si el ensamblado del contexto mantiene la distinción entre la intención del cliente y el texto que aporta una herramienta. Cuenta las acciones inadecuadas que permite y las acciones legítimas que bloquea, y después sigue la conversación completa tras un bloqueo. Impedir una escritura resuelve solo parte de la tarea si después el asistente entra en un bucle o nunca pide al cliente que aclare. Elige los umbrales con ejemplos de desarrollo, congélalos antes de la comparación con los casos reservados y registra la versión de la pregunta, el contexto proporcionado, las probabilidades devueltas, la regla aplicada, los errores y el coste. La demo grabada que aparece más abajo no provocó ningún rechazo de Jev, así que no puede demostrar ese beneficio.
Registra lo suficiente para diagnosticar un fallo: la tarea, las versiones del modelo y del harness, las acciones propuestas, los resultados de la ejecución, el estado final, los errores, la latencia y el coste. Deja fuera de ese registro los secretos y los datos personales innecesarios. Da al candidato la entrada de su tarea y las reglas que necesita, pero mantén inaccesibles las respuestas de referencia y los casos de prueba reservados. El evaluador y su evidencia también deben quedar fuera del control del candidato. Si un futuro optimizador puede editar el harness, no debe poder reescribir las comprobaciones ni los resultados que juzgan sus cambios.
Esta evidencia da un propósito a los componentes opcionales. Añade un router cuando las trazas muestren que la selección de la tarea necesita una etapa separada. Prueba un resumen cuando los historiales largos causen un fallo concreto. Prueba un guard contra acciones prohibidas, incluidos los casos que debería permitir. Los controles de acceso obligatorios se derivan de las restricciones de la tarea; los añadidos opcionales necesitan evidencia de que su beneficio justifica su coste.
Convierte el diseño en una primera implementación
Para nuestro ejemplo de soporte, ya tenemos un punto de partida razonado: un bucle acotado para interpretar solicitudes variadas, herramientas estrechas para las operaciones de cuenta, comprobaciones vinculantes en los servicios que hacen escrituras y registros explícitos de las acciones pendientes. Todavía no tenemos ningún requisito de código generado ni de memoria conversacional persistente.
Yo construiría primero un camino completo. Crea una pequeña cuenta de prueba y un resultado de reembolso esperado. Implementa las operaciones de consulta y de reembolso, incluidas sus rutas de rechazo. Conecta el modelo a esas operaciones. Después prueba los casos de negativa, de información que falta y de escritura interrumpida. Así aparecen los requisitos que faltan antes de que una colección de tareas más grande los haga más difíciles de ver.
En esta fase puede bastar un bucle sin framework. El flujo básico consiste en ensamblar el contexto, pedir la siguiente acción, validarla y ejecutarla, y luego devolver el resultado al modelo. Ser dueño de ese bucle también significa ser dueño de la conversión de mensajes, la cancelación, el estado y el tracing. Reutilizar un framework existente es útil cuando ahorra ese trabajo sin ocultar las decisiones que necesitamos controlar.
En esta serie, create_agent de LangChain es un punto de partida cómodo porque proporciona el bucle de modelo y herramientas y hooks para cambiar su comportamiento. Ya se ejecuta sobre LangGraph. Pasar más adelante a un workflow explícito de LangGraph significa tomar el control de más etapas alrededor de ese agente; no significa añadir un grafo a un sistema que antes no tenía ninguno.
El primer diseño no tiene que contener todas las capacidades comentadas arriba. Necesita lo suficiente para completar su tarea dentro de sus restricciones, más una evidencia que revele dónde falla. Antes de escribir la implementación, yo pondría las decisiones en una sola página:
| Pregunta | Decisión que hay que anotar |
|---|---|
| ¿Qué tarea estamos completando? | Quién llama, el resultado esperado, la negativa o el traspaso válidos y los efectos prohibidos |
| ¿Quién elige el siguiente paso? | Etapas conocidas en código; decisiones delegadas al modelo; el motivo de cada una |
| ¿Qué comprobaciones exigen interpretación? | La pregunta semántica, su contexto, el modelo candidato y el comportamiento ante la incertidumbre |
| ¿Qué puede pedir hacer el modelo? | Sin herramientas, operaciones tipadas, código generado o una combinación |
| ¿Quién lo autoriza y lo ejecuta? | Recursos permitidos, servicio que hace cumplir las reglas, reglas de aprobación y restricciones del sandbox |
| ¿Qué debe sobrevivir a una interrupción? | Estado de la conversación, registros de negocio, operaciones pendientes y comportamiento de recuperación |
| ¿Qué límites se aplican? | Restricciones del modelo o del proveedor, tratamiento de datos, llamadas, tiempo, gasto y cancelación |
| ¿Qué demuestra la finalización? | Comprobaciones de la respuesta, de las acciones y del estado; quién es dueño de su evidencia |
| ¿Qué hemos dejado fuera? | Componentes opcionales y el fallo o el nuevo requisito que justificaría cada uno |
Una respuesta útil es lo bastante concreta para cambiar la implementación. «Necesita memoria» es vago. «Debe reanudar un reembolso aprobado tras reiniciar un worker sin crear un segundo reembolso» nos dice qué hay que persistir y qué fallo hay que probar.
Esta es la parte 1 de Building and Evaluating Agent Harnesses. La parte 2 lleva más lejos la pregunta del flujo de control: ¿cuándo mejora un workflow explícito la gestión de una tarea lo suficiente para justificar sus etapas adicionales? Construiremos el workflow más pequeño que esté justificado alrededor del agente y lo compararemos con el bucle. Los routers, los workers en paralelo y los resúmenes son candidatos que investigar, no un destino predeterminado. Las partes posteriores separan el evaluador del candidato y miden la variación entre ejecuciones repetidas.
Opcional: inspecciona la implementación y sus límites
La demo complementaria es una versión pequeña y ejecutable del asistente de soporte. Se ejecuta en dos entornos:
- Tareas de soporte propias: doce tareas, cada una contra una base de datos SQLite nueva de clientes y pedidos.
- τ³-bench retail: ocho tareas de un benchmark público en el que un cliente simulado habla con el agente y unas comprobaciones de la base de datos puntúan el resultado.
Esta sección describe qué contiene la demo, qué deja fuera a propósito, qué muestran los resultados y cómo ejecutarla. Puedes saltártela y seguir usando el proceso de diseño anterior.
Qué contiene la demo
Un agente de LangChain elige las llamadas. Tiene cinco herramientas de cuenta (look_up_account, list_orders, issue_refund, update_address y set_preference) y un servidor MCP para consultar la política y las preguntas frecuentes. Un checkpointer en memoria guarda el estado de ejecución. No hay ejecutor de código ni memoria entre sesiones.
La demo registra dos configuraciones:
| Spec | Componentes |
|---|---|
plain | Resumen, un límite de 20 llamadas al modelo por ejecución y 2 reintentos ante un error de herramienta |
base | Todo lo de plain, más Schema-Guided Reasoning y un guard de escritura con Jev |
Schema-Guided Reasoning (SGR) hace que el modelo devuelva un objeto estructurado con el siguiente paso en lugar de un tool call nativo. El guard de Jev se ejecuta antes de cada escritura y hace una pregunta compuesta: ¿esta llamada incumpliría la política o iría más allá de lo que pidió el cliente? Las dos preguntas separadas de antes en este artículo, sobre la petición y su retirada, son un rediseño propuesto. Las ejecuciones grabadas no las usaron.
base añade SGR y Jev a la vez, así que los resultados propios no pueden mostrar cuál de los dos causó una diferencia.
Qué deja fuera la demo a propósito
- Las herramientas comprueban la integridad de los datos pero dejan la política de negocio al agente, así que un experimento puede mostrar una escritura que la política prohíbe. Un servicio real haría cumplir la política en la escritura.
- No hay un límite de tiempo ni de gasto para toda la tarea, y las escrituras reintentadas no tienen clave de idempotencia.
- El evaluador se ejecuta en el mismo proceso que el agente.
Por tanto, estos resultados no prueban la aplicación de reglas en producción, la seguridad de los reintentos ni la protección de la evidencia frente a manipulaciones.
Resultados en las tareas propias
Cada fila es una ejecución por tarea, con el modelo y los precios del endpoint registrados junto a los resultados. Una comprobación de estado compara la base de datos final con la esperada. El coste y la latencia son medias por tarea; el coste incluye Jev donde se ejecuta.
| Spec | Modelo | Comprobaciones de estado | Coste | Latencia |
|---|---|---|---|---|
base | openai/gpt-6-luna | 12/12 | $0.00073 | 11.9 s |
plain | openai/gpt-6-luna | 12/12 | $0.00033 | 5.8 s |
base | z-ai/glm-5.3-flash | 12/12 | $0.00101 | 9.7 s |
base | xiaomi/mimo-v2.6-flash | 7/12 | $0.00087 | 18.6 s |
- Con
gpt-6-luna, ambas specs superaron las doce tareas.basecostó unas 2,2 veces más y tardó aproximadamente el doble. - Jev permitió todas las escrituras que comprobó: doce entre las ejecuciones de
gpt-6-lunay GLM, y cuatro en la ejecución de MiMo. Nunca bloqueó una escritura, así que estas ejecuciones no prueban si detiene una incorrecta. - Los cinco fallos de MiMo fueron errores de formato de salida de SGR. No hay ejecución
plainde MiMo, así que no podemos decir si el tool calling nativo funcionaría mejor. - El resumen nunca se ejecutó. La solicitud más grande de las seis ejecuciones grabadas (estas cuatro y las dos de τ³-bench de más abajo) tuvo 8351 tokens de entrada, por debajo del umbral de activación de 12 000 tokens. Estos resultados no pueden mostrar si el resumen ayuda.
Resultados en τ³-bench
τ³-bench puntúa una ejecución reproduciendo los tool calls del agente en un entorno nuevo, así que el propio benchmark tiene que ejecutar las herramientas. El adaptador pausa el grafo antes de su nodo de herramientas, devuelve los tool calls y escribe los resultados del benchmark antes de reanudar.
Esto tiene dos consecuencias. Primero, el prompt, las herramientas y la política vienen de τ³-bench, así que estos resultados no son comparables con los de las tareas propias, aunque se use el mismo archivo de spec. Segundo, el reintento de herramientas y Jev nunca se ejecutan aquí: la única diferencia entre base y plain es SGR.
Ambas ejecuciones usan openai/gpt-6-luna para el agente y para el cliente simulado. La latencia de la simulación incluye el trabajo del simulador. El coste del agente y la latencia de la simulación son medias por tarea; el coste del simulador es el total de las ocho tareas.
| Spec | Superadas | Coste del agente | Latencia de la simulación | Coste del simulador, total |
|---|---|---|---|---|
base | 6/8 | $0.00238 | 59.0 s | $0.00453 |
plain | 7/8 | $0.00136 | 38.9 s | $0.00471 |
- Las ocho tareas vienen del split de test de retail y no tienen aserciones en lenguaje natural, así que solo las comprobaciones de la base de datos deciden la recompensa.
- Ambas specs terminaron las ocho tareas sin errores del adaptador. Todos los fallos fueron escrituras que faltaban.
basefalló las tareas 9 y 26.plainpasó la tarea 27 a una persona en lugar de completar el cambio. - Una segunda ejecución de
basetambién superó 6/8, pero falló las tareas 9 y 17. Las tareas que fallan cambian entre ejecuciones, así que una diferencia de una tarea no es motivo para preferirplain. Estas ejecuciones no miden esa variación.
Comprueba las cifras registradas
Los informes y trazas guardados conservan los resultados por tarea, los recuentos de tokens, las configuraciones, las versiones de los paquetes y los precios registrados. En las 393 llamadas de chat del agente de las seis ejecuciones grabadas, el coste calculado a partir de los tokens coincidió con el importe facturado en cada respuesta. Esta comprobación excluye las llamadas de Jev y del cliente simulado. Los precios son históricos, no un presupuesto para tu ejecución.
Ejecútalo tú mismo
Instala uv y usa la versión etiquetada que aparece abajo. Los tests se ejecutan sin conexión una vez instaladas las dependencias. Las dos ejecuciones en los entornos hacen llamadas de pago a modelos a través de OpenRouter y necesitan una clave de API.
git clone https://github.com/slavadubrov/agent-harness-lab-public
cd agent-harness-lab-public
git checkout v0.1.2-a1
cp .env.example .env # set OPENROUTER_API_KEY
make test
make a1-custom SPEC=harness/spec/plain.yaml
make a1-tau3 SPEC=harness/spec/plain.yaml
Cada ejecución sustituye los resultados en reports/article-a1/. Usa git diff para compararlos con la línea base confirmada en el repositorio. El Makefile instala desde el uv.lock congelado.
Para probar otro modelo u otro conjunto de herramientas, copia una spec de harness/spec/, cambia esos campos y pasa su ruta mediante SPEC. La configuración del proveedor y de los componentes personalizados acepta claves libres, así que la validación no puede detectar todas las erratas anidadas.
Puedes usar el proceso de diseño de este artículo sin ejecutar la demo. La demo hace inspeccionable un conjunto de decisiones; tu propio contrato de tarea decide cuáles de ellas pertenecen a tu harness.