自定义策略允许您为任何 Agent 行为编写规则:强制执行项目规范、防止偏离、限制破坏性操作、检测卡死的 Agent,或与 Slack、审批工作流等系统集成。它们使用与内置策略相同的 hook 事件系统以及 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
两种加载自定义策略的方式
方式一:基于约定(推荐)
将 *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} 的文件,其他文件会被忽略
- 每个文件独立加载(单文件失败开放)
- 与显式
--custom 和内置策略兼容并存
基于约定的策略是在团队中共享策略的最简便方式。将 .failproofai/policies/ 提交到 git,每位团队成员都能自动获取这些策略。
方式二:显式文件路径
# 使用自定义策略文件安装
failproofai policies --install --custom ./my-policies.js
# 替换策略文件路径
failproofai policies --install --custom ./new-policies.js
# 从配置中移除自定义策略路径
failproofai policies --uninstall --custom
解析后的绝对路径以 customPoliciesPath 的形式存储在 policies-config.json 中。每次 hook 事件都会重新加载该文件——事件之间不存在缓存。
同时使用两种方式
基于约定的策略与显式 --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) | 阻止操作 | Agent 不应执行此操作 |
instruct(message) | 添加上下文但不阻止 | 为 Agent 提供额外上下文以保持正轨 |
deny(message) - 消息会以 "Blocked by failproofai:" 为前缀显示给 Claude。单个 deny 会短路所有后续评估。
instruct(message) - 消息会附加到 Claude 当前工具调用的上下文中。所有 instruct 消息会被累积并一并发送。
您可以通过在 policyParams 中添加 hint 字段,为任何 deny 或 instruct 消息追加额外指导——无需修改代码。这对自定义(custom/)、项目约定(.failproofai-project/)和用户约定(.failproofai-user/)策略同样适用。详情请参阅配置 → hint。
信息性 allow 消息
allow(message) 允许操作并向 Claude 发送一条信息性消息。该消息以 additionalContext 的形式通过 hook 处理器的 stdout 响应发送——与 instruct 使用相同的机制,但语义不同:它是状态更新,而非警告。
| 函数 | 效果 | 适用场景 |
|---|
allow(message) | 允许并向 Claude 发送上下文 | 确认检查已通过,或说明为何跳过某项检查 |
使用场景:
- 状态确认:
allow("All CI checks passed.") — 告知 Claude 一切正常
- 失败开放说明:
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 执行工具之前 | 工具的输入(如 Bash 对应 { command: "..." }) |
PostToolUse | 工具执行完成之后 | 工具的输入 + tool_result(输出内容) |
Notification | Claude 发送通知时 | { message: "...", notification_type: "idle" | "permission_prompt" | ... } - hook 必须始终返回 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 则对每种事件类型都触发。
错误处理与失败模式
自定义策略采用失败开放原则:错误不会阻止内置策略运行或导致 hook 处理器崩溃。
| 失败情形 | 行为 |
|---|
customPoliciesPath 未设置 | 不运行显式自定义策略;约定策略和内置策略正常继续 |
| 文件未找到 | 警告记录到 ~/.failproofai/hook.log;内置策略继续运行 |
| 语法/导入错误(显式) | 错误记录到 ~/.failproofai/hook.log;跳过显式自定义策略 |
| 语法/导入错误(约定) | 错误记录;跳过该文件,其他约定文件继续加载 |
fn 运行时抛出异常 | 错误记录;该 hook 视为 allow;其他 hook 继续运行 |
fn 执行超过 10 秒 | 超时记录;视为 allow |
| 约定目录不存在 | 不运行约定策略;不报错 |
如需调试自定义策略错误,可监控日志文件:tail -f ~/.failproofai/hook.log
完整示例:多个策略
// my-policies.js
import { customPolicies, allow, deny, instruct } from "failproofai";
// 防止 Agent 写入 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();
},
});
// 引导 Agent 保持正轨:提交前验证测试
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 | 五个涵盖常见 Agent 失败模式的入门策略 |
examples/policies-advanced/index.js | 高级模式:传递性导入、异步调用、输出脱敏、会话结束 hook |
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/
无需安装命令——下次 hook 事件触发时,文件会被自动加载。