Hooks Auto-Evolutivos
Três hooks transformam cada correção 'não, não assim' numa skill ou regra que Claude lê na próxima sessão. Agentes que melhoram sozinhos, sem ajustar prompts.
Pare de configurar. Comece a construir.
Templates SaaS com orquestração de IA.
Claude começa do zero em cada sessão. Não se lembra que você detesta travessões. Não se lembra que lhe disse para renderizar antes de reportar que terminou. Não se lembra de nada, a menos que você escreva isso algures onde ele possa ler.
Os hooks resolvem isto. Três ficheiros, e cada correção que você dá ao Claude é capturada, analisada e escrita de volta no seu projeto automaticamente. Na próxima sessão, o erro já desapareceu antes do agente sequer começar.
O que é um hook?
Um hook é um script que Claude Code executa automaticamente em momentos específicos. Quando uma sessão começa. Quando um subagente está prestes a correr. Quando a sessão termina.
Você escreve um ficheiro .js, regista-o em settings.json, e Claude chama-o no momento certo. O hook recebe contexto como JSON no stdin, faz o que você quiser e sai. Sem polling. Sem processos em background para gerir.
O insight que faz isto funcionar
Todos os sistemas de aprendizagem de IA têm o mesmo problema: de onde vem o sinal?
O sinal mais limpo já está na sua sessão. Quando você diz "não, remove o travessão", isso é uma correção. Quando diz "sim, exatamente", isso é aprovação. Você é a verdade fundamental. Não precisa de avaliador de IA. Não há loop circular.
A transcrição da sessão (o ficheiro que Claude escreve enquanto você trabalha) contém tudo isto. Cada mensagem que você enviou. Cada agente que Claude criou, com o prompt completo que lhe foi dado e o output completo que devolveu. Cada ficheiro de skill que foi lido.
Aqui está o aspeto de três sessões depois de serem capturadas:
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: []O padrão é óbvio. O dream worker lê isto e raciocina: "queixa de travessão na sessão X e Y. Ambas correram linkedin-strategist. A sessão Z não teve queixa e não correu linkedin-strategist. A regra vai para linkedin-strategist."
Você não programa esta lógica. Um LLM faz isso. Esse é o truque todo.
Árvore de ficheiros final
.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 — Carregar lições antes do agente começar
Antes de qualquer agente correr, este script dispara. Verifica se há lições guardadas para aquele tipo de agente. Se houver, imprime-as num bloco <mnemosyne>. Claude Code adiciona automaticamente tudo o que for impresso aqui ao contexto do agente.
O agente acorda já a saber o que correu mal da última vez.
// .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 — Capturar a sessão quando termina
Quando a sua sessão Claude fecha, este script lê a conversa completa e extrai o sinal em bruto.
Sem regex. Sem pré-classificação. Captura três coisas: cada mensagem humana na íntegra, cada agente que correu com o seu prompt e output, cada ficheiro de skill que foi lido. É só isso. O dream worker faz a interpretação mais tarde.
// .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],
};
}Aqui está o aspeto de uma observação em disco:
{
"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"]
}Pequeno. Legível. Uma linha por sessão. Sem interpretação integrada.
Hook 3 — O dream worker
Este corre em background quando duas condições são satisfeitas: pelo menos 4 horas desde a última execução, e pelo menos 3 novas sessões capturadas.
Cria um processo claude -p único usando Haiku. O worker tem acesso de escrita e edição ao seu projeto. Lê as observações de sessão, classifica as mensagens humanas ele próprio, encontra padrões entre sessões e escreve regras diretamente nos ficheiros certos.
// .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."`O worker lê as suas últimas 20 sessões, identifica o que você continuou a corrigir e escreve a lição. Três sessões com a mesma correção é uma regra que vale a pena escrever. Uma correção é ruído.
Registar os hooks
{
"hooks": {
"SubagentStart": [{
"type": "command",
"command": "node .claude/hooks/subagent-start.js"
}],
"Stop": [{
"type": "command",
"command": "node .claude/hooks/on-stop.js",
"async": true
}]
}
}Dois hooks. É só isso.
O que você obtém depois de uma semana
.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 correctionsCada agente que corra na próxima semana já sabe o que correu mal na semana passada. Você não mudou nada.
Publicado por @speedy_devv
Pare de configurar. Comece a construir.
Templates SaaS com orquestração de IA.