Saltar al contenido principal
Las políticas personalizadas te permiten escribir reglas para cualquier comportamiento del agente: aplicar convenciones del proyecto, prevenir derivas, controlar operaciones destructivas, detectar agentes bloqueados o integrarte con Slack, flujos de aprobación y más. Utilizan el mismo sistema de eventos de hook y las mismas decisiones allow, deny, instruct que las políticas integradas.

Ejemplo rápido

// my-policies.js
import { customPolicies, allow, deny, instruct } from "failproofai";

customPolicies.add({
  name: "no-production-writes",
  description: "Block writes to paths containing 'production'",
  match: { events: ["PreToolUse"] },
  fn: async (ctx) => {
    if (ctx.toolName !== "Write" && ctx.toolName !== "Edit") return allow();
    const path = ctx.toolInput?.file_path ?? "";
    if (path.includes("production")) {
      return deny("Writes to production paths are blocked");
    }
    return allow();
  },
});
Instálala:
failproofai policies --install --custom ./my-policies.js

Dos formas de cargar políticas personalizadas

Opción 1: Basada en convención (recomendada)

Coloca archivos *policies.{js,mjs,ts} en .failproofai/policies/ y se cargarán automáticamente, sin necesidad de flags ni cambios de configuración. Funciona como los git hooks: coloca el archivo y listo.
# Nivel de proyecto — incluido en git, compartido con el equipo
.failproofai/policies/security-policies.mjs
.failproofai/policies/workflow-policies.mjs

# Nivel de usuario — personal, se aplica a todos los proyectos
~/.failproofai/policies/my-policies.mjs
Cómo funciona:
  • Se analizan tanto el directorio del proyecto como el del usuario (unión — no el primero que coincida por alcance)
  • Los archivos se cargan alfabéticamente dentro de cada directorio. Usa prefijos 01-, 02- para controlar el orden
  • Solo se cargan los archivos que coincidan con *policies.{js,mjs,ts}; el resto se ignoran
  • Cada archivo se carga de forma independiente (fail-open por archivo)
  • Funciona junto con --custom explícito y las políticas integradas
Las políticas por convención son la forma más sencilla de compartir políticas en un equipo. Incluye .failproofai/policies/ en git y todos los miembros del equipo las recibirán automáticamente.

Opción 2: Ruta de archivo explícita

# Instalar con un archivo de políticas personalizadas
failproofai policies --install --custom ./my-policies.js

# Reemplazar la ruta del archivo de políticas
failproofai policies --install --custom ./new-policies.js

# Eliminar la ruta de políticas personalizadas de la configuración
failproofai policies --uninstall --custom
La ruta absoluta resuelta se almacena en policies-config.json como customPoliciesPath. El archivo se carga de nuevo en cada evento de hook; no hay caché entre eventos.

Usar ambas juntas

Las políticas por convención y el archivo --custom explícito pueden coexistir. Orden de carga:
  1. Archivo explícito customPoliciesPath (si está configurado)
  2. Archivos de convención del proyecto ({cwd}/.failproofai/policies/, alfabético)
  3. Archivos de convención del usuario (~/.failproofai/policies/, alfabético)

API

Importación

import { customPolicies, allow, deny, instruct } from "failproofai";

customPolicies.add(hook)

Registra una política. Llámalo las veces que necesites para definir múltiples políticas en el mismo archivo.
customPolicies.add({
  name: string;                         // requerido - identificador único
  description?: string;                 // se muestra en la salida de `failproofai policies`
  match?: { events?: HookEventType[] }; // filtra por tipo de evento; omítelo para coincidir con todos
  fn: (ctx: PolicyContext) => PolicyResult | Promise<PolicyResult>;
});

Funciones auxiliares de decisión

FunciónEfectoCuándo usarla
allow()Permite la operación silenciosamenteLa acción es segura y no se necesita mensaje
deny(message)Bloquea la operaciónEl agente no debería realizar esta acción
instruct(message)Añade contexto sin bloquearProporciona contexto adicional al agente para mantenerlo en curso
deny(message) — el mensaje aparece para Claude con el prefijo "Blocked by failproofai:". Un solo deny cortocircuita toda evaluación posterior. instruct(message) — el mensaje se añade al contexto de Claude para la llamada de herramienta actual. Todos los mensajes instruct se acumulan y se entregan juntos.
Puedes añadir orientación adicional a cualquier mensaje deny o instruct incluyendo un campo hint en policyParams, sin necesidad de cambiar el código. Esto funciona también para políticas personalizadas (custom/), de convención de proyecto (.failproofai-project/) y de convención de usuario (.failproofai-user/). Consulta Configuración → hint para más detalles.

Mensajes informativos en allow

allow(message) permite la operación y envía un mensaje informativo a Claude. El mensaje se entrega como additionalContext en la respuesta stdout del manejador de hooks — el mismo mecanismo que usa instruct, pero con una semántica diferente: es una actualización de estado, no una advertencia.
FunciónEfectoCuándo usarla
allow(message)Permite y envía contexto a ClaudeConfirmar que una verificación pasó, o explicar por qué se omitió
Casos de uso:
  • Confirmaciones de estado: allow("All CI checks passed.") — le indica a Claude que todo está en orden
  • Explicaciones de fail-open: allow("GitHub CLI not installed, skipping CI check.") — le dice a Claude por qué se omitió una verificación para que tenga contexto completo
  • Los mensajes se acumulan: si varias políticas devuelven allow(message), todos los mensajes se unen con saltos de línea y se entregan juntos
customPolicies.add({
  name: "confirm-branch-status",
  match: { events: ["Stop"] },
  fn: async (ctx) => {
    const cwd = ctx.session?.cwd;
    if (!cwd) return allow("No working directory, skipping branch check.");

    // ... check branch status ...
    if (allPushed) {
      return allow("Branch is up to date with remote.");
    }
    return deny("Unpushed changes detected.");
  },
});

Campos de PolicyContext

CampoTipoDescripción
eventTypestring"PreToolUse", "PostToolUse", "Notification", "Stop"
toolNamestring | undefinedLa herramienta que se está invocando (p. ej. "Bash", "Write", "Read")
toolInputRecord<string, unknown> | undefinedLos parámetros de entrada de la herramienta
payloadRecord<string, unknown>Payload completo del evento raw de Claude Code
sessionSessionMetadata | undefinedContexto de sesión (ver más abajo)

Campos de SessionMetadata

CampoTipoDescripción
sessionIdstringIdentificador de sesión de Claude Code
cwdstringDirectorio de trabajo de la sesión de Claude Code
transcriptPathstringRuta al archivo de transcripción JSONL de la sesión

Tipos de eventos

EventoCuándo se disparaContenido de toolInput
PreToolUseAntes de que Claude ejecute una herramientaLa entrada de la herramienta (p. ej. { command: "..." } para Bash)
PostToolUseDespués de que una herramienta completaLa entrada de la herramienta + tool_result (la salida)
NotificationCuando Claude envía una notificación{ message: "...", notification_type: "idle" | "permission_prompt" | ... } — los hooks siempre deben devolver allow(), no pueden bloquear notificaciones
StopCuando la sesión de Claude finalizaVacío

Orden de evaluación

Las políticas se evalúan en este orden:
  1. Políticas integradas (en orden de definición)
  2. Políticas personalizadas explícitas de customPoliciesPath (en orden de .add())
  3. Políticas de convención del proyecto .failproofai/policies/ (archivos en orden alfabético, orden de .add() dentro de cada archivo)
  4. Políticas de convención del usuario ~/.failproofai/policies/ (archivos en orden alfabético, orden de .add() dentro de cada archivo)
El primer deny cortocircuita todas las políticas siguientes. Todos los mensajes instruct se acumulan y se entregan juntos.

Importaciones transitivas

Los archivos de políticas personalizadas pueden importar módulos locales usando rutas relativas:
// my-policies.js
import { isBlockedPath } from "./utils.js";
import { checkApproval } from "./approval-client.js";

customPolicies.add({
  name: "approval-gate",
  fn: async (ctx) => {
    if (ctx.toolName !== "Bash") return allow();
    const approved = await checkApproval(ctx.toolInput?.command, ctx.session?.sessionId);
    return approved ? allow() : deny("Approval required for this command");
  },
});
Se resuelven todas las importaciones relativas accesibles desde el archivo de entrada. Esto se implementa reescribiendo las importaciones from "failproofai" a la ruta real de dist y creando archivos .mjs temporales para garantizar la compatibilidad con ESM.

Filtrado por tipo de evento

Usa match.events para limitar cuándo se dispara una política:
customPolicies.add({
  name: "require-summary-on-stop",
  match: { events: ["Stop"] },
  fn: async (ctx) => {
    // Solo se dispara cuando la sesión finaliza
    // ctx.session.transcriptPath contiene el log completo de la sesión
    return allow();
  },
});
Omite match por completo para que se dispare en todos los tipos de eventos.

Manejo de errores y modos de fallo

Las políticas personalizadas son fail-open: los errores nunca bloquean las políticas integradas ni causan un crash en el manejador de hooks.
FalloComportamiento
customPoliciesPath no configuradoNo se ejecutan políticas personalizadas explícitas; las políticas por convención y las integradas continúan con normalidad
Archivo no encontradoSe registra una advertencia en ~/.failproofai/hook.log; las políticas integradas continúan
Error de sintaxis/importación (explícito)Error registrado en ~/.failproofai/hook.log; se omiten las políticas personalizadas explícitas
Error de sintaxis/importación (convención)Error registrado; ese archivo se omite, los demás archivos de convención siguen cargándose
fn lanza un error en tiempo de ejecuciónError registrado; ese hook se trata como allow; los demás hooks continúan
fn tarda más de 10 segundosTimeout registrado; se trata como allow
Directorio de convención inexistenteNo se ejecutan políticas de convención; sin error
Para depurar errores en políticas personalizadas, observa el archivo de log:
tail -f ~/.failproofai/hook.log

Ejemplo completo: múltiples políticas

// my-policies.js
import { customPolicies, allow, deny, instruct } from "failproofai";

// Prevent agent from writing to secrets/ directory
customPolicies.add({
  name: "block-secrets-dir",
  description: "Prevent agent from writing to secrets/ directory",
  match: { events: ["PreToolUse"] },
  fn: async (ctx) => {
    if (!["Write", "Edit"].includes(ctx.toolName ?? "")) return allow();
    const path = ctx.toolInput?.file_path ?? "";
    if (path.includes("secrets/")) return deny("Writing to secrets/ is not permitted");
    return allow();
  },
});

// Keep the agent on track: verify tests before committing
customPolicies.add({
  name: "remind-test-before-commit",
  description: "Keep the agent on track: verify tests pass before committing",
  match: { events: ["PreToolUse"] },
  fn: async (ctx) => {
    if (ctx.toolName !== "Bash") return allow();
    const cmd = ctx.toolInput?.command ?? "";
    if (/git\s+commit/.test(cmd)) {
      return instruct("Verify all tests pass before committing. Run `bun test` if you haven't already.");
    }
    return allow();
  },
});

// Prevent unplanned dependency changes during freeze
customPolicies.add({
  name: "dependency-freeze",
  description: "Prevent unplanned dependency changes during freeze period",
  match: { events: ["PreToolUse"] },
  fn: async (ctx) => {
    if (ctx.toolName !== "Bash") return allow();
    const cmd = ctx.toolInput?.command ?? "";
    const isInstall = /^(npm install|yarn add|bun add|pnpm add)\s+\S/.test(cmd);
    if (isInstall && process.env.DEPENDENCY_FREEZE === "1") {
      return deny("Package installs are frozen. Unset DEPENDENCY_FREEZE to allow.");
    }
    return allow();
  },
});

export { customPolicies };

Ejemplos

El directorio examples/ contiene archivos de políticas listos para ejecutar:
ArchivoContenido
examples/policies-basic.jsCinco políticas de inicio que cubren los modos de fallo más comunes de los agentes
examples/policies-advanced/index.jsPatrones avanzados: importaciones transitivas, llamadas asíncronas, limpieza de salidas y hooks de fin de sesión
examples/convention-policies/security-policies.mjsPolíticas de seguridad por convención (bloquea escrituras en .env, previene reescritura del historial de git)
examples/convention-policies/workflow-policies.mjsPolíticas de flujo de trabajo por convención (recordatorios de tests, auditoría de escrituras en archivos)

Usar los ejemplos con archivo explícito

failproofai policies --install --custom ./examples/policies-basic.js

Usar los ejemplos basados en convención

# Copiar al nivel del proyecto
mkdir -p .failproofai/policies
cp examples/convention-policies/*.mjs .failproofai/policies/

# O copiar al nivel del usuario
mkdir -p ~/.failproofai/policies
cp examples/convention-policies/*.mjs ~/.failproofai/policies/
No se necesita ningún comando de instalación — los archivos se detectan automáticamente en el siguiente evento de hook.