Build This Now
Build This Now
Echte BuildsVon der Idee zum SaaSGAN LoopSelf-Evolving HooksTrace to SkillVertriebsagentenKI-Sicherheits-AgentenAutonomer KI-SchwarmKI-E-Mail-SequenzenKI räumt sich selbst auf
speedy_devvkoen_salo
Blog/Real Builds/Self-Evolving Hooks

Self-Evolving Hooks

Drei Hooks verwandeln jede 'Nein, nicht so'-Korrektur in eine Regel oder Skill, die Claude in der nächsten Session liest. Selbstverbessernde Agenten, kein Prompt-Tuning nötig.

Hören Sie auf zu konfigurieren. Fangen Sie an zu bauen.

SaaS-Builder-Vorlagen mit KI-Orchestrierung.

Published Apr 1, 20268 min readReal Builds hub

Claude startet jede Session von Null. Es erinnert sich nicht, dass du em-Dashes hasst. Es erinnert sich nicht, dass du es gebeten hast, vor der Fertigmeldung zu rendern. Es erinnert sich an gar nichts, außer du schreibst es irgendwo hin, wo es nachlesen kann.

Hooks lösen das. Drei Dateien, und jede Korrektur, die du Claude gibst, wird automatisch erfasst, analysiert und in dein Projekt zurückgeschrieben. In der nächsten Session ist der Fehler schon behoben, bevor der Agent auch nur anfängt.


Was ist ein Hook?

Ein Hook ist ein Skript, das Claude Code automatisch zu bestimmten Momenten ausführt. Wenn eine Session startet. Wenn ein Subagent gleich loslegt. Wenn die Session endet.

Du schreibst eine .js-Datei, registrierst sie in settings.json, und Claude ruft sie zum richtigen Zeitpunkt auf. Der Hook bekommt Kontext als JSON auf stdin, macht, was du willst, und beendet sich. Kein Polling. Keine Hintergrundprozesse zu verwalten.


Die Erkenntnis, die das möglich macht

Jedes KI-Lernsystem hat dasselbe Problem: Woher kommt das Signal?

Das klarste Signal sitzt schon in deiner Session. Wenn du sagst "nein, entfern den em-Dash", ist das eine Korrektur. Wenn du sagst "ja genau", ist das Zustimmung. Du bist die Ground Truth. Kein KI-Evaluator nötig. Kein Kreislauf.

Das Session-Transcript (die Datei, die Claude während deiner Arbeit schreibt) enthält das alles. Jede Nachricht, die du geschickt hast. Jeden Agenten, den Claude gespawnt hat, mit dem vollständigen Prompt und der vollständigen Ausgabe. Jede Skill-Datei, die gelesen wurde.

So sehen drei Sessions aus, nachdem sie erfasst wurden:

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: []

Das Muster ist offensichtlich. Der Dream Worker liest das und schlussfolgert: "em-Dash-Beschwerde in Session X und Y. Beide haben linkedin-strategist genutzt. Session Z hatte keine Beschwerde und hat linkedin-strategist nicht genutzt. Regel geht an linkedin-strategist."

Du kodierst diese Logik nicht. Ein LLM macht das. Das ist der ganze Trick.


Finaler Dateibaum

.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 — Lernstoff laden, bevor der Agent startet

Bevor ein Agent startet, feuert dieses Skript. Es prüft, ob es gespeicherte Erkenntnisse für diesen Agenten-Typ gibt. Wenn ja, gibt es sie in einem <mnemosyne>-Block aus. Claude Code hängt alles, was hier ausgegeben wird, automatisch vor den Kontext des Agenten.

Der Agent wacht auf und weiß schon, was letztes Mal schief gelaufen ist.

// .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 — Die Session erfassen, wenn sie endet

Wenn deine Claude Session beendet wird, liest dieses Skript das gesamte Gespräch und zieht das rohe Signal heraus.

Kein Regex. Keine Vorklassifizierung. Es erfasst drei Dinge: jede menschliche Nachricht wortgenau, jeden Agenten, der mit seinem Prompt und seiner Ausgabe lief, jede Skill-Datei, die gelesen wurde. Das ist alles. Der Dream Worker macht die Interpretation später.

// .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],
  };
}

So sieht eine Beobachtung auf der Festplatte aus:

{
  "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"]
}

Klein. Lesbar. Eine Zeile pro Session. Keine eingebaute Interpretation.


Hook 3 — Der Dream Worker

Der läuft im Hintergrund, wenn zwei Bedingungen erfüllt sind: mindestens 4 Stunden seit dem letzten Run und mindestens 3 neue Sessions erfasst.

Er spawnt einen einmaligen claude -p Prozess mit Haiku. Der Worker hat Write- und Edit-Zugriff auf dein Projekt. Er liest die Session-Beobachtungen, klassifiziert die menschlichen Nachrichten selbst, findet Muster über Sessions hinweg und schreibt Regeln direkt in die richtigen Dateien.

// .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."`

Der Worker liest deine letzten 20 Sessions, erkennt, was du immer wieder korrigiert hast, und schreibt die Erkenntnis auf. Drei Sessions mit derselben Korrektur sind eine Regel, die es wert ist, aufgeschrieben zu werden. Eine Korrektur ist Rauschen.


Die Hooks registrieren

{
  "hooks": {
    "SubagentStart": [{
      "type": "command",
      "command": "node .claude/hooks/subagent-start.js"
    }],
    "Stop": [{
      "type": "command",
      "command": "node .claude/hooks/on-stop.js",
      "async": true
    }]
  }
}

Zwei Hooks. Das ist alles.


Was du nach einer Woche hast

.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

Jeder Agent, der nächste Woche läuft, weiß schon, was letzte Woche schief gelaufen ist. Du hast nichts geändert.


Posted by @speedy_devv

More in Real Builds

  • KI räumt sich selbst auf
    Drei overnight Claude Code-Workflows, die das Chaos der KI selbst bereinigen: slop-cleaner entfernt toten Code, /heal repariert kaputte Branches, /drift erkennt Pattern-Drift.
  • GAN Loop
    Ein Agent generiert, einer reißt ihn auseinander, sie loopen bis der Score nicht mehr steigt. GAN Loop Implementierung mit Agent-Definitionen und Rubrik-Templates.
  • KI-E-Mail-Sequenzen
    Ein Claude Code-Befehl erstellt 17 Lifecycle-E-Mails über 6 Sequenzen, verkabelt Inngest-Verhaltenstrigger und liefert einen verzweigten E-Mail-Funnel bereit zum Deployment.
  • KI-Sicherheits-Agenten
    Zwei Claude Code-Befehle starten acht Sicherheits-Sub-Agenten: Phase 1 scannt SaaS-Logik auf RLS-Lücken und Auth-Fehler, Phase 2 versucht echte Angriffe zu bestätigen.
  • Autonomer KI-Schwarm
    Ein autonomer Claude Code-Schwarm: ein 30-Minuten-Trigger, ein Orchestrator, Spezialisten-Sub-Agenten in Worktrees und fünf Gates, die overnight Features sicher shippen.
  • Vertriebsagenten
    Vier Claude Code-Agenten, die nach einem Zeitplan laufen, SEO-Posts schreiben, PostHog lesen, Karussells bauen und Reddit scouten. Kopiere die Definitionen und füge sie ein.

Hören Sie auf zu konfigurieren. Fangen Sie an zu bauen.

SaaS-Builder-Vorlagen mit KI-Orchestrierung.

On this page

Was ist ein Hook?
Die Erkenntnis, die das möglich macht
Finaler Dateibaum
Hook 1 — Lernstoff laden, bevor der Agent startet
Hook 2 — Die Session erfassen, wenn sie endet
Hook 3 — Der Dream Worker
Die Hooks registrieren
Was du nach einer Woche hast

Hören Sie auf zu konfigurieren. Fangen Sie an zu bauen.

SaaS-Builder-Vorlagen mit KI-Orchestrierung.