Skip to main content
Este documento explica cómo funciona failproofai internamente: cómo el sistema de hooks intercepta las llamadas a herramientas del agente, cómo se carga y fusiona la configuración, cómo se evalúan las políticas y cómo el panel de control monitorea la actividad del agente.

Descripción general

failproofai tiene dos subsistemas independientes:
  1. Manejador de hooks - Un subproceso CLI rápido que Claude Code invoca en cada llamada a herramienta del agente. Evalúa las políticas y devuelve una decisión.
  2. Monitor de agentes (Panel de control) - Una aplicación web Next.js para monitorear sesiones del agente y gestionar políticas.
Ambos subsistemas comparten archivos de configuración en ~/.failproofai/ y el directorio .failproofai/ del proyecto, pero se ejecutan como procesos separados y se comunican únicamente a través del sistema de archivos.

Manejador de hooks

Integración con Claude Code

Cuando ejecutas failproofai policies --install, escribe entradas como esta en ~/.claude/settings.json:
Claude Code entonces invoca failproofai --hook PreToolUse como subproceso antes de cada llamada a herramienta, pasando un payload JSON por stdin.

Formato del payload

Para eventos PostToolUse, el payload también contiene tool_result con la salida de la herramienta. El manejador impone un límite de 1 MB en stdin. Los payloads que superen este límite se descartan y todas las políticas permiten implícitamente.

Formato de respuesta

Denegar (PreToolUse):
Denegar (PostToolUse):
Instruir (cualquier evento excepto Stop):
Instruir en evento Stop:
  • Código de salida: 2
  • El motivo se escribe en stderr (no en stdout)
Permitir:
  • Código de salida: 0
  • stdout vacío
Permitir con mensaje: allow(message) permite que una política envíe contexto informativo de vuelta a Claude incluso cuando la operación está permitida. El manejador de hooks escribe el siguiente JSON en stdout (no en un archivo de configuración — esta es la respuesta del manejador a Claude Code, igual que las respuestas de deny e instruct anteriores):
  • Código de salida: 0 (la operación está permitida)
  • Cuando varias políticas devuelven allow con un mensaje, sus mensajes se unen con saltos de línea en una única cadena additionalContext
  • Si ninguna política proporciona un mensaje, stdout está vacío (igual que antes)

Pipeline de procesamiento

src/hooks/handler.ts implementa el pipeline completo:
Todo el proceso se ejecuta en menos de 100ms para payloads típicos sin llamadas a LLM.

Carga de configuración

src/hooks/hooks-config.ts implementa la carga de configuración en tres alcances.
Lógica de fusión:
  • enabledPolicies - unión deduplicada de los tres archivos
  • policyParams - por clave de política, gana completamente el primer archivo que la define
  • customPoliciesPath - gana el primer archivo que la define
  • llm - gana el primer archivo que lo define
El panel de control web utiliza readHooksConfig() (solo global) para leer y escribir, ya que no se invoca con un cwd de proyecto.

Evaluación de políticas

src/hooks/policy-evaluator.ts ejecuta las políticas en orden. Para cada política:
  1. Buscar el esquema params de la política (si tiene uno).
  2. Leer policyParams[policy.name] de la configuración fusionada.
  3. Combinar los valores proporcionados por el usuario sobre los valores por defecto del esquema para producir ctx.params.
  4. Llamar a policy.fn(ctx) con el contexto resuelto.
  5. Si el resultado es deny, detener inmediatamente y devolver esa decisión.
  6. Si el resultado es instruct, acumular el mensaje y continuar.
  7. Si el resultado es allow, continuar con la siguiente política.
Después de ejecutar todas las políticas:
  • Si se devolvió algún deny, emitir la respuesta de denegación.
  • Si se recopilaron respuestas instruct, emitir una única respuesta instruct con todos los mensajes unidos.
  • En caso contrario, emitir una respuesta allow (stdout vacío, salida 0).

Políticas integradas

src/hooks/builtin-policies.ts define las 39 políticas integradas como objetos BuiltinPolicyDefinition:
Las políticas que aceptan params declaran un PolicyParamsSchema con tipos y valores por defecto para cada parámetro. El evaluador de políticas inyecta los valores resueltos en ctx.params antes de llamar a fn. Las funciones de política leen ctx.params sin comprobaciones de nulidad porque los valores por defecto siempre se aplican primero. La coincidencia de patrones dentro de las políticas utiliza tokens de comando parseados (argv), no coincidencia de cadenas sin procesar. Esto evita que se omitan mediante inyección de operadores de shell (por ejemplo, un patrón para sudo systemctl status * no puede ser evadido añadiendo ; rm -rf / al comando).

Políticas personalizadas

src/hooks/custom-hooks-registry.ts implementa un registro respaldado por globalThis:
src/hooks/custom-hooks-loader.ts carga el archivo de políticas del usuario:
  1. Leer customPoliciesPath de la configuración; omitir si no está presente.
  2. Resolver a ruta absoluta; comprobar que el archivo existe.
  3. Reescribir todas las importaciones from "failproofai" a la ruta dist real para que customPolicies resuelva al mismo registro globalThis.
  4. Reescribir recursivamente las importaciones locales transitivas para garantizar compatibilidad con ESM.
  5. Escribir archivos .mjs temporales e importar el archivo de entrada con import().
  6. Llamar a getCustomHooks() para recuperar los hooks registrados.
  7. Limpiar todos los archivos temporales en un bloque finally.
Ante cualquier error (archivo no encontrado, error de sintaxis, fallo de importación), el error se registra en ~/.failproofai/hook.log y el cargador devuelve un array vacío. Las políticas integradas no se ven afectadas. Las políticas personalizadas se evalúan después de todas las políticas integradas. Un deny de una política personalizada sigue cortocircuitando las políticas personalizadas posteriores (pero en ese punto todos los integrados ya se han ejecutado).

Registro de actividad

Después de cada evento de hook, el manejador añade una línea JSONL a ~/.failproofai/hook-activity.jsonl:
Una línea por política que tomó una decisión distinta de allow. Las decisiones allow no se registran (para mantener el archivo pequeño).

Arquitectura del panel de control

El panel de control es una aplicación Next.js 16 que utiliza el App Router con React Server Components y Server Actions.
Flujo de datos:
  • Los componentes de página llaman a lib/projects.ts y lib/log-entries.ts para leer datos de proyectos/sesiones directamente desde el sistema de archivos (sin capa API para las lecturas).
  • La página de políticas utiliza Server Actions para todas las mutaciones (activar/desactivar, actualizar parámetros, instalar/eliminar).
  • El visor de sesión parsea el formato de transcripción JSONL de Claude y renderiza una línea de tiempo de mensajes y llamadas a herramientas.
Decisiones de diseño clave:
  • Sin base de datos: todo el estado persistente está en archivos planos (~/.failproofai/, ~/.claude/projects/).
  • Server Actions para mutaciones: no se necesita API REST para operaciones CRUD.
  • React Server Components para páginas de lectura: carga inicial más rápida, sin bundle de cliente para la obtención de datos.
  • Componentes de cliente solo donde se necesita interactividad (toggles de políticas, búsqueda de actividad, visor de logs).

Estructura de archivos