Un agente de IA es un modelo de lenguaje que trabaja en bucle. Lee la tarea, usa una herramienta como "lee este archivo" o "borra ese archivo", revisa el resultado y sigue adelante hasta terminar el trabajo. Cada vez que el modelo pide usar una herramienta, esa petición se llama tool call (llamada a herramienta).
El código que ejecuta este bucle se conoce como harness. El modelo decide qué quiere hacer. El harness lo ejecuta y también define qué tiene permitido hacer el modelo.
Un buen harness toma muchísimas decisiones pequeñas sobre la marcha. ¿Qué modelo debería encargarse de esta petición? ¿Es seguro ejecutar esta tool call? ¿Esta respuesta es lo bastante buena para entregarla? La mayoría de los harnesses responden a esto consultando a un modelo de chat y leyendo su respuesta. Eso implica el costo de una llamada completa al modelo cada vez, así que en la práctica casi todas las comprobaciones se omiten.
Jev, de TypeSafe AI, es un modelo pequeño diseñado exclusivamente para estas decisiones. Le describes la situación, le haces unas cuantas preguntas y te responde a cada una con un número. Nunca genera texto.
Esto cobra especial importancia cuando construyes un harness personalizado, es decir, tu propio bucle de agente en lugar de usar uno prefabricado. Un harness personalizado te permite elegir qué modelos se ejecutan, qué puede tocar el agente y qué se considera "trabajo terminado". Jev hace que las comprobaciones detrás de esas decisiones sean tan económicas que puedes ejecutarlas en cada paso.
En este tutorial construirás un harness con el Pi SDK, un kit de herramientas en TypeScript para crear agentes, y usarás Jev en tres puntos distintos. Al final, ejecutarás el harness terminado en un sandbox en vivo y podrás modificar su configuración tú mismo.
Accede al tutorial interactivo completo y al playground aquí:
https://academy.dair.ai/resources/jev-decisions-in-a-pi-sdk-harness
Esta guía se inspiró en el artículo de Sydney Runkle Building a Harness with Jev del blog de LangChain, donde muestra el enrutamiento de modelos y el control de herramientas como middleware listo para usar en LangChain. Aquí construirás esos mismos conceptos por tu cuenta con el Pi SDK y luego añadirás dos patrones más para gestionar fallos y comprobar respuestas.
Qué vas a construir
El harness tiene tres partes. Cada una le hace una pregunta a Jev en un momento distinto, y el resto de esta guía se refiere a ellas con estos nombres.

El ejemplo práctico es un agente que trabaja en una carpeta con notas de inspección de senderos, un archivo por salida. Puede leer, escribir y borrar de verdad esas notas, y por eso el gate (puerta de control) es tan importante.
Qué es Jev
TypeSafe define a Jev como un modelo de Sistema Uno. El nombre viene del psicólogo Daniel Kahneman, quien describió dos modos de pensamiento. El Sistema Uno es rápido y automático, como saber que una sartén está caliente. El Sistema Dos es lento y deliberado, como hacer una división larga.
En este harness, un modelo de lenguaje normal se encarga del trabajo pesado de leer archivos y redactar respuestas. Jev toma las decisiones rápidas a su alrededor. Jev es tan barato y veloz que puedes consultarlo en cada tool call, no solo en las que esperabas que fueran riesgosas.

Cada número es una probabilidad entre 0 y 1. Un 0.83 significa que Jev está bastante seguro de que la respuesta es sí. Un 0.03 significa que está bastante seguro de que la respuesta es no.
Tres tipos de preguntas
Toda petición a Jev tiene dos partes. El estado (state) es la situación que quieres evaluar, como una tool call o la petición de un usuario. Las preguntas son lo que quieres saber sobre esa situación. Jev responde a todas las preguntas en una sola llamada y al mismo tiempo, así que hacer tres preguntas tarda prácticamente lo mismo que hacer una.
Jev admite tres tipos de preguntas. La primera, que Jev llama noul, es una pregunta de sí o no.

Jev solo sabe lo que tú le cuentas, así que describe cada opción y cada nivel con palabras claras. Esas descripciones son el prompt.
Configuración
Jev está disponible a través de OpenRouter, un servicio que te da acceso a muchos modelos de IA con una sola clave API. Esa única clave cubre tanto el modelo de lenguaje como Jev. Instala los dos paquetes de Pi y configura tu clave.
1npm install @earendil-works/pi-agent-core @earendil-works/pi-ai
1OPENROUTER_API_KEY=...
Jev tiene su propia dirección web, distinta a la habitual de chat. Indica siempre una versión exacta, como typesafe/jev-1.13. Todo el cliente de Jev es una simple llamada fetch.
1const JEV = "typesafe/jev-1.13";23async function ask(state: unknown, questions: unknown) {4 const response = await fetch("https://openrouter.ai/api/alpha/decisions", {5 method: "POST",6 headers: {7 "Content-Type": "application/json",8 Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,9 },10 body: JSON.stringify({ model: JEV, state, questions }),11 signal: AbortSignal.timeout(2_000),12 });13 if (!response.ok) throw new Error(await response.text());14 return response.json();15}
El agente se queda esperando mientras Jev responde, por lo que la llamada se cancela tras dos segundos. Una respuesta normal tarda entre 200 y 400 milisegundos.
Dónde encaja Jev
La clase Agent de Pi ejecuta el bucle por ti y permite que tu propio código intervenga en momentos concretos de ese bucle. Estos puntos se llaman hooks. El harness usa un hook para cada una de sus tres partes.

Un primer gate
Empecemos con la versión más sencilla y útil del gate. Antes de cada tool call, hazle a Jev una pregunta de sí o no y bloquea la llamada si la respuesta apunta a que sí.
1import { Agent } from "@earendil-works/pi-agent-core";23const agent = new Agent({4 initialState: { systemPrompt, model, tools },5 streamFn: models.streamSimple.bind(models),67 beforeToolCall: async ({ toolCall, args }) => {8 const { answers } = await ask(9 { tool: toolCall.name, arguments: args },10 {11 destructive: {12 type: "noul",13 instructions: "Esta llamada a herramienta destruye o sobrescribe datos que no fueron creados en esta ejecución.",14 criteria: {15 true: "Borra archivos, trunca o sobrescribe contenido existente, elimina datos o hace un force-push sobre el historial.",16 false: "Lee, lista, crea un archivo nuevo o añade contenido a un archivo que esta ejecución ya creó.",17 },18 },19 },20 );2122 if (answers.destructive.noul >= 0.65) {23 return { block: true, reason: "Bloqueado: parece destructivo.", terminate: true };24 }25 },26});
Ese 0.65 es un umbral, el punto de corte a partir del cual el harness deja de confiar en una llamada. Todo lo que Jev puntúe en ese valor o por encima se bloquea.
Ahora, un agente que intente borrar tus notas se detendrá antes de que ocurra el borrado. Borrar una nota puntúa alrededor de 0.83, y leerla, 0.01. Sin embargo, el gate es poco preciso. Crear un archivo completamente nuevo puntúa cerca de 0.70, así que también se bloquea.
Consultar cuesta muy poco. Jev cobra 0.042 $ por millón de tokens de entrada, menos de un tercio del precio de entrada de GLM 5.3 Flash, el más barato de los dos modelos que usa este harness.
Del primer gate al harness completo
El primer gate funciona, pero deja cuatro huecos.
- El umbral está escondido dentro del hook, así que cuesta ajustarlo o probarlo.
- Todas las peticiones se ejecutan en el mismo modelo, sean fáciles o difíciles.
- Nada indica qué pasa si no se puede contactar con Jev.
- Nada comprueba si la respuesta final es buena.
Cada sección numerada a continuación cierra uno de esos huecos.
1. Centraliza los umbrales
Esta sección mejora el gate.
Los umbrales deciden qué puede hacer el agente, y los ajustarás constantemente en cuanto veas resultados reales. Por eso, sácalos del hook y llévalos a una función sencilla: decideGate(). Recibe los números de Jev y devuelve un veredicto: la decisión final del harness sobre una llamada. Mantener estas reglas en un solo lugar se conoce como política.
La política también añade una opción intermedia. Con un solo umbral únicamente puedes permitir o bloquear. Con dos umbrales obtienes tres veredictos.
- En blockAt o por encima, la llamada se bloquea.
- Por debajo de askAt, la llamada se ejecuta.
- Entre ambos valores, la llamada espera la aprobación de una persona.
Ese rango intermedio atrapa las llamadas sobre las que Jev tiene dudas y que un único punto de corte clasificaría mal en un sentido u otro.

1export function decideGate(signals: GateSignals, policy = DEFAULT_GATE_POLICY): GateVerdict {2 const worst = Math.max(3 signals.destructive.noul,4 signals.irreversible.noul,5 signals.outsideWorkspace.noul,6 );7 if (worst >= policy.blockAt) return { action: "block", ... };8 if (worst >= policy.askAt) return { action: "ask", ... };9 return { action: "allow", ... };10}
Al ser una función normal, puedes probarla pasándole números como los de arriba, sin necesidad de involucrar un modelo en vivo.
2. Elige un modelo por petición
Esta sección añade el router.
Algunas peticiones son sencillas, como leer un archivo. Otras son complejas, como averiguar por qué algo falló. Ejecutarlo todo en el modelo más potente es tirar el dinero, y hacerlo todo en uno barato da respuestas flojas a las peticiones difíciles. El router asigna cada petición al tier adecuado, es decir, al modelo rápido y barato o al potente y caro.
Antes de que empiece una petición, el router le hace a Jev dos preguntas en una sola llamada. Una elección (choice) selecciona el tier, y una puntuación (score) evalúa la complejidad de la petición.

1const ROUTER_QUESTIONS = {2 tier: choice("¿Qué tier de modelo debería encargarse de esta petición?", {3 fast: "Leer un archivo, extraer un dato de él o hacer una edición pequeña en un solo lugar.",4 powerful: "Trabajo que abarca varios archivos o un fallo sin causa evidente.",5 }),6 complexity: score("¿Cuánto razonamiento requiere esta petición?", [7 "Mecánico. Un solo paso, sin criterio.",8 "Localizado. Unos pocos pasos dentro de una misma área.",9 "Arquitectónico. Muchas piezas móviles o una causa raíz desconocida.",10 ]),11};
La política del router utiliza ambas respuestas así:
- Si la puntuación de complejidad es alta, usa el modelo potente, aunque Jev haya elegido el rápido.
- Si Jev no confía en su elección, usa el modelo potente por seguridad.
- En caso contrario, usa el modelo que eligió Jev.
1if (complexity.score >= policy.escalateAtComplexity) return powerful;2if (tierAnswer.confidence < policy.minConfidence) return policy.fallbackTier;3return tierAnswer.choice;
Este es solo un ejemplo de política. La tuya puede ponderar las respuestas de otra forma según tu dominio: por ejemplo, apostando por lo barato en un proceso por lotes de gran volumen o escalando siempre cualquier cosa que toque producción. Jev solo aporta las respuestas; tu código decide qué hacer con ellas.
Por qué elegir solo una vez
El router elige un modelo una única vez, cuando empieza la petición, y lo mantiene hasta que termina. Esto se debe al prompt caching (caché de prompts).
Cada vez que el agente da un paso, el modelo vuelve a leer toda la conversación hasta ese momento. Los proveedores de IA almacenan las conversaciones leídas recientemente para que releerlas sea barato. Pero cada modelo tiene su propio almacén. Si cambias de modelo a mitad de camino, el nuevo tendrá que volver a leerlo todo desde cero y a precio completo.
El fundador de Jev desglosa los números en un documento de diseño sobre agentes de programación. En una sesión larga, alternar entre Claude Opus y el más barato Sonnet y viceversa costó alrededor de un 50 % más que mantenerse en Opus todo el tiempo. Así que elige el modelo al principio, cuando la conversación aún es corta, y no lo cambies.
3. Prepárate para las caídas de Jev
Esta sección modifica tanto el gate como el router.
Cuando el harness consulta a Jev en cada tool call, el agente pasa a depender de él. Como cualquier servicio online, Jev puede ir lento o caerse. Decide de antemano qué hará cada parte si no obtiene respuesta. La decisión correcta varía según el componente.
El gate bloquea la llamada. Si el gate no puede consultar a Jev, no tiene forma de saber si la llamada es segura. Dejarla pasar podría borrar archivos, así que el gate la rechaza. En ingeniería esto se llama fallo cerrado (fail closed), como una puerta que se bloquea cuando se va la luz.
El router usa el modelo potente. Si el router no puede consultar a Jev, no sabe lo difícil que es la petición. El modelo potente puede con todo, así que la petición seguirá obteniendo una buena respuesta y tú pagarás un poco más. Esto es un fallo abierto (fail open): dejar que el trabajo continúe.

4. Verifica la respuesta
Esta sección añade el verificador. En los harnesses de agentes, un verificador es el paso que revisa el trabajo del agente antes de darlo por terminado.
Un agente puede acabar con una respuesta que omite algo o que afirma cosas que nunca llegó a comprobar en los archivos. Quien la lee muchas veces no se da cuenta. Revisar la respuesta antes de devolverla detecta estos casos mientras el agente aún puede volver a intentarlo.
El verificador envía a Jev la respuesta terminada junto con los archivos y los resultados de herramientas en los que se basó. Jev puntúa la calidad de la respuesta e indica si sus afirmaciones están fundamentadas (grounded), es decir, respaldadas por lo que el agente leyó realmente.

1const VERIFY_QUESTIONS = {2 quality: score("¿Qué tan bien satisface la respuesta la petición?", [3 "No responde a la petición.",4 "Responde en parte, con un vacío que el lector notaría.",5 "Responde completamente a la petición.",6 ]),7 grounded: noul("Todas las afirmaciones fácticas están respaldadas por los archivos o los resultados de herramientas de la transcripción."),8};
Dos reglas evitan que el agente se pase la vida reintentando. Tiene un máximo de dos intentos en total. Y cuando Jev no confía en su propia calificación, el harness acepta la respuesta en lugar de pagar por otro intento.
Seguridad y registro
Jev te da una probabilidad, y una probabilidad puede estar equivocada. Por eso, todo lo que el código pueda comprobar con certeza absoluta debería comprobarse en el código. En este harness, cualquier herramienta de archivos rechaza rutas fuera de la carpeta del proyecto, diga lo que diga Jev. Deja a Jev para las decisiones subjetivas que el código no puede tomar.
El gate solo examina la tool call en sí, es decir, el nombre de la herramienta y sus parámetros. Esto ayuda frente a la inyección de prompts, donde un texto oculto en un archivo o página web engaña al modelo para que haga algo dañino. El gate nunca ve el engaño, pero sí ve la llamada peligrosa a la que conduce.
Registra cada decisión junto con los números que la respaldan. El log explica por qué se bloqueó algo y ofrece datos reales para ajustar los umbrales. El harness escribe una línea por decisión en un archivo llamado decisions.jsonl. Como el gate ve todo lo que el agente intenta hacer, el log oculta direcciones de correo y claves, y acorta las entradas largas.
Pruébalo
El sandbox de abajo ejecuta el harness terminado de este tutorial, conectado al Jev real. Trabaja sobre la carpeta de notas de inspección de senderos, y su herramienta delete_path puede borrarlas de verdad.
Pruébalo aquí: https://academy.dair.ai/resources/jev-decisions-in-a-pi-sdk-harness
Por qué construir tu propio harness
Los agentes prefabricados toman estas decisiones con sus propias reglas internas. Un harness personalizado las pone en tu código. Tú eliges los modelos, defines los umbrales, decides cuándo interviene una persona y lees en el log exactamente por qué se tomó cada decisión.
Jev es lo que hace que esto sea viable. Cada decisión tarda unos cientos de milisegundos y cuesta una fracción mínima de centavo, así que puedes añadir una comprobación allá donde tu trabajo la necesite, no solo donde puedas permitirte una llamada completa al modelo. Las tres partes de este tutorial son un punto de partida. Un harness personalizado para tu propio dominio puede hacerle a Jev las preguntas que allí importen.
Otros usos
Estas mismas tres partes funcionan fuera de los agentes de programación.
- Bots de revisión de código. Puntúa cada cambio sugerido y muéstrale a una persona solo los que merecen la pena leer.
- Limpieza de registros. Pregunta si dos registros describen lo mismo antes de fusionarlos y deja los pares dudosos para una persona.
- Pipelines de documentos. Puntúa cada página extraída y reprocesa solo las de menor puntuación.
- Colas de aprobación. Solo las llamadas que caen en el rango de "preguntar a una persona" llegan a un revisor humano.
En todos los casos, el modelo de lenguaje hace el trabajo abierto y Jev responde a las pequeñas preguntas que lo rodean.
Las preguntas, umbrales y políticas de este tutorial son ejemplos didácticos, no configuraciones optimizadas para producción. Estamos evaluando cómo estos cambios en el harness afectan al costo y a la calidad de las respuestas, y pronto publicaremos una guía complementaria con esos resultados.
Dediqué una tarde con Opus 5.5 a preparar esta guía y el sandbox. Si te encuentras con algún problema, escríbeme por mensaje directo. Siéntete libre de copiar el artículo y dárselo a tus agentes para seguir experimentando con estas ideas.





