Пользовательские политики позволяют написать правила для любого поведения агента: применять соглашения проекта, предотвращать дрейф, блокировать деструктивные операции, обнаруживать зависшие агенты или интегрироваться с Slack, рабочими процессами одобрения и многим другим. Они используют ту же систему событий перехватчиков и решения allow, deny, instruct, что и встроенные политики.
Быстрый пример
// 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();
},
});
Установите её:
failproofai policies --install --custom ./my-policies.js
Два способа загрузки пользовательских политик
Вариант 1: На основе соглашений (рекомендуется)
Поместите файлы *policies.{js,mjs,ts} в .failproofai/policies/ и они будут загружены автоматически — никаких флагов или изменений конфигурации не требуется. Это работает как git hooks: поместите файл, и он просто работает.
# На уровне проекта — коммитится в git, делится с командой
.failproofai/policies/security-policies.mjs
.failproofai/policies/workflow-policies.mjs
# На уровне пользователя — личные, применяется ко всем проектам
~/.failproofai/policies/my-policies.mjs
Как это работает:
- Сканируются оба каталога — проекта и пользователя (объединение — не первый приоритет)
- Файлы загружаются в алфавитном порядке в каждом каталоге. Используйте префиксы
01-, 02- для управления порядком
- Загружаются только файлы, соответствующие
*policies.{js,mjs,ts}; остальные файлы игнорируются
- Каждый файл загружается независимо (fail-open для каждого файла)
- Работает вместе с явным
--custom и встроенными политиками
Политики на основе соглашений — это самый простой способ поделиться политиками с командой. Закоммитьте .failproofai/policies/ в git, и каждый член команды получит их автоматически.
Вариант 2: Явный путь к файлу
# Установите с файлом пользовательских политик
failproofai policies --install --custom ./my-policies.js
# Замените путь к файлу политик
failproofai policies --install --custom ./new-policies.js
# Удалите путь к пользовательским политикам из конфигурации
failproofai policies --uninstall --custom
Разрешённый абсолютный путь сохраняется в policies-config.json как customPoliciesPath. Файл загружается заново при каждом событии перехватчика — кэширования между событиями нет.
Использование обоих способов вместе
Политики на основе соглашений и явный файл --custom могут сосуществовать. Порядок загрузки:
- Явный файл
customPoliciesPath (если настроен)
- Файлы соглашений проекта (
{cwd}/.failproofai/policies/, в алфавитном порядке)
- Файлы соглашений пользователя (
~/.failproofai/policies/, в алфавитном порядке)
API
Импорт
import { customPolicies, allow, deny, instruct } from "failproofai";
customPolicies.add(hook)
Регистрирует политику. Вызывайте столько раз, сколько нужно для нескольких политик в одном файле.
customPolicies.add({
name: string; // обязательно - уникальный идентификатор
description?: string; // отображается в выводе `failproofai policies`
match?: { events?: HookEventType[] }; // фильтр по типу события; опустите, чтобы совпадал все
fn: (ctx: PolicyContext) => PolicyResult | Promise<PolicyResult>;
});
Помощники для принятия решений
| Функция | Эффект | Используйте когда |
|---|
allow() | Разрешить операцию молча | Действие безопасно, сообщение не требуется |
deny(message) | Заблокировать операцию | Агент не должен выполнять это действие |
instruct(message) | Добавить контекст без блокировки | Дайте агенту дополнительный контекст для выполнения задачи |
deny(message) — сообщение отображается Claude с префиксом "Blocked by failproofai:". Один deny короткозамыкает всю дальнейшую оценку.
instruct(message) — сообщение добавляется в контекст Claude для текущего вызова инструмента. Все сообщения instruct накапливаются и доставляются вместе.
Вы можете добавить дополнительные рекомендации к любому сообщению deny или instruct, добавив поле hint в policyParams — без изменения кода. Это работает для пользовательских (custom/), проектных (failproofai-project/) и пользовательских (failproofai-user/) политик. См. Конфигурация → hint для подробностей.
Информационные сообщения allow
allow(message) разрешает операцию и отправляет информационное сообщение обратно Claude. Сообщение доставляется как additionalContext в ответе stdout обработчика перехватчика — тот же механизм, используемый instruct, но семантически отличается: это обновление статуса, а не предупреждение.
| Функция | Эффект | Используйте когда |
|---|
allow(message) | Разрешить и отправить контекст Claude | Подтвердить, что проверка прошла, или объяснить, почему проверка была пропущена |
Варианты использования:
- Подтверждения статуса:
allow("All CI checks passed.") — указывает Claude, что всё зелено
- Объяснения fail-open:
allow("GitHub CLI not installed, skipping CI check.") — указывает Claude, почему проверка была пропущена, чтобы он имел полный контекст
- Несколько сообщений накапливаются: если несколько политик возвращают
allow(message), все сообщения объединяются с новыми строками и доставляются вместе
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.");
},
});
Поля PolicyContext
| Поле | Тип | Описание |
|---|
eventType | string | "PreToolUse", "PostToolUse", "Notification", "Stop" |
toolName | string | undefined | Вызываемый инструмент (например "Bash", "Write", "Read") |
toolInput | Record<string, unknown> | undefined | Входные параметры инструмента |
payload | Record<string, unknown> | Полная необработанная полезная нагрузка события от Claude Code |
session | SessionMetadata | undefined | Контекст сессии (см. ниже) |
| Поле | Тип | Описание |
|---|
sessionId | string | Идентификатор сессии Claude Code |
cwd | string | Рабочий каталог сессии Claude Code |
transcriptPath | string | Путь к файлу транскрипта JSONL сессии |
Типы событий
| Событие | Когда срабатывает | Содержимое toolInput |
|---|
PreToolUse | До запуска инструмента Claude | Вход инструмента (например { command: "..." } для Bash) |
PostToolUse | После завершения инструмента | Вход инструмента + tool_result (выход) |
Notification | Когда Claude отправляет уведомление | { message: "...", notification_type: "idle" | "permission_prompt" | ... } - перехватчики должны всегда возвращать allow(), они не могут блокировать уведомления |
Stop | Когда сессия Claude заканчивается | Пусто |
Порядок оценки
Политики оцениваются в следующем порядке:
- Встроенные политики (в порядке определения)
- Явные пользовательские политики из
customPoliciesPath (в порядке .add())
- Политики соглашений из проекта
.failproofai/policies/ (файлы в алфавитном порядке, .add() внутри)
- Политики соглашений из пользователя
~/.failproofai/policies/ (файлы в алфавитном порядке, .add() внутри)
Первый deny короткозамыкает все последующие политики. Все сообщения instruct накапливаются и доставляются вместе.
Транзитивные импорты
Файлы пользовательских политик могут импортировать локальные модули, используя относительные пути:
// 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");
},
});
Все относительные импорты, достижимые из входного файла, разрешаются. Это реализуется путём переписывания импортов from "failproofai" на фактический путь dist и создания временных файлов .mjs для обеспечения совместимости ESM.
Фильтрация по типам событий
Используйте match.events для ограничения срабатывания политики:
customPolicies.add({
name: "require-summary-on-stop",
match: { events: ["Stop"] },
fn: async (ctx) => {
// Срабатывает только при завершении сессии
// ctx.session.transcriptPath содержит полный журнал сессии
return allow();
},
});
Опустите match полностью, чтобы срабатывать на каждом типе события.
Обработка ошибок и режимы отказа
Пользовательские политики — это fail-open: ошибки никогда не блокируют встроенные политики и не приводят к сбою обработчика перехватчика.
| Отказ | Поведение |
|---|
customPoliciesPath не установлен | Явные пользовательские политики не запускаются; встроенные политики продолжают нормально работать |
| Файл не найден | Предупреждение записывается в ~/.failproofai/hook.log; встроенные политики продолжают работать |
| Синтаксис/ошибка импорта (явная) | Ошибка записывается в ~/.failproofai/hook.log; явные пользовательские политики пропускаются |
| Синтаксис/ошибка импорта (соглашение) | Ошибка записывается; этот файл пропускается, другие файлы соглашений загружаются |
fn выбрасывает ошибку во время выполнения | Ошибка записывается; этот перехватчик рассматривается как allow; другие перехватчики продолжают работу |
fn занимает больше 10 секунд | Истечение времени ожидания записывается; рассматривается как allow |
| Каталог соглашений отсутствует | Политики соглашений не запускаются; нет ошибки |
Для отладки ошибок пользовательских политик используйте файл журнала:tail -f ~/.failproofai/hook.log
Полный пример: несколько политик
// my-policies.js
import { customPolicies, allow, deny, instruct } from "failproofai";
// Предотвращайте запись агента в каталог secrets/
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();
},
});
// Держите агента на правильном пути: проверьте тесты перед коммитом
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();
},
});
// Предотвращайте незапланированные изменения зависимостей во время заморозки
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 };
Примеры
Каталог examples/ содержит готовые файлы политик:
| Файл | Содержимое |
|---|
examples/policies-basic.js | Пять начальных политик, охватывающих типичные режимы отказа агента |
examples/policies-advanced/index.js | Продвинутые паттерны: транзитивные импорты, асинхронные вызовы, очистка выводов и перехватчики завершения сессии |
examples/convention-policies/security-policies.mjs | Политики безопасности на основе соглашений (блокировка записей .env, предотвращение переписывания истории git) |
examples/convention-policies/workflow-policies.mjs | Политики рабочего процесса на основе соглашений (напоминания о тестах, аудит записей файлов) |
Использование примеров явных файлов
failproofai policies --install --custom ./examples/policies-basic.js
Использование примеров на основе соглашений
# Скопируйте на уровень проекта
mkdir -p .failproofai/policies
cp examples/convention-policies/*.mjs .failproofai/policies/
# Или скопируйте на уровень пользователя
mkdir -p ~/.failproofai/policies
cp examples/convention-policies/*.mjs ~/.failproofai/policies/
Команда установки не требуется — файлы выбираются автоматически при следующем событии перехватчика.