Skip to main content
このドキュメントでは、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 を返した場合、各メッセージは改行で結合されて 1 つの 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 はそれ以降のカスタムポリシーの評価を短絡しますが、組み込みポリシーはすでに実行済みです。

アクティビティログ

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

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

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

ファイル構成