Lección 6: Manos a la obra: escribir a mano un arnés de agente con controles
Objetivos de aprendizaje:
- Convertir el bucle de
stop_reasonde las lecciones anteriores en un buclewhilefuncional sobre@anthropic-ai/sdk, decidiendo por ti mismo si seguir llamando herramientas o devolver texto y cerrar- Construir bloques de contenido
tool_useytool_resultexactamente según la especificación, y enviar los varios resultados de un solo turno de vuelta dentro de un mismo mensajeuser- Equipar ese bucle con cuatro válvulas de control — tope de turnos, tope de presupuesto, detección de falta de progreso y aprobación para acciones de alto impacto — y decir con precisión en qué paso del bucle va cada una
Requisitos: Lee las Lecciones 2 a 5; entiende el bucle guiado por
stop_reason, las condiciones de parada, las salvaguardas de desbocamiento y la intervención human-in-the-loop | Anterior: Lección 5 <<
Primero, cómo se ve en ejecución
Las primeras cinco lecciones desarmaron la máquina pieza por pieza: cómo gira el bucle, cuándo debería detenerse, cómo se ve el desbocamiento, cómo interviene una persona. Esta lección suelda esas piezas en el arnés funcional más pequeño posible. Antes de cualquier código, mira lo que hace en una terminal — un agente cableado con dos herramientas de juguete (get_time reporta la hora, read_file lee un archivo dentro del proyecto), al que se le pasa una sola oración: «lee la primera línea de README.md, y luego dime qué hora es».
Mira de cerca lo que pasó: la persona dijo una sola oración, y cuántas herramientas se llamaron, cuál fue primero, y cuándo detenerse los decidió todo el modelo dentro del bucle. Esa es la línea entre un agente y un flujo de trabajo — el camino de un flujo de trabajo está fijado en código, mientras que un agente es el modelo dirigiendo dinámicamente su propio proceso y decidiendo qué herramientas usar1. El código anfitrión (el arnés que escribimos en esta lección) nunca especificó «lee el archivo primero, luego revisa la hora». Solo giró fielmente el bucle, ejecutó la herramienta que el modelo nombró, y devolvió el resultado. Ambas herramientas aquí son inofensivas, así que nada interrumpió la corrida — pero este arnés también tiene una válvula de aprobación soldada, y si el modelo echa mano de algo de alto impacto como borrar un archivo o disparar una petición, se detiene y espera un asentimiento humano antes de actuar (eso lo escribimos más adelante en la lección). El resto de esta lección construye, línea por línea, el código detrás de esa salida de terminal.
El bucle central: acarrea el esqueleto, mete el SDK real
El callModel de la Lección 2: El bucle central: de una ida y vuelta a operación continua era pseudocódigo. Ahora se vuelve el @anthropic-ai/sdk de verdad. El esqueleto del bucle es idéntico: envía una petición que carga messages, mira response.stop_reason — si es "tool_use", ejecuta las herramientas, cose los resultados de vuelta, y envía de nuevo; si no lo es (digamos, end_turn), devuelve el texto y sal del bucle2.
Aquí está la versión mínima sin ninguna válvula, para que el bucle mismo quede visible:
Pon esto al lado del esqueleto de la Lección 2 y la estructura no se ha movido: la línea while todavía dice «repite mientras stop_reason sea tool_use», y el cuerpo son todavía los mismos cuatro pasos — push del assistant, ejecuta herramientas, push del tool_result, reasigna response. El único cambio sustantivo es callModel volviéndose client.messages.create(...), más esa reasignación al final del cuerpo. Esa reasignación es lo que hace posible detenerse siquiera; quítala y stop_reason se queda en su valor viejo para siempre, que es exactamente el bucle muerto de la Lección 4: Desbocamiento y salvaguarda: bucles muertos, giro en vacío, agotamiento del presupuesto.
Los campos de tool_use / tool_result, sin que falte ni uno
runToolUses es donde de verdad se ejecuta la herramienta que el modelo nombró. Lo más fácil de equivocar aquí son los campos del bloque de contenido, así que sigue la especificación: un bloque tool_use carga id / name / input, un bloque tool_result carga tool_use_id (declarando a qué llamada responde) y content, y cuando la ejecución de la herramienta falla agregas is_error: true3. Hay una regla dura más: por más bloques tool_use que contenga una respuesta, ese mismo número de bloques tool_result debe volver, todos empacados en el único mensaje user que sigue inmediatamente3 — la línea messages.push({ role: "user", content: toolResults }) del cuerpo del bucle de arriba es lo que sostiene esa regla.
Fíjate en el try/catch: que una herramienta reviente no debería llevarse todo el arnés consigo. Envuelve el error en un tool_result marcado is_error: true y devuélvelo, y el modelo obtiene una oportunidad de reintentar con argumentos distintos o tomar otra ruta. Eso es mucho más estable que lanzar y matar el proceso.
Atornillando cuatro válvulas de control
El bucle gira ahora, pero es el bucle pelado de la Lección 2 — el que confía en el modelo y no se deja salida. Se detiene en el turno en que el modelo devuelve end_turn, sin ninguna frontera en medio. Y la autonomía de un agente implica costos más altos más la posibilidad de que los errores se compongan vuelta tras vuelta del bucle, con el modelo operando potencialmente durante muchos turnos1 — un bucle pelado apuesta toda la decisión de detener-o-continuar al modelo, lo que es demasiado arriesgado. Ahora soldamos las cuatro válvulas de las lecciones anteriores, una a la vez.
Cada válvula vigila una cosa, y ninguna de sus posiciones es arbitraria:
- Válvula 1, tope de turnos (Lección 3: Condiciones de parada: cuándo un agente debería renunciar):
turns >= MAX_TURNSse sienta en la cima misma del cuerpo, delante deturns++. Significa «antes de esta vuelta, verifica si otra vuelta todavía está permitida». Esta condición de parada explícita existe para que, junto alend_turnpropio del modelo, mantengas el control en tus propias manos1. - Válvula 2, tope de presupuesto (Lección 4: Desbocamiento y salvaguarda: bucles muertos, giro en vacío, agotamiento del presupuesto): cada vez que vuelve una respuesta, suma los tokens de
response.usagey detente en el techo. Cuando los turnos son pocos pero el contexto de cada turno es enorme, el conteo de turnos por sí solo no frenará el gasto; necesitas los tokens como una compuerta aparte e independiente. - Válvula 3, detección de falta de progreso (Lección 4): aplana las llamadas a herramientas de este turno en una firma y compárala con la anterior; idéntica significa giro en vacío. Esto atrapa el caso estancado donde los turnos no pasan del límite y el presupuesto no ha reventado, pero el modelo camina en el sitio, llamando la misma herramienta con los mismos argumentos una y otra vez.
- Válvula 4, la válvula de aprobación (Lección 5: Intervención y dirección: interrumpir, redirigir, human-in-the-loop): dentro de
runToolUses, delante de ejecutar de verdad una herramienta, las acciones de alto impacto obtienen una confirmación humana primero. La aprobación human-in-the-loop sobre acciones de alto impacto es precisamente la manera recomendada de contener el riesgo de agencia excesiva4.
La función de firma de la válvula 3 es sencilla hasta el aburrimiento — une los nombres y argumentos de cada bloque tool_use del turno en una sola cadena. Distinguir «qué se llamó con qué argumentos» es todo lo que necesita hacer:
La válvula de aprobación: encajada en el momento antes de la ejecución
De las cuatro válvulas, la posición de la válvula de aprobación es la que más importa y la más fácil de equivocar. Tiene que encajar en el momento en que el modelo nombró una herramienta pero la herramienta todavía no se ha ejecutado — imprime la acción por ocurrir, espera a una persona, ejecuta solo tras la confirmación. Un paso más tarde y el archivo ya está escrito, la petición ya enviada, y preguntar «¿confirmar?» no tiene sentido. Así que va dentro de runToolUses, delante de la línea impl(...):
approve es una función pasada desde afuera; en una terminal significa «imprime la acción, lee una línea de entrada»:
Un detalle que importa: incluso cuando la persona rechaza, igual devuelves un tool_result marcado is_error: true en lugar de no devolver nada. La especificación exige que cada tool_use tenga un tool_result correspondiente enviado de vuelta3; sáltalo y la siguiente petición se cae porque una llamada a herramienta no tiene resultado. Rechazar no es lo mismo que ignorar — un rechazo es en sí mismo un resultado que el modelo merece conocer, y un modelo que aprende que fue rechazado a menudo cambiará a una ruta que no necesita la acción de alto impacto en absoluto.
Dos herramientas de juguete, para que el bucle de verdad se ejecute
Las válvulas están puestas; lo que falta son herramientas que el modelo pueda llamar. Esta lección usa solo dos juguetes absolutamente seguros y mantiene las operaciones peligrosas afuera: get_time reporta la hora actual, y read_file lee un archivo — con path.resolve clavándolo firmemente dentro del directorio del proyecto, para que el modelo (o un modelo sacado de rumbo por la salida de una herramienta) no pueda ir a leer rutas fuera de límites como /etc/passwd:
Ninguna de las dos herramientas está en el conjunto HIGH_IMPACT, así que ninguna dispara aprobación — son inofensivas por construcción. Para demostrar la válvula de aprobación, agrega un write_file a toolImpls y a HIGH_IMPACT. Esta lección deliberadamente evita introducir una operación de escritura real para que ejecutar el ejemplo no pueda dañar tus archivos.
Juntándolo todo: un punto de entrada que puedes ejecutar con node agent.js
Por último, reúne runAgent, runToolUses, las definiciones de herramientas y la función de aprobación en un punto de entrada que puedas ejecutar directamente — la cosa detrás de la salida de terminal del inicio de esta lección:
Deja las piezas anteriores (import, client, MODEL, runAgent, runToolUses, signatureOf, approveInTerminal, toolImpls, tools, main) en un solo agent.js, define ANTHROPIC_API_KEY, ejecuta npm i @anthropic-ai/sdk, y node agent.js "tu tarea" se ejecutará.
Mira de nuevo estas cien y tantas líneas y notarás que ni una sola es un concepto nuevo: el bucle while y stop_reason vinieron de la Lección 2, MAX_TURNS de la Lección 3, el presupuesto y la detección de giro en vacío de la Lección 4, y la válvula de aprobación de la Lección 5. Un arnés no es un marco profundo; es esta capa de bucle-más-válvulas que tú mismo escribes y controlas. Mismo modelo, mismas dos herramientas — pero un arnés con estas cuatro válvulas y el bucle pelado de la Lección 2 pueden diferir enormemente en con qué estabilidad ejecutan la misma tarea, porque lo que decide si un agente es confiable es en gran medida esta capa externa de código de control, no solo el modelo adentro5.
Mantén también un sentido de proporción sobre la complejidad: no todo agente necesita las cuatro válvulas, y una línea que vale la pena recordar es que habría que considerar agregar complejidad solo cuando mejore los resultados de forma demostrable1. Una herramienta pequeña que dura de tres a cinco turnos en un entorno controlado podría estar bien con MAX_TURNS solo; cuatro válvulas son para los casos que operan muchos turnos seguidos y pueden echar mano de acciones de alto impacto.
Resumen
- El núcleo de un arnés funcional sigue siendo el bucle de la Lección 2: envía una petición que carga
messages→ verificastop_reason, y si estool_use, ejecuta las herramientas, cose untool_resultde vuelta, y envía de nuevo; si no lo es, devuelve texto y cierra2. Cambiar al SDK real solo conviertecallModelenclient.messages.create(...) - Los campos del bloque de contenido siguen la especificación sin que falte ninguno:
tool_usecargaid/name/input,tool_resultcargatool_use_id/contentmásis_errorante una falla; por más bloquestool_useque tenga un turno, ese mismo número de bloquestool_resultvuelve, todos empacados en el único mensajeuserque sigue inmediatamente3 - Cada una de las cuatro válvulas de control vigila un punto, y sus posiciones no se pueden barajar: el tope de turnos (Lección 3) y el tope de presupuesto (Lección 4) son las fronteras duras que hacen que el bucle con certeza se detenga, la detección de falta de progreso (Lección 4) atrapa el caminar en el sitio, y la válvula de aprobación (Lección 5) tiene que encajar delante de la ejecución de la herramienta — porque la autonomía de un agente trae costos más altos y errores que se componen, y el modelo puede operar durante muchos turnos1, así que el
end_turnpropio del modelo no puede sostenerlo - La válvula de aprobación que exige confirmación humana en acciones de alto impacto es la manera recomendada de contener el riesgo de agencia excesiva4; incluso ante un rechazo, devuelve un
tool_resultconis_errory no dejes la llamada colgando3 - Un arnés no es un marco profundo; es esta capa de bucle-más-válvulas que tú mismo escribes y controlas — mismo modelo, código de control distinto, y la confiabilidad puede diferir enormemente5. Pero tampoco apiles válvulas por apilarlas: agrega complejidad solo cuando mejore los resultados de forma demostrable1
Terminaste este curso. De «qué es un arnés» a escribir a mano un bucle con cuatro válvulas de control, lo que sostienes ahora no es solo un conjunto de conceptos — es código real que se ejecuta, que puedes editar, y al que puedes seguir agregándole control. Conéctalo a tus propias herramientas y déjalo hacer algo de trabajo por ti.
Footnotes
-
Building Effective AI Agents — Anthropic Engineering — https://www.anthropic.com/engineering/building-effective-agents ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
How tool use works — Claude API — https://platform.claude.com/docs/en/agents-and-tools/tool-use/how-tool-use-works ↩ ↩2
-
Handle tool calls — Claude API — https://platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls ↩ ↩2 ↩3 ↩4 ↩5
-
LLM06:2025 Excessive Agency — OWASP Gen AI Security Project — https://genai.owasp.org/llmrisk/llm062025-excessive-agency/ ↩ ↩2
-
The 2026 Agent Engineering Roadmap — GitHub (codejunkie99/agent-roadmap-2026) — https://github.com/codejunkie99/agent-roadmap-2026 ↩ ↩2