Build This Now
Build This Now
Builds ReaisDa Ideia ao SaaSGAN LoopHooks Auto-EvolutivosDo Trace à SkillAgentes de distribuiçãoAgentes de Segurança com IAEnxame Autónomo de IASequências de Email com IAA IA Limpa-se a Si Própria
speedy_devvkoen_salo
Blog/Real Builds/Self-Evolving Hooks

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.

Published Apr 1, 20268 min readReal Builds hub

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.json

Hook 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 corrections

Cada 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

More in Real Builds

  • A IA Limpa-se a Si Própria
    Três workflows noturnos do Claude Code que limpam a própria bagunça da IA: o slop-cleaner remove código morto, o /heal repara branches partidas, o /drift deteta deriva de padrões.
  • GAN Loop
    Um agente gera, outro destrói, e repetem até a pontuação parar de melhorar. Implementação do GAN Loop com definições de agente e templates de rubrica.
  • Sequências de Email com IA
    Um comando do Claude Code constrói 17 emails de ciclo de vida em 6 sequências, liga gatilhos comportamentais do Inngest e lança um funil de email com ramificações pronto a implementar.
  • Agentes de Segurança com IA
    Dois comandos do Claude Code disparam oito sub-agentes de segurança: a fase 1 analisa a lógica SaaS em busca de falhas de RLS e bugs de autenticação, a fase 2 testa para confirmar explorações reais.
  • Enxame Autónomo de IA
    Um enxame autónomo do Claude Code: um gatilho a cada 30 minutos, um orquestrador, sub-agentes especialistas em worktrees, e cinco portas que lançam funcionalidades com segurança durante a noite.
  • Agentes de distribuição
    Quatro agentes do Código Claude que funcionam de acordo com uma agenda, escrevem publicações de SEO, lêem o PostHog, constroem carrosséis e procuram o Reddit. Copia as definições e insere-as.

Pare de configurar. Comece a construir.

Templates SaaS com orquestração de IA.

On this page

O que é um hook?
O insight que faz isto funcionar
Árvore de ficheiros final
Hook 1 — Carregar lições antes do agente começar
Hook 2 — Capturar a sessão quando termina
Hook 3 — O dream worker
Registar os hooks
O que você obtém depois de uma semana

Pare de configurar. Comece a construir.

Templates SaaS com orquestração de IA.