このシリーズの過去 6 回の記事で、同じ通貨ベンチマークを
6 回構築し、そのたびにどのクラウドが命令を出し、どのクラウドがそれを受け取るかを変えました。同じドメインコア、同じ 38 件の評価ケース、同じ Decimal 比較、同じ公開レートソースをすべてのホップで使用し、変更したのはオーケストレーションの方向だけでした。

これで 3 つのクラウド間のすべての有向エッジが構築・測定されたことになります。本記事はその集計です。6 つの結果を並べたときの数値が示すこと、複数で再発した障害、そしてまだ測定されていないことです。

要約:A2A v1.0 はデプロイしたすべての方向で相互運用できました。
プロトコルがコストの高かったり、障害の原因になったり、最適化すべき部分になったことは一度もありませんでした。問題だったのはアイデンティティ、パッケージング、タイムアウトでした。

カバレッジマトリックス

3 つのクラウド、3 つのフレームワーク、6 つの可能な有向エッジ:

マスター(オーケストレーション) リモート(応答) ステータス
Microsoft Foundry、Azure Google ADK、Cloud Run デプロイ済み — 38 ケースマトリックス + ホストスモーク
Bedrock AgentCore、AWS Google ADK、Cloud Run デプロイ済み — 38 ケースマトリックス(ローカルハーネス) + ホストスモーク
Bedrock AgentCore、AWS Microsoft Foundry、Azure デプロイ済み — ホストスモーク
Microsoft Foundry、Azure Bedrock AgentCore、AWS デプロイ済み — ホストスモーク
Google ADK、Cloud Run Microsoft Foundry、Azure デプロイ済み — ライブ測定、5–6 回実行の中央値
Google ADK、Cloud Run Bedrock AgentCore、AWS デプロイ済み — ライブ測定、5 回実行の中央値

6 方向すべてデプロイ・測定済み。最後のケースが最も示唆に富みました。ローカルスイートが緑でコード完了した状態で 1 日置かれていましたが、デプロイすると 6 つの欠陥が浮上し、そのうち 5 つはローカルテストでは発見できませんでした。この比率は本シリーズで最も信頼できる発見です。

もう 1 つのリポジトリ gcp-adk-a2a-currency には Cloud Run 上の ADK コーディネーターが ADK リーフエージェントを呼び出すものがありますが、デフォルトの A2A ホップは GCP に留まります。プロトコルは検証しますが、クラウド境界を越えないため、上記 6 つとは比較できず、カバレッジにはカウントしません。

すべての実行で同じ 3 モードを使用しました:

モード パス
mcp_only マスターが自前の MCP 為替レートツールを呼び出す
a2a_only マスターが A2A v1.0 でリモートエージェントに委譲する
verified 両方を並行実行し、決定的コードで結果を比較する

どの実行でも、モデルがツールを選択し、回答を説明しました。算術を行ったり、合意を判断したりすることはありませんでした。それらは常に以下のコードで行いました:

difference = abs(primary.converted_amount - verifier.converted_amount)
relative_difference = difference / abs(primary.converted_amount)
agreed = relative_difference <= Decimal("0.005")

全画面モードに入る 全画面モードを終了

数値を並べて見る

レイテンシ列を読む前に provenance 列を読んでください。2 行は 114 レコードの評価マトリックス、2 行は単一のホストスモークケース、2 行は繰り返しライブ実行の小さな中央値です。これらは互換性のある証拠ではありません。

マスター → リモート リモートモデル / ホスティング mcp_only a2a_only verified 出典
Foundry → ADK gemini-2.5-flash、Cloud Run 297 ms 1.69 s 1.71 s 114 レコードマトリックス、中央値
AgentCore → ADK gemini-2.5-flash、Cloud Run 286 ms 2.09 s 1.87 s 114 レコードマトリックス、中央値
AgentCore → Foundry gpt-5-mini、Foundry ホスト ~4.2 s ~18.8 s ~18.1 s 単一ホストスモーク
Foundry → AgentCore Nova Micro、AgentCore 359 ms 25.1 s 28.2 s 単一ホストスモーク
ADK → Foundry gpt-5-mini、Foundry ホスト n/a 23.4 s n/a 5 回ライブ実行の中央値
ADK → AgentCore Nova Micro、AgentCore 2.96 s 18.92 s 16.19 s 5 回ライブ実行の中央値

2 つのマトリックス行の p95:Foundry → ADK a2a_only 4.82 s、verified 4.15 s;AgentCore → ADK a2a_only 6.10 s、verified 4.33 s。ADK → AgentCore 行の範囲:mcp_only 2.37–3.84 s、a2a_only 16.30–19.68 s、verified 15.64–22.86 s。

発見 1:ばらつきはプロトコルではなくリモートの実行時間

表の A2A レッグ時間は 1.69 s から 25.1 s まで、同じプロトコル、同じ SDK、同じ大陸横断ホップで 15 倍の差があります。変動要因はプロトコルではありません。

6 番目のエッジが存在する前には、より整然とした説明がありました:リモートの役割が決定要因だというものです。1 問に答えるリーフは約 2 秒、多ツール推論ループを実行するマスターは約 20 秒かかる、というものです。ADK → AgentCore の実行はこの理論を覆しました。AgentCore ワーカーは定義上リーフであり、1 つのツールコールで 1 問に答えるのに、それでも 18.92 秒かかりました。

6 行が実際に並ぶのは、リモートの 実行時間とモデル で、役割ではありません:

  • リモートが gemini-2.5-flash on Cloud Run のもの:1.69–2.09 s
  • リモートが gpt-5-mini on Foundry ホスティングのもの:18.8–23.4 s
  • リモートが Nova Micro on AgentCore Runtime のもの:18.9–25.1 s

このグループ化は両方向で成立し、どのクラウドがオーケストレーションしているかに関係ありません。正直な解釈は、委譲コストは 1 回の推論とリモートプラットフォームのリクエストオーバーヘッドの合計であり、2 つのホストエージェントランタイムは Cloud Run 上のコンテナより 1 オーダー大きいコストがかかる、ということです。そのうちどれがモデル速度で、どれがプラットフォームオーバーヘッドかは分離していません — それは同じモデルを両方のランタイムで実行する必要があり、まだ実行していません。

そうではないのはプロトコルです。ADK → Foundry パスで直接測定しました:認証付きエージェントカード取得は ウォームで 0.35 秒(コールドで 0.51 秒)、トークンはコール間でキャッシュされます。パスに 2 番目のクラウドを追加すること — Cloud Run ADK プロキシ経由の 23.4 秒に対してワークステーションからの直接コール 21.2 秒 — は約 2 秒のコストで、実行間のばらつき 18.9–29.5 秒に対してでした。マスター単体のばらつきの方が、追加クラウド全体のコストより大きかったのです。

クロスクラウドエージェントアーキテクチャを高速化したいなら、プロトコルやプロキシが時間を費やしている場所ではありません。

発見 2:verified モードは A2A コール 1 回分で、2 回分ではない

すべての実行で、verified は 2 つのレッグの合計ではなく、より遅いレッグのコスト程度で収まりました。MCP と A2A が並行実行されるためです:

  • Foundry → ADK: a2a_only 1.69 s、verified 1.71 s
  • AgentCore → ADK: a2a_only 2.09 s、verified 1.87 s(verified のマトリックス中央値が a2a_only を 下回った — 同じ分布、異なるケース構成)
  • AgentCore → Foundry: verified 実行内で A2A レッグ約 18.1 s、MCP レッグ約 3.1 s
  • Foundry → AgentCore: a2a_only 25.1 s、verified 28.2 s
  • ADK → AgentCore: a2a_only 18.92 s、verified 16.19 s — verified が再び a2a_only を 下回り、実行間のばらつきより小さい。これら 2 つは等しいと読むべきで、検証が無料プラス払い戻しであるとは読めません。

並行性は明示的で、偶発的ではありません:

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)

全画面モードに入る 全画面モードを終了

したがって、独立したクロスクラウド検証の代償は「リモートコール 1 回」で、「自分のベースラインにリモートコール 1 回を加えたもの」ではありません。

発見 3:ハッピーパスでは精度差はゼロ

デプロイしたすべてのクロスクラウド verified 実行で、正確な合意が報告されました — relative_difference: "0"agreed: true、警告なし:

  • AgentCore → ADK、ホスト:EUR と CHF、差ゼロ
  • AgentCore → Foundry、ホスト:レート 0.87873、金額 87.87300、両側
  • Foundry → AgentCore、ホスト:レート 0.87138、両側
  • Foundry → ADK、ホスト:agreed: truerelative_difference: 0
  • ADK → Foundry、ライブ:5 回の実行すべてで同一のクォート
  • ADK → AgentCore、ライブ:EUR 0.8713887.138 と CHF 0.8124881.248、両方合意、primary mcp-stdio:frankfurter-live、verifier aws-agentcore-a2a-worker

これは設計によるものです。両側が意図的に同じ Frankfurter 日次参照レートを読み取るため、不一致はプロトコル、モデル、コードにしか由来しません — データソースのずれによるものではありません。

最後の実行から得られた 1 つの詳細は、合意そのものよりも価値があります:両ターゲットが rate is stale; check the observation timestamp も出力したことです。Frankfurter の日次参照レートは 2026-07-30T00:00:00Z とタイムスタンプされ、31 日 04:09Z の実行に対して — 24 時間閾値を超えていました。注入されたフィクスチャではなく実際の公開レートで陳腐化ルールが発火し、モデルがそれを安心できる散文に丸め込まずにコーディネーターが表面化したことは、より有用なシグナルです。

114 レコードマトリックスの 96.77% 合意率は矛盾ではありません。38 ケースには意図的に注入された障害(タイムアウト、陈腐レート、強制不一致、悪意のあるツールテキスト)を含み、障害ケースが 100% を下回らせています。ライブアダプタレコード 60 件すべてが許容範囲内で合意しました。 実行全体で不一致だったのは注入したものだけで、検証側はすべてをフラグ立てました。

これで「検証に価値があるか」という質問に正直に答えられます:ハッピーパスでは精度で何も得られません。余分な 1 秒 — あるいは余分な 20 秒 — で得られるのは 独立した障害検知 です。異なるベンダーのモデルで、2 つ目のクラウド上で、2 つ目の実装が、ツールが侵害されたり陈腐化したり誤っていたりすると大声で不一致を報告します。

複数回出現した障害

6 回の構築、6 セットのバグ、そして多くの重複。これらは再発したものです。

A2A v0.3.0 → v1.0 のワイヤ断絶

6 回の構築のうち 5 回で発生し、通常は同じように見えました:

a2a.utils.errors.MethodNotFoundError: Method not found

全画面モードに入る 全画面モードを終了

または生 JSON-RPC で:

{"error": {"code": -32601, "message": "Method not found",
           "data": [{"reason": "METHOD_NOT_FOUND","metadata": {"detail": "'method' field is not a valid A2A method."}}]}}

全画面モードに入る 全画面モードを終了

v0.3.0 は message/send をルーティングします。v1.x は SendMessage を proto-JSON パラメータでルーティングします。フォールバックネゴシエーションはありません:クライアントは protocolVersion: 0.3.0 を明示的に宣言するエージェントカードを取得し、それでも v1.0 メソッドを呼び出します。

完全に認可されたエンドポイントで MethodNotFoundError を見る場合は、他の何かをデバッグする前に両側の a2a-sdk メジャーバージョンを確認してください — また、Foundry カードが 1 つの URL に 3 つのインターフェース(JSONRPC 1.0、JSONRPC 0.3、HTTP+JSON 0.3)を広告し、誤ったものを選択させてしまう可能性があるため、自分のクライアントのメソッド表記も確認してください。

6 回目の構築では同じずれを生む 3 つ目の方法を発見しました。これは 両端が正しい ため、より厄介です。v1.0 クライアントが "protocolVersion": "1.0" を広告する v1.0 ワーカーを呼び出し、以下を得ました:

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

全画面モードに入る 全画面モードを終了

a2a-sdk 1.1.2 は A2A-Version: 1.0 を送信します。AgentCore Runtime は許可リスト登録済みリクエストヘッダーのみを転送するため、ヘッダーは到着せず、サーバー側のバリデータは 欠落 ヘッダーを v0.3 として扱います:

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

全画面モードに入る 全画面モードを終了

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

"requestHeaderAllowlist": ["A2A-Version"]

全画面モードに入る 全画面モードを終了

3 回の構築、3 つのメカニズム — 推移的ピン、フレームワークエクストラ、そしてプロキシがヘッダーを静かに剥奪 — すべてが同じバージョンずれ症状に着地しました。2 つの A2A エンドポイント間の任意の中間体はプロトコルバージョンが失われる場所であり、欠如を古いバージョンにデフォルトすることは、トランスポートバグをクライアントバグのように見せます。

依存関係ピンは、ピンでなくなるとプロトコル制約になる

エコシステムピンは、これらの構築のさまざまな時点で相互排他的でした:

パッケージ a2a-sdk 要件
strands-agents 1.50.2 >=1.0.0,<2
agent-framework-a2a 1.0.0b260721 >=1.0.0,<2
google-adk 2.1.0 – 2.4.0 >=0.3.4,<0.4
google-adk 2.5.0 >=0.3.4,<2
a2ui-agent-sdk (0.4.0 まで) <0.4.0
strands-agents[a2a] (最新) <0.4

google-adk 2.5.0 が GCP 側をアンブロックしたリリースでした。A2UI は完全に削除されました。プロトコル関連のエクストラがエージェント全体を v0.3.0 に固定していたためです — コミット前にエクストラを監査してください。

Foundry → AgentCore の構築で有用な教訓が得られました。AgentCore の A2A サーバーエクストラは 0.3 系統を求め、Foundry 側のクライアントは 1.x を使用していました。両方を 1 つのバンドルにインストールすることは不可能でした。修正はバージョンアップではなくアーキテクチャ的でした:AgentCore アプリケーションバンドルを互換性のある SDK に固定し、クライアントを分離し、2 つに通信させました。Python パッケージがバージョンを共有していなくても、ネットワークプロトコルは相互運用できました。 パッケージバージョンの一致は同一プロセス要件であり、ワイヤ要件ではありません。

6 回目の構築はもう 1 つの利用可能な回答を取り、それも機能しました:エージェントループにはエージェントフレームワークを保持し、その [a2a] エクストラを削除し、A2A v1.0 サーバーサーフェスを a2a-sdk から直接構築する、というものです。

# 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="/"),
])

全画面モードに入る 全画面モードを終了

したがって、誤ったワイヤバージョンを固定するエクストラからの 2 つの検証済み脱出策があります:バンドルを分離する、またはエクストラをバイパスする。どちらもデプロイ済み実行で裏付けられています。各側のロックファイルテストにより、依存関係のバンプが静かに不一致を再導入することを防げます。

すべてのデフォルトタイムアウトは 1 つのクラウドを前提とする

コーディネーターの元々のアダプタあたり 10 秒タイムアウトは、ローカルでは寛大でしたが 6 回のクロスクラウド実行すべてで致命的でした。クリーンに失敗したのは唯一の良い点でした:

{"failures": {"a2a": "timeout: adapter timed out"}, "elapsed_ms": 10010}

全画面モードに入る 全画面モードを終了

この障害は、直接コールで既に 25 秒かかることが分かっていたパスから来ました。アダプタは問題なく、ポリシーが誤っていました。最終値:AgentCore および Foundry マスターホストパスは 60 秒、ADK クライアントおよび Foundry コーディネーターは 120 秒。

コールドスタートは単にレイテンシを追加するだけではありません。ADK ワーカーからのコールドスタート応答は、要求されたターゲットを完全に省略 しました。パースが厳密であるため、これは静かに短い回答ではなく、型付きの protocol 障害として表面化しました。部分的な応答を設計してください。

アイデンティティがすべてだった

6 回の構築を通じて最も強いパターン:A2A 相互運用性はプロトコルの衣をまとった認証問題です。 JSON-RPC の半分はどの方向でも簡単な半分でした。

AWS または GCP から Foundry 呼び出す:

  • 着信 A2A は オプトインおよびプレビュー です — ホスト Foundry エージェントは A2A を PATCH するまで Responses を話します。
  • PATCH の protocol_configuration置換で、マージではありません。仮定せず調査しました:{"a2a": {}} のみの PATCH で Responses が削除され、A2A エージェントカードも一緒に削除されました(400) — しかもそのリクエストは 200 を返しました。
  • 匿名 /.well-known/agent-card.json はありません。カードは Entra の背後にある <agent>/endpoint/protocols/a2a/agentCard/v1.0 にあります。発見自体が特権コール であるため、カード上の 404 または 401 は誤ったパスではなく、不足しているロール割り当てである可能性が高いです。
  • 呼び出し元にはプロジェクト上の Foundry Agent Consumer データプレーンロールが必要で、デプロイヤーには Foundry Project Manager が必要です。Azure Owner はどちらも含みません。
  • まずトークンオーディエンスを正しく取得してください:https://ai.azure.com/.default で、ARM オーディエンスではありません。誤ったスコープの完全に有効なトークンは、まったくスコープ問題に見えない 401 を生成します。

Azure から AgentCore 呼び出す:

  • AgentCore のデフォルトランタイム認可は IAM/SigV4 で、AWS 呼び出し元には正しいですが、AWS リクエストに署名できない Foundry コンテナには無用です。直接の AWS CLI 呼び出しは機能しましたが、クロスクラウドは機能しませんでした。
  • 修正はカスタム JWT オーソライザーと Cognito マシン間クライアントで、A2A アダプタが OAuth クライアントクレデンシャル交換を行い、トークンをキャッシュし、オーソライザーが発行者、クライアント、および必須の currencybench/invoke スコープを検証しました。

GCP から AgentCore 呼び出す — 同じ壁、異なる回答:

6 回目の構築は同じ CUSTOM_JWT アプローチから始め、それを AgentCore のデフォルト AWS_IAM オーソライザーへのキーレスフェデレーションに置き換えました:Cloud Run のメタデータサーバーが Google OIDC トークンを発行し、STS AssumeRoleWithWebIdentity が一時クレデンシャルと交換し、リクエストは SigV4 署名されます。コンテナ内に長期 AWS クレデンシャルは存在しません。

そこに至るまでに、どちらも正しい設定のように見えた 2 つの IAM 欠陥がありました。

条件キーはその名前の意味ではありません。 この信頼ポリシーは構文的に有効で、正しく読めますが、決して一致しません:

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

全画面モードに入る 全画面モードを終了

条件キー 実際の Google クレーム
accounts.google.com:oaud トークンの aud — オーディエンス文字列
accounts.google.com:aud トークンの azp — SA の数値クライアント ID
accounts.google.com:sub トークンの sub

オーディエンス文字列数値と比較されるため、STS は永遠に「Incorrect token audience」と答えます。オーディエンスを oaud で固定してください。

そしてサブジェクトも固定してください。オーディエンスだけでは認可になりません。
これがキーが正しくなった後でも重要な部分です。任意の Google プリンシパルは任意のオーディエンス文字列の ID トークンを要求できます — オーディエンスは呼び出し元が選択する値で、プラットフォームが付与する権限ではありません。オーディエンスのみに条件付けされた信頼ポリシーは、「ある Google アイデンティティがこれをミントした」という以上のことを証明せず、共有組織ではほとんど何も証明しないに等しいです。

したがってポリシーは sub にも条件付けし — アカウントが削除・再作成された場合に解放・再バインド可能なメールアドレスではなく、サービスアカウントの不変な数値 ID で:

gcloud iam service-accounts describe "$SA_EMAIL" --format='value(uniqueId)'

全画面モードに入る 全画面モードを終了

また、ベアラトークンを出荷する代わりに、呼び出し先のネイティブオーソライザーへフェデレーションする議論でもあります:信頼決定はアプリ設定に住むクレーム文字列マッチャーではなく、CloudTrail に実際のプリンシパル属性を持ち、ポリシーを編集することで失効可能な IAM ポリシーで行われます。

Google を OIDC プロバイダーとして登録すると Google フェデレーションが壊れます。 明白な次の手 — aws iam create-open-id-connect-provider for accounts.google.com — は誤りです。AWS は Google とネイティブにフェデレーションします。明示的なプロバイダーを作成すると STS がそのプロバイダーのサムプリントに対して検証するよう切り替わり、すべての交換が失敗します:

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

全画面モードに入る 全画面モードを終了

プロバイダーを削除すると修正されます。プリンシパルは裸のドメインです:"Federated": "accounts.google.com"

ここでの有用な診断は、これら 2 つのバグが異なるエラーを生成することです — InvalidIdentityToken はトークンがまったく検証できなかったことを意味し、信頼ポリシーの不一致は AccessDenied です。この区別が、「プロバイダー設定が誤っている」と「条件が誤っている」を最も速く見分ける方法です。

同じ AWS ランタイムへの 2 つのクロスクラウドコール、2 つの異なる回答:Azure からのベアラトークン、GCP からのキーレスフェデレーション。どちらもデプロイ・測定済みです。

そしてクレデンシャルは常に呼び出し側に存在します。Google が Azure を呼び出すには、Google Secret Manager に Azure クライアントシークレットが必要です。AWS が Azure を呼び出すには、AWS Secrets Manager に Entra サービスプリンシパルクレデンシャルが必要で、単一のシークレット ARN にスコープされます。シークレットが存在することを避ける方法はありません。ソースツリー、マニフェスト、シェル履歴に存在することを避ける方法はあります。

予算化すべき 1 つのタイミングトラップ:データプレーン RBAC の伝播に 105 秒かかりました。一方で az role assignment list は既に割り当てが完了していると表示していました。エージェント書き込みは常に 403 を返していました。これは伝播で、設定ミスではありません。ロールを再割り当てする前に待ってください。ソフト削除された Foundry アカウントも、パージされるまで再作成をブロックしました — Azure リソース状態にも記憶があります。

テストはソースツリーをインポートし、ユーザーはイメージを実行する

このクラスのバグは 3 回の構築で出現し、毎回緑のテストスイートには見えませんでした。

aiohttp が独立して 2 つを破壊しました。azure.identity.aioazure-identity のハード依存関係ではない aiohttp で非同期トランスポートを構築します — 通常のワークステーションには存在するため、ローカル実行はパスします:

File "azure/core/pipeline/transport/__init__.py", line 94, in __getattr__
    raise ImportError("aiohttp package is not installed")

全画面モードに入る 全画面モードを終了

AWS では、リポジトリルートの requirements に追加するだけでは不十分でした。CodeZip はアプリケーションバンドルを独立して解決するため、app/CurrencyCoordinator/pyproject.toml に固定する必要がありました。GCP では uv sync --frozen でコンテナ内に同じ不在が生じました。

このファミリーの他の 2 つは、どちらも ADK が Foundry を呼び出すクライアントイメージから:エントリに entra_auth.py が一切書かれていない COPY pyproject.toml uv.lock agent.py ./ 行のため、モジュールがイメージにまったく存在しなかったこと。および ADK のシングルエージェントモードがファイルを app.agent としてインポートするため解決できないフラットな from entra_auth import EntraAuth/sys.path にあり /app にないパッケージのサブモジュールです。相対インポートは両方のコンテキストで機能します。

Foundry マスター側でも同じ形状:MCP stdio ツールは実際のサブプロセスを実際のインポートパスで起動するため、-m mcp_server.server はデプロイバンドル内にないモノレポの兄弟をインポートできません。修正は azd に渡す前に rsync でバンドル内に入れることでした。エレガントではありません。ホストランタイムはリポジトリレイアウトを気にしません。

6 回目の構築はこのパターンの最もきれいな標本を生みました。コンテナはビルドされ、すべてのローカルテストはパスしましたが、起動時に死にました:

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 もこれを引き込みません。aiohttp と同じ形状 — ワークステーションに偶然存在するオプションの推移的依存関係 — ですが、初回コールではなく起動時に失敗したため、Cloud Run はヘルスチェックタイムアウトとして報告し、gcloud は誤解を招く Resource 'currency-adk-coordinator' already exists 競合を表示しました。実際のエラーはリビジョンログにのみ存在しました。

このクラスのバグを発見するのは、ローカルでコンテナの実際のディレクトリレイアウトを読み込むか、デプロイして呼び出すかの 2 つだけです。

from google.adk.cli.utils.agent_loader import AgentLoader
AgentLoader("/app").load_agent("app")   # 修正前の ModuleNotFoundError

全画面モードに入る 全画面モードを終了

系論:モデルを通るエラーは観測可能ではない

上記の STS 障害のデバッグに、愚かな理由で 2 回のデプロイサイクルを費やしました。アダプタは正確で実行可能なメッセージを発生させました — そしてそれは以下のように戻ってきました:

The remote agent reported an issue with the web identity token.

障害テキストはツール結果として返されるため、マスターモデルを通過し、モデルが言い換えます。デバッグの対象とするものは、単に発生させるのではなく、障害発生点でサーバーサイドにログ記録する必要があります。上流の詳細にも同じことが当てはまります:アダプタは STS レスポンスボディを破棄し、HTTP ステータスのみを報告していました。これは、オーディエンスの不一致と検証不能なトークンを区別できない半分そのものです。

エージェントカードの移植性は内部 URL の移植性と同じ

ADK の to_a2a() は公開カードにホスト、ポート、スキームを焼き付けます。ローカルデフォルトのままにすると、Cloud Run エージェントは http://127.0.0.1:10001 を広告するカードを配布し、リモートクライアントは忠実に自分自身を呼び出そうとします。コンテナは A2A_PUBLIC_URL を読み取る必要があります — Cloud Run は最初のデプロイが完了するまでサービス URL を公開しないため、デプロイスクリプトは2 パス目にしか設定できません。

最初の構築の後、これを ADK の癖として書きました。そうではありません。AgentCore ワーカーのカードも全く同じ理由で http://127.0.0.1:9000 — 自身のコンテナバインドアドレス — を広告します。a2a-sdk クライアントはカード URL でルーティングするため、本シリーズのすべてのクロスクラウドクライアントは、カードのインターフェースを実際にダイヤルしたエンドポイントに書き換えることになります:

for interface in card.supported_interfaces:
    interface.url = endpoint

全画面モードに入る 全画面モードを終了

クラウド境界を越えて「カードがメッセージの送信先を教えてくれる」を両方向で参考情報として扱ってください。

A2A ベース URL はベース URL のように見えるものではない

agentcore status はパスにパーセントエンコードされたランタイム ARN と /invocations サフィックスを出力します。その完全な 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 を代用しても同じです。パーセントエンコーディングには 2 つ目の咬みつきがあります:スコープ付き IAM ポリシーのランタイム ID を素朴な ${VAR##*/runtimes/} で導出すると、デプロイは正常に完了するが呼び出し時にのみ失敗する不正な ARN が得られます。分割する前にデコードしてください。

反対方向から来ると、Foundry はカードを手書きさせるようになります。MCP ツールが自動的にスキルとして表示される ADK の後では、後退のように感じられます。また、正直さへの一歩でもあります:カードは契約であり、その側ではそれを述べることを強いられます。

モデルレイヤーの障害はプロトコル障害ではない

相互運用バグのように読まれるため、分離する価値があります:

  • Nova Micro は「100 USD を EUR に変換せよ」を読み、ターゲット通貨が欠落していると主張し、ユーザーに確認を求めました。ツールの可用性が問題ではなく、自然言語引数抽出が問題でした。マスタープロンプトは既に存在する情報の確認要求を禁止するようになり、リグレッションテストがそれを保持します。
  • Gemini 2.5 Flash は実際にライブの hosted-adk-a2a クォートを非ライブと報告しました。ソース名がフィクスチャソース hosted-local-a2ahosted- プレフィックスを共有し、共有命令が部分文字列で区別するためです。データは正しく、散文のみが誤っていました。同じ命令は Nova Micro と gpt-5-mini では正しくラベル付けされます — したがってモデルバグではなく共有命令バグです。
  • gpt-5-minigemini-2.5-flash の両方が、すべての実行で「ツールを呼び出し、計算するな」を尊重しました。算術を Decimal に保持することで、合意チェックは数値を比較し、モデルの散文を比較しません。

ベンチマークをフェイルクローズにする

最も重要な非技術的教訓。エンドポイント設定が欠落しているときに決定的フィクスチャに静かにフォールバックするホストベンチマークツールは、何もないところからきれいに見える数値を渡し、それを公開することになります。両方のホストコーディネーターは現在拒否します:

{ "name": "CURRENCY_REQUIRE_GCP_ADK", "value": "1" }

全画面モードに入る 全画面モードを終了

これを設定すると、a2a_onlyverified はもっともらしいフィクスチャ回答ではなく gcp_adk_not_configured を返します。ローカルフィクスチャ実行はまだ機能しますが、名前で要求する必要があります(CURRENCY_ALLOW_LOCAL_FIXTURES=true)。

6 回反転しても生き残ったもの

ドメインコア。2 つのインターフェース — MCP バックの ExchangeRateTool と A2A バックの RemoteCurrencyAgent — で、Microsoft Agent Framework、Strands、ADK はすべてその外側にあり、すべてのホスティングランタイムもその外側にあります。

どのクラウドがオーケストレーションするかを反転させても、ドメインサービスを書き換える必要はありませんでした。フレームワーク非依存の Python が 6 回の構築すべてで算術、並行性、タイムアウトポリシー、障害分類を所有し、同じ 38 ケースと比較器がすべてに対して実行されました。この移植性が本当の結果です — 個々のレイテンシ数値よりも、そして 2 つの SDK を 1 つのプロセスに入れることよりも、はるかに。

6 回すべてで壊れたものはすべて継ぎ目で壊れました:プロトコルバージョン、依存関係エクストラ、アイデンティティフェデレーション、ヘッダー転送、URL 形状。ドメインロジックで壊れたものはありません。それがプロジェクト全体の結果の形状です。

型付き障害は 6 つを比較可能にする上で多くの役割を果たしました。各アダプタ例外は、1 つの境界で正確に validationproviderauthenticationtransporttimeoutprotocol のいずれかに正規化されます。「どのレイヤーが壊れたか」はログを grep する演習ではなく、研究上の質問だからです。

これが確立しないこと

緑のスモークテストから過大に主張したくなる誘惑こそが、本シリーズ全体が抵抗するために構築されたものであるため、率直に述べます:

  • 6 行のうち 2 行は単一観測です。 1 回のホスト要求はレイテンシ分布ではありません。Foundry → AgentCore の実行は測定されたアダプタ作業 28.2 秒に対してエンドツーエンドで約 45 秒かかりました。その差はモデルとホスト応答オーバーヘッドであり、1 サンプルではどちらの数値もベンチマークと呼ぶのは無責任です。
  • 完全な 38 ケースマトリックスがあるのは、GCP リモートパス 2 つのみです。 AgentCore ↔ Foundry ペアと ADK がマスターのパスは、まだマトリックスに対して実行されていません。
  • トークン使用量とクラウドコストはどこでも未測定です。 スロットリングやトークン有効期限下の動作、繰り返しのウォーム/コールド分布も同様です。
  • AgentCore → ADK マトリックスはローカルハーネスで実行されました。AgentCore ホスティングレイヤーを通していません。ホスティングを実際に使ったのはスモークテストのみです。このラベルを明示的に保持することで、ハーネスレイテンシを AgentCore に帰属させることを避けます。
  • ここではどのフレームワークやクラウドが他より優れているかを示すものはありません。 レイテンシ差はリモートが使用したモデルとランタイムを追跡するもので、プラットフォーム品質ではありません — また、モデル速度とプラットフォームリクエストオーバーヘッドを分離していません。それには両方のランタイムで同じモデルを実行する必要があります。
  • ADK → AgentCore の数値は広い IAM ポリシーで取得されました。 bedrock-agentcore:InvokeAgentRuntimeruntime/<id>runtime/<id>/* にスコープすると、エージェントカード取得で 403 が拒否されました。Resource: "*" のみが機能しました。AgentCore はまだ特定していない ARN 形状に対して認可し、デフォルトでデータプレーンイベントは CloudTrail に表示されないため、拒否を読み取れませんでした。したがってこの行は最小権限設定ではなく、そのように出荷しません。
  • 2 行は n=5、1 日、1 リージョンペア、1 モデルペアです。 p95、トークン数、コスト会計、これらのウォーム/コールド分離はありません。

6 行での集計

  1. A2A v1.0 は 6 方向すべてで相互運用できました。 スキーマ変換はなく、パース可能な JSON を求める以上のフレームワーク固有の接着剤はありませんでした。
  2. プロトコルが遅い部分になることはありません。 A2A レッグはリモートのモデルとランタイムを追跡しました — Cloud Run コンテナへは 1.7–2.1 秒、どちらのホストエージェントランタイムへも 18.8–25.1 秒。認証付きカード取得は 0.35 秒、パスに 2 番目のクラウドを追加するコストは約 2 秒でした。
  3. クロスクラウド A2A はアイデンティティプロジェクトです。 サービスプリンシパル、データプレーンロール、トークンオーディエンス、呼び出し先での非ネイティブ認証、目に見えない RBAC 伝播に予算を割いてください。呼び出し元がワークロード OIDC トークンをミントできる場合、呼び出し先のネイティブオーソライザーへのフェデレーションはベアラトークンを出荷するより優れています — そしてオーディエンスだけでなくサブジェクトも固定してください。
  4. バージョンずれは大声で、しかし遅れて失敗します。3 つの別々のメカニズムを通じて:推移的ピン、フレームワークエクストラ、バージョンヘッダーを剥奪するプロキシ。パッケージピンはプロセスを制約し、ワイヤを制約しません。バンドルを分離するか、エクストラをバイパスしてください。どちらも検証済みの脱出策です。
  5. イメージはソースツリーではありません。 緑のテストスイートを生き残ったすべてのバグは、パッケージングまたは継ぎ目のバグでした — そして最後の構築では、6 つの欠陥のうち 5 つが実際に何かがデプロイされるまで見えませんでした。
  6. 検証は独立した障害検知を買うもので、精度を買うものではありません。 ハッピーパスでは両クラウドは毎回正確に合意しました。注入された障害 — およびライブの Frankfurter クォートで発火した実際の陈腐レート警告 — が、2 つ目のクラウドが 2 つ目の分を稼ぐ場所です。

ソース

各リポジトリにはデプロイスクリプト、テスト、評価ケース、生の結果があります:

上流エージェントは
MCP、ADK、A2A の入門

クラウド境界を越えて A2A を実行したことがある方 — 特に AgentCore が実際に認可する ARN 形状を特定した方、またはこれら 6 つが行わなかったカード/バージョンの不一致に遭遇した方 — ぜひ知見を共有させてください。