Deuxième article de la série « Coder avec un agent IA, pour de vrai ». Dans le premier, un agent de code écrivait des tests verts qui ne testaient pas la bonne étape. J’avais conclu en disant que j’en avais tiré des règles, écrites dans un fichier que l’agent lit à chaque session. Voici ce fichier, comment il est organisé, et ce qu’il ne règle pas.

Le problème de départ est simple : chaque session d’un agent de code démarre avec un contexte vide. L’erreur corrigée hier sera refaite demain, avec la même assurance. La seule mémoire durable, c’est ce qu’on lui fait relire au démarrage.

Les exemples utilisent Claude Code, mais le principe vaut pour tout agent qui lit un fichier d’instructions (AGENTS.md, règles Cursor…). Les extraits sont raccourcis et anonymisés : aucun nom de projet.

La règle qui fait tout le reste

Le cœur du système tient en trois lignes de mon CLAUDE.md global :

# Leçons

Quand je te corrige, ou que tu constates que tu as commis une erreur : avant de poursuivre,
ajoute une leçon sous forme de règle en une ligne, afin de ne plus jamais commettre cette erreur.

Deux choix comptent ici :

  • C’est l’agent qui écrit la leçon, pas moi. Au moment de la correction, il a tout le contexte en tête : l’erreur, sa cause, ce qu’il aurait dû vérifier. Dans une semaine, je ne m’en souviendrai plus.
  • « Avant de poursuivre » : la leçon est écrite tout de suite, pas en fin de tâche. Une fin de tâche, ça s’oublie, ou la session s’arrête avant.

Le cycle complet ressemble à ça :

flowchart TD A[Erreur de l'agent] --> B[Je corrige] B --> C[L'agent écrit la leçon] C --> D{Liée à une stack ?} D -->|Oui| E[lessons/stack.md] D -->|Non| F[lessons.md] E --> G[Lu quand le contexte s'applique] F --> H[Chargé à chaque session]

L’organisation des fichiers

~/.claude/
├── CLAUDE.md                     # court : style, règles non négociables, index
├── instructions/
│   ├── about-me.md               # importé à chaque session
│   ├── qualite-code.md           # importé à chaque session
│   ├── git-workflow.md           # importé à chaque session
│   ├── lessons.md                # importé : leçons de méthode, valables partout
│   ├── docker.md                 # lu à la demande
│   ├── database-conventions.md   # lu à la demande
│   └── lessons/
│       ├── nginx.md              # leçons par stack, lues à la demande
│       ├── backend-node.md
│       └── mobile-expo.md
└── rules/
    └── knowledge-graph.md        # chargé automatiquement par Claude Code

Deux modes de chargement cohabitent.

Ce qui est chargé à chaque session

Le CLAUDE.md importe les fichiers qui s’appliquent à tous les projets, avec la syntaxe @chemin :

# Qualité et style de code

@instructions/qualite-code.md

# Workflow git

@instructions/git-workflow.md

Selon la documentation de Claude Code, les fichiers importés sont développés et chargés au lancement, avec au plus quatre niveaux d’imports imbriqués. Point important : un import organise, il n’économise rien. Un fichier importé coûte autant de contexte que s’il était écrit dans le CLAUDE.md.

Ce qui est lu à la demande

Le reste n’est pas importé. Le CLAUDE.md donne seulement un index, et demande à l’agent de lire le bon fichier quand le contexte s’y prête :

# Règles contextuelles (chargement au besoin)

Ces fichiers ne sont pas importés automatiquement.
Lire le fichier avec l'outil Read dès que son contexte s'applique :

- Projet Docker (docker-compose\*.yml ou Dockerfile présent) → ~/.claude/instructions/docker.md
- Base de données : schéma, migration, ORM → ~/.claude/instructions/database-conventions.md
- Configuration nginx (en-têtes, CSP, vhosts) → ~/.claude/instructions/lessons/nginx.md

Aujourd’hui, l’ensemble représente environ 590 lignes : à peu près la moitié chargée à chaque session, l’autre moitié à la demande. Les leçons d’une stack mobile n’ont rien à faire dans le contexte quand je configure un serveur nginx.

Ce qui a changé en trois mois

Ce fichier a un historique git, et la comparaison est instructive.

Début juillet 2026, le fichier de leçons faisait 13 lignes, pour 5 règles. En voici deux, telles quelles :

- Les exemples de documentation peuvent être simplifiés ou obsolètes.
- Pour les changements complexes, une séparation entre implémentation et validation améliore la qualité.

Et le CLAUDE.md référençait ses fichiers ainsi :

@see /home/<user>/.claude/instructions/lessons.md

@see n’est pas la syntaxe d’import de Claude Code, qui attend @chemin. Ces lignes n’étaient donc pas des imports : au mieux un renvoi que l’agent pouvait suivre, ou pas.

Aujourd’hui, le fichier de méthode compte 51 règles, plus six fichiers par stack. Et les règles ne ressemblent plus du tout aux premières.

Anatomie d’une leçon utile

Comparez les deux règles de juillet avec celle-ci, écrite après une analyse fausse :

- Une API groupée qui échoue en bloc ne dit PAS que tous ses éléments sont absents.
  L'API `nodes?nodes=a,b,c` renvoie 404 pour le LOT ENTIER dès qu'un seul id manque :
  mon script a annoncé 33,3 % de fiches mortes, alors que le taux réel, mesuré avec
  repli individuel, était de 0,3 %. Avant de tirer un taux d'un traitement par lots,
  retester un par un les lots en erreur. Signal d'alerte : un chiffre rond suspect.

Elle a trois parties :

  • La règle, à l’impératif, formulée de façon générale.
  • Le cas concret qui l’a fait naître, avec les chiffres.
  • Le signal qui doit déclencher la vigilance la prochaine fois.

« Les exemples de documentation peuvent être obsolètes » n’apprend rien à un modèle qui le sait déjà. Mon hypothèse, que je n’ai pas mesurée : une règle générique ne dit pas quand elle s’applique. Le cas concret sert de déclencheur. L’agent reconnaît la situation (« un lot qui échoue », « un chiffre rond ») avant de reconnaître le principe.

Autre exemple, très court celui-là, et l’un de mes préférés :

- Ne jamais transformer une observation de l'utilisateur en diagnostic.
  « Je vois le splash et ça crashe » = chronologie, pas causalité.

Les leçons marchent dans les deux sens

Toutes les leçons ne disent pas « vérifie davantage ». Certaines corrigent l’excès inverse :

- Calibrer le volume d'avertissements sur le COÛT RÉEL du changement : quand je viens
  de chiffrer une correction à « 6 lignes », lui opposer un paragraphe de risques
  théoriques est du zèle. Signaler les vrais pièges en UNE ligne, coder, et laisser
  les tests prouver.

Un agent qui a accumulé des dizaines de règles de prudence finit par tout entourer d’avertissements. Celle-ci rééquilibre.

Méthode ou stack : où ranger la leçon

La consigne précise où écrire :

  • une leçon liée à un outil ou une stack (nginx, Expo, Drizzle…) va dans instructions/lessons/<stack>.md, lu à la demande ;
  • une leçon de méthode, valable sur n’importe quel projet, va dans lessons.md, chargé partout.

Une troisième catégorie ne va dans aucun des deux : les faits propres à un projet (un chemin, un nom de service, une contrainte métier). Ils vont dans la mémoire du projet ou son CLAUDE.md. Sinon, le fichier global se remplit de détails qui ne concernent qu’un dépôt.

Ce qui ne marche pas (ou pas encore)

C’est la partie que les tutoriels sautent. Voici ce que je constate en relisant mes propres fichiers.

Une règle écrite n’est pas une règle appliquée

J’ai une leçon sur la langue des réponses : répondre en français, même quand l’agent vient de lire un fichier en anglais. Le texte de la leçon cite lui-même deux dérapages : des réponses en anglais après la lecture d’un skill rédigé en anglais, puis, présenté comme « même piège », un rapport entier en anglais à cause de consignes système en anglais. La règle existait déjà au moment du second.

La documentation de Claude Code le dit sans détour : ces fichiers sont traités comme du contexte, pas comme une configuration imposée.

« Une ligne » est devenu un paragraphe

La consigne demande une règle en une ligne. Techniquement, c’est respecté : une ligne Markdown par leçon. En pratique, sur 51 règles, la médiane est à 455 caractères, et 38 dépassent 300 caractères. Le fichier pèse environ 24 Ko, chargés à chaque session. La documentation recommande de rester sous 200 lignes par CLAUDE.md : plus c’est long, moins c’est suivi. Le cas concret qui rend une leçon utile est aussi ce qui la fait grossir. Je n’ai pas encore tranché ce compromis.

Les doublons s’accumulent

L’agent écrit la leçon au moment de l’erreur, sans toujours relire les précédentes. L’incident raconté dans le premier article a produit trois leçons voisines (le test qui ne rejoue pas la chaîne réelle, le fichier de config qui traverse un parseur, la sortie intermédiaire de l’outil), qui gagneraient à être fusionnées. Il faut un ménage périodique, et ce ménage, c’est moi qui le fais.

Le chargement à la demande dépend du jugement de l’agent

« Lire le fichier dès que son contexte s’applique » suppose que l’agent reconnaisse le contexte. Rien ne le garantit. Claude Code propose deux mécanismes plus déterministes, que je n’utilise pas encore partout :

  • les règles à portée de chemin : un fichier dans .claude/rules/ avec un champ paths: en frontmatter n’est chargé que lorsque l’agent lit ou modifie un fichier correspondant ;
  • les skills, chargés quand on les invoque ou quand l’agent les juge pertinents.

Les règles à portée de chemin conviennent à « tout ce qui touche src/api/** ». Elles conviennent moins à « publication sur les stores », qui n’est pas un chemin de fichier. D’où mon index manuel.

Ce qui doit être garanti va dans un hook

Pour ce qui ne doit jamais arriver, le texte ne suffit pas. Claude Code exécute des hooks : des commandes shell déclenchées avant ou après un outil, que l’agent ne peut pas « oublier ». Chez moi :

  • un hook SessionStart rappelle, à l’ouverture d’une session, de passer par Docker pour les commandes ;
  • des hooks PreToolUse interdisent la lecture et l’édition des fichiers .env ;
  • un hook PreToolUse doit refuser npm, yarn ou php lancés hors du conteneur.

Ce dernier contenait un bug, trouvé en écrivant cet article :

if echo "$cmd" | grep -qE "^(php|composer|npm|yarn|node)[[:space:]]"; then
    echo "ERREUR: Cette commande doit être exécutée via Docker." >&2
    exit 1
fi

⚠️ Pour un hook PreToolUse, exit 1 n’est pas bloquant. D’après la documentation des hooks, seul exit 2 bloque l’appel d’outil. Tout autre code est une erreur non bloquante : un avertissement s’affiche, et la commande s’exécute quand même. Le bon code :

if echo "$cmd" | grep -qE "^(php|composer|npm|yarn|node)[[:space:]]"; then
    echo "Cette commande doit être exécutée via Docker." >&2
    exit 2
fi

Ironie de la situation : c’est exactement le sujet du premier article. Un garde-fou écrit, jamais vérifié dans le sens « est-ce qu’il rejette bien ce qu’il doit rejeter ? ».

Je n’ai pas de mesure

Je ne sais pas dire combien d’erreurs ce système a évitées. Je vois les leçons qui s’ajoutent, pas les erreurs qui n’ont pas eu lieu. Ce que j’observe, en revanche : les nouvelles leçons portent sur des erreurs nouvelles, plus fines que les premières.

Pour démarrer chez vous

Pas besoin de 590 lignes. Une base suffit :

# ~/.claude/CLAUDE.md

# Style

- Réponses directes, sans préambule.

# Leçons

Quand je te corrige, ou que tu constates une erreur : avant de poursuivre,
ajoute une règle dans le fichier de leçons adapté, avec le cas concret qui l'a motivée.

- Leçon liée à une stack → ~/.claude/instructions/lessons/<stack>.md (lu à la demande)
- Leçon de méthode → ~/.claude/instructions/lessons.md

@instructions/lessons.md

# Règles contextuelles (à lire quand le contexte s'applique)

- Configuration nginx → ~/.claude/instructions/lessons/nginx.md

Et quelques habitudes :

  • Exiger le cas concret dans chaque leçon : sans lui, la règle est une banalité.
  • Relire et fusionner régulièrement : Claude Code propose /doctor prompt-audit pour repérer les contradictions et les références mortes.
  • Mettre en hook tout ce qui doit être garanti, et tester que le hook bloque vraiment.
  • Garder les faits de projet hors du fichier global.

Le fichier de leçons ne rend pas l’agent infaillible. Il rend ses erreurs moins répétitives, et il m’oblige à formuler ce que j’attends vraiment de lui. Le prochain article de la série passera en revue les erreurs les plus fréquentes qu’il contient, et comment les repérer avant qu’elles coûtent cher.