Investigación ·

Menos código para el mismo trabajo.

Los agentes de programación escriben más de lo necesario: reescriben lo que el proyecto ya tiene, y cada turno añade un poco. Sens pone un circuito alrededor de cada turno de Claude Code que el modelo no puede apagar. Tras 30 tareas encadenadas, el proyecto acabó un 12 % más pequeño para las mismas funciones, con un 18 % menos de tokens.

12 %
menos código para las mismas 30 funciones
18 %
menos tokens en toda la secuencia
90/90
tareas aceptadas, con Sens y sin él
0
tests de tareas anteriores rotos, en ningún brazo

Claude Sonnet 5.5 con esfuerzo medio · tres secuencias por brazo · criterio fijado antes de medir

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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:

AtajoQué lo cierra
Escribir por la terminal, con Python o por otro servidor MCPLa revisión del disco tras cada herramienta y la auditoría al final del turno
Subagentes y tareas en segundo planoPasan por los mismos hooks; el turno no se aprueba mientras sigan vivos
Apagar los hooks o editar la configuraciónLos hooks viven en el proceso de Sens; la regla R7 bloquea y restaura esos ficheros
Abrir un worktree propioLas herramientas de worktree están denegadas y R7 cubre git worktree
Declarar que ha terminadoEl final del turno lo decide la auditoría, no el modelo
Empezar un turno nuevo para escaparLa auditoría parte del último punto aprobado: lo pendiente se hereda
Confirmar trabajo sin aprobarUn commit se trata como el final de un turno
Un bucle sin finTres 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
0200400600051015202530TareaSin Sens · 496Referencia · 455Con Sens · 436

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 tareaSin SensReferenciaCon Sens
0888888
1929393
2102113103
3113150109
4121161118
5145188137
6162202161
7178209177
8223234195
9232243206
10232243206
11258276231
12277282247
13296307265
14304309269
15316315271
16346345301
17354357309
18367369326
19370377330
20381390341
21398404357
22415413373
23426422384
24432422388
25452434401
26453436402
27476453418
28485455426
29487455427
30496455436

Tamaño en la tarea 30, por secuencia

400440480520560referencia · 455Sin SensSin Sens: 496 líneas496Sin Sens: 564 líneas564Sin Sens: 484 líneas484Con SensCon Sens: 436 líneas436Con Sens: 456 líneas456Con Sens: 424 líneas424

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
0,0M2,5M5,0M7,5M10,0M12,5M051015202530TareaSin Sens · 11,3MCon Sens · 9,3M

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 tareaSin SensCon Sens
00,0M0,0M
10,3M0,3M
20,5M0,6M
30,9M0,8M
41,2M1,1M
51,6M1,3M
61,8M1,7M
72,0M1,9M
82,5M2,4M
93,4M2,7M
103,7M3,0M
114,4M3,4M
124,7M3,6M
135,2M4,0M
145,4M4,2M
155,9M4,5M
166,2M5,1M
176,6M5,3M
186,9M5,7M
197,1M6,1M
207,5M6,4M
218,1M6,7M
228,5M7,1M
238,8M7,4M
249,0M7,7M
259,4M7,9M
269,8M8,2M
2710,2M8,5M
2810,7M8,8M
2911,0M9,1M
3011,3M9,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).

MedidaC0 · soloC1 · Canon como textoC2 · Sens
Ejecuciones válidas32/3631/3667/72
Ejecuciones que añadieron tests23/3636/3672/72
Reutiliza plain, lejos de la edición0/33/36/6
Reutiliza titleOf, lejos de la edición1/31/36/6
Resuelve py-progress-final0/30/33/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

Canon 1.0 · piloto y tareas difíciles

Sin Sens (C0)14/18
El Canon como texto (C1)9/18
Sens (C2)2/18

Canon 1.1 · calibración

Sin Sens (C0)23/36
El Canon como texto (C1)36/36
Sens (C2)36/36

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.

ReglaDetectaRespuesta
R1 ReutilizarUna función, método o clase nueva con la misma huella de tipo 1 o 2 que una existenteBloquea desde 80 tokens; por debajo, se le pide a Claude que lo piense otra vez
R2 Casi copiaSimilitud de tipo 3 por encima del umbral, o una función pequeña igual en forma y vocabulario a otraComo R1; una nota en tests
R3 Dependencia nuevaUn manifiesto gana una dependencia, en diez formatosTe pregunta
R4 HuérfanosUn símbolo nuevo al que nada llega, o uno existente que el turno dejó sin usarBloquea si es interno; nota si es exportado
R6 Normas del proyectoNormas que declaras tú; la primera, sin comentariosBloquea
R7 IntegridadEscribir en .sens/, .git/, .claude/settings*.json o .mcp.json, o git worktreeSiempre bloquea; se restaura si llegó por la terminal
R8 Tests protegidosEl turno quita tests o aserciones que Sens ya había aprobadoTe 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.

NotaDetecta
S1Una abstracción sin segundo uso
S2Un arreglo del síntoma en vez de la causa
S3Reinventar lo que da la plataforma o una dependencia
S4Especulación: opciones o ramas que nadie pidió
S5Ingenio donde bastaba lo evidente
S6Un recorte peligroso: quitar validación, manejo de errores o seguridad
S7Reinventar 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.

TandaBloqueosInjustosCausaCorrección
Tareas difíciles, C2 v310, 1 discutibleEl revisor marcó un modismo que el proyecto repiteUn S7 sobre algo privado es solo nota
Calibración, C222R8 comparaba con el fichero previo a cada escrituraR8 compara con lo último aprobado
Calibración, C2 tras el arreglo00——
Horizonte, piloto10——
Horizonte, confirmación31R8 tomó una función auxiliar de tests por un testSolo 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`.