これは同一のクロスクラウド通貨ベンチマークの3回目の実行で、矢印が逆方向を指すのは初めてです。Cloud Run上のGoogle ADKマスター(Gemini 2.5 Flash、us-central1)がベンチマークポリシーを所有し、Amazon Bedrock AgentCoreワーカー(Nova Micro上のStrands Agents、us-east-1)をA2A v1.0経由で呼び出し、MCP為替レートツールと照合します。

前回の実行ではBedrockがマスターでADKがワーカーでした。逆転はデプロイメント変更で済むはずでした。ドメインコアはフレームワーク非依存なので、比較ロジックは移動していません。実際に起きたことは、逆転により6つの相互運用性の欠陥が露呈し、そのうち5つはローカルテストでは検出できなかったことです。それが興味深い部分なので、本記事は失敗から始めます。

このプロジェクトは何を目指しているのか?

ほとんどのA2Aデモは「HTTP 200が返ってきた」で止まります。それはスモークテストで、相互運用性ベンチマークではありません。

このベンチマークは3つのモードで実行され、リモート検証のコストと作業コストを分離できます:

モード 実行内容 目的
mcp_only ADKマスターがMCP stdio経由でレートツールを呼び出す ベースライン
a2a_only ADKマスターがAgentCoreワーカーに委譲 リモートエージェントのコスト
verified 両方を同時に実行し、決定論的に比較 精度/オーバーヘッド

比較は算術的で、モデルの判断ではありません。金額とレートはDecimalです。モデルに2つの数値が一致するかどうかを尋ねることはありません。


破綻した6つの事柄

1. Strands A2Aエクストラが誤ったプロトコルバージョンを話す

これはデプロイ前に検出され、ワーカー設計全体を決定づけました。

strands-agents[a2a]a2a-sdk<0.4を固定し、A2A v0.3ワイヤメソッド(message/send)を使用します。google-adk[a2a]==2.5.0a2a-sdk 1.xを使用し、v1.0(SendMessage)です。v1.0クライアントはv0.3サーバーを呼び出せません。エージェントカードにはバージョン交渉がありません。

これは本プロジェクトの初回実行でADKエージェントからA2UI拡張が除外されたのと同じ分断が、反対側から再発したものです。

修正:エージェントループにはstrands-agentsを使用し、A2A v1.0サーバー面はa2a-sdkから直接構築します。

# app/CurrencyWorker/main.py -- NOT strands.multiagent.a2a.A2AServer
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes

app = Starlette(routes=[
    *create_agent_card_routes(AGENT_CARD),
    *create_jsonrpc_routes(_request_handler, rpc_url="/"),
])

フルスクリーンモードに入る フルスクリーンモードを終了

テストはロックファイルがa2a-sdkを1.xに固定していることをアサートし、依存関係の更新で不整合が静かに再導入されないようにします。

2. google-adk[a2a]は自身のサーバー依存関係をインストールしない

Cloud Runコンテナは正常にビルドされ、ローカルテストもすべてパスしましたが、起動時に死にました:

ModuleNotFoundError: No module named 'sse_starlette'
  File ".../google/adk/a2a/_compat.py", line 769, in attach_a2a_routes_to_app

フルスクリーンモードに入る フルスクリーンモードを終了

ADKのto_a2a()a2a.server.routesをインポートしますが、これはsse_starletteを必要とし、これはa2a-sdk[http-server]エクストラでのみ提供されます。google-adk[a2a]も裸のa2a-sdkもこれを引き込みません。

コスト:ロールアウト1回の失敗。両側でa2a-sdk[http-server]を宣言するようになりました。

3. IAMトラストポリシーが決して一致し得なかった

コーディネーターはAWSキーを保持しません。フェデレーションを行います:Cloud RunのメタデータサーバーがGoogle OIDCトークンを発行し、STSのAssumeRoleWithWebIdentityが一時的認証情報を交換し、リクエストにSigV4署名します。

トラストポリシーは明らかに正しく見えましたが、静かに不可能でした:

"Condition": { "StringEquals": {
  "accounts.google.com:aud": "currencybench-agentcore-worker",
  "accounts.google.com:sub": "1019138736740282766.."
}}

フルスクリーンモードに入る フルスクリーンモードを終了

AWSはこれらの条件キーを名前の示唆するクレームにマップしません:

条件キー 実際のGoogleクレーム
accounts.google.com:oaud トークンのaud
accounts.google.com:aud トークンのazp(数値のクライアントID)
accounts.google.com:sub トークンのsub

オーディエンスの文字列数値と比較されていました。オーディエンスはaudではなくoaudで固定します。

4. Google向けOIDCプロバイダーの作成がGoogleフェデレーションを破壊する

自然な次のステップ——accounts.google.comをIAM OIDC IDプロバイダーとして登録する——は誤りです。AWSはGoogleとネイティブにフェデレーションします。明示的なプロバイダーを作成すると、STSがそのプロバイダーのサムプリントで検証するよう切り替わり、すべての交換が失敗しました:

<Code>InvalidIdentityToken</Code>
<Message>The web identity token provided could not be validated.</Message>

フルスクリーンモードに入る フルスクリーンモードを終了

プロバイダーを削除すると修正されました。プリンシパルは裸のドメインです:

"Principal": { "Federated": "accounts.google.com" }

フルスクリーンモードに入る フルスクリーンモードを終了

失敗モードに注意:無効なトークンと拒否されたトラストポリシーは異なるエラー(InvalidIdentityToken vs AccessDenied)であり、この区別が2つのバグを区別する最速の方法です。

5. AgentCoreがA2Aバージョンヘッダーを剥離し、SDKがv0.3にデフォルトする

フェデレーションが機能し、SigV4署名が受け入れられた状態で、ワーカーは自身の正しくバージョンされたクライアントを拒否しました:

A2A version '0.3' is not supported by this handler. Expected version '1.0'.

フルスクリーンモードに入る フルスクリーンモードを終了

クライアントはa2a-sdk 1.1.2で、正しいヘッダー(client_factory.pyA2A-Version: 1.0を設定)を送信します。ワーカーはv1.0でした。カードは"protocolVersion": "1.0"を宣伝していました。

ヘッダーは到着しませんでした。AgentCoreのA2Aランタイムは許可リストの入ったリクエストヘッダーのみを転送し、サーバー側バリデータは欠落したヘッダーをv0.3として扱います:

# a2a/utils/version_validator.py
if not actual_version:
    return constants.PROTOCOL_VERSION_0_3

フルスクリーンモードに入る フルスクリーンモードを終了

ドロップされたヘッダーと古いクライアントはサーバーにとって区別できません。修正はランタイム設定の1行です:

"requestHeaderAllowlist": ["A2A-Version"]

フルスクリーンモードに入る フルスクリーンモードを終了

これはプロジェクト全体のバージョンずれテーマが3つ目の形で到達したものです:最初は推移的固定、次はフレームワークエクストラ、そして今度はプロキシです。

6. エージェントカードが両方向でバインドアドレスを宣伝する

AgentCoreワーカーのカードはhttp://127.0.0.1:9000——コンテナ自身のバインドアドレス——を宣伝します。ADKのto_a2a()http://127.0.0.1:8080で同様です。a2a-sdkクライアントはカードURLでルーティングするため、クロスクラウド呼び出しはクライアントがインターフェイスを実際にダイヤルしたエンドポイントに書き換えない限り失敗します。

初回実行でADKについて文書化されています。これはADK特有の問題ではありません。

ボーナス:A2AエンドポイントはベースURLのように見えるURLではない

agentcore statusはパスにランタイムARNがパーセントエンコードされ、/invocationsサフィックスが付いたURLを出力します。その完全URL、サフィックス込みがA2Aベースです:

https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/arn%3Aaws%3A...%2F<id>/invocations
                                                                       └── card at <base>/.well-known/agent-card.json

フルスクリーンモードに入る フルスクリーンモードを終了

/invocationsを削除すると——より整ったベースに見えるもの——UnknownOperationExceptionが返されます。エンコードされたARNではなく裸のランタイムIDを使用する場合も同様です。パーセントエンコーディングは、スコープされたIAMポリシーのランタイムIDを導出する際の素朴な${VAR##*/runtimes/}シェルパースも破壊します。


測定内容

2026-07-31にデプロイされたCloud Runコーディネーター経由で、us-central1us-east-1、5回のウォーム呼び出しを各モードで実行:

モード 中央値 最小 最大
mcp_only 2.96 s 2.37 s 3.84 s
a2a_only 18.92 s 16.30 s 19.68 s
verified 16.19 s 15.64 s 22.86 s

2つの点が際立ちます。

リモートエージェントホップはツールベースラインの約6倍のコストです。それはネットワークレイテンシではありません——us-central1からus-east-1は数十ミリ秒です。2回目のモデルターンです:ワーカーはエージェントなので、委譲はNova Microがツール呼び出しを計画し、呼び出し、構造化された返信を書き込むことを意味し、Geminiが同じことを行う上に成り立ちます。A2A委譲は独立性を買い、独立性は推論のコストを伴います。

verifieda2a_onlyより遅くない。VerifiedモードはMCP呼び出しA2A呼び出しの両方を行い比較しますが、中央値は低いです。コーディネーターは両方を同時発行します:

mcp_task = self._call("mcp", self._mcp, request, failures)a2a_task = self._call("a2a", self._a2a, request, failures)mcp_quotes, a2a_quotes = await asyncio.gather(mcp_task, a2a_task)

フルスクリーンモードに入る フルスクリーンモードを終了

したがってverified ≈ max(mcp, a2a)で、和ではありません。独立検証は、リモートエージェントのコストをすでに支払っている場合、ほぼ無料です。2つの中央値の差は実行間のばらつきより小さいので、verifiedが実際に速いわけではなく、等しいと読み取るべきです。

Correctness、verifiedモード、100 USD → EURおよびCHF:

EUR  rate 0.87138  ->  87.138    CHF  rate 0.81248  ->  81.248
primary:  mcp-stdio:frankfurter-live
verifier: aws-agentcore-a2a-worker

フルスクリーンモードに入る フルスクリーンモードを終了

両ターゲットが一致しました。両方ともrate is stale; check the observation timestampも含んでいました——MCP側はFrankfurterの日次基準レートを2026-07-30T00:00:00Zとして返し、31日の04:09Zでの実行に対し、24時間閾値を超えていました。実在の公開レートで発動した陳腐化ルールは、フィクスチャではなく、ここではより有用な信号です。


本実行が確立しないこと

  • IAMポリシーはまだ最小権限ではありません。bedrock-agentcore:InvokeAgentRuntimeruntime/<id>およびruntime/<id>/*にスコープすると403で拒否され、Resource: "*"のみが機能しました。AgentCoreは未特定ARN形状に対して認可し、データプレーンイベントはデフォルトでCloudTrailにありません。上記測定は広いポリシーで行いました。
  • nは5、1リージョンペア、1日、1モデルペアです。p95なし、コスト計算なし、トークン数なし、コールド/ウォーム分布なし。38ケース×3モードのマトリックスは本方向では実行されていません。
  • どちらのクラウドが速いかについての主張はありません。Gemini 2.5 FlashとNova Microは異なる価格帯の異なるモデルです。6×はエージェント委譲のコストで、AWSのコストではありません。

保持する価値のある部分

ドメインコアは変更されませんでした。CurrencyCoordinatorは2つのダックタイピングされたアダプタを取るので、どちらのクラウドがマスターをホストするかはデプロイメントの決定です。それは維持されました:比較ロジック、陳腐化ルール、失敗分類、評価ハーネスはすべて逆転後も無傷で残りました。

破綻したものはすべて接合部でした——プロトコルバージョン、依存関係エクストラ、IDフェデレーション、ヘッダー転送、URL形状。6つの欠陥のうち5つは、何か実物がデプロイされるまで不可視でした。ここから得るべき1つのこと:ノートPCから出ない相互運用性テストは、モックをテストしているに過ぎません。