コーディングエージェントの機能は急速に進歩しています。私が初めてエージェントに触れたのは、Googleに入社して間もない約1年前のことでした。当時、大きな話題を呼んでいたのはModel Context Protocol(MCP)でした。これは、ツールのその場限りの個別実装を、ポータブルな共通実装へと置きえる(その他の機能も備えた)柔軟な技術です。
それから12か月が経ち、現在では多くの人々がMCPから離れ、次のトレンドとして『Agent Skills(エージェントスキル)』に注目しているようです。スキルは『段階的開示(progressive disclosure)』を導入することで、コンテキストのより効率的な利用を可能にし、結果として全体的なトークン消費を抑えられます。インファレンス(推論)コストが高騰する中、スキルがこれほど普及したのも不思議ではありません。
MCPとスキルの両方にそれぞれの『ファン層』が存在しますが、この期間に登場しながらも、これら人気の標準規格ほど頻繁には言及されていない、もう一つのコーディングエージェントの標準があります。それが『フック(hooks)』です。
MCPやスキルが(ツールや知識を追加することによって)エージェントの機能を拡張することに焦点を当てているのに対し、フックは異なるレイヤーで動作し、エージェントのループや開発プロセス全体をより高度に制御できるようにします。
エージェントフックの仕組み#
『フック』という名前は最初はピンとこないかもしれませんが、フックとは『コールバック』のことです。つまり、エージェントの処理ライフサイクルにおける特定の瞬間に呼び出されるプロシージャ(手続き)です。
フックは次の3つの要素で構成されます:
- トリガーイベント(Trigger Event):フックが呼び出されるタイミングです。通常は、実行フェーズ(preまたはpost)と、ツール呼び出しやモデル呼び出しなどのコンテキストで構成されます。たとえばAntigravityでは、
PreToolUse、PostInvocation、そしてStop(エージェント終了時)といったイベントがあります。 - 条件またはフィルター(Condition or Filter):トリガーイベントに基づく正規表現です。たとえば、ツール呼び出しの場合、フィルターをツール名に設定したり、ツールの引数を含めたりすることができます。具体的には、
run_command(git)というツール呼び出しに対してフックを作成することが可能です。 - プロシージャ(Procedure):シェルスクリプトやコマンドとして記述される、フックの本体処理です。このプロシージャを使用して、操作の許可・禁止を制御したり、モデルやツールの呼び出しを完全にオーバーライド(上書き)したり、ログ記録やテレメトリ(データ収集)などの副作用を発生させたりできます。
フックをいつ使用すべきか#
フックは、エージェントのライフサイクルの特定の瞬間をインターセプト(遮断)して、カスタムコマンドやスクリプトを注入します。適切な瞬間をインターセプトすることで、処理の流れを制御し、本来なら非決定論的(再現性がない)になりがちなプロセスに、決定論的(確実)な結果をもたらすことができます。
たとえば、開発者はシステムプロンプトやAGENTS.md(または同様のファイル)を介してコーディングガイドラインを適用しようとすることがよくあります。しかし、大規模言語モデル(LLM)の非決定論的な性質上、プロンプトベースのガイドラインには実行の保証がありません。まったく同じプロンプトであっても異なる結果が生じることがあり、エージェントがプロンプトの一部を都合よく無視することもあります。
プロンプトの代わりにフックを使用することで、特定のアクションを強制的に実行させることができます。たとえば、コード編集のたびに、コードが常にクリーンであることを確認するために、必ず静的解析ツール(いわゆるリンター)を実行させたいとします。プロンプトに『編集後は必ずリンターを実行すること』と記述した場合、エージェントはリンターを実行するかどうかを判断する『自律性(agency)』を持ってしまい、編集が『軽微』であると判断した場合には検証を完全にスキップしてしまう可能性があります。しかし、代わりにフック(この場合はファイル編集ツールをフィルタリングするPostToolUse)を作成すれば、編集後に静的解析ツールを実行することを決定論的に担保できます。
これらのライフサイクルイベントをインターセプトすることで、エージェントの動作制御、メトリクスの収集、ワークフローの安全確保など、いくつかのパターンを実装できます。以下でその一部を見ていきましょう。
エージェントを専門のツールへと誘導する#
フックは多くのシナリオで役立ちますが、私のお気に入りのユースケースは、エージェントの周囲にガードレールを設置すること、言い換えれば『エージェントの自律性(agency)を下げる』ことです。
これは、PreToolUseフックと、ツールへのアクセスを拒否してコーディングエージェントに『誘導ヒント(steering hint)』を返すスクリプトを組み合わせることで実装できます。この誘導ヒントには、代わりに実行させたい指示を含めます。たとえば、エージェントがGoファイルを読み込むためにシェルコマンドを使用するのを防ぎたい場合、誘導ヒントは次のようになります:“Tool call blocked - run_command(cat): do not use ‘cat’ for reading .go files, use ‘smart_read’ instead”(ツール呼び出しがブロックされました - run_command(cat):.goファイルの読み込みに’cat’を使用しないでください。代わりに’smart_read’を使用してください)。
悪意のあるプロンプトのインターセプト#
PreInvocationフックは、入力されたプロンプトをインターセプトし、安全性のヒューリスティクスや軽量な分類モデルに照らし合わせて評価できます。プロンプトがジェイルブレイク(脱獄)の試みのように見える場合、フックはリクエストを即座にブロックし、実行ループに到達する前にバックエンドシステムを保護できます。
認証情報の漏洩防止#
開発者が誤ってenvファイルや資格情報を、コーディングエージェントが読み取るアクティブなファイルに貼り付けてしまうことがあります。ファイルの読み込みを監視するPostToolUseフックや、LLMに送信されるペイロードをスキャンするPreInvocationフックは、信頼性の高いデータ損失防止(DLP)ゲートとして機能します。高エントロピーのキーや標準的なAPI形式に一致する文字列を検出した場合、フックは動的にシークレットを隠蔽(マスク)するか、実行を中断して認証情報の安全性を保ちます。
メモリの管理#
Agent Platform Memory BankやMemPalaceのような外部メモリシステムに接続されていない限り、エージェントは通常ステートレス(状態を持たない)です。
エージェントにメモリ機能を追加する一つの方法は、記憶と検索をツールとして登録することですが、そうすると、エージェントがそれらのツールを呼び出すという決定を明示的に下すかどうかに依存することになります。
フックシステムを使用すると、メモリの永続化と検索を自動化できます。セッションの終了時(Stopフックを使用)や、特定のターン数経過後(ステップ数やモデルの呼び出し回数を監視)にメモリ生成を紐付けることができます。
逆に、セッションの開始時やモデルの呼び出し前(例:PreInvocationフック)に、自動的にメモリを検索させることも可能です。たとえばAgent Platform Memory Bankでは、スコープ(例:ユーザーIDなど)や、クエリに基づく類似度によってメモリを検索できます。これは本質的に、メモリをベースにした検索拡張生成(RAG)と言えます。
テレメトリデータの収集#
フックシステムは、テレメトリコレクターやロガーを配置するのにも適した場所であり、エージェントの内部動作に対する優れた可視性を提供します。私個人としては、AIの支配者たちが実権を握る前に(冗談です :))、エージェントへの理解を深め、より良い関係を築くための試みとして、前々からエージェントの「不満・愚痴カウンター(curse word counter)」フックを作ってみたいという衝動に駆られています。
Antigravityにおけるフックの設定#
エージェントのエンジンによってコールバックに独自用語が使用されますが、このセクションでは、具体的なAntigravity方言のフックに焦点を当てます。
仕様の全貌については、公式のAntigravityフックドキュメントを参照してください。
Antigravityは、ワークスペースの.agents/ディレクトリ内にあるhooks.jsonファイル(またはホームディレクトリのグローバル設定~/.gemini/config/hooks.json)を探します。
先ほど説明した誘導ヒントと静的解析を実装する例を以下に示します:
{
"linter-safety-gate": {
"PostToolUse": [
{
"matcher": "write_to_file|replace_file_content|multi_replace_file_content",
"hooks": [
{
"type": "command",
"command": "./scripts/run-linter.sh",
"timeout": 15
}
]
}
]
},
"restrict-cat-on-go": {
"PreToolUse": [
{
"matcher": "run_command",
"hooks": [
{
"command": "./scripts/steer-go-reads.py"
}
]
}
]
}
}これらのフックへの入力は、stdinを介してJSONオブジェクトとして提供され、ツールの引数(toolCall.args)、アクティブなワークスペースパス(workspacePaths)、現在のセッションログのファイルパス(transcriptPath)などのコンテキストが含まれます。スクリプトはこれらを評価し、計算を実行し、許可("allow")、拒否("deny")、またはユーザーへの確認要求("ask")をAntigravityに伝えるJSONレスポンスをstdoutに出力します。
たとえば、そのインカミングペイロードを解析してエージェントを誘導するシンプルなPythonスクリプト(steer-go-reads.py)は、以下のように記述できます:
import sys
import json
def main():
# Read and parse the incoming trigger event payload from stdin
try:
payload = json.load(sys.stdin)
except Exception as e:
# Standard safety gate fallback
print(json.dumps({
"decision": "deny",
"reason": f"Failed to parse stdin payload: {e}"
}))
return
tool_call = payload.get("toolCall", {})
tool_name = tool_call.get("name")
tool_args = tool_call.get("args", {})
# Match the specific tool and check arguments
if tool_name == "run_command":
command_line = tool_args.get("CommandLine", "")
# Detect if command attempts to cat any Go source files
if "cat" in command_line and ".go" in command_line:
response = {
"decision": "deny",
"reason": "Tool call blocked - run_command(cat): do not use 'cat' for reading *.go files, use 'smart_read' instead."
}
print(json.dumps(response))
return
# Default to allow if no rules are violated
print(json.dumps({
"decision": "allow"
}))
if __name__ == "__main__":
main()制御を取り戻す#
エージェントはますますスマートで高速になり、私たちがかつてないほど速くコードを生成できるようにしてくれます。しかし、制御を伴わない速度は、災害のレシピそのものです。私は講演でよくこのような比喩を使います。もし速い車が好きなら、最も重要視すべきなのはエンジンではなくブレーキです。もしブレーキがエンジンよりも非力なら、止まることができず安全が脅かされてしまいます。
同じ考え方をコーディングエージェントにも適用すべきです。コードを速く書きたいのであれば、品質を犠牲にしてバグを持ち込まないようにするための強力な制御システムが必要です。なぜなら、もしバグを持ち込んでしまえば、遅かれ早かれアプリケーションに大きな問題を引き起こすことになるからです。
フックは、AIの自律性と堅牢なソフトウェアエンジニアリングとの間のギャップを埋め、再び制御を取り戻すためのガードレールを実装するのに最適な場所です。最近、Joe Bertolamiのこの記事で読んだように、「コードを書くことがボトルネックだったことは一度もない」のです。数十年にわたるエンジニアリングのベストプラクティスを忘れず、エージェントに適切なツールを装備させることで、現代の自律型コーディング体験を存分に楽しみましょう。




