Recherche ·

Moins de code pour le même travail.

Les agents de programmation écrivent plus que nécessaire : ils réécrivent ce que le projet contient déjà, et chaque tour en ajoute un peu. Sens place autour de chaque tour de Claude Code un circuit que le modèle ne peut pas désactiver. Après 30 tâches enchaînées, le projet était 12 % plus petit pour les mêmes fonctionnalités, avec 18 % de tokens en moins.

12 %
de code en moins pour les mêmes 30 fonctionnalités
18 %
de tokens en moins sur toute la séquence
90/90
tâches acceptées, avec et sans Sens
0
test de tâche précédente cassé, dans aucun des deux bras

Claude Sonnet 5.5 en effort moyen · trois séquences par bras · critère fixé avant la mesure

Le problème

Un agent qui travaille sur un vrai projet ne voit pas tout le projet. Il voit ce qu'il cherche, et il cherche ce qui lui vient à l'esprit. Quand la fonction dont il a besoin se trouve dans un autre module sous un autre nom, il la réécrit. Quand une tâche admet une solution courte et une longue, rien ne le pousse vers la courte.

La réponse habituelle, ce sont des règles écrites : un CLAUDE.md, une skill, le prompt système. Elles aident, mais ce sont des conseils. Le modèle peut ne pas les charger, les oublier en cours de tour ou décider qu'elles ne s'appliquent pas. Nous ne pouvons pas toucher aux poids du modèle, mais nous pouvons contrôler trois choses : ce que le modèle voit, ce qu'il a le droit de faire et quand il a le droit de terminer. Sens utilise les trois à la fois.

Le circuit

Claude Code accepte des hooks sous forme de callbacks dans son propre protocole de flux. Sens en traite cinq dans son processus, avec un invariant : une modification naît non approuvée. Seuls un audit réussi ou une personne l'approuvent.

  1. 01

    Vous envoyez un message

    UserPromptSubmit

    Sens prend un point de contrôle du projet et donne à Claude jusqu'à huit éléments qui existent déjà et concernent la demande, chacun avec sa signature, son fichier:ligne et son nombre d'utilisations.

  2. 02

    Avant chaque écriture

    PreToolUse · Write, Edit

    Sens reconstruit en mémoire le fichier qui en résulterait et lui applique les règles de modification. Il peut refuser l'écriture avant qu'elle n'ait lieu, avec la raison et le code à réutiliser.

  3. 03

    Avant chaque commande

    PreToolUse · Bash, PowerShell

    Sens protège ses propres chemins et la configuration, et traite git commit et git push comme la fin d'un tour : rien de non approuvé n'est validé.

  4. 04

    Après chaque outil

    PostToolUse

    Quel que soit l'outil, Sens repère les fichiers modifiés sur le disque et leur applique les mêmes règles.

  5. 05

    À la fin du tour

    Stop · SubagentStop

    Sens audite tout le diff depuis le dernier point approuvé : règles de modification, code orphelin et, si du code a changé, le relecteur. En cas de constat bloquant, Claude continue ; après trois tours, le tour est retenu pour vous.

Rien à contourner

Avant de construire quoi que ce soit, nous avons vérifié en direct que le mécanisme résiste à un modèle qui tente de s'y soustraire : un disableAllHooks écrit en cours de session n'arrête pas les callbacks, les sous-agents y passent, et un git commit refusé laisse le dépôt tel qu'il était. Chaque raccourci a de quoi le fermer :

RaccourciCe qui le ferme
Écrire par le terminal, Python ou un autre serveur MCPLa vérification du disque après chaque outil et l'audit à la fin du tour
Sous-agents et tâches en arrière-planIls passent par les mêmes hooks ; le tour n'est pas approuvé tant qu'ils tournent
Désactiver les hooks ou modifier la configurationLes hooks vivent dans le processus de Sens ; la règle R7 bloque et restaure ces fichiers
Ouvrir son propre worktreeLes outils de worktree sont refusés et R7 couvre git worktree
Déclarer le travail terminéC'est l'audit qui décide de la fin d'un tour, pas le modèle
Commencer un nouveau tour pour s'échapperL'audit part du dernier point approuvé : ce qui est en attente est conservé
Valider un travail non approuvéUn commit est traité comme la fin d'un tour
Boucler sans finTrois tours, puis le tour est retenu

La limite est explicite : le circuit ferme les erreurs et les raccourcis d'un modèle, pas ceux d'un programme hostile qui tournerait sur la même machine.

Résultats

Les tâches isolées partent d'un projet propre, et le tort d'un agent qui ne réutilise pas n'est pas dans une tâche, mais dans la somme. Horizonte mesure la somme : une ligne de commande en TypeScript pour les dépenses du foyer qui commence à 88 lignes, et 30 demandes produit dans un ordre fixe, chacune sur le résultat de la précédente. Huit concepts dont plusieurs tâches ont besoin sans le dire y sont semés : dates, mois et semaines, totaux, accents, montants, CSV, options de commande. La première fois, l'agent les écrit ; ensuite, le bon choix est de réutiliser ce qu'il a écrit.

Le critère a été fixé par écrit avant la mesure : Sens laisse le projet plus petit seulement si les trois séquences avec Sens finissent sous les trois séquences sans lui. Sans différence réelle, cela arrive par hasard une fois sur vingt.

Taille du projet après chaque tâche

  • Sans Sens
  • Référence
  • Avec Sens
0200400600051015202530TâcheSans Sens · 496Référence · 455Avec Sens · 436

Lignes de code du projet après chacune des 30 tâches. Les lignes fines sont chaque séquence ; les épaisses, leur médiane. La ligne pointillée est une solution de référence écrite pour réutiliser, qui crée tôt ses modules partagés.

Afficher les données
Après la tâcheSans SensRéférenceAvec 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

Taille à la tâche 30, par séquence

400440480520560référence · 455Sans SensSans Sens : 496 lignes496Sans Sens : 564 lignes564Sans Sens : 484 lignes484Avec SensAvec Sens : 436 lignes436Avec Sens : 456 lignes456Avec Sens : 424 lignes424

Les trois séquences avec Sens finissent sous les trois sans lui. Le critère est rempli : médiane de 436 lignes contre 496, 12 % de moins, avec un intervalle à 95 % de −140 à −28 lignes.

Tokens dépensés, cumulés sur les 30 tâches

  • Sans Sens
  • Avec Sens
0,0M2,5M5,0M7,5M10,0M12,5M051015202530TâcheSans Sens · 11,3MAvec Sens · 9,3M

Avec un projet plus petit à lire à chaque tâche, Sens dépense moins : 27,7 millions de tokens sur ses trois séquences contre 33,7 millions. Les tokens incluent les lectures de cache : ils mesurent le volume de travail, pas le coût exact.

Afficher les données
Après la tâcheSans SensAvec 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

D'où vient la différence

Pas de moins copier. Aucun des deux bras n'a vraiment copié de blocs : jscpd a trouvé 0, 6 et 6 lignes dupliquées sans Sens et aucune avec, et les sondes de chaque concept semé donnent des chiffres identiques ou presque dans les deux. La différence vient du fait d'écrire moins pour la même chose. Avec Sens, l'agent écrit plus de fonctions, et plus courtes : une médiane de 28 contre 20. Dans le pilote, pour lire les descriptions entre guillemets du CSV, l'agent sans Sens a écrit un lecteur CSV complet, 99 lignes ; avec Sens, il a vu que la description était le dernier champ et deux fonctions d'une ligne lui ont suffi.

Tokens au total : 33,7M sans Sens · 27,7M avec Sens

Tâches isolées

Douze tâches dans trois langages sur deux vrais projets, Sens lui-même en TypeScript et en Rust et la bibliothèque Python click à des commits fixés, chacune validée par des tests cachés et une solution de référence. Trois conditions avec le même modèle : Claude Code seul (C0), le Canon en texte dans le prompt système sans circuit (C1), et Sens complet (C2).

MesureC0 · seulC1 · Canon en texteC2 · Sens
Exécutions valides32/3631/3667/72
Exécutions ayant ajouté des tests23/3636/3672/72
Réutilise plain, loin de la modification0/33/36/6
Réutilise titleOf, loin de la modification1/31/36/6
Résout py-progress-final0/30/33/6

Le texte seul obtient déjà une bonne part de la réutilisation quand la fonction est proche. Il n'obtient pas les cas où elle est loin et nommée autrement, titleOf, 1 sur 3 contre 6 sur 6, ni la tâche qui exige de corriger la cause commune plutôt qu'un seul chemin, py-progress-final, 0 sur 3 contre 3 sur 6. Dans les tâches isolées, les lignes de code sont du bruit : C2 écrit environ deux lignes de moins par tâche, mais l'intervalle touche zéro. C'est pour cette variabilité qu'existe Horizonte.

Exécutions ayant ajouté au moins une ligne de test

Canon 1.0 · pilote et tâches difficiles

Sans Sens (C0)14/18
Le Canon en texte (C1)9/18
Sens (C2)2/18

Canon 1.1 · calibration

Sans Sens (C0)23/36
Le Canon en texte (C1)36/36
Sens (C2)36/36

Avec le Canon 1.0, l'agent avait presque cessé d'écrire des tests : il lisait « faites ce qui est demandé et rien de plus » comme une interdiction, et prenait l'approbation de Sens pour une exécution des tests. Le Canon 1.1 dit les deux choses qui manquaient : un test qui prouve la modification fait partie de la modification, et l'approbation de Sens n'est pas une exécution des tests. Claude Code seul ne reçoit pas le Canon : ses barres sont la référence de chaque lot.

Les règles

Les règles de modification sont déterministes. Elles comparent les empreintes de chaque fonction, méthode et classe, et de chaque suite de quatre instructions, calculées par un index en Rust sur tree-sitter qui garde le projet en mémoire : le dépôt de Sens lui-même, 556 fichiers et 10 000 unités, s'indexe en moins de deux secondes, et chercher les copies d'une unité prend de l'ordre d'une microseconde. Les copies exactes et celles aux noms changés correspondent par hachage ; celles qui ajoutent ou retirent des lignes, par MinHash sur des tokens normalisés. Le seuil de 0,80 et le plancher de 80 tokens pour bloquer viennent de la modification de 400 fonctions d'un vrai dépôt et de la relecture à la main de chaque correspondance.

RègleDétecteRéponse
R1 RéutiliserUne nouvelle fonction, méthode ou classe avec la même empreinte de type 1 ou 2 qu'une existanteBloque à partir de 80 tokens ; en dessous, Claude est invité à y repenser
R2 Quasi-copieUne similarité de type 3 au-dessus du seuil, ou une petite fonction identique à une autre par la forme et le vocabulaireComme R1 ; une remarque dans les tests
R3 Nouvelle dépendanceUn manifeste gagne une dépendance, dans dix formatsVous demande
R4 OrphelinsUn nouveau symbole que rien n'atteint, ou un existant que le tour a laissé inutiliséBloque s'il est interne ; une remarque s'il est exporté
R6 Règles du projetLes règles que vous déclarez ; la première, pas de commentairesBloque
R7 IntégritéÉcrire dans .sens/, .git/, .claude/settings*.json ou .mcp.json, ou git worktreeBloque toujours ; restauré si cela passe par le terminal
R8 Tests protégésLe tour retire des tests ou des assertions que Sens avait approuvésVous demande

Les règles ne voient pas les erreurs de jugement. Pour celles-ci, quand un tour qui a touché du code passe les règles, un relecteur lit le diff avec les candidats trouvés par l'index. Sa sortie n'est pas prise sur parole : tout constat dont la citation n'apparaît pas littéralement dans le diff est écarté, et seule une confiance élevée bloque.

RemarqueDétecte
S1Une abstraction sans second usage
S2Une correction du symptôme plutôt que de la cause
S3Réinventer ce que fournit la plateforme ou une dépendance
S4Spéculation : options ou branches que personne n'a demandées
S5De l'astuce là où l'évidence suffisait
S6Une coupe dangereuse : validation, gestion d'erreurs ou sécurité retirée
S7Réinventer ce que le projet a déjà, en citant un candidat

Ce qui n'a pas marché

Chaque blocage du circuit a été relu à la main, avec son diff et sa conversation. Les blocages sont rares, sept sur 228 exécutions de Sens, si bien qu'un seul blocage injuste pèse lourd. Nous visions moins de 5 % de blocages injustes et ne l'avons atteint dans aucun lot comportant des blocages ; chaque injustice avait une cause précise, désormais corrigée par un test qui la fixe.

LotBlocagesInjustesCauseCorrection
Tâches difficiles, C2 v310, 1 discutableLe relecteur a signalé un idiome que le projet répèteUn S7 sur un élément privé n'est qu'une remarque
Calibration, C222R8 comparait au fichier d'avant chaque écritureR8 compare au dernier état approuvé
Calibration, C2 après correction00——
Horizonte, pilote10——
Horizonte, confirmation31R8 a pris une fonction utilitaire de test pour un testSeul ce qui vérifie quelque chose compte comme test

Une mauvaise liste est pire que pas de liste. La première version de Sens suggérait huit symboles sans rapport avec la demande ; le modèle les a lus, n'a pas cherché plus loin et a réécrit à la main la fonction des accents les trois fois, alors que le Canon en texte, sans liste, l'a importée les trois fois. Avec la recherche refaite, la fonction apparaît parmi les suggestions et Sens l'utilise à chaque fois, sans rien bloquer.

Limites

  • Un seul modèle. Toutes les exécutions ont utilisé Claude Sonnet 5.5 en effort moyen.
  • Un projet, un langage, trois séquences par bras. Le critère de confirmation est exigeant, toutes sous toutes, mais l'ampleur de l'effet a un intervalle large.
  • Nous avons écrit les tâches. Pour qu'elles ne biaisent pas le résultat, les tâches, leurs tests et la référence ont été enregistrés avant la première exécution, et le critère fixé avant la mesure.
  • Pas d'outils MCP dans le banc. Sens a été mesuré sans les requêtes à l'index qu'offre l'application : le résultat est une borne inférieure.
  • Dix-neuf langages pas encore mesurés au banc. Vue, Svelte et les langages ajoutés ensuite sont couverts par des tests, pas par des exécutions de l'agent.
  • Les tokens incluent les lectures de cache. Ils mesurent le volume de travail, pas le coût exact.
  • Le relecteur manque de précision. Sur les sept remarques et blocages relus qui venaient de lui, cinq étaient erronés. Ses remarques n'arrêtent rien, mais elles parviennent au modèle et à vous.
  • Conventions non écrites. Sens ne connaît pas les règles implicites d'un projet, comme garder les imports lourds à l'intérieur d'une fonction.

Méthode

456 exécutions de l'agent en cinq lots : un pilote, trois tâches difficiles sur Sens lui-même, douze tâches de calibration, puis le pilote et la confirmation d'Horizonte. Toutes les conditions ont utilisé claude-sonnet-5-5 en effort moyen, avec Claude Code en --safe-mode pour que la configuration de l'auteur ne s'infiltre pas dans les exécutions. Chaque tâche a été validée avant usage : au départ, les tests du projet passent et les tests cachés échouent, et avec la référence appliquée tous passent. Une régression doit échouer deux fois de suite pour compter. Les différences sont des médianes avec un intervalle bootstrap à 95 %, 10 000 rééchantillonnages à graine fixe ; le critère d'Horizonte est un test exact de permutation.

Le reproduire

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>

Les données de chaque exécution, son diff et les tâches se trouvent dans le dossier bench/ du dépôt de Sens.

Le Canon

Le texte que reçoit chaque session, mot pour mot, en anglais comme le lit le modèle. C'est le circuit qui en fait plus qu'un conseil.

# 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`.