Vai al contenuto principale

title: Architettura description: “Come il gestore di hook, il caricamento della configurazione e la valutazione delle politiche funzionano internamente” icon: sitemap

Questo documento spiega come failproofai funziona internamente: come il sistema di hook intercetta le chiamate agli strumenti dell’agente, come viene caricata e unita la configurazione, come vengono valutate le politiche e come il dashboard monitora l’attività dell’agente.

Panoramica

failproofai ha due sottosistemi indipendenti:
  1. Gestore di hook - Un veloce subprocess CLI che Claude Code richiama ad ogni chiamata dello strumento dell’agente. Valuta le politiche e restituisce una decisione.
  2. Agent Monitor (Dashboard) - Un’applicazione web Next.js per il monitoraggio delle sessioni dell’agente e la gestione delle politiche.
Entrambi i sottosistemi condividono file di configurazione in ~/.failproofai/ e nella directory .failproofai/ del progetto, ma vengono eseguiti come processi separati e comunicano solo tramite il filesystem.

Gestore di hook

Integrazione con Claude Code

Quando esegui failproofai policies --install, scrive voci come questa in ~/.claude/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "failproofai --hook PreToolUse"
          }
        ]
      }
    ],
    "PostToolUse": [ ... ]
  }
}
Claude Code quindi richiama failproofai --hook PreToolUse come subprocess prima di ogni chiamata allo strumento, passando un payload JSON su stdin.

Formato del payload

{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/myproject/sessions/abc123.jsonl",
  "cwd": "/home/user/myproject",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "sudo apt install nodejs" }
}
Per eventi PostToolUse, il payload contiene anche tool_result con l’output dello strumento. Il gestore applica un limite di 1 MB per stdin. I payload che superano questo limite vengono scartati e tutte le politiche consentono implicitamente.

Formato della risposta

Deny (PreToolUse):
{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Blocked by failproofai: sudo command blocked"
  }
}
Deny (PostToolUse):
{
  "hookSpecificOutput": {
    "additionalContext": "Blocked by failproofai because: API key detected in output"
  }
}
Instruct (qualsiasi evento tranne Stop):
{
  "hookSpecificOutput": {
    "additionalContext": "Instruction from failproofai: Verify tests pass before committing."
  }
}
Evento Stop instruct:
  • Exit code: 2
  • Motivo scritto su stderr (non stdout)
Allow:
  • Exit code: 0
  • Stdout vuoto
Allow con messaggio: allow(message) consente a una politica di inviare un contesto informativo a Claude anche quando l’operazione è consentita. Il gestore di hook scrive il seguente JSON su stdout (non in un file di configurazione — questa è la risposta del gestore a Claude Code, proprio come le risposte deny e instruct sopra):
// Written to stdout by the hook handler process
{
  "hookSpecificOutput": {
    "additionalContext": "All CI checks passed on branch 'feat/my-feature'."
  }
}
  • Exit code: 0 (l’operazione è consentita)
  • Quando più politiche restituiscono allow con un messaggio, i loro messaggi vengono uniti con newline in una singola stringa additionalContext
  • Se nessuna politica fornisce un messaggio, stdout è vuoto (come prima)

Pipeline di elaborazione

src/hooks/handler.ts implementa la pipeline completa:
stdin JSON
  → parse payload (max 1 MB)
  → extract session metadata (session_id, cwd, tool_name, tool_input, etc.)
  → readMergedHooksConfig(cwd)    ← merges project + local + global config
  → register enabled builtin policies with resolved params
  → load custom policies from customPoliciesPath (if set)
  → register custom policies into policy registry
  → evaluate all policies (builtins first, then custom)
      → first deny short-circuits
      → instruct decisions accumulate
      → allow messages accumulate
  → write JSON decision to stdout
  → persist event to ~/.failproofai/hook-activity.jsonl
  → exit
L’intero processo viene eseguito in meno di 100ms per i payload tipici senza chiamate LLM.

Caricamento della configurazione

src/hooks/hooks-config.ts implementa il caricamento della configurazione a tre ambiti.
[1] {cwd}/.failproofai/policies-config.json        ← project  (highest priority)
[2] {cwd}/.failproofai/policies-config.local.json  ← local
[3] ~/.failproofai/policies-config.json             ← global   (lowest priority)
Logica di unione:
  • enabledPolicies - unione deduplicate tra tutti e tre i file
  • policyParams - per ogni politica, il primo file che la definisce vince completamente
  • customPoliciesPath - il primo file che la definisce vince
  • llm - il primo file che la definisce vince
Il dashboard web utilizza readHooksConfig() (solo globale) per lettura e scrittura, poiché non viene richiamato con un cwd di progetto.

Valutazione delle politiche

src/hooks/policy-evaluator.ts esegue le politiche in ordine. Per ogni politica:
  1. Cerca lo schema params della politica (se ne ha uno).
  2. Legge policyParams[policy.name] dalla configurazione unita.
  3. Unisce i valori forniti dall’utente sui valori predefiniti dello schema per produrre ctx.params.
  4. Chiama policy.fn(ctx) con il contesto risolto.
  5. Se il risultato è deny, ferma immediatamente e restituisci quella decisione.
  6. Se il risultato è instruct, accumula il messaggio e continua.
  7. Se il risultato è allow, continua alla politica successiva.
Dopo che tutte le politiche vengono eseguite:
  • Se è stato restituito qualsiasi deny, emetti la risposta di deny.
  • Se sono stati raccolti risultati instruct, emetti una singola risposta instruct con tutti i messaggi uniti.
  • Altrimenti, emetti una risposta allow (stdout vuoto, exit 0).

Politiche integrate

src/hooks/builtin-policies.ts definisce tutte le 26 politiche integrate come oggetti BuiltinPolicyDefinition:
interface BuiltinPolicyDefinition {
  name: string;
  description: string;
  fn: (ctx: PolicyContext) => PolicyResult;
  match: {
    events: HookEventType[];
    tools?: string[];
  };
  defaultEnabled: boolean;
  category: string;
  beta?: boolean;
  params?: PolicyParamsSchema;
}
Le politiche che accettano params dichiarano un PolicyParamsSchema con tipi e valori predefiniti per ogni parametro. L’evaluator di politiche inietta i valori risolti in ctx.params prima di chiamare fn. Le funzioni di politica leggono ctx.params senza null-guarding perché i valori predefiniti vengono sempre applicati per primi. Il pattern matching all’interno delle politiche utilizza token di comando parsati (argv), non corrispondenza di stringhe grezze. Questo previene il bypass tramite iniezione di operatori shell (ad esempio, un pattern per sudo systemctl status * non può essere bypassato aggiungendo ; rm -rf / al comando).

Politiche personalizzate

src/hooks/custom-hooks-registry.ts implementa un registro supportato da globalThis:
const REGISTRY_KEY = "__failproofai_custom_hooks__";

export const customPolicies = {
  add(hook: CustomHook): void { ... }
};

export function getCustomHooks(): CustomHook[] { ... }
export function clearCustomHooks(): void { ... }  // used in tests
src/hooks/custom-hooks-loader.ts carica il file di politica dell’utente:
  1. Leggi customPoliciesPath dalla configurazione; salta se assente.
  2. Risolvi al percorso assoluto; verifica che il file esista.
  3. Riscrivi tutti gli import from "failproofai" al percorso dist effettivo in modo che customPolicies si risolva nello stesso registro globalThis.
  4. Riscrivi ricorsivamente gli import locali transitivi per garantire la compatibilità ESM.
  5. Scrivi file .mjs temporanei e import() il file di entry.
  6. Chiama getCustomHooks() per recuperare gli hook registrati.
  7. Pulisci tutti i file temporanei in un blocco finally.
In caso di errore (file non trovato, errore di sintassi, errore di import), l’errore viene registrato in ~/.failproofai/hook.log e il loader restituisce un array vuoto. Le politiche integrate non sono interessate. Le politiche personalizzate vengono valutate dopo tutte le politiche integrate. Un deny di politica personalizzata continua a cortocircuitare ulteriori politiche personalizzate (ma tutte le integrate sono già state eseguite a questo punto).

Logging dell’attività

Dopo ogni evento di hook, il gestore aggiunge una riga JSONL a ~/.failproofai/hook-activity.jsonl:
{
  "timestamp": "2026-04-06T12:34:56.789Z",
  "sessionId": "abc123",
  "eventType": "PreToolUse",
  "toolName": "Bash",
  "policyName": "block-sudo",
  "decision": "deny",
  "reason": "sudo command blocked by failproofai",
  "durationMs": 12
}
Una riga per ogni politica che ha preso una decisione non-allow. Le decisioni allow non vengono registrate (per mantenere il file piccolo).

Architettura del dashboard

Il dashboard è un’applicazione Next.js 16 che utilizza l’App Router con React Server Components e Server Actions.
app/
  layout.tsx                  ← Root layout (theme, telemetry, nav)
  projects/page.tsx           ← Server component: list all Claude projects
  project/[name]/page.tsx     ← Server component: list sessions in a project
  project/[name]/session/
    [sessionId]/page.tsx      ← Server component: render session viewer
  policies/page.tsx           ← Client component: policy management + activity log
  actions/
    get-hooks-config.ts       ← Read config + policy list
    update-hooks-config.ts    ← Toggle policy on/off
    update-policy-params.ts   ← Update policy parameters
    get-hook-activity.ts      ← Paginate/search activity log
    install-hooks-web.ts      ← Install/remove hooks from the browser
  api/
    download/[project]/[session]/route.ts   ← Export session as ZIP/JSONL
Flusso di dati:
  • I componenti pagina chiamano lib/projects.ts e lib/log-entries.ts per leggere i dati di progetto/sessione direttamente dal filesystem (nessun livello API per le letture).
  • La pagina Policies utilizza Server Actions per tutte le mutazioni (toggle, aggiornamento params, installa/rimuovi).
  • Il visualizzatore di sessione parsifica il formato di trascritto JSONL di Claude e renderizza una timeline di messaggi e chiamate di strumenti.
Decisioni di design chiave:
  • Nessun database - tutto lo stato persistente è in file semplici (~/.failproofai/, ~/.claude/projects/).
  • Server Actions per mutazioni - nessuna API REST necessaria per le operazioni CRUD.
  • React Server Components per pagine di lettura - caricamento iniziale più veloce, nessun bundle client per il recupero dati.
  • Componenti client solo dove è necessaria l’interattività (toggle di politiche, ricerca attività, visualizzatore di log).

Layout dei file

failproofai/
├── bin/
│   └── failproofai.mjs           # CLI router (hook / dashboard / install / etc.)
├── src/hooks/
│   ├── handler.ts                # Hook event pipeline
│   ├── builtin-policies.ts       # 26 policy definitions
│   ├── policy-evaluator.ts       # Policy execution engine
│   ├── policy-registry.ts        # Policy registration and lookup
│   ├── policy-types.ts           # TypeScript interfaces
│   ├── hooks-config.ts           # Multi-scope config loading
│   ├── custom-hooks-registry.ts  # globalThis-backed hook registry
│   ├── custom-hooks-loader.ts    # ESM loader for user JS hooks
│   ├── manager.ts                # install / remove / list operations
│   ├── install-prompt.ts         # Interactive policy selection prompt
│   ├── hook-logger.ts            # Logging to hook.log
│   ├── hook-activity-store.ts    # Persist activity to hook-activity.jsonl
│   └── llm-client.ts             # LLM API client (for AI-powered policies)
├── app/                          # Next.js dashboard (pages + server actions)
├── lib/                          # Shared utilities
│   ├── projects.ts               # Enumerate Claude projects from filesystem
│   ├── log-entries.ts            # Parse Claude transcript JSONL format
│   ├── paths.ts                  # Resolve system paths
│   └── ...
├── components/                   # Shared React UI components
├── contexts/                     # React context providers (theme, auto-refresh, telemetry)
├── examples/                     # Example custom hook files
└── __tests__/                    # Unit and E2E tests