mcpField
A
agent-runtime-hooks

agent-runtime-hooks

github.com/lobehub開発83.6k98 回インストール今日更新
リポジトリ

agent-runtime-hooks について

`execAgent` を使う開発者向けのライフサイクルフックで、ステップ、ツール呼び出し、圧縮、人間による介入などを扱います。ローカル処理、ツール結果のモック、Webhook による判定に対応します。

主な機能

  1. 0116 種類のライフサイクルイベント
  2. 02登録順でのフック実行
  3. 03ローカルハンドラーによる結果モック
  4. 04Webhook によるイベント配信
  5. 05ツール呼び出しの許可・拒否

活用シーン

  1. 01Agent のステップとツール呼び出しの記録
  2. 02テストでのツール結果のモック
  3. 03Webhook を介したツール実行の審査
  4. 04人間の承認とサブエージェントイベントの追跡

MCP 対応

MCP ツールは他のツールと同じイベント経路を使い、identifier、apiName、toolSource で絞り込めます。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/lobehub/lobehub --skill agent-runtime-hooks

インストール後、Claude Code で/agent-runtime-hooksを入力して読み込まれたか確認します。

詳細

エージェントランタイムのフック

execAgent({ hooks }) を通じてライフサイクルフックを登録します。HookDispatcher は操作ごとにフックを保存し、登録順にディスパッチします。

フックの種類

5つのカテゴリにわたる16種類のフック:

execAgent({ hooks })
  │
  ├─ beforeStep ──────────── Before each step executes
  │     │
  │     ├─ [call_llm]        LLM inference
  │     │
  │     ├─ [call_tool]
  │     │     ├─ beforeToolCall ── Before tool executes (supports mocking)
  │     │     ├─ (tool execution)
  │     │     ├─ afterToolCall ─── After tool completes (optional result control)
  │     │     └─ onToolCallError ─ Tool threw an exception
  │     │
  │     ├─ [request_human_approve]
  │     │     ├─ beforeHumanIntervention ── Before agent pauses
  │     │     ├─ afterHumanIntervention ─── After approve/reject + resume
  │     │     └─ onStopByHumanIntervention ── User rejected, agent halted
  │     │
  │     ├─ [compress_context]
  │     │     ├─ beforeCompact ──── Before compression starts
  │     │     ├─ afterCompact ───── After compression completes
  │     │     └─ onCompactError ─── Compression failed
  │     │
  │     ├─ [callAgent] (via execSubAgentTask)
  │     │     ├─ beforeCallAgent ── Before sub-agent starts
  │     │     ├─ afterCallAgent ─── After creation/start returns
  │     │     └─ onCallAgentError ── Sub-agent failed
  │     │
  │     └─ afterStep ──────────── After step completes
  │
  ├─ (next step...)
  │
  ├─ onComplete ───────────── Operation reaches terminal state
  └─ onError ──────────────── Error during execution

主要なファイル

ファイル役割
packages/agent-runtime/src/types/hooks.tsイベントの種類と必須/任意フィールド
apps/server/src/services/agentRuntime/hooks/types.ts登録とWebhookの種類
apps/server/src/services/agentRuntime/hooks/HookDispatcher.ts登録、ディスパッチ、ローカルモック、HTTP/QStash配信
apps/server/src/services/agentRuntime/hooks/webhookPayload.tsイベントの投影とメール情報の付加
apps/server/src/modules/AgentRuntime/adapters/toolCallHookContext.ts共有ツールイベントコンテキストビルダー
apps/server/src/modules/AgentRuntime/adapters/ServerToolTransport.tsツール通知と実行結果
apps/server/src/modules/AgentRuntime/RuntimeExecutors.tsツール、圧縮、人による介入の実行
apps/server/src/services/agentRuntime/AgentRuntimeService/ (executeStep/, intervention.ts)ステップイベントと人による介入後の続行
apps/server/src/services/agentRuntime/CompletionLifecycle/index.ts終端イベント
apps/server/src/services/aiAgent/subAgentRuns.tsサブエージェントイベント

登録

const hooks: AgentHook[] = [
  {
    id: 'observe-step',
    type: 'afterStep',
    handler: async (event) => {
      console.log(event.operationId, event.stepIndex);
    },
  },
];
await aiAgentService.execAgent({ agentId, prompt, hooks });

完了時に、hookDispatcher.unregister(operationId) を通じて登録をクリーンアップします。

Webhook フック

Webhook のみのフックは、ローカルモードとキューモードの両方で配信されます。handler と webhook の両方を持つフックの場合、ローカルディスパッチではハンドラーが呼び出され、キューディスパッチではシリアライズされた Webhook が使用されます。永続化されるのは Webhook の設定のみのため、プロセスの再起動後は関数を利用できません。

const hook: AgentHook = {
  id: 'tool-notification',
  type: 'afterToolCall',
  matcher: '^fs/readFile$',
  webhook: {
    url: 'https://example.com/hooks',
    delivery: 'fetch',
    timeout: 5, // seconds; default 30
    headers: { Authorization: 'Bearer ${HOOK_TOKEN}' },
    allowedEnvVars: ['HOOK_TOKEN'],
  },
};
  • matcher は ${identifier}/${apiName} に対する正規表現で、ツールイベントでのみサポートされます。省略、空、または * の場合は、すべてのツールに一致します。
  • ヘッダーの環境変数テンプレートは送信時にのみ allowedEnvVars から解決されます。解決前のテンプレートを永続化し、解決済みのシークレットは決して永続化しないでください。
  • 通知ではレスポンスの内容は無視され、HTTP 204 が返される場合があります。
  • responseHandling: 'toolCall' を指定した beforeToolCall は、ツールの実行前に任意の HTTP 判定を待ちます。介入するには、{ "decision": "allow" } または { "decision": "deny", "reason": "禁止执行该操作" } を含む HTTP 2xx の JSON を返してください。型は { decision: 'allow' } | { decision: 'deny'; reason?: string } です。deny で reason が省略または空の場合は、Blocked by beforeToolCall hook. が使用されます。拒否理由は分類 hook_denied とともにツール結果/カードに保持されます。
  • afterToolCall でも responseHandling: 'toolCall' を使用できます。アーカイブおよびモデルによる処理の前に、リクエスト/結果全体を同期的に確認します。拒否すると、元の結果はレシーバーからの任意の理由で置き換えられ、コンテンツとブロック状態の両方に反映されます。deny で reason が省略または空の場合は、beforeToolCall と同じ Blocked by ${type} hook. のルールに従い、Blocked by afterToolCall hook. が使用されます。beforeToolCall と同様に、理由はモデルコンテキストとツールカードに含まれます。ツールの副作用、再試行、または課金を取り消すものではありません。遅延完了とランタイムの境界については、結果制御の仕様を参照してください。
  • 環境変数によるレスポンスモードは ignore(通知のデフォルト)と toolCall(before と after の両方で任意の判定に対応)です。有効になるのは、サブスクライブされたイベントのみです。削除された toolResult/toolCallAndResult の値は toolCall に置き換えてください。after のみの確認を選択するには、イベントリストを使用します。有効な判定を含む既存の after レスポンスは、現在、結果を制御します。
  • HTTP 204、空の成功レスポンス本文、無効な JSON、トップレベルの decision を含まない JSON(配列、スカラー値、null、旧形式のネストされた hookSpecificOutput のみの場合を含む)は、onError: 'block' であっても通知レスポンスです。後続のフックも引き続き確認してください。通知を、後続のポリシーをスキップする許可として扱わないでください。明示的な無効判定、2xx 以外のステータス、サイズ超過の本文、無効な UTF-8、ネットワーク障害、タイムアウトには、onError: 'continue' | 'block'(デフォルトは continue)が適用されます。ブロック時には、before と after の両方のフックで、hook_control_error がツール結果のコンテンツおよび理由として使用されます。キャンセルによって許可が与えられることはありません。
  • 追加のレスポンスフィールドは許可されますが、破棄されます。使用されるのは decision と、deny の場合の任意の文字列 reason のみです。updatedInput/additionalContext によって、ツールの引数や会話コンテキストが変わることはありません。
  • 設定:packages/types/src/agentHook.ts、レスポンス解析:packages/types/src/agentHookResponse.ts、HTTP 配信:apps/server/src/services/agentRuntime/hooks/httpWebhook.ts。
フック設定の有効期間
  • 環境フック(AGENT_HOOK_WEBHOOK_*)は、人間による承認後や遅延完了後も、常に実行中のワーカーの現在の環境から取得します。設定を永続化したり、以前の環境のフックを復元したりしないでください。
  • コードから指定されたWebhookフック(execAgent({ hooks }))は、既存の操作ごとのシリアライズと永続化の仕組みを維持します。呼び出し元のフックは、ランタイムスナップショットまたは永続化された操作レコードから復元します。環境変数のテンプレートは、シークレットを展開してストレージに保存することなく維持してください。
  • 復元した呼び出し元のフックを各操作について順番に評価し、その後、最終ラウンドで現在の環境フックを一度評価します。操作間で呼び出し元のフックIDを重複排除しないでください。環境フックのみの場合、操作を永続ストレージに保存する必要はありません。環境フックを削除すると、それ以降の結果への制御は停止します。一致する結果制御が残っていない場合は、結果を許可します。承認や再起動をまたいで以前の環境ポリシーを固定しないでください。
  • 遅延完了は空のプレースホルダーに一度だけ公開します。重複したコールバックでは、拒否を含め、最初の最終結果を保持します。呼び出し元ポリシーのストレージエラーは、読み取りの失敗を空のフックリストとして扱わず、そのまま伝播させてください。設定済みフックへの配信に失敗した場合に、デフォルトで許可してはいけません。これらの失敗はonErrorに従います。
  • 回帰テストでは、承認後およびスナップショットの有効期限切れ後の環境フックの削除・追加、レビュー用マーカーなしでの公開、永続化された呼び出し元フックによる制御の継続を検証してください。
結果フック後に公開する
  • アーカイブ、メッセージ内容・状態の書き込み、ストリーミング、モデルによる利用の前に、結果制御の完了を待ちます。許可された結果またはサニタイズ済みの拒否を直接永続化してください。toolResultControlの保留中・許可・ブロックのメタデータを追加したり、履歴の投影を通じて生の出力を隠したりしないでください。
  • 人間による承認の発生元は決定を確定し、回答を非公開の解決データ/送信待ちデータに保持します。MessageModel.resolveHumanApprovalはデフォルトで確定のみを行います。結果制御後にpublishResult: trueを指定して呼び出すのは、ランタイムだけです。停止/拒否の受領通知は、実行済みツールの出力を含まないため、直接公開できます。
  • 既存の介入操作IDまたは遅延コールバックの親操作を通じて、元の呼び出し元フックを復元します。メモリー上のフック判定は、ツールが所有する状態と分離してください。ツールがtype: 'blocked'を返しても、それはフックによる拒否ではありません。
  • 起動に失敗したメンバーを含め、すべての遅延バックフィルで、同じく書き込みより先にフックを実行します。重複した最終書き込みの調停には既存の空のプレースホルダーを使い、代替となるレビューステートマシンを導入しないでください。
  • スレッドにサブエージェントまたはグループメンバーの完了ブリッジがある場合、そのブリッジだけがツール結果を公開します。スレッドのライフサイクルフックはスレッドのメタデータのみを更新し、承認後の復旧時も含め、制御を経ていない要約をツールメッセージに事前入力しないでください。
  • 回帰テストでは、フック実行中に回答が保留されること、公開とロールバックの原子性、ポリシー削除後の重複コールバック、メンバーの起動が一部失敗した場合を検証してください。履歴の読み取り側に結果レビューのロジックが不要な状態にしてください。

イベント

イベントタイミングとペイロード
beforeStepステップの前;AgentHookEvent
afterStepステップの後;コンテンツ、ツール呼び出し/結果、使用量の合計
onComplete終了状態;done、error、interrupted、max_steps、cost_limit などの理由
onError操作エラー;errorMessage、errorDetail、利用可能なエラーメタデータ
beforeToolCallツール実行の前;共有ツールコンテキストとローカルの mock() コールバック
afterToolCallツール実行の後;構造化された result と mocked
onToolCallErrorツール実行で例外が発生;共有ツールコンテキストと error
beforeHumanIntervention承認の前;pendingTools、operationId、stepIndex
afterHumanIntervention承認の判断と続行;action、省略可能な toolCallId、rejectionReason
onStopByHumanIntervention人間による拒否で実行を停止;省略可能な toolCallId と rejectionReason
beforeCompact圧縮の前;messageCount、tokenCount、stepIndex
afterCompact圧縮の後;groupId、messagesBefore、messagesAfter、summary
onCompactError圧縮エラー;error、tokenCount、stepIndex
beforeCallAgentサブエージェント作成の前;agentId、instruction
afterCallAgent作成/開始の戻り値;agentId、subOperationId、success、省略可能な threadId
onCallAgentErrorサブエージェント呼び出しの失敗;agentId、error

CallAgent イベントは、parentOperationId で識別される親操作でディスパッチされます。分離された子は threadId を提供し、共有グループのメンバーは共有の会話を使用します。子の完了は、その子の onComplete イベントで通知されます。

ツールコンテキスト

3 つのツールイベントには buildToolCallHookContext() を使用します。

  • 必須フィールド:ネイティブの toolCallId、assistantMessageId、identifier、apiName、有効な args、callIndex、stepIndex、operationId、executor。
  • 省略可能な関連情報は、実行コンテキストから取得されます:エージェント、トピック、セッション、スレッド、グループ、タスク、ワークスペース、ドキュメント、ソース/ツールメッセージ、親操作の ID。
  • userId には runtime.userId ?? origin.userId が使用されます。
  • toolSource はツールの由来を示し、executor はサーバー/クライアントのディスパッチ経路を示します。executionTarget と activeDeviceId は、実行計画とデバイスの選択を表します。
  • afterToolCall.result には content、success、省略可能な executionTime、および state.type: 'blocked' などのエラー/状態データが含まれます。mocked は、ローカルフックから提供された結果であることを示します。

Sandbox ツールと MCP ツールは、同じイベント経路を使用します。identifier、apiName、toolSource でフィルタリングします。

ローカルモック

dispatchBeforeToolCall() は event.mock(result) を公開します。最初に受理されたモックが優先され、残りのモックハンドラーはスキップされます。モックの場合、このメソッドは { isMocked: true, result } を返し、実行を続行する場合は null を返します。

event.mock({ content: '{"items":[]}', success: true });

ツール監視のペイロードには BeforeToolCallObservationEvent を使用します。コールバックはインメモリのハンドラーイベントに属します。dispatchBeforeToolCall() は Webhook を配信しません。

Webhook ペイロード

createWebhookPayloadBuilder() は eventFields を選択し、hookId/hookType を追加してから、webhook.body をマージします。フィールドが重複する場合、最終的な値はボディによって決まります。ツール結果通知のペイロードでは redactResultForEvents() を使って生の skill Work データをトリミングします。コントロールには結果全体が渡されます。Work の登録では、プロセス内の結果全体が保持されます。ローカルハンドラーでは finalState を利用できます。シリアライズされたペイロードには、イベントのデータフィールドが含まれます。

メールが選択されている場合、ビルダーは最終的に有効な userId から userEmail を解決し、指定されたメールアドレスの値を置き換えます。メールのみのプロジェクションではイベント ID を使用します。明示された ID が無効な場合、メールアドレスがない場合、または検索エラーが発生した場合、メールアドレスは省略されます。

メールのクエリでは、ディスパッチャーごとに最大 1,000 ユーザーを 5 分間保持するキャッシュを共有します。クエリのタイムアウトはデータベース層で処理されます。ビルダーのオプションの第 4 引数 { signal } を使うと、待機中の呼び出し元が個別にキャンセルできます。キャンセルされた場合は undefined が返されるため、呼び出し元は配信を中止できます。Fetch と QStash では、エンリッチされたペイロードを使用します。

配信エラー

ディスパッチでは、各ハンドラーまたは Webhook の処理完了を待ちます。通常のエラーはログに記録され、ディスパッチは続行されます。fallback: 'none' を指定した Webhook で発生したエラーは CriticalHookDeliveryError として蓄積され、同じ階層にある他のフックの実行後にスローされます。QStash の配信では、デフォルトで Fetch へのフォールバックが使用されます。QStash 署名付きエンドポイントでは fallback: 'none' を使用してください。

コメント