ADKエージェントをAgent Runtimeにデプロイし、Agent Identityで「どのエージェントが何をしたか」をAudit Logで追跡してみた

ADKエージェントをAgent Runtimeにデプロイし、Agent Identityで「どのエージェントが何をしたか」をAudit Logで追跡してみた

2026.05.05

お疲れさまです。とーちです。

前回、ADK + Gemini 3.1 Proで planner / builder / evaluator を実装してみた記事を書きました (実装したコードのリポジトリはこちら)。あちらはあくまでローカルで adk web を立ち上げて動かすところまでで、AIエージェントをGoogle Cloudにデプロイしたり、認証・権限をきちんと設計したりする部分はスコープ外にしていました。

今回はその続きとして、

  • ADKで作ったエージェントをGoogle Cloudの Agent Runtime にデプロイし
  • そのエージェントに Agent Identity を付与して
  • エージェント固有のSPIFFE IDでGCS (Cloud Storage) にアクセスさせ
  • Cloud Audit Logに 「どのエージェントが書き込んだか」 が記録されることまで

を含めて確認してみました。最終的にはGemini Enterpriseをフロント (チャットUI) として、Gemini Enterprise → Agent Runtime → Agent Identity → GCS という経路で動かしています。

なるべく再現できるレベルで手順を残しているので、興味のある方の参考になれば嬉しいです。

Agent Identity / SPIFFE IDとは

まず、今回の主役である Agent Identity について軽く整理しておきます。

Agent Identityは、Gemini Enterprise Agent Platform (Agent Runtime) で動かすエージェントに対して、エージェント固有のID(x509証明書とセットで発行される) を自動で付与してくれる仕組みです。デプロイ時に .agent_engine_config.jsonidentity_type: AGENT_IDENTITY を指定するだけで有効になります。

このIDは SPIFFE というオープン標準に沿った形式です。SPIFFEはワークロード (サービスやコンテナ) のIDの表現方法とその証明書の仕様を定めており、IDの文字列形式だけでなくx509証明書の仕様もセットで標準化されています。Google Cloud Agent RuntimeでのAgent Identityでは以下のような文字列になります。

principal://agents.global.org-<ORG_ID>.system.id.goog/resources/aiplatform/projects/<PROJECT_NUMBER>/locations/<LOCATION>/reasoningEngines/<AGENT_ENGINE_ID>

Agent Identityには大きく以下の使い分けがあります ( こちらの記事 も参照)。

  • User Identity: エンドユーザーの認証情報を流用 (ユーザーがログインしている場合)
  • Agent Identity (2-legged): エージェント自身の固有IDだけで他リソースにアクセス
  • Agent Identity (3-legged): エージェントID + ユーザーIDの両方で他リソースにアクセス

今回はGCSへのアクセスをエージェントの責任で行いたかったので 2-legged の Agent Identity を使う形になっています。

カスタムサービスアカウントと比べた場合のメリット

Agent IdentityはCompute EngineにSAをアタッチするような従来のアプローチと比べて、以下の点で優れています。

  • ライフサイクルが自動連動: Agent Identityはエージェントのライフサイクルに紐づいており、エージェントを削除するとAgent Identityも自動で無効化されます。カスタムSAはエージェントを削除してもSA自体は残り続けるため、削除し忘れが発生しやすいです

  • 盗まれた認証情報を使い回せない: エージェントがGCSなどGoogle Cloudのサービスにアクセスする際はOAuth2のアクセストークンを使います。Agent IdentityはこのトークンにCertificate-bound Token(証明書と暗号的に紐づけられたアクセストークン)という方式を採用しており、トークンを使うたびにAgent Runtimeコンテナの証明書を提示する必要があります。証明書の秘密鍵はコンテナの外に持ち出せないため、トークンが盗まれても別の環境からは使えません。これが従来のSAとは異なる部分になります

※以下は公式ドキュメントの記述をもとにした概念図です。

  • 運用コストが低い: エージェントを増やすたびに「SA作成 → 権限付与 → アタッチ」が必要だったカスタムSAと違い、Agent IdentityではSA作成が不要です。SPIFFE IDに対してIAM権限を付与するだけで済みます

出典: Use Agent Identity with Agent Runtime | Google Cloud

今回実現したいこと

最終的な構成は以下のようなイメージです。

今回のシナリオはとてもシンプルで、 builderエージェント (実体はplanner→builder→evaluatorの一連のフロー) がGCSバケットにファイルを書き込んだとき、Cloud Audit Logの principalSubject にエージェントのSPIFFE IDが記録されることを確認する というものです。

やってみた

ここからが実際の手順です。実施した順番に沿って書いていきます。

前提条件

  • gcloudのcurrent projectが対象プロジェクトになっていること
  • 必要なAPIが有効化されていること: aiplatform.googleapis.com, storage.googleapis.com
  • ADKのバージョン: 今回は 1.31.1 を使用。 uv run adk --version で確認可能
  • Cloud StorageのData Access Audit Log (データ書き込み) を有効化しておく: これが今回のキモのひとつで、有効化していないとAudit LogにSPIFFE IDが記録されません

Audit Logの有効化はGCPコンソールから行います。「IAMと管理 → 監査ログ → Cloud Storage → データ書き込み」にチェックを入れて保存です。

CleanShot 2026-05-05 at 14.23.54.png

コードの事前改修

前回記事のADKエージェントは write_file / read_file / list_workspace / read_rubric でローカルファイルシステムを使う実装になっていました。Agent Runtime上ではローカルファイルシステムが使えないので、これらをGCS対応に書き換えました。前回からの差分はこちらのコミットで確認できます。

verify_html ツールについては今回の主眼 (Agent Identity) とは関係なく、依存関係も増えそうだったので一旦コメントアウトしています。

GCS書き込み・読み込みの実装はこんな感じです。

def write_file(path: str, content: str) -> dict:
    """GCS バケットにファイルを書き込む。

    Agent Runtime 上では Agent Identity の認証情報が ADC 経由で自動注入される。
    """
    client = gcs.Client()
    bucket = client.bucket(GCS_BUCKET_NAME)
    blob = bucket.blob(path)
    blob.upload_from_string(content, content_type="text/plain; charset=utf-8")
    return {"status": "ok", "path": f"gs://{GCS_BUCKET_NAME}/{path}", "bytes": len(content)}


def read_file(path: str) -> dict:
    """GCS バケットからファイルを読む。"""
    client = gcs.Client()
    bucket = client.bucket(GCS_BUCKET_NAME)
    blob = bucket.blob(path)
    if not blob.exists():
        return {"status": "not_found", "path": f"gs://{GCS_BUCKET_NAME}/{path}"}
    return {
        "status": "ok",
        "path": f"gs://{GCS_BUCKET_NAME}/{path}",
        "content": blob.download_as_text(encoding="utf-8"),
    }

ポイントは、特に明示的な認証情報をコードに埋め込んでいない ところです。gcs.Client() を引数なしで呼び出すと ADC (Application Default Credentials) が使われますが、Agent Runtime上で動いているときはこのADCに Agent Identityが自動で使われる 仕組みになっています。(出典)。

なお、Agent Runtimeはus-central1リージョンでデプロイする予定だったので、モデルもglobalエンドポイント専用の gemini-3.1-pro-preview から gemini-2.5-pro に変更しています。

.agent_engine_config.json の作成

Agent Identityを有効にする方法は公式ドキュメントによると以下の3種類があります。

  • Vertex AI Python SDK: client.agent_engines.create()configidentity_type: AGENT_IDENTITY を指定する
  • Agents CLI で agents-cli deploy --agent-identity
  • ADK deploy で .agent_engine_config.json を使う ← 今回はこれ

今回はADK deployを使っていたので、.agent_engine_config.json をエージェントディレクトリに配置する方法を採用しました。

中身はこれだけです。

{ "identity_type": "AGENT_IDENTITY" }

配置場所: agents/plan_build_eval/.agent_engine_config.json

identity_typeAGENT_IDENTITY を指定すると、デプロイ時にエージェント固有の暗号的に証明されたSPIFFE IDが自動付与されます。シンプルですね。

GCSバケットの作成

builderエージェントがファイルを書き込む先として、GCSバケットを作成しておきます。

gsutil mb -p <YOUR_PROJECT_ID> -l us gs://agent-identity-demo-20260504

バケット名は agent-identity-demo-20260504 (リージョン: us) としました。

Agent Runtimeへのデプロイ実行

Agent Runtimeへのデプロイには adk deploy agent_engine コマンドを使います。これを使うと、コードをパッケージ化してコンテナにビルドし、マネージドなAgent Runtimeサービスにデプロイしてくれます (参考: Deploy Agent | adk.dev)。

実際のデプロイコマンドはこんな感じです。

cd /path/to/gemini-coding-agents

uv run adk deploy agent_engine \
  --project=<YOUR_PROJECT_ID> \
  --region=us-central1 \
  --display_name="plan-build-eval-agent" \
  agents/plan_build_eval

コマンド実行時のログに以下が出力され、.agent_engine_config.json が読み込まれていることが確認できました。

Reading agent engine config from .../agents/plan_build_eval/.agent_engine_config.json

デプロイ結果として、Agent Engine ID とSPIFFE IDが発行されます。

ここまでで、Agent Identity付きのエージェントがGoogle Cloud上にデプロイされた状態になりました。

Agent Identityの確認

デプロイしたエージェントに本当にAgent Identityが紐付いているかを確認します。gcloud ai reasoning-engines のような専用コマンドはgcloud SDK 566.0.0時点では存在しなかったので、REST APIを直接叩く形で確認しました (参考: List Agents Identities)。

curl -s -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://us-central1-aiplatform.googleapis.com/v1/projects/<PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<AGENT_ENGINE_ID>"

レスポンスの spec 配下の以下2フィールドを見るとAgent Identityの付与状況が分かります。

{
  "spec": {
    "identityType": "AGENT_IDENTITY",
    "effectiveIdentity": "agents.global.org-<ORG_ID>.system.id.goog/resources/aiplatform/projects/<PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<AGENT_ENGINE_ID>"
  }
}

identityTypeAGENT_IDENTITY になっており、effectiveIdentity にエージェント固有のSPIFFE IDが割り当たっていることが確認できました。

GCSバケットへのIAM権限付与

GCSバケットへのIAM権限を、Agent IdentityのSPIFFE IDに対して付与します。今回は roles/storage.objectUser を付与しました。

gcloud storage buckets add-iam-policy-binding gs://agent-identity-demo-20260504 \
  --member="principal://agents.global.org-<ORG_ID>.system.id.goog/resources/aiplatform/projects/<PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<AGENT_ENGINE_ID>" \
  --role="roles/storage.objectUser" \
  --project=<YOUR_PROJECT_ID>

なお、adk deploy agent_engine で新規デプロイするとAgent Runtime IDが変わり、SPIFFE IDも変更されます。その場合は古いIDのIAMバインディングを削除して新しいIDで再付与という作業が必要になります。

上記は私の勘違いでした。 --agent_engine_id オプションを指定してデプロイするとアップデート扱いになり、Agent Runtime IDが変わらないためSPIFFE IDも維持されます。

uv run adk deploy agent_engine \
  --project=<PROJECT_NUMBER> \
  --region=us-central1 \
  --agent_engine_id=<AGENT_ENGINE_ID> \
  agents/plan_build_eval

ハマりポイント: Uniform Bucket Level Accessの有効化が必要

principal:// 形式のAgent IdentityでGCSにアクセスするには、Uniform Bucket Level Access (均一バケットレベルアクセス) の有効化が必要 です。

最初これを有効にしないままGemini EnterpriseからエージェントにGCS書き込みを依頼したところ、エージェントがハングして応答が返らない状態になりました。Cloud Loggingで以下のコマンドを使ってAgent Runtimeのログを確認したところ、

gcloud logging read "resource.type=\"aiplatform.googleapis.com/ReasoningEngine\" AND resource.labels.reasoning_engine_id=\"<AGENT_ENGINE_ID>\"" \
  --project=<YOUR_PROJECT_ID> \
  --limit=20 \
  --format="table(timestamp, severity, textPayload)" \
  --freshness=30m

以下のエラーが記録されていました。

412 PreconditionFailed: The type of authentication token used for this request
requires that Uniform Bucket Level Access be enabled.

そのため、素直にUniform Bucket Level Accessを有効にして対応しました。

gcloud storage buckets update gs://agent-identity-demo-20260504 \
  --uniform-bucket-level-access \
  --project=<YOUR_PROJECT_ID>

Gemini EnterpriseをフロントとしてAgent Runtimeに接続

Agent RuntimeにデプロイしたエージェントはAPI経由で直接呼び出すこともできますが、今回はチャットUIから手軽に呼び出したかったので Gemini Enterprise をフロントとして接続します。

1. Gemini EnterpriseのAgentsページから「エージェントを追加」をクリック

CleanShot 2026-05-05 at 13.47.44.png

2. エージェントタイプの選択画面で「Agent Engineによるカスタムエージェント」を選択

CleanShot 2026-05-05 at 13.51.11.png

3. 認可の設定はAgent Identityで直接GCSにアクセスするためスキップ

CleanShot 2026-05-05 at 13.52.33.png

ここでは認可の設定をスキップしています。今回はGCSへのアクセスをAgent Identityで直接行うため、ユーザー認可を介す必要がないのでこの選択になります。

4. エージェント名・説明・推論エンジンのリソースパスを入力して「作成」をクリック

CleanShot 2026-05-05 at 13.55.09.png

ここで入力する3項目について少し補足しておきます。

項目 説明
エージェント名 Gemini Enterprise上での表示名。デプロイしたAgent Runtimeエージェントの名前と別でもOKです (実際、私は別名にしました)
エージェントの説明 エージェントの説明文
Agent Engine 推論エンジン デプロイしたAgent Engineのリソースパス (projects/<PROJECT_ID>/locations/<LOCATION>/reasoningEngines/<AGENT_ENGINE_ID>) をそのまま入力

5. Agentsページに戻ると一覧に追加されている

CleanShot 2026-05-05 at 14.00.27.png

Gemini EnterpriseのAgentsページに戻るとエージェントの一覧に、先ほど追加したAgent Runtimeエージェントが追加されているのが分かります。

6. Gemini Enterpriseのチャット画面を開く

CleanShot 2026-05-05 at 14.01.57.png

概要からPreviewのリンクをクリックし、Gemini Enterpriseのチャット画面を開きます。

7. @ を入力するとエージェント選択ウィンドウが開く

CleanShot 2026-05-05 at 14.28.51.png

チャット画面で @ を入力するとエージェント選択ウィンドウが開くので、作成したエージェントを選択します。

8. 「フィボナッチ数列を出力するPythonスクリプトを作成してください」と入力

実際にエージェントを呼び出して動かしてみます。

CleanShot 2026-05-05 at 14.29.46.png

回答の全文は以下のような感じでした。冒頭のほうが英語になっているのが少しイマイチではありますが、まず spec をきちんと出力し、それに基づいて実装、evaluatorによる検証まで行ってくれているのが分かります。

Done.
The spec has been saved to `spec.md`.

Here is the content:
# Fibonacci Sequence Generator Spec

## 1-line Summary

A Python script that calculates and prints the Fibonacci sequence up to a specified number of terms provided as a command-line argument.

## Functional Requirements

- The script must be implemented in Python.
- It must accept one command-line argument, `n`, which represents the number of Fibonacci terms to generate.
- If `n` is less than or equal to 0, the script should output nothing.
- If `n` is 1 or greater, the script will calculate the Fibonacci sequence from the 1st to the nth term.
- The Fibonacci sequence starts with F(0) = 0 and F(1) = 1. The output sequence, however, should start from F(1).
- The calculated numbers should be printed to standard output, with each number on a new line.
- If the argument is not a number or if no argument is provided, the script must print an error message to standard error and exit with a status code of 1.

## Output Format

- The Python code should be written in a single file named `fibonacci.py`.

## Task-specific Rubric

1.  **Argument Parsing**: The `argparse` module must be used for parsing command-line arguments.
2.  **Calculation Logic**: The Fibonacci sequence calculation logic must be implemented using a loop (iteration), not recursion, to ensure efficient performance.
3.  **Error Handling**: Proper error handling must be implemented for invalid inputs (e.g., non-numeric arguments, no arguments).
4.  **Function Responsibility**: Functions should have a single responsibility (e.g., argument processing, calculation, and output should be appropriately separated).
5.  **Main Guard**: The script execution must be guarded by an `if __name__ == "__main__":` block.
承知いたしました。spec に基づいて、フィボナッチ数列を計算して出力する Python スクリプトを作成します。

まず、`spec.md` と永続 rubric の要件を確認しました。
- Python で実装し、`fibonacci.py` という名前で保存する。
- `argparse` モジュールを使用してコマンドライン引数 `n` を受け取る。
- `n` は生成する項数を表す整数。
- 計算ロジックは再帰ではなくループを使用する。
- 引数が提供されない、または非数値である場合は、標準エラーにメッセージを出力し、ステータスコード `1` で終了する。
- 関数の責務を分離し、`if __name__ == "__main__":` ガードを使用する。
- 永続 rubric に従い、ファイルは `workspace/` 配下に配置する。

これらの要件をすべて満たすコードを作成し、指定されたパスに書き出します。`spec.md` の要件に従って、フィボナッチ数列を計算する Python スクリプトを作成しました。

以下のファイルを生成しました。
- `workspace/fibonacci.py`永続 rubric (必達):
1. **出力先制限**: PASS. `workspace/fibonacci.py` に生成されている。
2. **外部依存禁止**: PASS. `argparse`, `sys` はPythonの標準ライブラリである。
3. **動作可能性**: PASS. `python workspace/fibonacci.py <n>` の形式で実行可能。
4. **入出力境界の検証**: PASS. `try-except` ブロックで `ValueError` や `SystemExit` を適切に処理し、エラーメッセージを出力して終了している。

永続 rubric (推奨):
1. **可読性**: PASS. 各関数は短く、ネストも浅い。
2. **命名**: PASS. 変数名、関数名ともに処理内容が分かりやすい。
3. **コメント方針**: PASS. Docstringで関数の目的が説明されており、コード内のコメントも適切。
4. **重複**: PASS. 重複したロジックは見られない。
5. **マジックナンバー**: PASS. `sys.exit(1)` やフィボナッチ数列の初期値 `0, 1` は自明なため問題ない。

タスク固有 rubric:
1. **Argument Parsing**: PASS. `argparse` モジュールが適切に使用されている。
2. **Calculation Logic**: PASS. フィボナッチ数列の計算に再帰ではなく `for` ループを使用している。
3. **Error Handling**: PASS. 引数がない場合、非数値の場合に標準エラーへメッセージを出力し、ステータスコード1で終了するよう実装されている。
4. **Function Responsibility**: PASS. `create_parser`, `calculate_fibonacci`, `main` のように、責務が適切に分離されている。
5. **Main Guard**: PASS. `if __name__ == "__main__":` ブロックでスクリプトの実行が保護されている。

総合判定: PASS
すべてのrubricを満たしている。

planner→builder→evaluatorの直列処理がそのままGemini Enterprise上でも動いているのが確認できました。

GCSバケットのコンソールでも、builderエージェントが書き込んだファイルが実際に作成されていることが確認できました。

CleanShot 2026-05-05 at 15.45.44.png

Agent Identityの効果実証 (GCS + Cloud Audit Log)

ここからが今回の本題です。エージェントがGCSに書き込んだ際に、Cloud Audit Logにエージェント固有のSPIFFE IDが記録されるかを確認します。

builderエージェントが workspace/fibonacci.py 等のファイルをGCSに書き込んだことが確認できたので、GCSバケットに対するオブジェクト作成操作のAudit Logを gcloud logging read で取得します。

gcloud logging read "resource.type=\"gcs_bucket\" AND resource.labels.bucket_name=\"agent-identity-demo-20260504\" AND protoPayload.methodName=\"storage.objects.create\"" \
  --project=<YOUR_PROJECT_ID> \
  --limit=5 \
  --format=json \
  --freshness=30m

得られたAudit Logの authenticationInfo フィールドはこんな感じになっていました。

{
  "authenticationInfo": {
    "principalEmail": null,
    "principalSubject": "principal://agents.global.org-<ORG_ID>.system.id.goog/resources/aiplatform/projects/<PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<AGENT_ENGINE_ID>"
  }
}

ポイントは以下です。

  • principalEmailnull になっている (人間ユーザーやサービスアカウントのメールアドレスは記録されない)
  • principalSubjectAgent IdentityのSPIFFE ID がそのまま記録されている

つまり、このGCSへの書き込みは「ある人間がやった」のでもなく「あるサービスアカウントが代わりにやった」のでもなく、「このエージェント (このReasoning Engine) が自分の権限で書き込んだ」 というところまでAudit Logで追跡できる、というわけです。

GCPコンソールのCloud Loggingで見てもしっかり同じ情報が記録されていました。

CleanShot 2026-05-05 at 14.41.37.png

ここまでで、

  • Agent Identityを付与したエージェントをAgent Runtimeにデプロイし
  • そのSPIFFE IDに対してGCSバケットの roles/storage.objectUser を直接付与し
  • エージェントがGCSに書き込んだ操作がCloud Audit Logに エージェントのSPIFFE IDとともに 記録される

ことが確認できました。

まとめ

今回はADKで作ったplanner / builder / evaluatorをAgent Runtimeにデプロイし、Agent Identityを使ってGCSにアクセスさせ、Cloud Audit LogにエージェントのSPIFFE IDが記録されることを確認しました。

実際に手を動かしてみて感じたポイントは以下です。

  • .agent_engine_config.json に1行書くだけでAgent Identityが付与される手軽さ: デプロイ体験としてはかなりシンプルです
  • IAMの粒度が「エージェント単体」になる: 共有サービスアカウントで複数エージェントが動く構成と比べて、最小権限の設計がしやすそうだなと感じました
  • Audit LogのprincipalSubjectでエージェントを追跡できる: 多数のエージェントを運用するときの監査・トラブルシュート観点でとても良い仕組みだなと思いました

所感

3-legged Agent Identity (エージェントID + ユーザーIDを組み合わせた権限委譲) のシナリオも試してみたいなと思っています。今回試した2-leggedはエージェント単独の権限で動かすものでしたが、ユーザーごとのアクセス制御が必要な業務システムだと3-legged側のニーズが大きそうです。

また、Terraform等でAgent RuntimeリソースとIAMバインディングを一緒にコード化する運用方法も気になっており、そのあたりも別途試してみたいところです。

参考資料

以上、とーちでした。

この記事をシェアする

関連記事