Files
maestro/docs/auto-prompter-hook-feasibility-study.md
T
oss-sync 57702b4f34
CI / build-and-test (push) Canceled after 0s
sync: update from private repo (2336949a)
2026-09-17 11:29:31 +00:00

49 KiB
Raw Blame History

汎用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() である。

  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 に追加する。

[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.ts
  • src/engine/agent-loop/tool-execution.ts
  • src/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.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名は次で十分である。

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、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 を基盤として利用する。

推奨イベント:

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

基本画面

  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は詳細設定へ隠す。

利用者に見えない指示挿入を避け、適用時には次のような通知を出す。

自動ルール「削除前の確認」が適用されました。
今回の操作は実行せず、復元方法を確認するようエージェントへ伝えました。

承認カードには、操作内容、対象、承認理由、承認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と既定値

新規候補

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.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基盤の中心概念にしない設計が妥当である。