Hooks Auto-Évolutifs
Trois hooks transforment chaque correction 'non, pas comme ça' en skill ou règle que Claude lit à la prochaine session. Des agents qui s'améliorent seuls, sans tuning de prompt.
Arrêtez de configurer. Commencez à construire.
Templates SaaS avec orchestration IA.
Claude repart de zéro à chaque session. Il ne se souvient pas que tu détestes les tirets cadratins. Il ne se souvient pas que tu lui as dit de rendre avant d'annoncer que c'est fini. Il ne se souvient de rien, sauf si tu l'écris quelque part où il peut le lire.
Les hooks règlent ça. Trois fichiers, et chaque correction que tu donnes à Claude est capturée, analysée, et réécrite automatiquement dans ton projet. À la session suivante, l'erreur a disparu avant même que l'agent commence.
C'est quoi un hook ?
Un hook est un script que Claude Code lance automatiquement à des moments précis. Quand une session commence. Quand un sous-agent est sur le point de tourner. Quand la session se termine.
Tu écris un fichier .js, tu l'enregistres dans settings.json, et Claude l'appelle au bon moment. Le hook reçoit le contexte sous forme de JSON sur stdin, fait ce que tu veux, et sort. Pas de polling. Pas de processus en arrière-plan à gérer.
L'intuition qui rend ça possible
Tous les systèmes d'apprentissage IA ont le même problème : d'où vient le signal ?
Le signal le plus propre est déjà dans ta session. Quand tu dis "non, supprime le tiret cadratin", c'est une correction. Quand tu dis "oui exactement", c'est une approbation. Tu es la vérité terrain. Pas besoin d'évaluateur IA. Pas de boucle circulaire.
La transcription de session (le fichier que Claude écrit pendant que tu travailles) contient tout ça. Chaque message que tu as envoyé. Chaque agent que Claude a lancé, avec le prompt complet qui lui a été donné et la sortie complète qu'il a retournée. Chaque fichier de skill qui a été lu.
Voici à quoi ressemblent trois sessions une fois capturées :
session X:
human_messages: ["write a LinkedIn post", "no em-dashes please", "yes that's better"]
agents_run: [{ type: "linkedin-strategist", output: "post with em-dash — great hook" }]
skills_read: ["linkedin-strategist"]
session Y:
human_messages: ["write another post", "still has em-dashes wtf", "good"]
agents_run: [{ type: "linkedin-strategist", output: "The future is here — changing everything" }]
skills_read: ["linkedin-strategist"]
session Z:
human_messages: ["write a carousel", "looks good"]
agents_run: [{ type: "carousel-designer" }]
skills_read: []Le pattern est évident. Le dream worker lit ça et raisonne : "plainte sur les tirets cadratins dans les sessions X et Y. Les deux ont lancé linkedin-strategist. La session Z n'avait aucune plainte et n'a pas lancé linkedin-strategist. La règle va dans linkedin-strategist."
Tu ne codes pas cette logique. Un LLM le fait. C'est tout le truc.
Arborescence finale des fichiers
.claude/
hooks/
subagent-start.js <- agents wake up with lessons already loaded
on-stop.js <- captures the session raw, no pre-classification
dream/
dream.js <- finds patterns, writes rules
learning/
sessions/
2026-04-08.jsonl <- one observation per session
global.md <- lessons that apply to everything
agents/
linkedin-strategist.md <- lessons for one specific agent
settings.jsonHook 1 — Charger les leçons avant que l'agent commence
Avant qu'un agent tourne, ce script se déclenche. Il vérifie s'il y a des leçons sauvegardées pour ce type d'agent. S'il y en a, il les affiche dans un bloc <mnemosyne>. Claude Code préfixe automatiquement tout ce qui est affiché ici au contexte de l'agent.
L'agent se réveille en sachant déjà ce qui a merdé la dernière fois.
// .claude/hooks/subagent-start.js
'use strict';
const fs = require('fs');
const path = require('path');
const root = path.resolve(__dirname, '..', '..', '..');
const coreDir = path.join(root, '.claude', 'core');
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', c => raw += c);
process.stdin.on('end', () => {
try {
const event = JSON.parse(raw);
const agentType = (event.agent_type || '').replace(/^[^:]+:/, '').trim().toLowerCase();
const parts = [];
// Global lessons — apply to every agent
const global = readFile(path.join(coreDir, 'learning', 'global.md'));
if (global) parts.push(`### Global Learnings\n\n${global}`);
// Agent-specific lessons
if (agentType) {
const learned = readFile(path.join(coreDir, 'learning', 'agents', `${agentType}.md`));
if (learned) parts.push(`### Learnings for ${agentType}\n\n${learned}`);
}
if (parts.length === 0) { process.exit(0); return; }
const attr = agentType ? ` agent="${agentType}"` : '';
process.stdout.write(`<mnemosyne${attr}>\n\n${parts.join('\n\n')}\n\n</mnemosyne>\n`);
} catch {}
process.exit(0);
});
function readFile(p) {
try { return fs.readFileSync(p, 'utf8').trim(); } catch { return ''; }
}Hook 2 — Capturer la session quand elle se termine
Quand ta session Claude se ferme, ce script lit la conversation complète et en extrait le signal brut.
Pas de regex. Pas de pré-classification. Il capture trois choses : chaque message humain mot pour mot, chaque agent qui a tourné avec son prompt et sa sortie, chaque fichier de skill qui a été lu. C'est tout. Le dream worker fait l'interprétation plus tard.
// .claude/hooks/on-stop.js
'use strict';
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { spawn } = require('child_process');
const root = path.resolve(__dirname, '..', '..', '..');
const coreDir = path.join(root, '.claude', 'core');
const COOLDOWN_MS = 4 * 3_600_000;
const MIN_SESSIONS = 3;
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', c => raw += c);
process.stdin.on('end', () => {
try {
const event = JSON.parse(raw);
const { session_id, transcript_path } = event;
if (!transcript_path || !fs.existsSync(transcript_path)) { process.exit(0); return; }
const obs = parseSession(session_id || 'unknown', transcript_path);
writeObservation(obs);
if (shouldDream()) spawnDream();
} catch {}
process.exit(0);
});
function parseSession(sessionId, transcriptPath) {
const lines = fs.readFileSync(transcriptPath, 'utf8').split('\n').filter(Boolean);
const humanMessages = [];
const agentsRun = [];
const skillsRead = new Set();
let pendingAgent = null;
for (const line of lines) {
let e; try { e = JSON.parse(line); } catch { continue; }
const role = e.message?.role;
const content = e.message?.content;
if (!Array.isArray(content)) continue;
for (const block of content) {
// Every human message, verbatim
if (role === 'user' && block.type === 'text') {
const text = (block.text || '').trim();
if (text.length > 2) humanMessages.push(text.slice(0, 300));
}
// Agent output arrives as a tool_result
if (role === 'user' && block.type === 'tool_result' && pendingAgent) {
const parts = Array.isArray(block.content)
? block.content
: [{ type: 'text', text: String(block.content || '') }];
const meta = parts.find(p => p.type === 'text' && p.text?.includes('agentId:'));
if (meta) {
const output = parts
.filter(p => p !== meta && p.type === 'text' && p.text)
.map(p => p.text).join('\n').trim();
agentsRun.push({
type: pendingAgent.type,
prompt_preview: pendingAgent.prompt,
output_preview: output.slice(0, 400).replace(/\s+/g, ' '),
});
pendingAgent = null;
}
}
// Track what was spawned and what was read
if (role === 'assistant' && block.type === 'tool_use') {
if (block.name === 'Agent') {
const t = (block.input?.subagent_type || 'unknown')
.replace(/^[^:]+:/, '').toLowerCase();
pendingAgent = { type: t, prompt: (block.input?.prompt || '').slice(0, 150) };
}
if (block.name === 'Read') {
const m = (block.input?.file_path || '').match(/skills\/([^/]+)\/SKILL\.md$/i);
if (m) skillsRead.add(m[1]);
}
}
}
}
return {
id: `sess-${Date.now()}-${crypto.randomBytes(2).toString('hex')}`,
ts: new Date().toISOString(),
session_id: sessionId,
transcript_path: transcriptPath,
human_messages: humanMessages,
agents_run: agentsRun,
skills_read: [...skillsRead],
};
}Voici à quoi ressemble une observation sur le disque :
{
"ts": "2026-04-08T14:32:11.000Z",
"session_id": "a7b3c2d",
"human_messages": [
"Write a LinkedIn post about AI agents",
"no don't use em-dashes, remove them",
"yes exactly, that is what I wanted"
],
"agents_run": [{
"type": "linkedin-strategist",
"prompt_preview": "Write a LinkedIn post about AI agents building tools",
"output_preview": "Here is the post. It uses an em-dash to make it punchy..."
}],
"skills_read": ["linkedin-strategist"]
}Compact. Lisible. Une ligne par session. Pas d'interprétation intégrée.
Hook 3 — Le dream worker
Celui-ci tourne en arrière-plan quand deux conditions sont remplies : au moins 4 heures depuis le dernier run, et au moins 3 nouvelles sessions capturées.
Il lance un processus claude -p à usage unique avec Haiku. Le worker a accès en écriture et édition à ton projet. Il lit les observations de session, classe lui-même les messages humains, trouve des patterns à travers les sessions, et écrit les règles directement dans les bons fichiers.
// .claude/hooks/dream/dream.js (the prompt sent to Haiku)
`You analyze recent sessions and write one-line rules to prevent repeated mistakes.
★ = new since last dream. These are fresh signal.
## Sessions
★ 2026-04-08T14:32 | agents:[linkedin-strategist] | skills:[linkedin-strategist]
Human messages:
1. "Write a LinkedIn post about AI agents"
2. "no don't use em-dashes, remove them"
3. "yes exactly, that is what I wanted"
linkedin-strategist output: "Here is the post. It uses an em-dash to make it punchy..."
★ 2026-04-07T10:15 | agents:[linkedin-strategist] | skills:[linkedin-strategist]
Human messages:
1. "write another post"
2. "still has em-dashes wtf"
3. "good"
linkedin-strategist output: "The future is here — changing everything about how..."
## Where to write
- .claude/learning/agents/{type}.md for one specific agent
- .claude/learning/global.md for every agent
- .claude/skills/{name}/SKILL.md fix the skill that caused the mistake
## Rules
- 1 session = noise. Same correction in 2+ sessions = write it.
- One-line rules only. Specific, not vague.
- Read the target file first. Do not duplicate existing rules.
- Max 5 new rules per run.
Good: "Never use em-dashes. Use commas or short sentences instead."
Bad: "Be more careful with formatting."`Le worker lit tes 20 dernières sessions, repère ce que tu corrigeais sans cesse, et écrit la leçon. Trois sessions avec la même correction, c'est une règle qui mérite d'être écrite. Une seule correction, c'est du bruit.
Enregistrer les hooks
{
"hooks": {
"SubagentStart": [{
"type": "command",
"command": "node .claude/hooks/subagent-start.js"
}],
"Stop": [{
"type": "command",
"command": "node .claude/hooks/on-stop.js",
"async": true
}]
}
}Deux hooks. C'est tout.
Ce que tu obtiens après une semaine
.claude/
learning/
global.md
Never use em-dashes. Use commas or short sentences instead.
<!-- dream 2026-04-08 -->
agents/
linkedin-strategist.md
Always write in first person when the topic is personal experience.
<!-- dream 2026-04-09 -->
skills/
linkedin-strategist/
SKILL.md <- 2 new rules added from repeated correctionsChaque agent qui tourne la semaine prochaine sait déjà ce qui a cassé la semaine dernière. Tu n'as rien changé.
Posté par @speedy_devv
Arrêtez de configurer. Commencez à construire.
Templates SaaS avec orchestration IA.