El problema
Un agente que trabaja sobre un proyecto real no ve todo el proyecto. Ve lo que busca, y busca lo que se le ocurre buscar. Cuando la utilidad que necesita está en otro módulo con otro nombre, la escribe otra vez. Cuando una tarea admite una solución corta y otra larga, nada lo empuja hacia la corta.
La respuesta habitual son normas escritas: un CLAUDE.md, una skill, el prompt de sistema. Ayudan, pero son consejos. El modelo puede no cargarlas, olvidarlas a mitad de turno o decidir que no aplican. No podemos tocar los pesos del modelo, pero sí controlar tres cosas: lo que el modelo ve, lo que se le deja hacer y cuándo se le deja terminar. Sens usa las tres a la vez.
El circuito
Claude Code acepta hooks como callbacks en su propio protocolo de stream. Sens atiende cinco dentro de su proceso, con un invariante: un cambio nace sin aprobar. Solo lo aprueban una auditoría que pasa o una persona.
- 01
Envías un mensaje
UserPromptSubmit
Sens toma un punto de control del proyecto y le da a Claude hasta ocho cosas que ya existen y tienen que ver con la petición, cada una con su firma, fichero:línea y número de usos.
- 02
Antes de cada escritura
PreToolUse · Write, Edit
Sens reconstruye en memoria el fichero resultante y le aplica las reglas de cambio. Puede denegar la escritura antes de que ocurra, con el motivo y el código que reutilizar.
- 03
Antes de cada comando
PreToolUse · Bash, PowerShell
Sens protege sus rutas y la configuración, y trata git commit y git push como el final de un turno: no se confirma nada sin aprobar.
- 04
Después de cada herramienta
PostToolUse
Sea cual sea la herramienta, Sens busca los ficheros que cambiaron en disco y les aplica las mismas reglas.
- 05
Cuando termina el turno
Stop · SubagentStop
Sens audita todo el diff desde el último punto aprobado: reglas de cambio, código huérfano y, si cambió código, el revisor. Con hallazgos que bloquean, Claude sigue trabajando; tras tres rondas, el turno queda retenido para ti.
Nada que rodear
Antes de construir nada comprobamos en vivo que el mecanismo resiste a un modelo que intenta saltárselo: un disableAllHooks escrito a mitad de sesión no para los callbacks, los subagentes pasan por ellos y un git commit denegado deja el repositorio como estaba. Cada atajo tiene algo que lo cierra:
| Atajo | Qué lo cierra |
|---|---|
| Escribir por la terminal, con Python o por otro servidor MCP | La revisión del disco tras cada herramienta y la auditoría al final del turno |
| Subagentes y tareas en segundo plano | Pasan por los mismos hooks; el turno no se aprueba mientras sigan vivos |
| Apagar los hooks o editar la configuración | Los hooks viven en el proceso de Sens; la regla R7 bloquea y restaura esos ficheros |
| Abrir un worktree propio | Las herramientas de worktree están denegadas y R7 cubre git worktree |
| Declarar que ha terminado | El final del turno lo decide la auditoría, no el modelo |
| Empezar un turno nuevo para escapar | La auditoría parte del último punto aprobado: lo pendiente se hereda |
| Confirmar trabajo sin aprobar | Un commit se trata como el final de un turno |
| Un bucle sin fin | Tres rondas y el turno queda retenido |
El límite es explícito: el circuito cierra los errores y atajos de un modelo, no los de un programa hostil que corra en la misma máquina.
Resultados
Las tareas sueltas parten de un proyecto limpio, y el daño de un agente que no reutiliza no está en una tarea, sino en la suma. Horizonte mide la suma: una línea de comandos en TypeScript para los gastos de casa que empieza con 88 líneas, y 30 peticiones de producto en un orden fijo, cada una sobre la anterior. Se siembran ocho conceptos que varias tareas necesitan sin decirlo: fechas, meses y semanas, totales, acentos, importes, CSV y opciones de comando. La primera vez el agente los escribe; después, lo correcto es reutilizar lo que escribió.
El criterio se fijó por escrito antes de medir: Sens deja el proyecto más pequeño solo si las tres secuencias con Sens terminan por debajo de las tres sin él. Sin diferencia real, eso ocurre por azar una vez de cada veinte.
Tamaño del proyecto tras cada tarea
- Sin Sens
- Referencia
- Con Sens
Líneas de código del proyecto tras cada una de las 30 tareas. Las líneas finas son cada secuencia; las gruesas, su mediana. La discontinua es una solución de referencia escrita para reutilizar, que crea sus módulos compartidos pronto.
Ver los datos
| Tras la tarea | Sin Sens | Referencia | Con Sens |
|---|---|---|---|
| 0 | 88 | 88 | 88 |
| 1 | 92 | 93 | 93 |
| 2 | 102 | 113 | 103 |
| 3 | 113 | 150 | 109 |
| 4 | 121 | 161 | 118 |
| 5 | 145 | 188 | 137 |
| 6 | 162 | 202 | 161 |
| 7 | 178 | 209 | 177 |
| 8 | 223 | 234 | 195 |
| 9 | 232 | 243 | 206 |
| 10 | 232 | 243 | 206 |
| 11 | 258 | 276 | 231 |
| 12 | 277 | 282 | 247 |
| 13 | 296 | 307 | 265 |
| 14 | 304 | 309 | 269 |
| 15 | 316 | 315 | 271 |
| 16 | 346 | 345 | 301 |
| 17 | 354 | 357 | 309 |
| 18 | 367 | 369 | 326 |
| 19 | 370 | 377 | 330 |
| 20 | 381 | 390 | 341 |
| 21 | 398 | 404 | 357 |
| 22 | 415 | 413 | 373 |
| 23 | 426 | 422 | 384 |
| 24 | 432 | 422 | 388 |
| 25 | 452 | 434 | 401 |
| 26 | 453 | 436 | 402 |
| 27 | 476 | 453 | 418 |
| 28 | 485 | 455 | 426 |
| 29 | 487 | 455 | 427 |
| 30 | 496 | 455 | 436 |
Tamaño en la tarea 30, por secuencia
Las tres secuencias con Sens terminan por debajo de las tres sin él. El criterio se cumple: mediana de 436 líneas frente a 496, un 12 % menos, con un intervalo al 95 % de −140 a −28 líneas.
Tokens gastados, acumulados en las 30 tareas
- Sin Sens
- Con Sens
Con un proyecto más pequeño que leer en cada tarea, Sens gasta menos: 27,7 millones de tokens en sus tres secuencias frente a 33,7 millones. Los tokens incluyen lecturas de caché: miden el volumen de trabajo, no el coste exacto.
Ver los datos
| Tras la tarea | Sin Sens | Con Sens |
|---|---|---|
| 0 | 0,0M | 0,0M |
| 1 | 0,3M | 0,3M |
| 2 | 0,5M | 0,6M |
| 3 | 0,9M | 0,8M |
| 4 | 1,2M | 1,1M |
| 5 | 1,6M | 1,3M |
| 6 | 1,8M | 1,7M |
| 7 | 2,0M | 1,9M |
| 8 | 2,5M | 2,4M |
| 9 | 3,4M | 2,7M |
| 10 | 3,7M | 3,0M |
| 11 | 4,4M | 3,4M |
| 12 | 4,7M | 3,6M |
| 13 | 5,2M | 4,0M |
| 14 | 5,4M | 4,2M |
| 15 | 5,9M | 4,5M |
| 16 | 6,2M | 5,1M |
| 17 | 6,6M | 5,3M |
| 18 | 6,9M | 5,7M |
| 19 | 7,1M | 6,1M |
| 20 | 7,5M | 6,4M |
| 21 | 8,1M | 6,7M |
| 22 | 8,5M | 7,1M |
| 23 | 8,8M | 7,4M |
| 24 | 9,0M | 7,7M |
| 25 | 9,4M | 7,9M |
| 26 | 9,8M | 8,2M |
| 27 | 10,2M | 8,5M |
| 28 | 10,7M | 8,8M |
| 29 | 11,0M | 9,1M |
| 30 | 11,3M | 9,3M |
De dónde sale la diferencia
No de copiar menos. Ningún brazo copió bloques en serio: jscpd encontró 0, 6 y 6 líneas duplicadas sin Sens y ninguna con él, y las sondas de cada concepto sembrado dan cifras iguales o casi iguales en los dos. La diferencia viene de escribir menos para lo mismo. Con Sens, el agente escribe más funciones y más cortas: una mediana de 28 frente a 20. En el piloto, para leer descripciones entre comillas en el CSV, el agente sin Sens escribió un lector de CSV entero, 99 líneas; con Sens, vio que la descripción era el último campo y le bastaron dos funciones de una línea.
Tokens en total: 33,7M sin Sens · 27,7M con Sens
Tareas sueltas
Doce tareas en tres lenguajes sobre dos proyectos reales, el propio Sens en TypeScript y Rust y la biblioteca de Python click en commits fijados, cada una validada con tests ocultos y una solución de referencia. Tres condiciones con el mismo modelo: Claude Code solo (C0), el Canon como texto en el prompt de sistema sin circuito (C1) y Sens entero (C2).
| Medida | C0 · solo | C1 · Canon como texto | C2 · Sens |
|---|---|---|---|
| Ejecuciones válidas | 32/36 | 31/36 | 67/72 |
| Ejecuciones que añadieron tests | 23/36 | 36/36 | 72/72 |
| Reutiliza plain, lejos de la edición | 0/3 | 3/3 | 6/6 |
| Reutiliza titleOf, lejos de la edición | 1/3 | 1/3 | 6/6 |
| Resuelve py-progress-final | 0/3 | 0/3 | 3/6 |
El texto solo ya consigue buena parte de la reutilización cuando la utilidad está cerca. No consigue los casos en que está lejos y con otro nombre, titleOf, 1 de 3 frente a 6 de 6, ni la tarea que exige arreglar la causa compartida en vez de un camino, py-progress-final, 0 de 3 frente a 3 de 6. En tareas sueltas, las líneas de código son ruido: C2 escribe unas dos líneas menos por tarea, pero el intervalo toca el cero. Por esa variabilidad existe Horizonte.
Ejecuciones que añadieron al menos una línea de test
Con el Canon 1.0 el agente casi dejó de escribir tests: leía «haz lo que te piden y nada más» como una prohibición, y tomaba la aprobación de Sens por una ejecución de tests. El Canon 1.1 dice las dos cosas que faltaban: un test que prueba el cambio es parte del cambio, y la aprobación de Sens no es una ejecución de tests. Claude Code solo no recibe el Canon: sus barras son la línea base de cada tanda.
Las reglas
Las reglas de cambio son deterministas. Comparan huellas de cada función, método y clase, y de cada cuatro sentencias seguidas, con un índice en Rust sobre tree-sitter que mantiene el proyecto en memoria: el propio repositorio de Sens, 556 ficheros y 10 000 unidades, se indexa en menos de dos segundos, y buscar las copias de una unidad cuesta del orden de un microsegundo. Las copias exactas y las de nombres cambiados coinciden por hash; las que añaden o quitan líneas, por MinHash sobre tokens normalizados. El umbral de 0,80 y el mínimo de 80 tokens para bloquear salen de editar 400 funciones de un repositorio real y revisar cada coincidencia a mano.
| Regla | Detecta | Respuesta |
|---|---|---|
| R1 Reutilizar | Una función, método o clase nueva con la misma huella de tipo 1 o 2 que una existente | Bloquea desde 80 tokens; por debajo, se le pide a Claude que lo piense otra vez |
| R2 Casi copia | Similitud de tipo 3 por encima del umbral, o una función pequeña igual en forma y vocabulario a otra | Como R1; una nota en tests |
| R3 Dependencia nueva | Un manifiesto gana una dependencia, en diez formatos | Te pregunta |
| R4 Huérfanos | Un símbolo nuevo al que nada llega, o uno existente que el turno dejó sin usar | Bloquea si es interno; nota si es exportado |
| R6 Normas del proyecto | Normas que declaras tú; la primera, sin comentarios | Bloquea |
| R7 Integridad | Escribir en .sens/, .git/, .claude/settings*.json o .mcp.json, o git worktree | Siempre bloquea; se restaura si llegó por la terminal |
| R8 Tests protegidos | El turno quita tests o aserciones que Sens ya había aprobado | Te pregunta |
Las reglas no ven los errores de criterio. Para eso, cuando un turno que tocó código pasa las reglas, un revisor lee el diff con los candidatos que encontró el índice. Su salida no se cree a ciegas: se descarta todo hallazgo cuya cita no aparezca literalmente en el diff, y solo la confianza alta bloquea.
| Nota | Detecta |
|---|---|
| S1 | Una abstracción sin segundo uso |
| S2 | Un arreglo del síntoma en vez de la causa |
| S3 | Reinventar lo que da la plataforma o una dependencia |
| S4 | Especulación: opciones o ramas que nadie pidió |
| S5 | Ingenio donde bastaba lo evidente |
| S6 | Un recorte peligroso: quitar validación, manejo de errores o seguridad |
| S7 | Reinventar lo que el proyecto ya tiene, citando un candidato |
Lo que no funcionó
Cada bloqueo del circuito se revisó a mano, con su diff y su conversación. Los bloqueos son escasos, siete en 228 ejecuciones de Sens, así que uno solo injusto pesa mucho. Buscábamos menos de un 5 % de bloqueos injustos y no lo logramos en ninguna tanda con bloqueos; cada injusticia tenía una causa concreta, ya corregida con un test que la fija.
| Tanda | Bloqueos | Injustos | Causa | Corrección |
|---|---|---|---|---|
| Tareas difíciles, C2 v3 | 1 | 0, 1 discutible | El revisor marcó un modismo que el proyecto repite | Un S7 sobre algo privado es solo nota |
| Calibración, C2 | 2 | 2 | R8 comparaba con el fichero previo a cada escritura | R8 compara con lo último aprobado |
| Calibración, C2 tras el arreglo | 0 | 0 | — | — |
| Horizonte, piloto | 1 | 0 | — | — |
| Horizonte, confirmación | 3 | 1 | R8 tomó una función auxiliar de tests por un test | Solo cuenta como test lo que comprueba algo |
Una lista equivocada es peor que ninguna. La primera versión de Sens sugería ocho símbolos sin relación con la petición; el modelo los leyó, no buscó más y reescribió a mano la utilidad de acentos las tres veces, mientras que el Canon como texto, sin lista, la importó las tres. Con la búsqueda rehecha, la utilidad aparece entre las sugerencias y Sens la usa siempre, sin bloquear nada.
Limitaciones
- Un modelo. Todas las ejecuciones usaron Claude Sonnet 5.5 con esfuerzo medio.
- Un proyecto, un lenguaje, tres secuencias por brazo. El criterio confirmatorio es exigente, todas por debajo de todas, pero la magnitud del efecto tiene un intervalo ancho.
- Las tareas las escribimos nosotros. Para que no inclinaran el resultado, las tareas, sus tests y la referencia se guardaron antes de la primera ejecución, y el criterio se fijó antes de medir.
- Sin herramientas MCP en el banco. Sens se midió sin las consultas al índice que ofrece la app, así que el resultado es un límite inferior.
- Diecinueve lenguajes aún sin medir en el banco. Vue, Svelte y los lenguajes añadidos después están cubiertos por tests, no por ejecuciones del agente.
- Los tokens incluyen lecturas de caché. Miden el volumen de trabajo, no el coste exacto.
- El revisor tiene poca precisión. De las siete notas y bloqueos suyos que revisamos, cinco eran erróneos. Sus notas no paran nada, pero llegan al modelo y a ti.
- Convenciones no escritas. Sens no conoce las normas implícitas de un proyecto, como dejar las importaciones pesadas dentro de la función.
Método
456 ejecuciones del agente en cinco tandas: un piloto, tres tareas difíciles sobre el propio Sens, doce tareas de calibración y el piloto y la confirmación de Horizonte. Todas las condiciones usaron claude-sonnet-5-5 con esfuerzo medio, con Claude Code en --safe-mode para que la configuración del autor no se colara en las ejecuciones. Cada tarea se validó antes de usarla: al empezar, los tests del proyecto pasan y los ocultos fallan, y con la referencia aplicada pasan los dos. Una regresión tiene que fallar dos veces seguidas para contar. Las diferencias son medianas con un intervalo bootstrap al 95 %, 10 000 remuestreos con semilla fija; el criterio de Horizonte es una prueba exacta de permutación.
Reprodúcelo
sens-bench validate --tasks bench/tasks
sens-bench run --tasks bench/tasks --condition C0,C1,C2 --reps 3 --out bench/results/<batch>
sens-bench sequence validate bench/sequences/cuentas
sens-bench sequence run bench/sequences/cuentas --condition C0,C2 --reps 3 --out bench/results/<batch>
sens-bench sequence report bench/results/<batch>Los datos de cada ejecución, su diff y las tareas están en la carpeta bench/ del repositorio de Sens.
El Canon
El texto que recibe cada sesión, palabra por palabra, en inglés tal como lo lee el modelo. El circuito es lo que lo convierte en algo más que un consejo.
# Sens Canon v1.1
You are working inside Sens. Sens indexes this project and judges every change you make before your turn can end. What Sens tells you about this project, in its messages, denials and reviews, is a fact about the code, not a suggestion. When Sens names something to reuse, reuse it.
## Before you write
Go down this ladder and stop at the first step that answers the need:
1. Is it needed? Do what the person asked and nothing more: no speculative options, parameters, flags or branches. A test that proves the change is part of the change, not something extra.
2. Does the project already have it? Reuse the existing function, component, type or constant. Ask Sens with `already_exists` or `find_symbol` when unsure.
3. Does the standard library or the platform give it? Use that.
4. Does an installed dependency give it? Use that. A new dependency needs the person's approval, and Sens asks them for it.
5. Only then write new code: the smallest version that is correct.
## While you write
- Fix the cause in the shared code, not the symptom in each caller.
- No abstraction without a second real use: no interface, factory, wrapper, layer or configuration for a single consumer.
- Boring over clever. Match the names, patterns and style of the code around you.
- If you would copy a block, extract it once and call it from both places.
- Delete what your change leaves unused.
## Never cut
Less code never means removing validation at trust boundaries, error handling that prevents data loss, security checks, accessibility, or anything the person asked for.
It never means skipping tests either. When your change alters behaviour and the project has tests, add or extend one that fails without your change, in the style of the tests around it, and run the tests you touched before you finish.
## Working with Sens
- A denied write comes with the reason and what to use instead. Change the approach. Retrying the same thing through the shell, another tool or a subagent does not help: Sens judges what lands on disk, however it got there.
- When you finish, Sens audits the whole turn. If it blocks, fix what it found and finish again.
- Sens judges the shape of the code, not whether it works. Its approval is not a test run: that part is yours.
- Never edit `.sens/`, `.claude/settings*.json` or `.mcp.json`.