ANTIGRAVITY LABEN
記事一覧/Agents & Manager
Agents & Manager/2026-04-26上級

Antigravity で A2A プロトコルを使ってエージェント間通信を実装する

Google Antigravity 上でエージェント同士を通信させる A2A プロトコルの実装ガイド。プロトコルの設計思想、エンドポイント定義、認証、エラーハンドリング、本番運用までを実例ベースで解説します。

antigravity435a2a-protocolmulti-agent21agent-communicationproduction59

A2A(Agent-to-Agent)プロトコルは、独立して動作するエージェント同士が相互に依頼・応答・進捗共有できるようにするための共通仕様です。Google が2025年後半から本格的に提唱を始め、Antigravity でも対応が進んでいます。1つの大きなエージェントを作るより、役割分担した複数の小さなエージェントを連携させた方が、保守も拡張も楽になることが多いのですが、その「連携」を標準化したのが A2A です。

ここではAntigravity 上で A2A を使ったエージェント間通信の実装を、最小サンプルから本番運用レベルまで段階的に組み上げていきます。私自身がプロトタイプ開発で何度も書き直してたどり着いた「動かしやすい構成」を共有します。

なぜ単一エージェントではなく A2A なのか

「全てを1つのエージェントに任せる」のは、最初は速いです。プロンプトを長く書いてツールを増やせばたいてい何でもこなせます。しかし規模が大きくなると、次の問題が次々に出てきます。

第一に、コンテキストウィンドウの飽和です。1つのエージェントに全責任を負わせると、過去の会話・ツールの定義・ドキュメント・例示が膨れ上がり、肝心の判断のためのトークン余裕がなくなります。

第二に、評価が難しくなります。失敗したとき「どの判断ステップが間違ったか」を後から追えません。専門エージェントごとに切り出してあれば、ログから「ここで失敗した」が即座に分かります。

第三に、ツール権限の設計が雑になります。ファイル書き込み、外部 API 呼び出し、決済処理など、同じエージェントに集約していると、思わぬ場面で危険なツールが呼ばれます。

A2A はこれらを解消するために、エージェント同士を「サービス」として扱います。各エージェントは公開された Capability(できること)と Endpoint(受信口)を持ち、リクエストとレスポンスを構造化されたメッセージで交換します。

A2A メッセージの基本構造

A2A は基本的に JSON ベースのリクエスト/レスポンス形式です。Antigravity の Manager Agent から Surface Agent を呼ぶ場合の最小構造は次のような形になります。

{
  "jsonrpc": "2.0",
  "id": "req-001",
  "method": "agent.invoke",
  "params": {
    "capability": "code_review",
    "input": {
      "diff": "...",
      "language": "typescript"
    },
    "metadata": {
      "trace_id": "trace-abc-123",
      "deadline_ms": 30000
    }
  }
}

JSON-RPC 2.0 がベースになっていて、method は通常 agent.invoke または agent.streamparams の中に呼び出したい Capability と入力を入れます。

レスポンスは次のような形です。

{
  "jsonrpc": "2.0",
  "id": "req-001",
  "result": {
    "status": "completed",
    "output": {
      "issues": [
        {"severity": "warning", "line": 42, "message": "..."}
      ],
      "summary": "..."
    },
    "metadata": {
      "tokens_used": 1820,
      "elapsed_ms": 8421
    }
  }
}

ここで重要なのは、metadata です。トレース ID とトークン使用量を返すのは A2A 仕様で強く推奨されており、これがあるかないかで本番運用での観測可能性が大きく変わります。

Antigravity でのエージェント定義

Antigravity 上で A2A 対応エージェントを定義するには、エージェントの設定ファイルに capabilities セクションを追加します。

# .antigravity/agents/code-reviewer.yaml
name: code-reviewer
description: "TypeScript / Python のコードレビュー専門エージェント"
model: gemini-3-flash
a2a:
  enabled: true
  capabilities:
    - id: code_review
      description: "差分コードのレビュー"
      input_schema:
        type: object
        properties:
          diff: {type: string}
          language: {type: string, enum: [typescript, python, go]}
        required: [diff, language]
      output_schema:
        type: object
        properties:
          issues: {type: array}
          summary: {type: string}
  auth:
    method: api_key
    header: X-A2A-Token

a2a.enabled: true で受信エンドポイントが有効化され、capabilities で公開する機能を JSON Schema 形式で定義します。スキーマがあると呼び出し側が間違った入力を渡したときに即座にエラーで弾けるので、デバッグが格段に楽になります。

呼び出し側エージェントの実装

呼び出す側のエージェント(たとえば Manager Agent)は、A2A クライアントを使ってメッセージを送ります。Antigravity SDK を使った実装例です。

from antigravity.a2a import A2AClient
 
client = A2AClient(
    target_agent="code-reviewer",
    auth={"api_key": os.environ["CODE_REVIEWER_TOKEN"]}
)
 
result = await client.invoke(
    capability="code_review",
    input={
        "diff": diff_content,
        "language": "typescript"
    },
    metadata={
        "trace_id": trace_id,
        "deadline_ms": 30000
    }
)
 
if result.status == "completed":
    issues = result.output["issues"]
    # 後続処理
elif result.status == "failed":
    logger.error(f"Code review failed: {result.error}")

deadline_ms は重要なパラメータです。これを設定しないと、呼ばれたエージェントが無限ループに入ったときに呼び出し側が永遠に待つことになります。本番では必ず妥当な値(私は基本30秒、長い処理でも60秒)を設定してください。

ストリーミング応答の扱い

長時間かかるエージェント呼び出しでは、進捗を返したいことがあります。A2A では agent.stream メソッドで部分応答を返すことができます。

async for chunk in client.stream(
    capability="long_analysis",
    input=large_dataset
):
    if chunk.type == "progress":
        print(f"進捗: {chunk.percentage}%")
    elif chunk.type == "partial_result":
        # 中間結果を UI に反映
        update_ui(chunk.data)
    elif chunk.type == "completed":
        final_result = chunk.data
        break

ストリーミングは特にユーザー対面のアプリケーションで効果的です。「3分待たされて結果が返ってくる」よりも「30秒ごとに途中経過が見える」方が体感が大きく違います。

認証と権限スコープ

A2A では認証は実装側に委ねられていますが、最低限 API キーまたは OIDC トークンを使うべきです。私が実運用で使っている構成は次のようなものです。

auth:
  method: oidc
  issuer: https://auth.example.com
  audience: a2a://code-reviewer
  scopes:
    - capability:code_review:invoke
    - metadata:trace:write

スコープを capability 単位で切ると、たとえば「外部公開エージェントは code_review だけ呼べる、内部エージェントは code_reviewauto_fix を呼べる」という権限分離が綺麗にできます。

これを最初からやっておくと、後でエージェントを社外公開するときに認証回りで悩まずに済みます。

エラーハンドリングの設計

A2A のエラーは JSON-RPC のエラーコード体系に従います。私が本番で対応している主なエラーは次の通りです。

try:
    result = await client.invoke(...)
except A2AError as e:
    if e.code == -32000:
        # Server error: エージェント側の内部エラー(再試行可能)
        await retry_with_backoff()
    elif e.code == -32602:
        # Invalid params: 入力が schema に合わない(即時失敗)
        logger.error(f"Bad input: {e.data}")
        raise
    elif e.code == -32603:
        # Internal error: フレームワーク側のバグ(運用に通知)
        notify_oncall(e)
    elif e.code == 408:
        # Timeout: deadline_ms を超えた
        # → 部分結果が返っているか確認、必要なら再試行
        if e.data.get("partial_result"):
            return e.data["partial_result"]
        await retry_with_longer_deadline()

特に Timeout(408)の扱いは丁寧にやるのがおすすめです。部分結果が返っている場合があるので、そのまま捨てると無駄なコストになります。

メッセージのトレーシング

複数のエージェントが連鎖して呼ばれると、どこで何が起きたか追えなくなりがちです。trace_id を最初のリクエストで作って、後続の全 A2A 呼び出しに引き回すのが基本です。

async def handle_user_request(user_input):
    trace_id = generate_trace_id()
    
    plan = await planner.invoke(
        capability="plan",
        input={"goal": user_input},
        metadata={"trace_id": trace_id}
    )
    
    for step in plan.output["steps"]:
        result = await executor.invoke(
            capability="execute_step",
            input=step,
            metadata={"trace_id": trace_id}  # 同じ trace_id を引き継ぐ
        )

これで OpenTelemetry や独自のトレースバックエンドに集約すれば、「ユーザーの1回のリクエストが内部で何個のエージェントを呼び、それぞれ何ミリ秒かかったか」が1つのタイムラインで見えます。

失敗の局所化と再試行

複数エージェントが連鎖していると、1つが失敗したときの影響範囲が問題になります。私が採用している方針は「失敗は局所化する」です。

呼び出し側は、呼んだエージェントが失敗しても、それを上位に投げ返す前に「このエージェントが失敗したことで全体が失敗する必要があるか」を判断します。たとえばコードレビューが失敗してもデプロイは続行できることが多いので、ログを残してスキップします。

try:
    review_result = await reviewer.invoke(...)
    findings = review_result.output["issues"]
except A2AError as e:
    logger.warning(f"Review skipped due to: {e}")
    findings = []  # 空リストで続行

逆に、計画立てるエージェントが失敗したときは続行不可能なので、即座に上位に伝える。これを各エージェントごとに整理して、ハンドブックに書いておくと、運用時の判断が早くなります。

本番投入前のチェックリスト

A2A を本番に投入する前に、最低限次は確認しています。

入力スキーマと出力スキーマが定義され、CI で検証されているか。deadline_ms が全呼び出しに設定されているか。認証が API キー以上の強度で実装されているか。trace_id が全エージェント間で引き継がれているか。エラー時の上位への伝播ポリシーが文書化されているか。

これらが揃っていれば、エージェント間の連携が壊れたときも、原因を素早く特定できます。

実装の出発点

最初から大規模に組もうとせず、まず2つのエージェントを A2A で繋いでみてください。Manager Agent が Reviewer Agent を呼ぶ、というシンプルな構成で十分です。これだけでもメッセージのフォーマット、認証、エラー処理、トレーシングの基本が体得できます。

そこから3つ目、4つ目と増やしていくときに、この記事のチェックリストを横に置いて参照していただけると、つまずきが減ると思います。マルチエージェント設計は楽しい領域ですが、A2A のような共通プロトコルがないと急速に混沌に向かいます。最初にきちんと足場を組んでから走り出すのがおすすめです。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

Agents & Manager2026-06-19
Antigravity のサンドボックスでマルチエージェントを動かしても、隔離は思ったほど効いていない — 被害範囲を封じ込める運用メモ
Antigravity のサンドボックスは「隔離した気持ち」を与えますが、共有ボリューム・広すぎる許可ドメイン・承認疲れの3つで隔離は漏れます。漏れどころを塞ぎ、被害範囲を設計で封じ込め、隔離をテストで証明する運用メモです。
Agents & Manager2026-06-15
Antigravity のマルチエージェントを本番で壊さない封じ込め設計 — 連鎖障害を止める3つの境界の実装メモ
Antigravity のマルチエージェント構成は単体では綺麗に動くのに、本番では小さな失敗が全体に波及します。連鎖障害を止めるための制御の階層化・信頼境界・観測と冪等性という3つの境界を、TOML 設定と相関IDラッパーの実装まで含めて整理しました。
Agents & Manager2026-04-10
Antigravityマルチエージェント設計の実践ガイド — エージェント間通信エラーから本番安定稼働まで
Antigravityでマルチエージェントを設計・実装する完全ガイド。エージェント間通信エラーの解決から本番稼働パターン、パフォーマンス最適化まで体系的に解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →