チュートリアル 5: セッションフックと Permission Handling による監査ログ
スクリプト: src/python/scripts/tutorials/05_audit_hooks.py
学べること
session.on()を通じてすべてのセッションイベントをインターセプトする方法- カスタム
on_permission_requestハンドラの実装方法 - 特定のツール実行を承認または拒否する方法
- イベントストリームから構造化された監査ログを構築する方法
前提条件
copilotCLI がインストール済みかつ認証済み(はじめに を参照)github-copilot-sdkがインストール済み
セッションフック
session.on(handler) はセッションで起きるすべてを観察する主要な方法です。ターン開始からツール呼び出し、エラーまで、すべてのイベントがハンドラを通過します。
これにより session.on は以下に最適です:
- 監査ログ — 誰が何をいつ呼び出したかを記録
- モニタリング — ツール使用状況、レイテンシ、エラーの追跡
- 進捗表示 — エージェントが何をしているかをリアルタイムで表示
- テスト — 特定のイベントが正しい順序で発生したことをアサート
ステップ 1 — セッションイベントで監査ログを構築する
import time
import json
from typing import Any
from copilot.generated.session_events import SessionEventType
audit_log: list[dict[str, Any]] = []
start_time = time.time()
def record(event_name: str, detail: str = "") -> None:
audit_log.append({
"ts": round(time.time() - start_time, 3),
"event": event_name,
"detail": detail,
})
def on_event(event: Any) -> None:
et = event.type
if et == SessionEventType.ASSISTANT_TURN_START:
record("TURN_START")
elif et == SessionEventType.ASSISTANT_INTENT:
record("INTENT", event.data.intent)
elif et == SessionEventType.TOOL_EXECUTION_START:
record("TOOL_START", event.data.tool_name)
elif et == SessionEventType.TOOL_EXECUTION_COMPLETE:
err = getattr(event.data, "error", None)
record("TOOL_COMPLETE", f"error={err.message if err else None}")
elif et == SessionEventType.ASSISTANT_TURN_END:
record("TURN_END")
elif et == SessionEventType.SESSION_IDLE:
record("SESSION_IDLE")
elif et == SessionEventType.SESSION_ERROR:
record("SESSION_ERROR", event.data.message)
session.on(on_event)
ステップ 2 — パーミッションハンドラを実装する
on_permission_request コールバックはすべてのツール実行前に呼び出されます。ポリシーに基づいて approved または denied を返します:
from copilot.types import PermissionRequest, PermissionRequestResult
def permission_handler(
request: PermissionRequest,
context: dict,
) -> PermissionRequestResult:
tool_name = getattr(request, "tool_name", "unknown")
# 例: すべてのツール呼び出しを拒否(読み取り専用の監査に有用)
if should_deny(tool_name):
print(f"[Permission] DENIED: {tool_name}")
return PermissionRequestResult(kind="denied-interactively-by-user", rules=[])
print(f"[Permission] APPROVED: {tool_name}")
return PermissionRequestResult(kind="approved", rules=[])
セッション設定に登録します:
session = await client.create_session(
SessionConfig(
on_permission_request=permission_handler,
...
)
)
ステップ 3 — 実行して監査ログを確認する
reply = await session.send_and_wait(MessageOptions(prompt=prompt), timeout=300)
print(reply.data.content)
print("\n=== Audit Log ===")
print(json.dumps(audit_log, indent=2))
サンプルの監査ログ出力:
[
{"ts": 0.001, "event": "SEND", "detail": "What are 3 best practices..."},
{"ts": 0.012, "event": "TURN_START", "detail": ""},
{"ts": 0.015, "event": "INTENT", "detail": "answer_question"},
{"ts": 2.341, "event": "TURN_END", "detail": ""},
{"ts": 2.342, "event": "SESSION_IDLE", "detail": ""}
]
パーミッションハンドラのパターン
| パターン | ユースケース |
|---|---|
すべて approved |
開発 / 信頼された環境 |
すべて denied |
読み取り専用の監査モード — 副作用なし |
| ツール名で承認 | 安全なツールのみ許可、リスクのあるものは拒否 |
| ユーザーに確認 | 機密アクションへのインタラクティブな承認 |
| ログ後に承認 | すべてのツール呼び出しをブロックせずに記録 |
スクリプトの実行
cd src/python
# すべてのツールを承認(デフォルト)
uv run python scripts/tutorials/05_audit_hooks.py \
--prompt "What are best practices for Python error handling?"
# すべてのツール呼び出しを拒否
uv run python scripts/tutorials/05_audit_hooks.py --deny-tools
# カスタム CLI サーバー(オプション)
uv run python scripts/tutorials/05_audit_hooks.py --cli-url localhost:3000
まとめ
session.on(handler)はすべてのセッションイベントをインターセプト — ログ、モニタリング、テストに使用on_permission_requestはすべてのツール実行前に呼び出され、実行するかどうかを制御する- 両方のフックはリッチなイベントデータ(ツール名、インテント、エラー詳細など)を受け取る
- タイムスタンプ付きの監査ログを構築してセッション全体のエージェントの動作を追跡する
- パーミッションハンドラからの
deniedレスポンスはセッションの継続を妨げない