エージェント間(A2A)プロトコル

ADK-Rust は、クロスネットワークのエージェント通信のために A2A Protocol v1.0.0 を実装しています。実装は adk-server 内で a2a-v1 機能フラグの背後にあり、11 個すべての JSON-RPC オペレーション、REST バインディング、エージェントカードの検出、およびバージョン交渉をカバーしています。仕様が許容するよりもセマンティクスが狭い唯一のオペレーションについては、Operation coverage を参照してください。ワイヤ型は a2a-protocol-types によって提供されます。これは、@tomtom215a2a-rust)による、Foundation に検証された Rust の A2A SDK です。

概要

A2A は次のような場合に有用です:

  • サードパーティのエージェントサービスと統合する場合
  • 専門化されたエージェントを持つマイクロサービスアーキテクチャを構築する場合
  • クロス言語のエージェント通信を可能にする場合(A2A クライアントを持つ任意の言語)
  • エージェントシステム間で正式な契約を強制する場合

単純な内部編成であれば、A2A の代わりにローカルのサブエージェントを使うと、より良いパフォーマンスが得られます。

v1.0.0 への準拠

この実装は、A2A Protocol v1.0.0 仕様に完全に準拠しています:

機能仕様セクション状態
機能宣言付きのエージェントカード§8
すべてのタスク状態変更における RFC 3339 タイムスタンプ§5.6.1
SendMessage のメッセージ ID の冪等性§3.3.1
プッシュ通知の認証(Bearer + token)§13.2
INPUT_REQUIRED のマルチターン再開フロー§3.4.3
入力検証(parts、ID、metadata サイズ)§3.3
Content-Type: application/a2a+json に対する応答§9
最初の SSE ストリーミングイベントとしての Task オブジェクト§3.1.2
マルチターン向けのコンテキストスコープ付きタスク検索§3.4.1
バージョンネゴシエーション(A2A-Version ヘッダー)§9.1
状態マシンの検証(終端状態)§4.1.3

エージェントカード

すべてのA2Aエージェントは、/.well-known/agent-card.jsonにその機能、スキル、およびサポートされるインターフェースを記述したエージェントカードを公開します。

use adk_server::a2a::v1::card::build_v1_agent_card;
use a2a_protocol_types::{AgentCapabilities, AgentSkill};

let card = build_v1_agent_card(
    "my-agent",
    "A helpful research agent",
    "http://localhost:3001/jsonrpc",
    "1.0.0",
    vec![AgentSkill {
        id: "research".to_string(),
        name: "Research & Summarize".to_string(),
        description: "Researches topics and produces structured summaries".to_string(),
        tags: vec!["research".to_string()],
        examples: None,
        input_modes: None,
        output_modes: None,
        security_requirements: None,
    }],
    AgentCapabilities::none()
        .with_streaming(true)
        .with_push_notifications(true),
);

エージェントカードには以下が含まれます:

  • エージェント名、説明、バージョン
  • プロトコルバインディングとバージョンを含むサポートされるインターフェース
  • 機能: streamingpushNotificationsextendedAgentCard
  • エージェント設定から派生したスキル
  • デフォルトの入力/出力モード

機能は現在、AgentCapabilitiesパラメータを通じて明示的に宣言されます。ハードコードされたデフォルトはもうありません。

A2A v1 によるエージェントの公開

LLM統合を備えた完全なA2A v1.0.0 サーバーを構築します:

use std::sync::Arc;
use a2a_protocol_types::{AgentCapabilities, AgentSkill};
use adk_agent::LlmAgentBuilder;
use adk_server::a2a::v1::card::{CachedAgentCard, build_v1_agent_card};
use adk_server::a2a::v1::executor::V1Executor;
use adk_server::a2a::v1::jsonrpc_handler::jsonrpc_handler;
use adk_server::a2a::v1::push::NoOpPushNotificationSender;
use adk_server::a2a::v1::request_handler::RequestHandler;
use adk_server::a2a::v1::rest_handler::rest_router;
use adk_server::a2a::v1::task_store::InMemoryTaskStore;
use adk_server::a2a::v1::version::version_negotiation;
use adk_runner::RunnerConfig;
use adk_session::InMemorySessionService;
use axum::Router;
use axum::routing::post;
use tokio::sync::RwLock;

// 1. Create your agent
let model = adk_model::GeminiModel::new(&api_key, "gemini-2.5-flash")?;
let agent = LlmAgentBuilder::new("my-agent")
    .description("A helpful agent")
    .model(Arc::new(model))
    .instruction("You are a helpful assistant.")
    .build()?;

// 2. Set up A2A infrastructure
let task_store = Arc::new(InMemoryTaskStore::new());
let executor = Arc::new(V1Executor::new(task_store.clone()));
let push_sender = Arc::new(NoOpPushNotificationSender);

// 3. Build agent card with capabilities
let card = build_v1_agent_card(
    "my-agent", "A helpful agent",
    "http://localhost:3001/jsonrpc", "1.0.0",
    vec![/* skills */],
    AgentCapabilities::none().with_streaming(true),
);
let cached_card = Arc::new(RwLock::new(CachedAgentCard::new(card)));

// 4. Create runner config for LLM invocation
let session_service = Arc::new(InMemorySessionService::new());
let runner_config = Arc::new(RunnerConfig {
    app_name: "my-agent".to_string(),
    agent: Arc::new(agent),
    session_service,
    artifact_service: None,
    memory_service: None,
    plugin_manager: None,
    run_config: None,
    compaction_config: None,
    context_cache_config: None,
    cache_capable: None,
    request_context: None,
    cancellation_token: None,
});

// 5. Wire up the handler and routes
let handler = Arc::new(RequestHandler::with_runner(
    executor, task_store, push_sender, cached_card, runner_config,
));

let app = Router::new()
    .route("/jsonrpc", post(jsonrpc_handler))
    .with_state(handler.clone())
    .merge(rest_router(handler))
    .layer(axum::middleware::from_fn(version_negotiation));

// 6. Serve
let listener = tokio::net::TcpListener::bind("0.0.0.0:3001").await?;
axum::serve(listener, app).await?;

これにより公開されるもの:

  • GET /.well-known/agent-card.json — ETag キャッシュ付きのエージェントカード
  • POST /jsonrpc — JSON-RPC エンドポイント(v1 の 11 のすべてのオペレーション。 Operation coverage を参照)
  • すべてのオペレーション用のRESTルート
  • すべてのルートでのA2A-Versionヘッダーのネゴシエーション

JSON-RPC オペレーション

11 個すべてのA2A v1.0.0 オペレーションがサポートされています:

メソッド説明
SendMessageメッセージを送信し、タスクを作成/再開する
SendStreamingMessageSendMessage と同じだが、SSE ストリームを返す
GetTaskIDでタスクを取得
CancelTask実行中のタスクをキャンセル
ListTasksフィルタリングとページネーション付きでタスクを一覧表示
SubscribeToTaskSSE を介してタスク更新を購読
CreateTaskPushNotificationConfigプッシュ通知用の webhook を登録する
GetTaskPushNotificationConfigプッシュ通知設定を取得する
ListTaskPushNotificationConfigsタスクのプッシュ設定を一覧表示する
DeleteTaskPushNotificationConfigプッシュ通知設定を削除する
GetExtendedAgentCard拡張エージェントカードを取得

SendMessage

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "SendMessage",
  "params": {
    "message": {
      "messageId": "msg-123",
      "role": "ROLE_USER",
      "parts": [{"text": "Research quantum computing"}]
    }
  }
}

レスポンスには、status、history、artifacts を含む Task オブジェクトが含まれます。レスポンスは Content-Type: application/a2a+json を使用します。

SendStreamingMessage

SendMessage と同じリクエスト形式です。次の内容を含む SSE ストリームを返します:

  1. 最初のイベントは完全な Task オブジェクトです(仕様 §3.1.2 に準拠)
  2. 以降のイベントは TaskStatusUpdateEvent です(Working、Completed など)
  3. アーティファクトのイベントは TaskArtifactUpdateEvent です

マルチターン会話

タスクが INPUT_REQUIRED 状態に達したら、同じ contextId を使ってフォローアップメッセージを送信し、再開します:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "SendMessage",
  "params": {
    "message": {
      "messageId": "msg-456",
      "role": "ROLE_USER",
      "contextId": "ctx-original",
      "parts": [{"text": "Yes, include more details on error correction"}]
    }
  }
}

ハンドラは自動的に contextId によって既存のタスクを見つけ、INPUT_REQUIRED から Working に遷移させ、新しいメッセージを履歴に追加して、処理を継続します。

冪等性

同じ messageId を持つ重複した SendMessage リクエストは、再処理せずに以前作成されたタスクを返します。これは SendMessageSendStreamingMessage の両方に適用されます。

プッシュ通知認証

クライアントが CreateTaskPushNotificationConfig 経由で webhook を登録すると、サーバーは webhook 配信に認証ヘッダーを含めます:

  • Authorization: Bearer <credentials>authentication フィールドに bearer 認証情報がある場合
  • a2a-notification-token: <token>token フィールドが存在する場合

両方のヘッダーを同時に設定できます。SSRF 保護は、webhook URLs がプライベート IP 範囲および localhost に対して有効であるかを検証します。

入力検証

すべての受信リクエストは、処理前に検証されます:

検証エラー
部分がゼロのメッセージInvalidParams (-32602)
空または空白のみの messageIdInvalidParams (-32602)
messageId が 256 文字を超えるInvalidParams (-32602)
taskId が空、または空白のみInvalidParams (-32602)
taskId が 256 文字を超えるInvalidParams (-32602)
メタデータが 64 KB を超えるInvalidParams (-32602)

リモートエージェントの利用

RemoteA2aAgent を使用してリモート A2A エージェントと通信します:

use adk_server::a2a::RemoteA2aAgent;

let remote_agent = RemoteA2aAgent::builder("prime_checker")
    .description("Checks if numbers are prime")
    .agent_url("http://localhost:8001")
    .build()?;

// Use as a sub-agent in a local agent hierarchy
let root_agent = LlmAgentBuilder::new("root")
    .model(Arc::new(model))
    .sub_agent(Arc::new(remote_agent))
    .build()?;

A2A クライアント

プロトコルレベルで直接通信する場合:

use adk_server::a2a::client::v1_client::A2aV1Client;

// Discover agent card
let card = A2aV1Client::resolve_agent_card("http://localhost:3001").await?;
let client = A2aV1Client::new(card);

// Send message
let task = client.send_message(message).await?;

// Get task
let task = client.get_task(&task_id, Some(10)).await?;

// List tasks
let tasks = client.list_tasks(None, None, None, None).await?;

// Cancel task
client.cancel_task(&task_id).await?;

// Streaming
let response = client.send_streaming_message(message).await?;

// Push notification CRUD
let config = client.create_push_notification_config(config).await?;
client.delete_push_notification_config(&task_id, &config_id).await?;

エラーハンドリング

A2A のエラーは、JSON-RPC コードと HTTP ステータスコードの両方にマッピングされます:

エラーJSON-RPC コードHTTP 状態
TaskNotFound-32001404
TaskNotCancelable-32002409
PushNotificationNotSupported-32003400
UnsupportedOperation-32004400
ContentTypeNotSupported-32005415
InvalidAgentResponse-32006502
VersionNotSupported-32009400
InvalidParams-32602400
MethodNotFound-32601404
内部-32603500

サンプルの実行

完全な A2A v1.0.0 のサンプルエージェントが 2 つ含まれています:

cargo run --manifest-path examples/a2a-research-agent/Cargo.toml
cargo run --manifest-path examples/a2a-writing-agent/Cargo.toml --bin a2a-writing-agent
cargo run --manifest-path examples/a2a-writing-agent/Cargo.toml --bin client

クライアントは以下を検証します: agent card の検出、SendMessage(実際の LLM を持つ両方のエージェント)、GetTask、ListTasks、CancelTask のエラーパス、SendStreamingMessage、push notification CRUD、GetExtendedAgentCard、バージョン交渉、およびエラーパス。

ベストプラクティス

  1. 能力を正確に宣言する — エージェントが実際にサポートする内容に基づいて streamingpushNotifications を設定する
  2. 長い操作にはストリーミングを使うSendStreamingMessage はクライアントにリアルタイムの進行状況を提供する
  3. 複数ターンのフローを処理する — メッセージ間で会話状態を維持するために contextId を使う
  4. Webhook の URLs を検証する — SSRF 保護は組み込みだが、本番環境では HTTPS を使う
  5. 適切なタイムアウトを設定する — リモートエージェント呼び出しのリクエストタイムアウトを構成する
  6. 冪等性を使う — クライアントは同じ messageIdSendMessage を安全に再試行できる

: ← Server | : Evaluation →

オペレーションのカバレッジ

v1 の JSON-RPC オペレーション 11 個すべてがディスパッチされ、実装されています。

操作ステータス
SendMessageエージェントを駆動し、その出力をアーティファクトとして記録する
SendStreamingMessageエージェントを駆動し、生成されたアーティファクトのチャンクをストリームする
GetTask, ListTasksフル
CancelTaskフル
SubscribeToTask (tasks/resubscribe)スナップショットのみ — 以下を参照
CreateTaskPushNotificationConfig, GetTaskPushNotificationConfig, ListTaskPushNotificationConfigs, DeleteTaskPushNotificationConfigフル
GetExtendedAgentCardフル

SubscribeToTask はスナップショットです

この操作はタスクとその現在のステータスを返し、その後ストリームを閉じます。後続の更新は配信しないため、クライアントは進捗のためにこれを待つべきではありません。

ライブでの再接続には、元のリクエストより長く存続するタスクごとのイベントキューが必要です。参照実装はこれをそれぞれの A2A SDKs から取得します。adk-python と adk-go はどちらも tasks/resubscribe を完全に SDK のキューマネージャーに委譲しており、ADK コードではどちらも実装していません。このサーバーは a2a-protocol-types 上で手作業で構築されており、サーバーランタイムではなくワイヤ型を提供するため、まだキューは存在しません。

ライブ更新が必要な場合は SendStreamingMessage を使用してください。

ストリーミングイベント契約

SendStreamingMessage は、到着したエージェントイベントをそのまま変換します:

エージェントイベントA2Aイベント
最初、出力前Task、その後 TaskStatusUpdateEventWorking
コンテンツ、partial = trueTaskArtifactUpdateEventappend、最後のチャンクではない
内容, partial = falseTaskArtifactUpdateEvent — 最終チャンク
ストリーム終了TaskStatusUpdateEventCompleted
ストリームエラーTaskStatusUpdateEventFailed

すべての1回の応答のチャンクは、クライアントが再構成できるように同じアーティファクトIDを共有します。連結されたテキストは永続化されるため、後のGetTaskはストリームされた内容を返します。これは、adk-pythonとadk-goがそれらのSDKs上で実装する契約に一致します。

エージェント間(A2A)プロトコル - ADK-Rust ドキュメント | ADK-Rust