Agent-zu-Agent (A2A) Protokoll

ADK-Rust implementiert das A2A Protocol v1.0.0 für die agentenübergreifende Kommunikation über Netzwerke. Die Implementierung befindet sich in adk-server hinter dem Feature-Flag a2a-v1 und deckt alle 11 JSON-RPC-Operationen, REST-Bindings, die Erkennung von Agent Cards und die Versionsverhandlung ab. Siehe Operation coverage für die eine Operation, deren Semantik enger ist, als die Spezifikation zulässt. Wire-Typen werden von a2a-protocol-types bereitgestellt — dem von der Foundation verifizierten Rust A2A SDK von @tomtom215 (a2a-rust).

Überblick

A2A ist nützlich, wenn:

  • Integration mit Agentendiensten von Drittanbietern
  • Aufbau von Microservice-Architekturen mit spezialisierten Agenten
  • Ermöglichung der agentenübergreifenden Kommunikation zwischen Sprachen (jede Sprache mit einem A2A-Client)
  • Erzwingen formaler Verträge zwischen Agentensystemen

Für eine einfache interne Organisation verwenden Sie lokale Unteragenten statt A2A für bessere Leistung.

v1.0.0-Konformität

Die Implementierung ist vollständig konform mit der Spezifikation des A2A Protocol v1.0.0:

FunktionSpezifikationsabschnittStatus
Agent-Card mit Fähigkeiten-Deklaration§8
RFC-3339-Zeitstempel bei allen Aufgabenstatusänderungen§5.6.1
Nachrichten-ID-Idempotenz für SendMessage§3.3.1
Authentifizierung für Push-Benachrichtigungen (Bearer + Token)§13.2
INPUT_REQUIRED-Mehrfachrunden-Fortsetzungsablauf§3.4.3
Eingabevalidierung (Teile, IDs, Metadatengröße)§3.3
Content-Type: application/a2a+json bei Antworten§9
Task-Objekt als erstes SSE-Streaming-Ereignis§3.1.2
Kontextgebundene Task-Suche für Multi-Turn§3.4.1
Versionsaushandlung (A2A-Version-Header)§9.1
Zustandsmaschinenvalidierung (Endzustände)§4.1.3

Agent Cards

Jeder A2A-Agent stellt unter /.well-known/agent-card.json eine Agent Card bereit, die seine Fähigkeiten, Skills und unterstützten Schnittstellen beschreibt.

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),
);

Die Agent Card enthält:

  • Agentenname, Beschreibung und Version
  • Unterstützte Schnittstellen mit Protokollbindung und Version
  • Fähigkeiten: streaming, pushNotifications, extendedAgentCard
  • Aus der Agent-Konfiguration abgeleitete Skills
  • Standard-Eingabe-/Ausgabemodi

Fähigkeiten werden jetzt explizit über den Parameter AgentCapabilities deklariert — keine hartcodierten Standardwerte mehr.

Bereitstellung eines Agenten über A2A v1

Erstelle einen vollständigen A2A v1.0.0-Server mit LLM-Integration:

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?;

Dies stellt bereit:

  • GET /.well-known/agent-card.json — Agent Card mit ETag-Caching
  • POST /jsonrpc — JSON-RPC-Endpunkt (alle 11 v1-Operationen; siehe Operation coverage)
  • REST-Routen für alle Operationen
  • A2A-Version-Header-Aushandlung auf allen Routen

JSON-RPC-Operationen

Alle 11 A2A-v1.0.0-Operationen werden unterstützt:

MethodeBeschreibung
SendMessageEine Nachricht senden, eine Aufgabe erstellen/fortsetzen
SendStreamingMessageWie SendMessage, gibt aber einen SSE-Stream zurück
GetTaskEine Aufgabe anhand der ID abrufen
CancelTaskEine laufende Aufgabe abbrechen
ListTasksAufgaben mit Filterung und Paginierung auflisten
SubscribeToTaskÜber SSE für Aufgabenaktualisierungen abonnieren
CreateTaskPushNotificationConfigEin Webhook für Push-Benachrichtigungen registrieren
GetTaskPushNotificationConfigEine Push-Benachrichtigungskonfiguration abrufen
ListTaskPushNotificationConfigsPush-Konfigurationen für eine Aufgabe auflisten
DeleteTaskPushNotificationConfigEine Push-Benachrichtigungskonfiguration entfernen
GetExtendedAgentCardErweiterte Agent-Card abrufen

SendMessage

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

Die Antwort enthält ein Task-Objekt mit Status, Verlauf und Artefakten. Die Antwort verwendet Content-Type: application/a2a+json.

SendStreamingMessage

Dasselbe Anfrageformat wie SendMessage. Gibt einen SSE-Stream zurück, bei dem:

  1. Das erste Ereignis ein vollständiges Task-Objekt ist (gemäß Spezifikation §3.1.2)
  2. Nachfolgende Ereignisse TaskStatusUpdateEvent sind (Working, Completed usw.)
  3. Artefakt-Ereignisse TaskArtifactUpdateEvent sind

Mehrstufige Unterhaltungen

Wenn eine Aufgabe den Zustand INPUT_REQUIRED erreicht, sende eine Folge-Nachricht mit demselben contextId, um sie fortzusetzen:

{
  "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"}]
    }
  }
}

Der Handler findet die vorhandene Aufgabe automatisch anhand von contextId, überführt sie von INPUT_REQUIRED zu Working, hängt die neue Nachricht an den Verlauf an und setzt die Verarbeitung fort.

Idempotenz

Doppelte SendMessage-Anfragen mit demselben messageId geben die zuvor erstellte Aufgabe zurück, ohne erneut verarbeitet zu werden. Dies gilt sowohl für SendMessage als auch für SendStreamingMessage.

Authentifizierung für Push-Benachrichtigungen

Wenn ein Client über CreateTaskPushNotificationConfig einen Webhook registriert, fügt der Server den Webhook-Zustellungen Authentifizierungs-Header hinzu:

  • Authorization: Bearer <credentials> — wenn das Feld authentication Bearer-Anmeldedaten enthält
  • a2a-notification-token: <token> — wenn das Feld token vorhanden ist

Beide Header können gleichzeitig gesetzt werden. Der SSRF-Schutz validiert Webhook-URLs gegen private IP-Bereiche und localhost.

Eingabevalidierung

Alle eingehenden Anfragen werden vor der Verarbeitung validiert:

ValidierungFehler
Nachricht mit null TeilenInvalidParams (-32602)
Leere oder nur aus Leerzeichen bestehende messageIdInvalidParams (-32602)
messageId mit mehr als 256 ZeichenInvalidParams (-32602)
Leere oder nur aus Leerzeichen bestehende taskIdInvalidParams (-32602)
taskId mit mehr als 256 ZeichenInvalidParams (-32602)
Metadaten mit mehr als 64 KBInvalidParams (-32602)

Verwenden eines Remote-Agenten

Verwende RemoteA2aAgent zur Kommunikation mit einem entfernten A2A-Agenten:

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-Client

Für die direkte Kommunikation auf Protokollebene:

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?;

Fehlerbehandlung

A2A-Fehler werden sowohl auf JSON-RPC-Codes als auch auf HTTP-Statuscodes abgebildet:

FehlerJSON-RPC CodeHTTP Status
TaskNotFound-32001404
TaskNotCancelable-32002409
PushNotificationNotSupported-32003400
UnsupportedOperation-32004400
ContentTypeNotSupported-32005415
InvalidAgentResponse-32006502
VersionNotSupported-32009400
InvalidParams-32602400
MethodNotFound-32601404
Intern-32603500

Ausführen der Beispiele

Zwei vollständige A2A-Beispielagenten v1.0.0 sind enthalten:

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

Der Client validiert: Agent-Card-Erkennung, SendMessage (beide Agenten mit realen LLM), GetTask, ListTasks, Fehlerpfad CancelTask, SendStreamingMessage, Push-Benachrichtigungs-CRUD, GetExtendedAgentCard, Versionsaushandlung und Fehlerpfade.

Bewährte Vorgehensweisen

  1. Fähigkeiten präzise deklarieren — setze streaming, pushNotifications basierend darauf, was dein Agent tatsächlich unterstützt
  2. Streaming für lange Operationen verwendenSendStreamingMessage gibt Clients Echtzeit-Fortschritt
  3. Multi-Turn-Flows handhaben — verwende contextId, um den Gesprächszustand über Nachrichten hinweg beizubehalten
  4. Webhook-URLs validieren — SSRF-Schutz ist eingebaut, aber verwende HTTPS in der Produktion
  5. Angemessene Timeouts setzen — konfiguriere Request-Timeouts für entfernte Agent-Aufrufe
  6. Idempotenz verwenden — Clients können SendMessage mit derselben messageId sicher erneut versuchen

Vorher: ← Server | Weiter: Auswertung →

Operationsabdeckung

Alle 11 v1 JSON-RPC-Operationen werden verteilt und implementiert.

VorgangUmsetzung
SendMessageSteuert den Agenten und zeichnet seine Ausgabe als Artefakt auf
SendStreamingMessageSteuert den Agenten und streamt Artefakt-Chunks, sobald sie erzeugt werden
GetTask, ListTasksVollständig
CancelTaskVollständig
SubscribeToTask (tasks/resubscribe)Nur Snapshot — siehe unten
CreateTaskPushNotificationConfig, GetTaskPushNotificationConfig, ListTaskPushNotificationConfigs, DeleteTaskPushNotificationConfigVollständig
GetExtendedAgentCardVollständig

SubscribeToTask ist eine Momentaufnahme

Der Vorgang gibt die Aufgabe und ihren aktuellen Status zurück und schließt dann den Stream. Er liefert keine nachfolgenden Aktualisierungen, daher darf ein Client nicht darauf warten, um Fortschritt zu erhalten.

Ein Live-Wiederanbinden erfordert eine ereignisbasierte Warteschlange pro Aufgabe, die über die ursprüngliche Anfrage hinaus besteht. Die Referenzimplementierungen erhalten dies aus ihren A2A SDKs — adk-python und adk-go delegieren beide tasks/resubscribe vollständig an den Warteschlangen-Manager von SDK, und keine von beiden implementiert es in ADK-Code. Dieser Server ist in a2a-protocol-types von Hand geschrieben, das Wire-Typen statt einer Server-Runtime bereitstellt, daher existiert die Warteschlange noch nicht.

Verwenden Sie SendStreamingMessage, wenn Live-Aktualisierungen erforderlich sind.

Vertrag für Streaming-Ereignisse

SendStreamingMessage übersetzt Agentenereignisse, sobald sie eintreffen:

Agent-EreignisA2A-Ereignis
zuerst, vor der AusgabeTask, dann TaskStatusUpdateEventWorking
Inhalt, partial = trueTaskArtifactUpdateEventappend, nicht letzter Chunk
Inhalt, partial = falseTaskArtifactUpdateEvent — letzter Chunk
Stream endetTaskStatusUpdateEventCompleted
Stream-FehlerTaskStatusUpdateEventFailed

Alle Chunks einer Antwort teilen sich eine Artefakt-ID, sodass ein Client sie wieder zusammensetzen kann. Der zusammengefügte Text wird gespeichert, sodass ein späteres GetTask das zurückgibt, was gestreamt wurde. Das entspricht dem Vertrag, den adk-python und adk-go über ihre SDKs implementieren.

Agent-zu-Agent (A2A) Protokoll - ADK-Rust Dokumentation | ADK-Rust