# 汎用Hook機構とAuto Prompterの実現可能性調査 調査日: 2026-07-24 対象: MAESTRO(gitea-agent-orchestrator) 注記: 本調査ではコード変更を行っていない。 ## 1. 結論 **縮小して実装すべきである。** 推奨範囲は次のとおり。 - 観測は、既存 `EventLogger` を拡張した非同期・fail-openのイベント通知とする。 - 制御は、初期段階ではツール実行前だけに同期的な `PolicyGate` を追加する。 - Auto Prompterは、決定的条件で候補を絞った後、必要な場合だけ小型LLMが登録済みルールIDを三値判定する。 - 承認機構はHookへ移さず、独立した承認サービスへ一般化してHookから呼び出す。 - Completeでは、初期段階は観測と軽量な最終確認だけを行う。 - Piece/Movementは、初期段階では観測対象とscope情報に限定し、遷移制御へ介入しない。 全面的な汎用Hook化、既存権限処理の置換、生成ストリーム途中の判定、Piece/Movement遷移制御は推奨しない。 最終判断は以下である。 > **ツール実行前Hookだけ実装し、既存承認機構を中心に拡張する。観測Hookと監査ログは既存EventLogger上へ追加する。** 製品方針は [Auto Prompter ワークスペース機能 設計方針](./auto-prompter-workspace-design.md) を正とする。一般ユーザーがワークスペース単位で設定できる機能として提供するが、公開は段階化する。初期段階では常時適用ルール、テンプレート、決定的条件、shadow判定を中心とし、自由記述の自然言語条件を使う制御actionは評価基準を満たした後に有効化する。 EU AI Actの透明性、監査、人間監督、provenance、保持方針は [EU AI Act透明性対応 設計付録](./auto-prompter-eu-ai-act-transparency-design.md) を参照する。法的義務と自主的統制を分け、hidden chain-of-thoughtの保存や一律5年保持を前提にしない。 --- ## 2. 現在の関連機能と処理フロー ### 2.1 エージェントのメインループ 中心は `src/engine/agent-loop.ts` の `executeMovement()` である。 1. Movement用プロンプトとツールを準備 2. iterationごとに途中指示を取り込む 3. LLMをストリーミング実行 4. tool callを通常ツール、`transition`、`complete` に分類 5. 通常ツールを実行 6. 待機・承認状態を確認 7. `complete` またはMovement遷移を処理 ### 2.2 LLM呼び出し前後 LLM呼び出し前: - キャンセル確認 - 途中指示の取り込み - watchdog - コンテキスト超過検査・圧縮 - LLM呼び出し開始イベント LLM呼び出し後: - SSEのテキスト・tool call集約 - エラー回復 - usage更新 - tool call分類 - tool dispatch - `complete` / `transition` 処理 主な実装: - `src/engine/agent-loop/llm-iteration.ts` - `src/engine/llm-stream.ts` - `src/llm/openai-compat.ts` ### 2.3 ストリーミング応答 `OpenAICompatClient.chat()` がOpenAI互換SSEを読み、`consumeLlmStream()` が以下を集約する。 - テキスト - tool call - tool引数delta - usage - reasoning文字数 - backend情報 - retry/error 現状ではテキストchunkがUI callbackへ即時送信される。生成途中のAuto Prompter判定には、ストリーム保留、abort、再生成、部分表示の整合処理が必要となるため、初期実装では扱わない方が安全である。 ### 2.4 動作途中の追加プロンプト iteration間で `checkInterjections()` がユーザーコメントを取得し、次の形式で `messages` に追加する。 ```text [interjection] ユーザーの追加指示 ``` Auto Prompterの追加指示にも同じmessage injection方式を再利用できる。DBコメントを経由させる必要はなく、message injection部分を内部サービスとして抽出するのが適切である。 ### 2.5 ツール実行 ```text dispatchRegularToolCalls └─ executeRegularToolCallCached ├─ tool_callイベント ├─ キャッシュ判定 └─ executeRegularToolCall ├─ Movement内の許可判定 ├─ 引数JSON parse └─ executeTool └─ 各ツールモジュールへルーティング ``` 主な実装: - `src/engine/agent-loop/tool-dispatcher.ts` - `src/engine/agent-loop/tool-execution.ts` - `src/engine/tools/index.ts` 許可外ツールは `isAllowedRegularTool()` で拒否され、エラーのtool resultとしてモデルへ返される。拒否記録はベストエフォートで、記録失敗によってツール処理を壊さない。 ### 2.6 許可申請と承認 現在は `RequestTool` 専用フローである。 ```text RequestTool → tool_requestsへpending保存 → pendingToolApprovalを設定 → MovementをWAITING_HUMAN_TOOL_REQUESTで終了 → UIに承認カード → approve/deny API → approveならlocal_tasks.granted_toolsへツール名追加 → parked jobを再開 → 同じMovementを最初から再実行 ``` 主な実装: - `src/engine/tools/tool-request.ts` - `src/engine/agent-loop/pending-states.ts` - `src/bridge/local-tasks-tool-requests-api.ts` - `src/db/repositories/tool-requests.ts` - `ui/src/components/chat/ToolRequestApproval.tsx` 現在の承認単位は「タスク中のツール名」であり、引数、操作内容、Movement、期限、回数は承認対象へ固定されない。 ### 2.7 PieceとMovement `src/engine/piece-runner.ts` の `runPiece()` がMovementループを所有する。 - Piece定義の検証 - Movement選択 - 再訪問回数制限 - Movement開始通知 - `executeMovement()` - Movement結果の保存 - `COMPLETE` / `ABORT` / `ASK` / 各種待機状態の変換 - 次Movementの選択 - Piece完了通知 ### 2.8 CompleteとMovement遷移 - `complete`: JSON引数を検証し、成功・中断・ユーザー入力待ちへ変換 - `transition`: 現在Movementの `rules` に含まれる遷移先だけ受理 - 不正なtool callにはtool resultで理由を返し、再試行 - pending subtaskがある成功Completeは自動待機へ変換 実装は `src/engine/agent-loop/terminal-control.ts` にある。 ### 2.9 会話・状態・イベント - 会話履歴: `Conversation`。メモリ上のmessagesとJSONL transcript - タスク状態: Repository/SQLite - 実行中状態: `ToolContext`、`ExecuteMovementOptions` - 監査・診断: `EventLogger` による `logs/events.jsonl` - UI通知: callback → reporter → SSE/DB comment - ログ: 通常logger、activity log、event log `EventLogger` は構造化イベント、run ID、連番、相関ID、Movement scope、マスキング、サイズ制限、書き込み失敗時のNoop化を既に備える。 現状、一般的なmiddleware/pluginバスはない。`EventLogger` は観測イベントバスに最も近いが、購読型ではなくファイル出力型である。 --- ## 3. Hook追加候補地点 | タイミング | 分類 | 理由 | |---|---|---| | ツール実行前 | 初期実装に必要 | 副作用前に確実に保留・拒否・承認できる | | ツール拒否時 | 初期実装に必要 | 既存ブロック結果と監査を統合しやすい | | ツール実行後 | 初期実装に必要 | 結果監査。観測専用・非ブロッキング | | Complete受理前 | 初期実装に必要 | 最終確認用。ただし最初は軽量 | | エラー発生時 | 初期実装に必要 | 観測イベントのみ | | Movement開始前 | 将来追加すると有用 | Movement別ルール選択、固定指示 | | Movement完了前 | 将来追加すると有用 | 遷移前確認。ただし回帰リスクあり | | Movement遷移後 | 将来追加すると有用 | 監査のみなら低リスク | | Piece開始前 | 将来追加すると有用 | 監査・ルールスナップショット | | LLM呼び出し前 | 将来追加すると有用 | コンテキスト検査や固定指示 | | LLM応答後 | 将来追加すると有用 | テキスト品質検査。ただし再生成設計が必要 | | 許可申請時 | 将来追加すると有用 | 承認理由の監査・ポリシー拒否 | | ユーザー承認・拒否時 | 将来追加すると有用 | 承認監査、再申請制御 | | Movement遷移前の制御 | 複雑化が大きい | Piece中核と競合しやすい | | Piece完了前の制御 | 複雑化が大きい | Completeと二重の終了判定になる | | 生成stream途中 | 複雑化が大きい | 保留、abort、再生成、部分表示整合が必要 | | LLM tokenごとのHook | 複雑化が大きい | 現行OpenAI互換APIでは実用的でない | 初期Hook名は次で十分である。 ```text tool.before tool.after tool.denied completion.before_accept runtime.error ``` --- ## 4. 推奨するHookアーキテクチャ ### 4.1 観測と制御のハイブリッド ```text ┌─ Auto Prompter 処理地点 ── PolicyGate ─┼─ Approval Service │ 同期・制御 └─ Security Policy │ └──── EventSink ────── Audit / Trace / Notification 非同期・観測 ``` ### 4.2 制御用インターフェース ```ts type GateDecision = | { kind: "allow" } | { kind: "redirect"; messages: RegisteredPrompt[]; consumeOriginalAction: true; } | { kind: "require_approval"; request: ApprovalRequest; consumeOriginalAction: true; } | { kind: "deny"; reasonCode: string; modelMessage: string; } | { kind: "stop"; reasonCode: string; }; interface PolicyGate { evaluate( point: ControlPoint, context: Readonly, ): Promise; } ``` Hookが本体状態を直接変更せず、返却値だけで制御する構成とする。 ### 4.3 観測用インターフェース ```ts interface EventSink { publish(event: HookObservation): void; } ``` 観測Hookは原則として以下を守る。 - awaitしない - 失敗しても本体を止めない - 本体状態を変更しない - timeoutを短くする - 管理者通知はqueueへ送る 観測、助言、制御、承認を単一の自由度が高いHook型へ統合しない。 | 種類 | 実装 | |---|---| | 観測 | `EventSink` | | 助言 | `GateDecision.redirect` | | 拒否・停止 | `PolicyGate` | | 承認 | `ApprovalService`をPolicyGateから呼ぶ | --- ## 5. Auto Prompterの責任範囲 Auto Prompterが行うのは、登録済みルールの選択までとする。 行わせない処理: - 新しいポリシー文の自由生成 - 停止理由の自由生成 - ツール引数の書き換え - 直接的な権限付与 - Piece/Movement遷移先の生成 - 管理者通知文の自由生成 - ルール自体の自動変更 ```text ルール候補の決定的フィルタ → 必要な候補だけ意味判定 → rule ID + 三値結果 → MAESTROが登録済みactionを実行 ``` ```ts type AutoPrompterAction = | "inject_instruction" | "deny_current_tool" | "request_approval" | "stop_task" | "audit_only" | "notify_admin"; ``` `notify_admin` 単独で利用者制限を実行してはならない。 --- ## 6. 判定モデルの構成 ### 6.1 入力 会話全文は原則として渡さない。 ```json { "rules": [ { "id": "rule-12", "condition": "元に戻すことが難しい削除を行おうとしている" } ], "current": { "piece": "coding", "movement": "implement", "hook_point": "tool.before", "tool": { "name": "Bash", "category": "command_execution", "arguments_summary": "rm ...", "arguments": "必要な範囲に限定した文字列" } }, "context": { "latest_user_message": "...", "recent_messages": ["最大数件"], "previous_rule_fires": [ {"rule_id": "rule-12", "count": 0} ], "approval_state": "none" } } ``` | 情報 | 方針 | |---|---| | 会話全文 | 原則渡さない | | 最新ユーザー入力 | 渡す | | 直近会話 | 必要時のみ2~4 turn | | Piece/Movement | 渡す | | ツール名・カテゴリ | 必須 | | ツール引数 | 対象キーのみ。秘密値を除去 | | ツール結果 | `tool.after` ルールだけ | | 過去発火 | rule ID、回数、直近fingerprintのみ | | ユーザー権限 | 決定的フィルタ側で使用 | | 登録済み指示本文 | Judgeへ渡さない方が安全 | Judge入力は外部送信境界として扱う。監査保存時と同じfield allowlistを先に適用し、その後で秘密値をマスクする。会話は必要箇所の要約または最大2~4 turnへ制限し、条件文、tool引数、Web取得内容は命令ではなく非信頼データとして構造的に分離する。Judge用endpoint、model、timeout、最大並列数は本体LLMと独立して設定し、設定値はJob開始時に固定する。 ### 6.2 出力 ```json { "decisions": [ { "rule_id": "rule-12", "result": "match", "confidence": 0.91 }, { "rule_id": "rule-18", "result": "unknown", "confidence": 0.42 } ] } ``` 結果は `match | no_match | unknown` に限定し、説明文は要求しない。 確信度は監査・shadow評価には使えるが、安全保証には利用しない。 - `match`: 登録actionを実行 - `no_match`: 通常処理 - `unknown`: - `audit_only` / 追加指示では発火しない - 重大な削除・外部送信では決定的安全規則または承認へフォールバック - タスク停止は発火しない 複数ルールは、Hook pointとカテゴリで候補を絞り、最大10件程度を一括判定する。毎ルール個別呼び出しは避ける。 ### 6.3 小型LLMを使わない条件 - tool名・カテゴリ - Piece/Movement - ユーザー権限 - 発火回数 - 既存承認状態 - 既知のファイル削除 - `chmod`、`chown`、`rm` 等の操作 - 外部通信ツール - 言語文字比率 - 許可外ツール - 同一入力fingerprint - タスク/Piece/Movement scope ### 6.4 Judge失敗時の扱い | ルール種別 | timeout・schema不正・`unknown`時 | |---|---| | 常時適用 | Judgeを使用しない | | スタイル・説明改善 | 発火させず監査だけ残す | | `audit_only` | 判定失敗イベントだけ残し、本体処理を継続する | | 危険操作 | Judgeへ委ねず、決定的規則または既存安全境界で処理する | | 承認候補 | 決定的条件が成立していれば承認へ、安全性に無関係な意味条件だけなら発火しない | | `stop_task` | Judge失敗だけでは停止しない | Judgeの出力schemaにはversionを持たせ、未対応version、未知のrule ID、重複IDを不正応答として扱う。 --- ## 7. ツール実行前ゲート 実現可能である。最適な論理順序は、引数parseと基本許可判定の後、キャッシュ判定より前である。 現行の `executeRegularToolCallCached()` はcache routingを行った後で `executeRegularToolCall()` 内の許可判定と引数parseへ進む。このため、既存関数へ単純にHookを追加するだけではcache hitがPolicyGateを迂回する。機能ON時だけ、parse・基本許可判定・PolicyGateをcache routingより前へ移すpreflight経路が必要である。機能OFF時はpreflight、clone、normalize、serializeを行わず、現在のdispatcherへ直接短絡する。 ```text tool call受信 → JSON parse → 基本許可判定 → ToolPolicyContext構築 → PolicyGate.evaluate(tool.before) ├─ allow ├─ redirect ├─ require_approval ├─ deny └─ stop → allowの場合だけcache/executeTool ``` preflightのparse結果は `valid | invalid` として保持する。不正JSONをPolicyGate独自の入力エラーへ置き換えず、tool名・カテゴリだけで成立する決定的規則を除いて意味判定を行わない。Policyで保留されなければ、既存tool validationへ渡して従来と同じエラーtool resultを生成する。これにより機能ON時も入力検証の責任とエラー形式を変えない。 ### 7.1 batch事前評価 `dispatchRegularToolCalls()` は `Read`、`Glob`、`Grep`、`WebSearch`、`WebFetch`、`ReadImage` を並列実行できる。特に外部通信を含むbatchでは、各callを実行しながら個別にPolicyGateへ掛けると、1件を保留した時点で兄弟callが実行済みになる。 初期実装では、iteration内のregular tool callを副作用前に一括preflightする。競合解決は、各call内のrule競合とiteration全体のbatch集約を分離する。 ```text 全regular tool callをparse・基本許可判定 → callごとにrule actionを解決 stop > deny > require_approval > redirect > audit_only / allow → iteration全体へbatch集約 stopが1件以上 └─ regular・complete・transitionを含む全callを実行せず結果を記録してABORT require_approvalまたはredirectが1件以上 └─ regular・complete・transitionを含む全callを実行せず結果を記録して保留 denyだけが含まれる └─ deny対象は実行せず、残りは実行して全callへtool resultを作る 全件allow / audit_only └─ 既存の並列・逐次dispatcherへ渡す ``` `require_approval` または `redirect` でiterationを保留する場合、同じassistant messageへco-emitされたComplete/transitionも受理しない。全tool call IDへ合成tool resultを記録してから、承認待ちまたは次iterationへ進む。`stop` は同様に全tool resultを記録した後、hard-terminal abort reason `policy_stopped` を持つABORTへ変換する。denyだけの場合はdeny対象へ合成resultを作り、残りは既存dispatcherへ渡す。InteractiveBrowse等が待機へ入った場合を含め、以後の記録・再開は既存挙動を維持する。 preflight後に実行しないcallについては、grounding reservationをreleaseまたは非引用の結果でfulfillし、未解決の出典予約を残さない。初期実装では安全側へ寄せ、制御状態を変えるdecisionが含まれるiterationを部分実行しない。将来、tool間の独立性を証明できる場合だけ、カテゴリ別の部分実行を検討する。 ### 7.2 追加指示とmessage順序 元のtool callを実行せず、対応するtool resultを作る。 ```text 実行は保留されました。 登録済みルール rule-12 により、次の確認を行ってから新しいtool callを作成してください: 「削除対象と復元方法を確認してください」 ``` 定型指示をuser messageとして追加し、次iterationへ戻す。元のtool callは消費済みとし、LLMが改めて新しい引数で呼び直す。 providerのtool call対応関係を壊さないため、同一iterationでは次の順序を必須とする。 ```text assistant: tool calls tool: call Aの実結果または合成結果 tool: call Bの実結果または合成結果 tool: call Cの実結果または合成結果 user: 統合済みの登録指示 ``` 追加指示は全tool resultの後に1件だけ追記する。複数のredirectは優先順位順に重複除去して統合する。既存の途中コメント取得処理を直接呼ばず、Conversationへ規定位置で追加する `InstructionInjector` を共通サービスとして利用する。 ### 7.3 承認対象の固定 ```ts approvalFingerprint = SHA-256( canonicalJson({ taskId, piece, movement, toolName, normalizedArguments, policyRuleId, policyRuleVersion, }) ); ``` 承認後は同じfingerprintの操作だけを許可する。`once` と再利用可能scopeは別のrecord型として扱う。 - `once`: `approvalRequestId` とランダムnonceを持ち、`approved` から `consumed` へのCASをtool実行直前に1回だけ成功させる。同じfingerprintでも別のcallへ再利用しない。 - `task_argument`: task内で同じfingerprintへ再利用できる。初期実装には含めない。 `once` は実行の試行を承認するscopeとし、toolが失敗しても消費済みへ戻さない。再試行には新しい承認を要求する。再開後に再生成されたcallは、request ID、nonce、fingerprintがすべて一致する場合だけ消費できる。 request IDとnonceはモデルのtool引数へ追加しない。parked jobのPolicy resume contextへ固定し、task、Piece、Movement、rule、fingerprintが一致する唯一の未消費approved recordをサーバー側で選ぶ。候補が0件または複数件ならCASを行わず再承認とする。 `jobId` は監査相関には保存するが、操作fingerprintには含めない。Continueや再開でJobが変わっても同じ操作を識別できるが、承認を再利用できるのは明示的な再利用scopeだけである。`once` はrequest IDとnonceのCASを別途要求する。 現在のようにMovement全体を再実行すると異なる引数が生成される可能性がある。安全性が高いのは承認済みtool callの保存・直接実行だが、変更範囲が大きい。初期実装では再生成されたtool callのfingerprint照合が現実的である。 fingerprint不一致では元の承認を流用しない。同じMovementでの承認要求回数に上限を設け、上限到達後は決定的に拒否して理由を返す。再生成、Job再開、auto-parkをまたいで上限がリセットされないよう、発火回数、承認要求回数、拒否済みfingerprint、直近状態fingerprintをDBへ保存する。メモリ上の `PolicyState` は同一実行内のキャッシュに限定する。 ### 7.4 Complete受理前ゲート 現行の終端処理では、native winner選択、control tool result記録、pending子タスクのauto-park、Movement result生成が連続する。Hook追加時はpending確認を分岐前へ移すが、auto-park経路でも既存control tool result記録を必ず維持する。初期実装の `completion.before_accept` は、本当に終端可能なCompleteだけへ適用する。 ```text native winner選択 → pending子タスク等を確認 ├─ pendingあり: 既存control tool resultsを記録 │ → 既存WAIT_SUBTASKSへauto-parkし、Hookは発火しない └─ pendingなし: completion.before_accept ├─ allow: 既存control tool resultsを記録してMovement result生成 └─ deny / redirect: 全control callへ `Error:` tool resultを記録 → 統合済み指示を追記 → 次のLLM iterationで再推論 ``` Complete発火回数はtask+Movement+rule単位でDBへ保存し、resume後も原則1回の上限を維持する。初期実装は決定的確認だけとし、Judgeによる完了可否判定は行わない。 --- ## 8. 既存ユーザー承認機構との統合 承認機構本体はHookへ移行せず、`ApprovalService` として独立させ、既存 `RequestTool` とHookの両方から利用する。 ```text RequestTool ───┐ ├─ ApprovalService PolicyGate ────┘ ``` ```ts type ApprovalScope = | { kind: "once"; approvalRequestId: string; nonce: string; fingerprint: string; consumedAt?: string } | { kind: "task_tool"; taskId: string; toolName: string } | { kind: "task_argument"; taskId: string; fingerprint: string } | { kind: "movement"; taskId: string; movement: string; toolName: string } | { kind: "persistent_tool"; spaceId: string; toolName: string }; ``` 初期実装は以下だけでよい。 - Policy承認は今回だけ - このタスク中、このツール - 拒否 PolicyGateからの引数単位承認では、既存の `addGrantedTool(taskId, toolName)` に相当するtask+tool名の権限追加を行わない。Policy承認は専用approval recordへ保存し、fingerprint一致時だけPolicyGateがallowする。既存 `RequestTool` の「このタスク中、このツール」承認は互換経路として維持し、Policy承認とは混在させない。 評価は二段階に分ける。 ```text 管理者拒否 / workspace tool policy / 既存基本許可 → 通過したcallだけPolicyGate → 拒否済みfingerprint / one-shot承認 / rule actionを評価 → allow時も各tool内のsandbox / SSRF / path検証を必ず実行 ``` Policy承認は、workspace tool policyと基本許可を通過済みのcallに対する追加ゲートだけを解除する。未許可toolをPolicy承認によって許可せず、後段のsandbox、SSRF、path検証を迂回しない。 同一fingerprintの拒否後は再申請しない。引数が変われば別fingerprintとし、タスク単位の承認要求上限を設ける。 競合するPolicy actionの優先順位は次に固定する。 ```text stop > deny > require_approval > redirect > audit_only / allow ``` このPolicy action優先順位はcall内のAuto Prompter rule競合だけに適用する。既存安全境界は前段で評価し、PolicyGateによって緩和しない。 ### 8.1 Job単位のPolicy Snapshot Jobのclaim・workspace準備時に次をsnapshotし、そのJobの間は変更しない。 - `available`、workspace `enabled` - 有効ruleのID、version、条件、action、scope、発火上限 - 常時適用する登録済み指示 - Judge model、endpoint、timeout、判定schema version 初回claim時にsnapshotをJob ID単位のDB recordへ永続化し、再claim・waiting_humanからのresumeでは現在のworkspace設定を読み直さず、必ず既存snapshotを復元する。常時ルールと条件付きルールは同じsnapshotを参照し、Job途中の設定変更・無効化は次のJobから反映する。 Policy approvalへparkする際は、approvalRequestId、nonce、fingerprint、snapshot IDをJobのPolicy resume contextへCASで紐付ける。resume時はこのcontextと既存snapshotを同一transaction境界で取得し、one-shot消費後にcontextを消費済みへ更新する。Job終端時はsnapshot/contextを完了状態にし、監査保持期間に従って削除する。 監査イベントにはsnapshot ID/versionを記録する。tool callごとの設定DB取得は行わない。機能OFFのJobではsnapshot/contextを作成しない。 --- ## 9. 監査ログ 既存 `EventLogger` を基盤として利用する。 推奨イベント: ```text policy_evaluation policy_rule_matched policy_instruction_injected policy_tool_denied approval_requested approval_decided approval_fingerprint_mismatch policy_task_stopped policy_notification_queued ``` 記録候補: - user ID - task/job/run ID - Piece/Movement - Hook point - tool名・カテゴリ - 安全な引数要約 - rule ID・rule version - 三値結果 - Judge model ID/version - confidence - action - approval request ID - 承認・拒否 - 実際の実行有無 - tool resultの成功/失敗 - timestamp - 通知queue ID 保存しないもの: - 生の認証情報 - 完全な会話全文 - 生のAuthorization header - 必要性のない完全なtool引数 - Webページ全文 - 「悪意ある利用者」等の人物評価ラベル Auto Prompter側でfield allowlistとマスキングを行い、その後で既存 `EventLogger` のマスキングとサイズ制限を適用する。 ```text 記録 → UI内の警告 → 追加承認 → 管理者通知 → 管理者ポリシーによる制限 ``` 意味判定1回だけで利用者を制限してはならない。 --- ## 10. PieceとMovementへ適用できる範囲 ### 薄いHookだけで可能 - Piece開始・終了の観測 - Movement開始・終了の観測 - Piece/Movement情報の監査付与 - Movement別ルール候補の切り替え - Movement開始時の固定指示 - 現在Movementとtoolカテゴリの整合性判定 - 遷移後イベント ### 状態遷移変更が必要 - Movement完了を保留 - 遷移前に承認待ちへ駐車 - 特定条件まで遷移禁止 - Piece完了前の再検証 - Hook判断による別Movementへの移動 ### 後回しにすべきもの - Auto PrompterによるMovement遷移先変更 - HookによるMovementスキップ - HookによるPiece差し替え - 複数Hookの投票で遷移決定 - Piece完了とCompleteの二重validator 初期段階ではPiece/Movementは観測対象とscope情報に留める。 --- ## 11. 既存機能の置き換え・統合候補 | 対象 | 現在 | 移行案 | 効果 | リスク | 推奨 | |---|---|---|---|---|---| | ツール許可判定 | `isAllowedRegularTool` | 既存判定後にPolicyGate | 引数単位制御 | 二重権限体系 | そのまま残す | | 許可外ブロック | `tool-execution.ts` | `tool.denied`を発行 | 監査統一 | 制御移行で迂回リスク | 一部だけ共通化 | | RequestTool | 専用tool | ApprovalService利用 | 承認ロジック共通化 | 既存挙動回帰 | 共通サービスを呼ぶ | | ユーザー承認要求 | tool request専用 | ApprovalService | 引数単位承認可能 | schema/API変更 | 共通サービス化 | | 承認結果 | APIでtool overlay追加 | scope別decision | 柔軟な承認 | 複雑化 | 一部共通化 | | 途中プロンプト | interjection直書き | `InstructionInjector` | Auto Prompterと再利用 | message順序 | 共通サービス化 | | Complete検証 | terminal-control | `completion.before_accept`追加 | 登録ルール確認 | 完了不能ループ | 既存を残し薄く追加 | | Movement遷移検証 | rules照合 | 観測eventのみ | 監査 | 中核回帰 | 置き換えない | | エラー処理 | 各層 | `runtime.error`発行 | 観測統一 | Hookで回復すると不透明 | 購読側へ一部追加 | | ロギング | logger/EventLogger | policy event追加 | 診断向上 | 個人情報 | 購読側へ追加 | | イベント通知 | callbacks/EventLogger | EventSink adapter | 統一 | 二重イベント | 一部共通化 | | 再試行制御 | loop-safety等 | Auto Prompter発火上限だけ追加 | 無限ループ防止 | 汎用化は複雑 | 既存を残す | | コスト制限 | runtime側 | 観測のみ | 可視化 | Hook遅延 | 置き換えない | | タイムアウト | AbortController/client | 観測のみ | 診断 | 安全境界弱体化 | 置き換えない方が安全 | | ツール回数制限 | tool loop tracker | rule上限と別管理 | 明確 | 二重カウンタ | 置き換えない | | デバッグ観測 | EventLogger | policy event追加 | 追跡容易 | イベント量 | 購読側へ移行可能 | | セキュリティチェック | 各tool/SSRF等 | PolicyGateは追加層 | 意味的判定追加 | Hookを信頼しすぎる | 既存を残す | Hook導入の効果は既存処理の大量削減ではなく、追加ポリシーの安全な挿入位置と共通監査形式の提供である。 --- ## 12. UIとUX ### 基本画面 1. ルール名 2. いつ確認するか 3. 対象 4. どのような場合か 5. そのとき何をするか | 内部用語 | UI表示 | |---|---| | `tool.before` | ツールを実行する前 | | `completion.before_accept` | 作業を完了する前 | | `inject_instruction` | 確認事項を伝えて、もう一度考えさせる | | `deny_current_tool` | 今回の操作を実行しない | | `request_approval` | 実行前に確認を求める | | `stop_task` | タスクを停止する | | `audit_only` | 履歴へ記録する | | `notify_admin` | 管理者へ知らせる | ### 一般ユーザー向け対象カテゴリ - コマンド実行 - ファイルの変更 - データの削除 - インターネット検索 - 外部サービスへの送信 - Git操作・GitHub操作 - ブラウザ操作 - SSH・外部マシン操作 - すべてのツール 個別tool名指定、優先度、最大発火回数、クールダウン、Piece/Movement限定、confidence閾値、`unknown`時の扱い、管理者通知、rule version、shadow modeは詳細設定へ隠す。 利用者に見えない指示挿入を避け、適用時には次のような通知を出す。 ```text 自動ルール「削除前の確認」が適用されました。 今回の操作は実行せず、復元方法を確認するようエージェントへ伝えました。 ``` 承認カードには、操作内容、対象、承認理由、承認scope、引数変更時は再承認になることを表示する。 --- ## 13. 変更が必要なファイル、クラス、関数 ### 初期実装で変更 - `src/engine/agent-loop/tool-execution.ts` - `executeRegularToolCallCached()` - tool実行前ゲート - redirect/deny/approval結果のtool result化 - `src/engine/agent-loop/tool-dispatcher.ts` - iteration全callのbatch preflight - Policyで実行しないcallの合成結果とgrounding reservation解放 - `src/engine/agent-loop.ts` - 定型指示のmessage追加 - PolicyStateの保持 - Complete前の軽量ゲート - `src/engine/agent-loop/movement-setup.ts` / `src/engine/agent-loop/prompt.ts` - Job snapshotの常時指示をstatic preambleへ安定順序で合成 - `src/engine/agent-loop/types.ts` - PolicyService、state、callback型 - `src/engine/tools/core.ts` - ToolContextへpolicy/approval context追加 - `src/engine/piece-runner.ts` - task scopeのPolicyStateをMovement間で共有 - Piece/Movement情報をcontextへ設定 - `src/engine/agent-loop/pending-states.ts` - Policy approval専用のwaiting result - `src/worker.ts` - Policy waitReasonの永続化と完了結果の処理 - `policy_stopped` をhard-terminal abort reasonへ追加しretryを禁止 - `src/db/repositories/jobs.ts` - Policy resume contextとsnapshot IDのCAS紐付け・専用resume - `src/db/repositories/approval-requests.ts` - one-shot承認の `approved → consumed` CASと発火状態永続化 - `src/db/repositories/policy-run-snapshots.ts` - 初回claim時のsnapshot作成、再claim時の復元、終端時の完了・保持期限処理 - `src/progress/event-log.ts` - policy eventのpayload規約 - `src/engine/tools/tool-request.ts` - ApprovalService呼び出しへ段階移行 - `src/bridge/local-tasks-tool-requests-api.ts` - 汎用approval APIへ互換委譲 - `src/bridge/policy-approval-requests-api.ts` - Policy専用approve/denyとresume API - `ui/src/components/chat/ToolRequestApproval.tsx` - 汎用承認カードへの段階移行 - `ui/src/components/chat/ChatComposer.tsx` / `ChatComposerPanel.tsx` - Policy waitReasonの入力ロックと承認カード配置 - DB schema / migration / Repository facade - policy rule、approval request、発火状態tableの作成と公開method追加 - Bridge route registration - Policy rule・approval APIを既存routerへmount - Workspace設定取得・更新API - master enabledをJob claim snapshotへ同梱し、rule取得をON時だけ実行 - ワークスペースSettings親画面 - Auto Prompter設定route・navigation・権限表示を追加 - feature flag / config schema / normalization - `available`、Phase availability、Judge設定のvalidationと既定値 ### 新規候補 ```text src/engine/policy/ policy-gate.ts policy-context.ts policy-state.ts auto-prompter.ts deterministic-matcher.ts semantic-rule-judge.ts rule-conflict-resolver.ts instruction-injector.ts approval-service.ts approval-fingerprint.ts policy-audit.ts src/db/repositories/ policy-rules.ts approval-requests.ts src/bridge/ policy-rules-api.ts policy-approval-requests-api.ts ui/src/components/settings/ AutoPrompterRulesForm.tsx ui/src/components/chat/ PolicyApprovalCard.tsx PolicyActionNotice.tsx ``` 実装時にはアプリ内ヘルプと更新履歴も同じPRで更新する必要がある。 --- ## 14. 初期実装の最小範囲 ここでいう初期実装は、Phase 0~4を順に通過した最初のGA範囲を指す。各Phaseは個別に検証・feature flag配信し、前Phaseの受入条件を満たすまで次の制御機能を有効化しない。 ### 含める - `tool.before` 制御点 - `tool.after` 観測イベント - `tool.denied` 観測イベント - `completion.before_accept` の決定的確認 - ルールCRUD - toolカテゴリ・Piece・Movement・回数による決定的絞り込み - 小型LLMによる三値・rule ID判定のshadow評価 - 登録済み定型指示の追加 - 今回のtool call拒否 - 既存承認フローへの接続 - ruleごとの最大発火回数 - 同一fingerprintの重複防止 - 基本監査イベント - shadow mode ### 除外 - 自由記述の自然言語条件による拒否、停止、承認の本番制御 - Judge結果だけに依存する安全上の許可・拒否 - delegate子エージェント内からの独立したユーザー承認待ち - Piece/Movement遷移の制御 - LLM呼び出し前後の汎用middleware - 生成stream途中の判定 - 管理者自動通知 - 不正利用者の自動制限 - 複雑なrule依存関係 - plugin marketplace - Judgeによる採点 - 全既存機能のHook化 - 恒久承認 - 承認済みtool callの直接再実行 --- ## 15. 将来拡張 - Movement開始時の固定指示 - LLM応答後の言語・読みやすさ確認 - 引数単位の承認 - 管理者通知queue - task/space/organization scopeのルール - rule versioningと公開テンプレート - offline replay評価 - 複数Judgeの比較 - 管理者ポリシーと個人ルールの階層化 - 観測subscriber plugin - Complete reviewer - Piece開始・終了監査 - ルール推奨機能。ただし自動有効化はしない --- ## 16. アーキテクチャ案の比較 | 観点 | 案1 直接Hook | 案2 Event Bus | 案3 Middleware | 案4 承認中心 | 案5 ハイブリッド | |---|---|---|---|---|---| | 現行コードとの相性 | 良 | 観測のみ良 | 部分的 | ツールだけ良 | 最良 | | 変更範囲 | 小 | 中 | 大 | 小~中 | 中 | | 実装難易度 | 低 | 中 | 高 | 中 | 中 | | オーバーヘッド | 小 | 小 | 中 | 小 | 小~中 | | 順序制御 | 呼び出し順 | 不透明になりやすい | 明示可能 | 限定的 | 明示可能 | | 非同期観測 | 弱い | 強い | 中 | 弱い | 強い | | 制御 | 強い | 不向き | 強い | ツール承認のみ | 強い | | デバッグ | 容易 | 因果追跡が必要 | chain追跡が必要 | 容易 | 相関IDで可能 | | エラー隔離 | 個別 | 良 | 設計依存 | 良 | 良 | | テスト | 容易 | 中 | 高コスト | 容易 | 中 | | 承認統合 | 中 | 不向き | 良 | 最良 | 最良 | | Piece/Movement | 追加しやすい | 観測向き | 包装可能 | 弱い | 段階導入可能 | | plugin化 | 弱い | 強い | 強い | 弱い | 将来可能 | | 既存置換 | 少ない | 観測を統合 | 多い | 承認のみ | 必要部分だけ | | 過剰抽象化 | 低 | 中 | 高 | 低 | 中 | 案5を採用し、制御点は明示的な直接呼び出し、制御ロジックは `PolicyGate`、承認は `ApprovalService`、観測は `EventLogger` adapter、Auto PrompterはPolicyGateの一 evaluatorとする。 --- ## 17. セキュリティとプライバシー 主なリスク: - 自然言語条件にprompt injectionが含まれる - tool引数やWeb本文がJudgeを操作する - ルールの定型指示がMovement指示と矛盾する - Auto Prompterが権限判定を上書きする - Judge失敗で危険操作がfail-openになる - 管理者が会話全文を監査できる - tool引数にcredentialが含まれる - 通知先へ機密情報が流れる - 誤検知が利用者評価に利用される 対策: - ルール条件、tool引数、会話を明確にdataとして区切る - Judgeに自由文やactionを生成させない - 登録済みrule ID以外を拒否 - tool引数はfield allowlistとマスキング - 重大操作はLLM判定ではなく決定的ルール - 既存SSRF・sandbox・tool policyを残す - 個人評価ではなく「この操作の判定」として記録 - 監査閲覧権限をtask/space scopeへ制限 - 保持期間を設定可能にする - 通知は安全な要約のみ - 管理者拒否を最優先 - Judge timeout時の扱いをaction種別ごとに固定 --- ## 18. パフォーマンス 各tool実行前に小型LLMを必ず呼ぶ構成は避ける。 ```text ruleなし → 0コスト 対象Hookなし → 0コスト カテゴリ不一致 → 0コスト 決定的条件で確定 → LLMなし 意味判定候補あり → 一括Judge ``` 対策: - rule候補上限 - rule definitionのtoken budget - Judge専用worker/role - 同一fingerprint cache - 並列tool callは一括判定 - 観測イベントを同期通知しない - 通知queue - Judge timeoutを短くする - モデルversionを固定し、変更時はshadow評価 目標値例: - 決定的判定: 5ms未満 - Judge判定P95: 1秒未満 - 通常toolの追加遅延: 50ms未満 - Judge対象率: 全tool callの10%未満 --- ## 19. テスト計画 ### Unit - rule scope filter - 三値schema検証 - 未登録rule ID拒否 - fingerprint canonicalization - 発火回数 - cooldown - 同一状態dedupe - conflict resolver - secret masking - GateDecisionごとの処理 - Hook失敗時のfail policy ### ツール実行前 - allowなら1回だけ実行 - redirect/deny/approvalなら実行されない - 元tool callへtool resultが返る - 追加指示後に元callが自動実行されない - 並列batchの一部だけ拒否 - redirect/approval/stopを含むbatchは兄弟callを部分実行しない - regularとco-emitされたComplete/transitionへ全件tool resultを返す - stopは `policy_stopped` のhard-terminal ABORTとなり、retry 0回でpending子タスクを既存経路で取り消す - 不正JSONは既存tool validationと同じエラーになる - cache hit前にもpolicyが適用される - 承認後のfingerprint不一致を拒否 ### 承認 - 一回承認 - one-shotのCAS消費は並行実行でも1回だけ成功 - 消費済みone-shotは同じfingerprintでも再利用不可 - tool失敗後の再試行には新しい承認が必要 - タスク中承認 - 拒否 - 二重決定 - concurrent approval - 他ユーザーによる決定拒否 - 管理者拒否優先 - 拒否後の再申請抑止 - 再開後のMovement整合 - 承認待ち中にruleを編集・無効化しても、再開Jobは永続化済み旧snapshotを使用 - resume context候補が0件・複数件ならone-shotを消費せず再承認 ### Auto Prompter - 日本語条件 - 曖昧条件 - `unknown` - tool引数・Web本文内のprompt injection - 存在しないrule ID - Judge timeout - 不正JSON - モデル変更差分 ### Piece/Movement回帰 - Hook無効時の既存挙動一致 - transition - Complete - ASK - subtask/browser/tool approval wait - loop guard - conversation replay - KV prefix安定性 ### プライバシー - API key - Bearer token - password - URL query secret - shell環境変数 - PIIを含む会話 - audit APIの権限 --- ## 20. 段階的な実装ロードマップ ### Phase 0: Shadow観測 - `tool.before` contextを構築 - 既存動作は変えない - 決定的matcherとJudge結果を監査 - Job snapshot、batch preflight、OFF短絡の互換性を検証 - 誤検知・遅延を測定 ### Phase 1: Audit + 常時ルール - rule CRUD - EventLogger統合 - 発火制御 - UIへ「記録のみ」ルール - 常時適用ルールをstatic preambleへ一度だけ合成 - 一般ユーザー向けテンプレートとON/OFF設定 - 管理者通知なし ### Phase 2: 追加指示・拒否 - `redirect` - 決定的条件だけを制御actionへ使用 - 追加権限を持つユーザーが作成した決定的ルールに限り `stop_task` - `deny` - 元tool callを消費 - 無限ループ防止 - rule actionの透明表示 ### Phase 3: 既存承認統合 - ApprovalService抽出 - 一回承認 - fingerprint - 既存RequestTool互換adapter - 汎用承認カード ### Phase 4: Complete前の軽量確認 - 決定的条件中心 - 発火はMovement当たり原則1回 - 上限超過時は停止または通常Complete ### Phase 5: 評価後の拡張判断 - Movement開始Hook - 引数単位承認 - 管理者通知 - Complete Reviewer - 自然言語Judgeの品質基準を満たした場合だけ制御actionへ段階適用 - 一般ユーザーの自由記述条件はこの評価後に公開判断 - 公開テンプレート --- ## 21. 敵対的レビュー ### 21.1 Auto Prompterは本当に必要か 多くのユースケースは以下で代替できる。 - Movementプロンプトの改善 - toolカテゴリごとの固定ポリシー - Bash等への決定的検査 - 既存承認の引数単位拡張 - Complete前Reviewer - 管理者定義テンプレート Auto Prompterを導入すると、同じ入力でも登録rule、Judge model、model version、発火履歴によって挙動が変わる。Pieceだけを見ても実行理由を説明できなくなり、再現性と予測可能性が低下する。 ### 21.2 自然言語条件は不安定 - 「危険」「難しい」「大きく外れた」等の基準が曖昧 - 条件文に例外が書かれない - 条件同士が重複する - Judge model変更で結果が変わる - 日本語の否定・二重否定を誤る - tool引数中の文字列と実際の操作を区別できない - prompt injectionの新しい攻撃面になる 一般ユーザーに自然言語ruleを自由入力させると、サポート不能な挙動が増える可能性がある。 ### 21.3 Hook基盤はコードを簡潔にしない 既存処理はすでに、権限、tool dispatch、RequestTool、wait state、Piece mapping、Complete validation、EventLogger、retry/watchdogという意味のある専用境界を持つ。 これらをHookへ移してもコード総量は減らず、処理の追跡先が増える。汎用middleware chainへ移すと、Hookの順番とstate変更を理解しなければ本体を追えなくなる。 ### 21.4 承認疲れ 誤検知が多いと、利用者は承認内容を読まず機械的に許可するようになる。安全性向上のための承認が形骸化する可能性がある。 ### 21.5 監査ログが新しいリスクになる 判定精度改善のため会話や引数を多く記録すると、監査基盤自体が機密情報の集積点になる。既存の正規表現マスキングだけでは任意のcredential、URL query、ファイル本文、個人情報、独自token形式を完全には防げない。 「不正利用の可能性」というラベルは、誤検知時に利用者へ不利益を与える危険がある。 ### 21.6 Piece/Movementへの介入は危険 Movementには既に目的、tool集合、遷移rules、再訪問制限、Complete、ASK、wait/resumeがある。別の条件遷移系を追加すると、完了不能、同じMovementへの反復、追加指示と元promptの矛盾が起こり得る。 ### 21.7 パフォーマンス tool利用の多いタスクでは、Judge呼び出しがLLM本体より多くなる可能性がある。小型モデルでもGPU queueを消費し、通常ジョブと競合する。 ### 21.8 より単純な代替案 1. 既存toolカテゴリへ危険度metadataを追加 2. Bash・削除・外部送信を決定的に検査 3. 既存承認を一回限り・引数fingerprint対応へ拡張 4. Complete前に固定Reviewerを1回実行 5. EventLoggerへ承認・拒否イベントを追加 6. Movement別の管理者定型指示だけ許可 7. 自然言語条件はshadow modeに限定 この代替案の方が再現性、説明可能性、性能、保守性で優れる可能性がある。 ### 21.9 中止・縮小基準 以下のいずれかがPoCで確認された場合、自然言語Auto Prompterは中止する。 - 対象ruleのprecisionが90%未満 - 危険操作のrecallが決定的ルールを上回らない - Judge P95が1秒を超える - 全tool callの20%以上でJudgeが必要 - `unknown` が10%以上 - 承認要求の拒否・無視率が高い - model変更で発火率が大きく変化 - 自然言語ruleの問い合わせが運用負担になる - 監査に必要な情報量がプライバシー要件を超える - Hook無効時でも既存挙動と一致しない この場合は「固定ルール+既存承認拡張+監査ログ」に縮小する。 --- ## 22. 最終判断 ### 採用 - **ツール実行前Hookだけ実装** - **既存承認機構を中心に拡張** - **観測Hookと監査ログは既存EventLogger上に実装** - **Auto Prompterはワークスペース設定として一般ユーザーへ提供** - **自然言語Judgeは最初はshadow modeとし、決定的ルールと常時ルールを先行公開** - **決定的条件を優先し、意味判定は限定的に使用** ### 不採用・延期 - 全面Hook化 - 権限・sandbox・timeout・retryのHookへの移行 - Piece/Movement遷移制御 - 生成stream途中の判定 - 品質基準を満たす前の、一般ユーザーへの自由な自然言語rule制御action公開 - Auto Prompterによる自動利用者制限 - 汎用plugin基盤 - 既存承認機構のHook内部への全面移行 Hook導入によって既存コードが大幅に簡潔になる見込みはない。価値があるのは、**ツール副作用直前に追加ポリシーを安全に適用できる明示的な境界と、既存EventLoggerへ統一された監査情報を残せること**である。 Auto Prompter自体は、そのHook基盤上の任意機能として扱い、Hook基盤の中心概念にしない設計が妥当である。