メインコンテンツへスキップ
このドキュメントでは、failproofai の内部動作を説明します。フックシステムがエージェントのツール呼び出しをどのようにインターセプトするか、設定がどのように読み込まれてマージされるか、ポリシーがどのように評価されるか、そしてダッシュボードがエージェントのアクティビティをどのように監視するかについて解説します。

概要

failproofai には独立した 2 つのサブシステムがあります。
  1. フックハンドラー - Claude Code がエージェントのツール呼び出しごとに起動する高速な CLI サブプロセス。ポリシーを評価して判断を返します。
  2. エージェントモニター(ダッシュボード) - エージェントセッションの監視とポリシー管理のための Next.js ウェブアプリケーション。
両サブシステムは ~/.failproofai/ とプロジェクトの .failproofai/ ディレクトリにある設定ファイルを共有しますが、それぞれ独立したプロセスとして動作し、ファイルシステムを通じてのみ通信します。

フックハンドラー

Claude Code との連携

failproofai policies --install を実行すると、~/.claude/settings.json に以下のようなエントリが書き込まれます。
Claude Code はツール呼び出しごとに failproofai --hook PreToolUse をサブプロセスとして起動し、JSON ペイロードを stdin で渡します。

ペイロードの形式

PostToolUse イベントの場合、ペイロードにはツールの出力を含む tool_result も含まれます。 ハンドラーは stdin の上限を 1 MB に制限しています。この上限を超えるペイロードは破棄され、すべてのポリシーが暗黙的に allow となります。

レスポンスの形式

Deny(PreToolUse):
Deny(PostToolUse):
Instruct(Stop 以外のイベント):
Stop イベントの instruct:
  • 終了コード:2
  • 理由は stdout ではなく stderr に書き込まれます
Allow:
  • 終了コード:0
  • stdout は空
メッセージ付き Allow: allow(message) を使うと、操作が許可された場合でもポリシーから Claude に情報コンテキストを返すことができます。フックハンドラーは以下の JSON を stdout に書き込みます(設定ファイルへの書き込みではなく、deny や instruct と同様に Claude Code へのハンドラーレスポンスです)。
  • 終了コード:0(操作は許可される)
  • 複数のポリシーがメッセージ付きで allow を返した場合、それらのメッセージは改行で結合されて単一の additionalContext 文字列になります
  • いずれのポリシーもメッセージを提供しない場合、stdout は空(従来と同じ動作)

処理パイプライン

src/hooks/handler.ts がパイプライン全体を実装しています。
LLM 呼び出しなしで、典型的なペイロードの処理は 100ms 以内に完了します。

設定の読み込み

src/hooks/hooks-config.ts が 3 スコープの設定読み込みを実装しています。
マージのロジック:
  • enabledPolicies - 3 つのファイルにわたって重複排除した和集合
  • policyParams - ポリシーキーごとに、最初に定義したファイルが完全に優先
  • customPoliciesPath - 最初に定義したファイルが優先
  • llm - 最初に定義したファイルが優先
ウェブダッシュボードはプロジェクトの cwd を持たないため、読み取りと書き込みには readHooksConfig()(グローバルのみ)を使用します。

ポリシーの評価

src/hooks/policy-evaluator.ts がポリシーを順番に実行します。 各ポリシーについて:
  1. ポリシーの params スキーマを参照します(存在する場合)。
  2. マージ済み設定から policyParams[policy.name] を読み取ります。
  3. ユーザー指定の値をスキーマのデフォルト値にマージして ctx.params を生成します。
  4. 解決されたコンテキストを使って policy.fn(ctx) を呼び出します。
  5. 結果が deny の場合、即座に停止してその判断を返します。
  6. 結果が instruct の場合、メッセージを蓄積して処理を続行します。
  7. 結果が allow の場合、次のポリシーへ進みます。
すべてのポリシーの実行後:
  • deny が返された場合、deny レスポンスを出力します。
  • instruct が収集された場合、すべてのメッセージを結合した単一の instruct レスポンスを出力します。
  • それ以外の場合、allow レスポンス(stdout 空、終了コード 0)を出力します。

組み込みポリシー

src/hooks/builtin-policies.ts が 39 個の組み込みポリシーを BuiltinPolicyDefinition オブジェクトとして定義しています。
params を受け取るポリシーは、各パラメーターの型とデフォルト値を持つ PolicyParamsSchema を宣言します。ポリシー評価器は fn を呼び出す前に解決済みの値を ctx.params に注入します。デフォルト値は常に先に適用されるため、ポリシー関数は null チェックなしで ctx.params を読み取れます。 ポリシー内のパターンマッチングは生の文字列マッチングではなく、解析済みのコマンドトークン(argv)を使用します。これにより、シェル演算子インジェクションによるバイパスを防ぎます(例:sudo systemctl status * というパターンは、コマンドに ; rm -rf / を追加しても迂回できません)。

カスタムポリシー

src/hooks/custom-hooks-registry.tsglobalThis を使ったレジストリを実装しています。
src/hooks/custom-hooks-loader.ts がユーザーのポリシーファイルを読み込みます。
  1. 設定から customPoliciesPath を読み取り、存在しない場合はスキップします。
  2. 絶対パスに解決してファイルの存在を確認します。
  3. すべての from "failproofai" インポートを実際の dist パスに書き換え、customPolicies が同じ globalThis レジストリに解決されるようにします。
  4. ESM 互換性を確保するため、推移的なローカルインポートを再帰的に書き換えます。
  5. 一時的な .mjs ファイルを書き出し、エントリファイルを import() します。
  6. getCustomHooks() を呼び出して登録済みフックを取得します。
  7. finally ブロックですべての一時ファイルを削除します。
エラーが発生した場合(ファイルが見つからない、構文エラー、インポート失敗など)、エラーは ~/.failproofai/hook.log に記録され、ローダーは空の配列を返します。組み込みポリシーへの影響はありません。 カスタムポリシーはすべての組み込みポリシーの後に評価されます。カスタムポリシーの deny はそれ以降のカスタムポリシーを短絡させますが(その時点ではすべての組み込みポリシーはすでに実行済みです)。

アクティビティのログ記録

各フックイベントの後、ハンドラーは JSONL の 1 行を ~/.failproofai/hook-activity.jsonl に追記します。
非 allow の判断をしたポリシーごとに 1 行記録されます。allow の判断はファイルサイズを抑えるため記録されません。

ダッシュボードのアーキテクチャ

ダッシュボードは App Router を使用した Next.js 16 アプリケーションで、React Server Components と Server Actions を採用しています。
データフロー:
  • ページコンポーネントは lib/projects.tslib/log-entries.ts を呼び出して、ファイルシステムから直接プロジェクト/セッションデータを読み取ります(読み取りに API レイヤーなし)。
  • Policies ページはすべての変更操作(切り替え、パラメーター更新、インストール/削除)に Server Actions を使用します。
  • セッションビューアーは Claude の JSONL トランスクリプト形式をパースし、メッセージとツール呼び出しのタイムラインを描画します。
主要な設計方針:
  • データベースなし - すべての永続状態はプレーンファイル(~/.failproofai/~/.claude/projects/)に保存
  • 変更操作に Server Actions を使用 - CRUD 操作に REST API が不要
  • 読み取りページに React Server Components を使用 - 初期表示が高速でデータフェッチのクライアントバンドルが不要
  • インタラクティビティが必要な箇所にのみクライアントコンポーネントを使用(ポリシーの切り替え、アクティビティ検索、ログビューアー)

ファイル構成