49 KiB
汎用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 ワークスペース機能 設計方針 を正とする。一般ユーザーがワークスペース単位で設定できる機能として提供するが、公開は段階化する。初期段階では常時適用ルール、テンプレート、決定的条件、shadow判定を中心とし、自由記述の自然言語条件を使う制御actionは評価基準を満たした後に有効化する。
EU AI Actの透明性、監査、人間監督、provenance、保持方針は EU AI Act透明性対応 設計付録 を参照する。法的義務と自主的統制を分け、hidden chain-of-thoughtの保存や一律5年保持を前提にしない。
2. 現在の関連機能と処理フロー
2.1 エージェントのメインループ
中心は src/engine/agent-loop.ts の executeMovement() である。
- Movement用プロンプトとツールを準備
- iterationごとに途中指示を取り込む
- LLMをストリーミング実行
- tool callを通常ツール、
transition、completeに分類 - 通常ツールを実行
- 待機・承認状態を確認
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.tssrc/engine/llm-stream.tssrc/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 に追加する。
[interjection] ユーザーの追加指示
Auto Prompterの追加指示にも同じmessage injection方式を再利用できる。DBコメントを経由させる必要はなく、message injection部分を内部サービスとして抽出するのが適切である。
2.5 ツール実行
dispatchRegularToolCalls
└─ executeRegularToolCallCached
├─ tool_callイベント
├─ キャッシュ判定
└─ executeRegularToolCall
├─ Movement内の許可判定
├─ 引数JSON parse
└─ executeTool
└─ 各ツールモジュールへルーティング
主な実装:
src/engine/agent-loop/tool-dispatcher.tssrc/engine/agent-loop/tool-execution.tssrc/engine/tools/index.ts
許可外ツールは isAllowedRegularTool() で拒否され、エラーのtool resultとしてモデルへ返される。拒否記録はベストエフォートで、記録失敗によってツール処理を壊さない。
2.6 許可申請と承認
現在は RequestTool 専用フローである。
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.tssrc/engine/agent-loop/pending-states.tssrc/bridge/local-tasks-tool-requests-api.tssrc/db/repositories/tool-requests.tsui/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名は次で十分である。
tool.before
tool.after
tool.denied
completion.before_accept
runtime.error
4. 推奨するHookアーキテクチャ
4.1 観測と制御のハイブリッド
┌─ Auto Prompter
処理地点 ── PolicyGate ─┼─ Approval Service
│ 同期・制御 └─ Security Policy
│
└──── EventSink ────── Audit / Trace / Notification
非同期・観測
4.2 制御用インターフェース
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<PolicyContext>,
): Promise<GateDecision>;
}
Hookが本体状態を直接変更せず、返却値だけで制御する構成とする。
4.3 観測用インターフェース
interface EventSink {
publish(event: HookObservation): void;
}
観測Hookは原則として以下を守る。
- awaitしない
- 失敗しても本体を止めない
- 本体状態を変更しない
- timeoutを短くする
- 管理者通知はqueueへ送る
観測、助言、制御、承認を単一の自由度が高いHook型へ統合しない。
| 種類 | 実装 |
|---|---|
| 観測 | EventSink |
| 助言 | GateDecision.redirect |
| 拒否・停止 | PolicyGate |
| 承認 | ApprovalServiceをPolicyGateから呼ぶ |
5. Auto Prompterの責任範囲
Auto Prompterが行うのは、登録済みルールの選択までとする。
行わせない処理:
- 新しいポリシー文の自由生成
- 停止理由の自由生成
- ツール引数の書き換え
- 直接的な権限付与
- Piece/Movement遷移先の生成
- 管理者通知文の自由生成
- ルール自体の自動変更
ルール候補の決定的フィルタ
→ 必要な候補だけ意味判定
→ rule ID + 三値結果
→ MAESTROが登録済みactionを実行
type AutoPrompterAction =
| "inject_instruction"
| "deny_current_tool"
| "request_approval"
| "stop_task"
| "audit_only"
| "notify_admin";
notify_admin 単独で利用者制限を実行してはならない。
6. 判定モデルの構成
6.1 入力
会話全文は原則として渡さない。
{
"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 出力
{
"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へ直接短絡する。
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集約を分離する。
全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を作る。
実行は保留されました。
登録済みルール rule-12 により、次の確認を行ってから新しいtool callを作成してください:
「削除対象と復元方法を確認してください」
定型指示をuser messageとして追加し、次iterationへ戻す。元のtool callは消費済みとし、LLMが改めて新しい引数で呼び直す。
providerのtool call対応関係を壊さないため、同一iterationでは次の順序を必須とする。
assistant: tool calls
tool: call Aの実結果または合成結果
tool: call Bの実結果または合成結果
tool: call Cの実結果または合成結果
user: 統合済みの登録指示
追加指示は全tool resultの後に1件だけ追記する。複数のredirectは優先順位順に重複除去して統合する。既存の途中コメント取得処理を直接呼ばず、Conversationへ規定位置で追加する InstructionInjector を共通サービスとして利用する。
7.3 承認対象の固定
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だけへ適用する。
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の両方から利用する。
RequestTool ───┐
├─ ApprovalService
PolicyGate ────┘
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承認とは混在させない。
評価は二段階に分ける。
管理者拒否 / 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の優先順位は次に固定する。
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、workspaceenabled- 有効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 を基盤として利用する。
推奨イベント:
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 のマスキングとサイズ制限を適用する。
記録
→ 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
基本画面
- ルール名
- いつ確認するか
- 対象
- どのような場合か
- そのとき何をするか
| 内部用語 | 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は詳細設定へ隠す。
利用者に見えない指示挿入を避け、適用時には次のような通知を出す。
自動ルール「削除前の確認」が適用されました。
今回の操作は実行せず、復元方法を確認するようエージェントへ伝えました。
承認カードには、操作内容、対象、承認理由、承認scope、引数変更時は再承認になることを表示する。
13. 変更が必要なファイル、クラス、関数
初期実装で変更
src/engine/agent-loop/tool-execution.tsexecuteRegularToolCallCached()- 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 → consumedCASと発火状態永続化
- one-shot承認の
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と既定値
新規候補
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を必ず呼ぶ構成は避ける。
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.beforecontextを構築- 既存動作は変えない
- 決定的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 より単純な代替案
- 既存toolカテゴリへ危険度metadataを追加
- Bash・削除・外部送信を決定的に検査
- 既存承認を一回限り・引数fingerprint対応へ拡張
- Complete前に固定Reviewerを1回実行
- EventLoggerへ承認・拒否イベントを追加
- Movement別の管理者定型指示だけ許可
- 自然言語条件は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基盤の中心概念にしない設計が妥当である。