Sommaire
Le problème que personne ne veut voir
On parle beaucoup d’hallucinations factuelles des LLM. « L’IA a inventé une référence », « l’IA a cité un package qui n’existe pas ». OK. C’est un problème connu, documenté, sur lequel tout le monde travaille.
Mais il y a un autre problème, plus insidieux, dont personne ne parle : la complaisance comportementale.
Je développe depuis janvier 2026 une PWA de gestion immobilière avec Claude Code comme co-pilote principal. Pas un jouet, une vraie app en production, avec une vraie cliente qui l’utilise au quotidien. Express, React, Supabase, AssemblyAI, déployé sur un VPS Hostinger.
Et au bout de 3 mois de travail intensif, voici ce que j’ai observé :
Les 5 patterns toxiques
1. La complaisance
Claude Code livre vite pour faire plaisir. La réponse rapide prime sur la réponse correcte. Tu demandes un diagnostic sur un bug ? Il te sort une hypothèse en 30 secondes sans avoir regardé la donnée réelle. Ça a l’air pro. C’est faux.
2. Le mensonge par omission
Je lui ai prescrit d’utiliser un outil spécifique (Stitch MCP) pour générer des visuels. Il a généré du HTML/CSS à la place, un résultat médiocre, et me l’a présenté comme si c’était normal. Sans me dire qu’il n’avait pas utilisé l’outil prescrit.
3. Le non-respect des règles
Claude Code a un système de fichiers d’instructions (CLAUDE.md) où tu peux écrire des règles. J’en ai écrit. Beaucoup. Il les ignore dès que le chemin facile existe. Même quand je les répète 3 fois dans la même session.
4. L’echo chamber inter-agents
Claude Code peut lancer des sous-agents pour des tâches complexes. En pratique, les agents se confortent mutuellement. L’un propose une approche facile, l’autre la valide, le résultat m’est présenté comme un « consensus ». C’est un consensus entre oui-oui.
5. L’oubli inter-sessions
Tu corriges un comportement en session 1. En session 2, la correction est oubliée. Tu recorriges. Session 3, oubliée. Tu finis par écrire la correction dans un fichier de mémoire. Session 4 : le fichier existe mais n’est pas consulté.
Le vrai coût
Le problème n’est pas que l’IA fait des erreurs. Le problème c’est que si je dois tout vérifier et tout surveiller, il n’y a aucun gain de productivité. L’outil devient un coût, pas une aide.
La réflexion : pourquoi les solutions classiques échouent
CLAUDE.md ne suffit pas
Les instructions dans CLAUDE.md sont l’équivalent d’un panneau « Interdit de rouler à 150 km/h ». Sans radar, sans amende, sans barrière physique. La recherche académique confirme : la conformité des LLM aux instructions décroît linéairement avec le nombre d’instructions. Au-delà de 20 règles, c’est < 30 % de conformité en mode agent.
Les hooks d’avertissement ne suffisent pas
J’avais déjà construit un système de hooks (BRAKE) qui injectait des messages d’avertissement avant certaines actions. Le problème : ces messages font exit 0, ils informent, ils ne bloquent pas. Claude les voit, les ignore, et continue.
Les fichiers de mémoire ne suffisent pas
J’ai 30+ fichiers markdown de feedbacks, corrections, protocoles. Ils existent. Ils ne sont pas consultés. C’est comme avoir une bibliothèque fermée à clé.
Le constat fondamental
Tout mécanisme qui dépend de la bonne volonté de l’IA échouera.
La fiabilité doit être mécanique, pas volontaire.
Les recherches : qu’est-ce qui existe ?
Avant de foncer tête baissée dans une implémentation, j’ai fait un tour exhaustif de ce qui existe.
Systèmes de mémoire agent
| Solution | Stars GitHub | Maturité | Ce que ça fait |
|---|---|---|---|
| Mem0 | 51K | 3 ans | Mémoire persistante, API simple |
| Letta (ex-MemGPT) | 22K | 2.5 ans | Runtime agent avec mémoire intégrée |
| Cognée | 15K | 2.5 ans | Mémoire cognitive structurée |
| Hindsight | 6.5K | 5 mois | Mémoire biomimatique 3 niveaux, 91.4 % sur LongMemEval |
| Zep | 4.3K | 3 ans | Mémoire temporelle, principalement SaaS |
Constat : tous font du recall. Aucun ne fait de l’enforcement. Ils résolvent « j’ai oublié », pas « je m’en fous ».
Frameworks de guardrails
| Solution | Stars | Compatible Claude Code ? |
|---|---|---|
| NeMo Guardrails (NVIDIA) | 5.9K | Non, conçu pour chatbots |
| Guardrails AI | 6.6K | Non, valide le format, pas le comportement |
Solutions spécifiques Claude Code
| Solution | Pertinence |
|---|---|
| Hooks natifs (PreToolCall/Stop) | Le seul mécanisme qui peut bloquer une action, via exit 2 |
| Plugin Hindsight | Auto-recall sur chaque prompt, auto-retain en fin de session |
| Langfuse | Observabilité, traces, scoring, dashboard |
La solution : combiner 3 couches complémentaires.
L’architecture : 3 couches, 3 fonctions, zéro bonne volonté requise
┌─────────────────────────────────────────────────────┐
│ PROMPT UTILISATEUR │
└──────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────┐
│ COUCHE B — HINDSIGHT (mémoire) │
│ Hook UserPromptSubmit → auto-recall │
│ Injecte les feedbacks pertinents dans le contexte │
│ L’IA ne choisit PAS de les voir. Ils sont là. │
└──────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────┐
│ L’IA RAISONNE │
│ (avec les feedbacks injectés en face d’elle) │
└──────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────┐
│ COUCHE A — BRAKE HARD (enforcement) │
│ Hook PreToolCall → gate.sh │
│ Vérifie AVANT chaque outil : │
│ - Read code sans DB query ? → exit 2 (BLOQUE) │
│ - Deploy sans audit ? → exit 2 (BLOQUE) │
│ - HTML au lieu de Stitch ? → exit 2 (BLOQUE) │
│ Exit 2 = outil refusé. Pas un warning, un MUR. │
└──────────────────────┬──────────────────────────────┘
▼ (si autorisé)
┌─────────────────────────────────────────────────────┐
│ OUTIL EXECUTE │
└──────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────┐
│ COUCHE C — LANGFUSE (observabilité) │
│ Trace passive : quel outil, quel contexte, │
│ quel résultat. Dashboard consultable. │
│ Ne bloque rien. OBSERVE. │
└─────────────────────────────────────────────────────┘
| Couche | Rôle | Analogie |
|---|---|---|
| Hindsight | Je ne peux pas dire « j’ai oublié » | La mémoire arrive automatiquement |
| BRAKE | Je ne peux pas dire « j’ai vu mais j’ai décidé autrement » | L’outil est physiquement refusé |
| Langfuse | Je ne peux pas dire « ça s’est bien passé » | Les traces sont là, vérifiables |
Tutoriel : implémenter le système complet
Prérequis
- Claude Code installé (
npm install -g @anthropic-ai/claude-code) - Docker et Docker Compose
- Python 3.11+
Étape 1. Couche A : BRAKE Hard (enforcement)
C’est le cœur du système. Le seul mécanisme qui bloque réellement dans Claude Code.
1.1 Créer la structure
mkdir -p ~/.claude/brake1.2 gate-parse.py : Le parser Python
#!/usr/bin/env python3
"""Parse l'input du hook PreToolCall, met a jour session-state, retourne les infos."""
import json, os, sys
from datetime import datetime
SESSION_FILE = os.path.expanduser("~/.claude/brake/session-state.json")
try: inp = sys.stdin.read()
except: inp = ""
tool_name = "unknown"; file_path = ""; command = ""
try:
d = json.loads(inp); tool_name = d.get("tool_name","unknown")
ti = d.get("tool_input",{})
if isinstance(ti,dict):
file_path = ti.get("file_path",ti.get("path",""))
command = ti.get("command","")
except: pass
try:
with open(SESSION_FILE) as f: state = json.load(f)
except:
state = {"db_queried":False,"roko_invoked":False,"tests_run":False,
"context_mode":"dev","files_read":0,"files_edited":0,
"deploy_attempted":False,"tools":[]}
state["tools"].append({"tool":tool_name,"time":datetime.now().isoformat()[:19]})
cmd_lower = command.lower()
if tool_name=="Bash" and any(k in cmd_lower for k in
["supabase","pipeline_jobs","from('","from(\"",".select(","psql"]):
state["db_queried"] = True
if tool_name=="Bash" and "roko" in cmd_lower: state["roko_invoked"] = True
if tool_name=="Bash" and any(k in command for k in
["npm test","npm run test","jest","vitest","mocha","pytest"]):
state["tests_run"] = True
if tool_name in ("Read","Grep"): state["files_read"] = state.get("files_read",0)+1
if tool_name in ("Edit","Write"): state["files_edited"] = state.get("files_edited",0)+1
if tool_name=="Bash" and any(k in command for k in
["rsync","pm2 restart","pm2 reload","docker push","npm run deploy"]):
state["deploy_attempted"] = True
with open(SESSION_FILE,"w") as f: json.dump(state,f)
print(f"{tool_name}|||{file_path}|||{command}|||{state['db_queried']}|||"
f"{state['roko_invoked']}|||{state['tests_run']}|||"
f"{state['files_read']}|||{state['files_edited']}|||{state.get('context_mode','dev')}")1.3 gate.sh : Le script de blocage
#!/usr/bin/env bash
# gate.sh — PreToolCall hook : BLOQUE les actions non conformes (exit 2)
# Exit 0 = autorise | Exit 2 = BLOQUE
trap 'exit 0' ERR
BRAKE_DIR="$HOME/.claude/brake"
INPUT=""
if [[ ! -t 0 ]]; then INPUT=$(cat 2>/dev/null || true); fi
PARSED=$(echo "$INPUT" | python3 "$BRAKE_DIR/gate-parse.py" 2>/dev/null \
|| echo "unknown||||||False|||False|||False|||0|||0|||dev")
TOOL_NAME=$(echo "$PARSED" | cut -d'|' -f1)
FILE_PATH=$(echo "$PARSED" | cut -d'|' -f4)
COMMAND=$(echo "$PARSED" | cut -d'|' -f7)
DB_QUERIED=$(echo "$PARSED" | cut -d'|' -f10)
ROKO_INVOKED=$(echo "$PARSED" | cut -d'|' -f13)
TESTS_RUN=$(echo "$PARSED" | cut -d'|' -f16)
FILES_READ=$(echo "$PARSED" | cut -d'|' -f19)
[[ "$TOOL_NAME" == "unknown" || -z "$TOOL_NAME" ]] && exit 0
# REGLE 1 : lecture code serveur sans DB
if [[ "$TOOL_NAME" == "Read" || "$TOOL_NAME" == "Grep" ]]; then
if echo "$FILE_PATH" | grep -qE '(server/|worker|pipeline|api/)' 2>/dev/null; then
if [[ "$DB_QUERIED" == "False" && "${FILES_READ:-0}" -gt 2 ]]; then
echo "BRAKE BLOQUE : Lecture serveur sans requete base (${FILES_READ})." >&2; exit 2; fi; fi; fi
# REGLE 2 : edition sans diagnostic base
if [[ "$TOOL_NAME" == "Edit" || "$TOOL_NAME" == "Write" ]]; then
if echo "$FILE_PATH" | grep -qE '\.(js|ts|jsx|tsx|py)$' 2>/dev/null; then
if [[ "$DB_QUERIED" == "False" && "${FILES_READ:-0}" -gt 3 ]]; then
echo "BRAKE BLOQUE : Edition apres $FILES_READ lectures sans DB." >&2; exit 2; fi; fi; fi
# REGLE 3 : publication sans audit
if [[ "$TOOL_NAME" == "Bash" ]]; then
if echo "$COMMAND" | grep -qE '(rsync|pm2 restart|pm2 reload|docker push|npm run deploy)' 2>/dev/null; then
if [[ "$ROKO_INVOKED" == "False" ]]; then
echo "BRAKE BLOQUE : Publication sans audit contradicteur." >&2; exit 2; fi; fi; fi
# REGLE 4 : push sans tests
if [[ "$TOOL_NAME" == "Bash" ]]; then
if echo "$COMMAND" | grep -qE 'git push.*(main|develop|origin)' 2>/dev/null; then
if [[ "$TESTS_RUN" == "False" ]]; then echo "BRAKE BLOQUE : Push sans tests." >&2; exit 2; fi; fi; fi
exit 01.4 Reset de session + hooks settings.json
# Reset l'etat de session (a lancer au debut de chaque session Claude Code)
echo '{"db_queried":false,"roko_invoked":false,"tests_run":false,"context_mode":"dev","files_read":0,"files_edited":0,"deploy_attempted":false,"tools":[]}' \
> ~/.claude/brake/session-state.json
# Ajouter dans ~/.claude/settings.json :
{
"hooks": {
"PreToolCall": [{
"matcher": "Read|Grep|Edit|Write|Bash",
"hooks": [{"type":"command","command":"bash $HOME/.claude/brake/gate.sh","timeout":5}]
}]
}
}Étape 2. Couche B : Hindsight (mémoire persistante)
Hindsight est un système de mémoire biomimatique pour agents IA. Il organise les souvenirs en 3 niveaux (faits, observations, modèles mentaux) et les retrouve via 4 stratégies en parallèle.
Ce qui nous intéresse : le plugin Claude Code qui injecte automatiquement les memories pertinentes dans le contexte de l’IA à chaque prompt.
# Lancer Hindsight (voir docker-compose.yml complet dans l'article original)
# Image : pgvector/pgvector:pg18 + ghcr.io/vectorize-io/hindsight:latest
# Note : utiliser pgvector externe, le pg0 embarque a un bug (#675)
# Verifier
curl -s http://localhost:8888/health
# {"status": "healthy", "database": "connected"}
# Creer la bank globale
curl -s -X PUT http://localhost:8888/v1/default/banks/david-global \
-H "Content-Type: application/json" \
-d '{"description": "Regles globales, feedbacks, protocoles"}'
# Ingerer un feedback exemple
curl -s -X POST http://localhost:8888/v1/default/banks/david-global/memories \
-H "Content-Type: application/json" \
-d '{"items":[{"content":"Regle : requeter la base AVANT lire le code source.","context":"feedback-rule","tags":["feedback"]}],"async":false}'
# Installer le plugin Claude Code
claude plugin marketplace add vectorize-io/hindsight
claude plugin install hindsight-memory# ~/.hindsight/claude-code.json
{
"hindsightApiUrl": "http://localhost:8888",
"bankId": "david-global",
"bankMission": "Memoire globale. Regles de travail, feedbacks critiques, protocoles obligatoires.",
"retainMission": "Extraire : corrections comportementales, decisions techniques, preferences de travail.",
"autoRecall": true,
"autoRetain": true,
"recallBudget": "mid",
"recallMaxTokens": 2048,
"recallTypes": ["world","experience","observation"],
"recallContextTurns": 2,
"retainEveryNTurns": 5,
"debug": false
}Étape 3. Couche C : Langfuse (observabilité)
Langfuse se lance via Docker Compose (image langfuse/langfuse:2) sur le port 3001. L’intégration Claude Code se fait via le hook Stop. Dashboard : http://localhost:3001.
Les contreparties
Latence : Hindsight ajoute 1-3 secondes par prompt. Sur 50 échanges, ~2 minutes. Perceptible, pas bloquant.
Faux positifs : BRAKE peut bloquer des actions légitimes (refactoring sans DB query). Mitigation : mode log-only la première semaine (exit 0 + log), review, puis activation progressive.
Maintenance : 3 systèmes = 3 points de défaillance. Dégradation gracieuse partout : trap 'exit 0' ERR dans gate.sh, Langfuse passif.
L’état actuel (28 mars 2026)
| Couche | Status | Détail |
|---|---|---|
| A : BRAKE Hard | Opérationnel | 5 règles exit 2, dégrade gracieusement, testé |
| B : Hindsight | Opérationnel | 232 faits extraits, 51 observations, recall fonctionne |
| C : Langfuse | À déployer | Prévu ce week-end |
La question : est-ce que ça change réellement le comportement de l’IA au quotidien ? Métriques à suivre : blocages BRAKE, pertinence recall, taux de conformité, temps gagné vs perdu.
Rendez-vous dans un mois
Ce système est en production sur tous mes projets Claude Code. Le 28 avril 2026, article de suivi avec les données réelles : combien de blocages, combien de faux positifs, combien de recalls pertinents. Et le verdict honnête.
Parce que c’est facile de construire un système. C’est autre chose de vivre avec.
David Perrot, Ingénieur en Informatique, Testeur de limites, constructeur de garde-fous.
Hindsight (vectorize-io) : Système de mémoire agent
Langfuse : Observabilité pour LLM
Claude Code · Hooks Claude Code