A2A エージェントディスカバリー
このデプロイメントはエージェントを公開できるため、別のデプロイメントや任意のHTTPクライアントがエージェントを見つけて呼び出せます。A2Aは、あるエージェントが別のエージェントに到達するためのプロトコルであり、人がチャットアプリを通じてボットに到達するのと同じ仕組みです。このページでは、入力すべき内容と返される内容を正確に示します。
このページのすべての応答は、実行中のデーモンからの実際の出力です。ここにあるものは何も例示ではありません。
認証
2つのディスカバリー用GETは認証不要です。カタログカードとエイリアスごとのエージェントカードは、ピアがペアリング前に公開されているサーフェスを検出できるよう、トークンなしで読み取れます。SendMessage のPOSTは異なります。ツールを使用可能な完全なエージェントのターンを実行するため、他のすべての書き込み系サーフェスと同様に、ゲートウェイのペアリング認証の対象になります。[gateway] require_pairing が有効な場合(デフォルト)は、タスクのPOSTにペアリングから派生したベアラートークンを渡してください:
curl -X POST http://localhost:42617/a2a/agent_alpha \
-H "Authorization: Bearer $ZEROCLAW_TOKEN" \
-H "A2A-Version: 1.0" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{...}}'
未認証の task POST は 401 を返し、エージェントターンにはなりません。以下の discovery GET にはヘッダーは不要です。トークンの取得方法については gateway pairing のドキュメントを参照してください。
全体を2つのリクエストで
エージェントを検出するには、2つのGETリクエストだけで済みます。
まず、デプロイメントが公開しているエージェントを問い合わせます:
curl http://localhost:42617/.well-known/agents-card.json
次に、それらのエージェントの1つに何ができるかを尋ねます。
curl http://localhost:42617/a2a/agent_alpha/.well-known/agent-card.json
最初のリクエストでは、エージェントの URL のリストが返されます。2 番目のリクエストでは、1 つのエージェントのスキルと、作業を送信する URL が返されます。これがディスカバリの対象範囲のすべてです。このページの残りの部分は、これら 2 つのレスポンスを注意深く読むだけです。
リクエスト1: エージェントの一覧を表示
curl http://localhost:42617/.well-known/agents-card.json
応答:
{
"name": "ZeroClaw agents",
"description": "Discovery catalog enumerating published A2A agents on this ZeroClaw install. Not a runnable agent; each entry below serves its own A2A card and endpoint. Skills are aggregated from the published agents, each tagged with its owning alias.",
"supportedInterfaces": [
{
"url": "http://localhost:42617/.well-known/agents-card.json",
"protocolBinding": "catalog",
"protocolVersion": "1.0"
},
{
"url": "http://localhost:42617/a2a/agent_beta",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
},
{
"url": "http://localhost:42617/a2a/agent_alpha",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
}
],
"version": "0.8.7",
"capabilities": {
"streaming": false,
"pushNotifications": false,
"extendedAgentCard": false
},
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"skills": [
{
"id": "agent_beta/github-issue-triage",
"name": "github-issue-triage",
"description": "Issue triage and lifecycle management agent for ZeroClaw.",
"tags": ["github", "issues", "triage", "agent_beta"]
},
{
"id": "agent_beta/github-pr-review-session",
"name": "github-pr-review-session",
"description": "Human-reviewer co-pilot for ZeroClaw PR reviews.",
"tags": ["github", "pull-requests", "review", "agent_beta"]
},
{
"id": "agent_alpha/zeroclaw",
"name": "zeroclaw",
"description": "Help users operate and interact with their ZeroClaw agent instance.",
"tags": ["operations", "cli", "gateway", "agent_alpha"]
},
{
"id": "agent_alpha/skill-creator",
"name": "skill-creator",
"description": "Create new skills, modify and improve existing skills, and measure skill performance.",
"tags": ["skills", "authoring", "evaluation", "agent_alpha"]
},
{
"id": "agent_alpha/changelog-generation",
"name": "changelog-generation",
"description": "Changelog generation skill for ZeroClaw releases.",
"tags": ["changelog", "release", "automation", "agent_alpha"]
}
]
}
こう読みます。supportedInterfaces はURLの一覧です。catalog とタグ付けされたものはこの一覧自体なので、無視してください。JSONRPC とタグ付けされた2つがエージェントの agent_alpha と agent_beta です。これらのURLが作業の送信先になります。skills は公開されている全エージェントのスキルを集約したもので、各 id には所有エイリアスがプレフィックスとして付き、tags にもそのエイリアスがタグ付けされます。これにより、一度読むだけでインストール全体の機能範囲と、各要素の所有者を把握できます。
リクエスト2: 1つのエージェントを検査する
リストからURLを取得し、カードパスを追加します:
curl http://localhost:42617/a2a/agent_alpha/.well-known/agent-card.json
応答:
{
"name": "agent_alpha",
"description": "ZeroClaw agent 'agent_alpha'.",
"supportedInterfaces": [
{
"url": "http://localhost:42617/a2a/agent_alpha",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
}
],
"version": "0.8.7",
"capabilities": {
"streaming": false,
"pushNotifications": false,
"extendedAgentCard": false
},
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"skills": [
{
"id": "zeroclaw",
"name": "zeroclaw",
"description": "Help users operate and interact with their ZeroClaw agent instance.",
"tags": ["operations", "cli", "gateway"]
},
{
"id": "skill-creator",
"name": "skill-creator",
"description": "Create new skills, modify and improve existing skills, and measure skill performance.",
"tags": ["skills", "authoring", "evaluation"]
},
{
"id": "changelog-generation",
"name": "changelog-generation",
"description": "Changelog generation skill for ZeroClaw releases.",
"tags": ["changelog", "release", "automation"]
}
]
}
これで3つのことが分かりました。エージェントの名前は agent_alpha です。3つのスキル、zeroclaw、skill-creator、および changelog-generation を持ち、それぞれが何をするかの平易な説明があります。そして、単一の JSONRPC インターフェース URL、http://localhost:42617/a2a/agent_alpha が、タスクを POST するアドレスです。
カード description は、エイリアスのアイデンティティドキュメントが設定されている場合、そこから取得されます。AIEOS アイデンティティの bio がこの行を提供し、設定されていない場合はそのアイデンティティの名前にフォールバックします。アイデンティティが設定されていない場合、カードは上記に示した中立的なデフォルト ZeroClaw agent '<alias>'. を使用します。
エージェントが何を表示するかを選択します
エージェントは、持っているすべてのスキルを公開する必要はありません。同じデプロイメント内の agent_beta エージェントは、独自に選択したセットを公開します:
curl http://localhost:42617/a2a/agent_beta/.well-known/agent-card.json
{
"name": "agent_beta",
"description": "ZeroClaw agent 'agent_beta'.",
"supportedInterfaces": [
{
"url": "http://localhost:42617/a2a/agent_beta",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
}
],
"version": "0.8.7",
"capabilities": {
"streaming": false,
"pushNotifications": false,
"extendedAgentCard": false
},
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"skills": [
{
"id": "github-issue-triage",
"name": "github-issue-triage",
"description": "Issue triage and lifecycle management agent for ZeroClaw."
},
{
"id": "github-pr-review-session",
"name": "github-pr-review-session",
"description": "Human-reviewer co-pilot for ZeroClaw PR reviews."
}
]
}
デプロイでは、どのエージェントを公開するか、そして各エージェントがどのスキルを公開するかを選択しました。これこそが公開の目的です。エージェントごとに、外部の世界がどのスキルを見られるかを決定します。
タスクを送信する
エージェントのインターフェース URL とスキルを取得したら、その URL に JSON-RPC の SendMessage POST として作業を送信し、自分が使用する A2A プロトコルのバージョンを A2A-Version ヘッダーで通知します(このサーバーは 1.0 を使用します):
curl -X POST http://localhost:42617/a2a/agent_alpha \
-H "Authorization: Bearer $ZEROCLAW_TOKEN" \
-H "A2A-Version: 1.0" \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "SendMessage",
"params": {
"message": {
"messageId": "msg-1",
"role": "ROLE_USER",
"parts": [{ "text": "Reply with PONG" }]
}
}
}'
エージェントはターンを実行し、完了したタスクで応答します。返信はタスクの artifact 内にある text part です。結果は SendMessageResponse で、その task ブランチに Task が格納されます:
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"task": {
"id": "0ef19fcb-b5e4-4c26-afce-d80451c8861e",
"contextId": "a2a_agent_alpha_06cb22f5-12bf-4b26-9ebc-9c063ab520a4",
"status": { "state": "TASK_STATE_COMPLETED" },
"artifacts": [
{
"artifactId": "5346ae32-1b63-40c0-9aaa-345d815c792e",
"parts": [
{ "text": "PONG" }
]
}
]
}
}
}
インターフェースのURLは、ディスカバリーとタスクの両方で同じです。変わるのはリクエストだけです。このエンドポイントは SendMessage のみを受け付け、A2A-Version: 1.0 のみを受け付けます。それ以外の method を指定すると JSON-RPC の -32601 が返され、空のメッセージでは -32602 が返されます。サポートされていないバージョンでは VERSION_NOT_SUPPORTED エラーが返され、JSON-RPC 形式ではないボディの場合は HTTP 400 が返されます。
露出と一つの鋭いエッジ
タスクエンドポイントは、カードの有効化ゲートおよび公開ゲートを共有します。すなわち、[a2a.server] enabled が設定されており、かつエイリアスが有効化かつ公開されている場合にのみ応答します。未公開または不明なエイリアスへのタスク POST は、そのカードと同様に 404 を返します。ただし、カードの認証姿勢は共有しません。ディスカバリーカードは公開されたままですが、タスクの呼び出しにはゲートウェイのベアラートークンが必要であり、それがない場合は 401 を返します。
注意すべき点の1つ: インターフェースURLは、単なるGETに対してエージェントではなくWebダッシュボードで応答します。ゲートウェイが認識しないパスに対してダッシュボードの配信にフォールバックするためです:
curl -i http://localhost:42617/a2a/translator
HTTP/1.1 200 OK
content-type: text/html
Discovery(.well-known パス)と SendMessage の POST が、サポートされている公式の仕様範囲です。インターフェース URL に対する単純な GET はプロトコルの一部ではありません。代わりに .well-known パスでカードを読み取ってください。
エージェントが提供される場所
カードは Web ゲートウェイによって、他のすべてと同じアドレスおよびポートで提供されます。ゲートウェイが localhost:42617 にある場合、そこにカタログとすべてのエージェントカードが存在します。2 つ目のサーバーを実行することも、2 つ目のポートを開くこともありません。
このデプロイメントをリバースプロキシまたはパブリックホスト名の背後に置く場合、カード内のURLはクライアントが実際に到達するアドレスと一致している必要があります。公開URLは次の順序で解決されます。設定した場合は明示的なパブリックベースURL、次に設定した場合はA2A固有のホストとポートのオーバーライド、その後ゲートウェイ自身のアドレスです。オーバーライドはプロキシのケース用に存在します。プロキシの背後にいない場合は触れる必要はなく、カードはゲートウェイのアドレスを直接アドバタイズします。
有効にする
Discovery はオンにするまでオフであり、誤って何も漏洩しないように 3 つの独立した方法でオフになっています:
- A2A サーバーはデフォルトでデプロイメント全体に対して無効になっています。
- 各エージェントは、サーバーがオンであっても、デフォルトで非公開です。
- 公開されたエージェントは、指定したスキルのみを公開し、それ以外は何も公開しません。
サーバーを一度有効化し、到達可能にしたい特定のエージェントを published としてマークし、それぞれが公開するスキルを列挙します。無効化されている、または published になっていないエージェントはカタログに表示されず、そのカードパスは 404 を返します。不明なエージェント名も同様に 404 を返します。
名前付きスキルは、エージェントが実際に保持している本物のスキルに解決される場合にのみカードに表示されます。エージェントのスキルバンドルのいずれかに存在し、その SKILL.md に有効な YAML フロントマターがなければなりません。解決されない名前、またはエージェントが宣言していないバンドル内のスキルは、広告されるのではなく、黙ってドロップされます。
空の skills: [] 配列の最も一般的な原因は、skill_bundles を宣言していないエージェントに a2a.exposed_skills を設定することです。exposed_skills はエージェントの解決済みスキルセットを絞り込むだけであり、それ自体でスキルを読み込むことはありません。バンドルが宣言されていない場合、フィルターが保持するものが何もないため、すべての名前が除外され、カードには何も公開されません。所有するバンドルを agents.<alias>.skill_bundles に追加してください。設定の検証では、このケースが起動時の警告(a2a_exposed_skills_without_bundles)として表示されます。
公開が実際に露出するもの
公開する前にこれをお読みください。サーバーが有効になり、エイリアスが公開されると、POST /a2a/{alias} はそのエイリアスに対してエージェントのターンを完全に実行します。これは、チャットサーフェスが使用するのと同じパスを通じてエージェントを呼び出し、そのエージェントに設定されたツールセット全体(シェル、ファイル、ブラウザ、およびそのエイリアスが持つその他すべて)を使用します。
そのタスクエンドポイントは、他のすべての書き込み面と同様に、ゲートウェイのベアラー/ペアリング認証の背後にあります。呼び出し元が公開エージェントを起動するには、ペアリング由来のベアラートークンが必要です。認証されていないリクエストは 401 を返し、エージェントのターンには決して至りません。
ディスカバリーカードはその認証の背後にはありません。カタログとエイリアスごとのカードはトークンなしで読み取れるため、公開されたサーフェスは、リスナーに到達できるすべての呼び出し元に対してエージェント名と公開スキルを通知します。これがディスカバリーの目的です。ピアはペアリングする前にカードを読み取ります。これはまた、エージェントの呼び出しには依然としてトークンが必要であるにもかかわらず、公開によってそのメタデータがゲートウェイに到達できる誰にでも露出することを意味します。
公開はどちらの軸においても露出に関する決定です。カードのメタデータは公開され、有効なトークンを保持する者は誰でも公開されたエイリアスをそのフルツールセットで呼び出せます。スイッチを切り替える前に:
- バインドの構成範囲を限定してください。信頼できないネットワークにリスナーを直接公開するのではなく、ゲートウェイをプライベートインターフェイスにバインドするか、リバースプロキシの背後に配置してください。これにより、認証なしで公開されるカードを読み取れる対象も制限されます。
- すべてのトークン保持者による呼び出しを許容できるツールセットを持ち、その名前とスキルを認証なしで公開しても構わないエイリアスのみを公開してください。相互運用に必要な最小限まで
exposed_skillsを絞り込んでください。 - エイリアスがどのツールやスキルバンドルを持つかを判断する際は、公開されたエイリアスをリモートで呼び出し可能な実行面として扱ってください。
- クロスデプロイメントの相互運用は、呼び出し元のピアとトークンを共有します。そのクレデンシャルは他のものと同様にスコープし、ローテーションしてください。
複数のデプロイメントの接続方法
Discovery は任意の数のデプロイメントにまたがって構成されます。各デプロイメントは自身のアドレスで独自のカタログを公開します。複数のデプロイメントアドレスを知っているクライアントは、各カタログを取得し、エージェントを読み取り、それらすべてにわたる到達可能なすべてのエージェントの結合マップを保持します。レジストリも中央サーバーもありません。クライアントだけがアドレスを知る必要があり、各デプロイメントに直接通信します。
具体的な図。あなたは個人のデプロイメントを実行します。チームは共有のものを実行します。データチームは3つ目を実行します。クライアントは3つのカタログすべてを取得します:
curl http://personal.example:42617/.well-known/agents-card.json
curl http://team.example:42617/.well-known/agents-card.json
curl http://data.example:42617/.well-known/agents-card.json
それぞれが独自のエージェントリストを返します。クライアントは、たとえば personal の notes エージェント、team の deploy エージェント、data の query エージェントを認識します。それらを使用するには、そのエージェントのカードを取得し、上記とまったく同じようにそのエージェントの URL にタスクを送信します。デプロイごとに変わるものはありません。異なるホストを指すだけで、同じ 2 回の読み取りと 1 回の POST です。
送信A2Aクライアントを呼び出し中
上記はすべて、デプロイメントが検出され、呼び出される方向の説明です。逆方向となるのはアウトバウンドで、あるデプロイメントが作業を リモートの A2A エージェントに委任し、その返信を自身の作業で利用します。これがアウトバウンドクライアントであり、[a2a.client] の下で設定します。デフォルトではオフで、有効にするまで何も登録されないため、設定されていないインストールには、アウトバウンドの A2A に関連するフットプリントは一切ありません。
[a2a.client]
enabled = true # a2a_* ツールを登録します。デフォルトは false
[[a2a.client.peers]]
name = "team" # すべての a2a_* ツールの `peer` 引数
base_url = "https://team.example.com"
token = "${TEAM_A2A_TOKEN}" # 呼び出し時に環境変数から解決されます。空の場合は匿名
tags = ["production"]
各 peer は、リモートの A2A サーバーのオリジンを指定します。クライアントは base_url から well-known カードのパスと JSON-RPC タスクのパスを導出します。エージェントの呼び出し元が URL を入力することはなく、入力するのは peer 名だけです。
認証情報はシークレットとして型付けされています。token に ${VAR} を指定すると、呼び出し時に環境変数から解決されます。リテラル値はそのまま送信され、空の値の場合は Authorization ヘッダーを送信しません。このフィールドはシークレットとしてマークされているため、設定およびスキーマの表示では、読み取り可能な値ではなく、書き込み専用の暗号化された機密情報として扱われます。これは http_request が認証トークンに対して用いているのと同じシークレットの扱い方です。
外向きの呼び出しは SSRF の攻撃対象面となるため、ポリシーでは既定でプライベート、ループバック、リンクローカルのホストへの接続を拒否します。127.0.0.1 や RFC1918 セグメントを指すローカルまたはイントラネット環境では、allow_private_hosts = true を設定するか、対象のホストを allowed_private_hosts に追加してください。許可リストは指定したホストのみを対象とし、グローバルなポリシーを緩めることはありません。ホスト名の解決は、ガードを通過したアドレスに固定されます。リダイレクトは無効化されており、レスポンス本体は max_response_bytes で上限が設けられ、各リクエストには request_timeout_secs によるタイムアウト上限が設定されています。
4つのツールは影響の大きさが異なります。a2a_discover と a2a_get_task は読み取り操作であり、アクションとしてカウントされません。a2a_send と a2a_cancel はピアの状態を変更するため Act 操作に分類され、デフォルトで承認が必要です。読み取り専用の自律性モードでは、ネットワークI/Oの前にこれらを拒否します。a2a_send は、ピアが公開しているスキルすべてに対するリモート実行の許可として扱ってください。
外向きクライアントを無効にすることは、ロールバックではなく封じ込めです。[a2a.client] enabled = false を設定すると a2a_* ツールが登録されなくなるため、外向き通信インシデントを封じ込めるには、このスイッチを無効にしてデーモンを再起動し(レジストリを再構築し)ます。封じ込めは、すでに実行中のデーモンには即座には反映されません。そのエージェントタスクは、再起動またはレジストリの再構築までビルド済みの a2a_* ツールを保持し続けるため、オペレーターは再起動までの間、外向き通信が継続しているものとして扱う必要があります。このスイッチを無効にしても、同じリリースに含まれるインバウンドゲートウェイの移行(SendMessage v1 サーフェス)や共通のワイヤモデルの変更は取り消されません。すでに v1 を使用している外部の呼び出し元は引き続き動作します。v1 以前のインバウンド動作を求めるオペレーターは、このスイッチ 1 つではなく、リリース全体をロールバックする必要があります。ゲートウェイのバージョンネゴシエーション、JSONRPC のメソッド名、列挙型と Part の形状は、いずれも v1 に一括で移行されています。
ユースケース
デプロイメントを互いに接続するいくつかの具体的な理由。
研究用デプロイメントは文献検索を専門のデータデプロイメントに委ねます。研究エージェントはデータデプロイメントの search エージェントを発見し、クエリをタスクとして送信し、結果を自身の作業に取り込みます。研究側はデータ側の認証情報やインデックスを保持せず、エージェントの URL のみを知っています。
オンコールのデプロイメントは、インシデントをチーム所有のデプロイメントにファンアウトします。各チームのデプロイメントで triage エージェントを検出し、それぞれに同じインシデントをタスクとして送信し、回答を収集します。各チームは自身の triage エージェントが公開するものを制御します。オンコール側はカードを読み取り、タスクを送信するだけです。
個人のデプロイメントは、ログインを共有せずに企業のデプロイメントの審査済みエージェントを呼び出します。企業の invoice エージェントを発見し、下書きリクエストを送信して結果を受け取ります。企業はどのエージェントとスキルを公開するかを決定します。あなたは企業のデプロイメント内のシートを得ることはなく、エージェントエンドポイントのみを取得します。
A2A は MCP ではありません
これらは異なる問題を解決するものであり、互いに組み合わせて使えます。MCP は 1 つのエージェントをそのツールやコンテキストに接続します。つまり、単一のエージェントが何を呼び出せるかに答えるものです。A2A はエージェントを対等な存在として他のエージェントに接続します。つまり、他のどのエージェントに作業を引き渡せるかに答えるものです。A2A を介して到達するエージェントは、その仕事を行うために内部的に MCP ツールを使用しているかもしれませんが、あなたはそれを見ることもなければ気にする必要もありません。カードにはスキルが表示され、その背後にあるツールは表示されません。エージェントに能力を与えるには MCP を使い、エージェント同士が互いに委任し合えるようにするには A2A を使いましょう。