Agent-zu-Agent (A2A)-Protokoll

ADK-Rust implementiert das A2A Protocol v1.0.0 für die netzwerkübergreifende Kommunikation zwischen Agenten. Die Implementierung befindet sich in adk-server hinter dem Feature-Flag a2a-v1 und deckt alle 11 JSON-RPC-Operationen, REST-Bindungen, die Erkennung von Agentenkarten und die Versionsaushandlung ab. Siehe Abdeckung der Operationen für die eine Operation, deren Semantik enger gefasst ist, als es die Spezifikation erlaubt. Die 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 für:

  • Die Integration von Agentendiensten Dritter
  • Den Aufbau von Microservices-Architekturen mit spezialisierten Agenten
  • Die Ermöglichung einer sprachübergreifenden Kommunikation zwischen Agenten (jede Sprache mit einem A2A-Client)
  • Die Durchsetzung formaler Verträge zwischen Agentensystemen

Für eine einfache interne Organisation sollten Sie lokale Unteragenten anstelle von A2A verwenden, um eine bessere Leistung zu erzielen.

Konformität mit v1.0.0

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

FunktionSpezifikationsabschnittStatus
Agentenkarte mit Fähigkeitendeklaration§8✅
RFC-3339-Zeitstempel bei allen Änderungen des Aufgabenstatus§5.6.1✅
Idempotenz der Nachrichten-ID für SendMessage§3.3.1✅
Authentifizierung von Push-Benachrichtigungen (Bearer + token)§13.2✅
Ablauf zum Fortsetzen von INPUT_REQUIRED über mehrere Runden§3.4.3✅
Eingabevalidierung (Teile, IDs, Metadatengröße)§3.3✅
Content-Type: application/a2a+json in Antworten§9✅
Aufgabenobjekt als erstes Streaming-Ereignis SSE§3.1.2✅
Kontextbezogene Aufgabensuche für mehrere Interaktionen§3.4.1✅
Versionsaushandlung (A2A-Version-Header)§9.1✅
Zustandsautomatenvalidierung (Endzustände)§4.1.3✅

Agentenkarten

Jeder A2A-Agent stellt unter /.well-known/agent-card.json eine Agentenkarte bereit, die seine Fähigkeiten, Kompetenzen 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 Agentenkarte umfasst:

  • Name, Beschreibung und Version des Agenten
  • Unterstützte Schnittstellen mit Protokollbindung und Version
  • Fähigkeiten: streaming, pushNotifications, extendedAgentCard
  • Aus der Agentenkonfiguration abgeleitete Kompetenzen
  • Standard-Eingabe-/Ausgabemodi

Fähigkeiten werden jetzt explizit über den Parameter AgentCapabilities deklariert – keine fest codierten Standardwerte mehr.

Einen Agenten über A2A v1 bereitstellen

Erstellen Sie 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-3.7-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 Folgendes bereit:

  • GET /.well-known/agent-card.json – Agentenkarte mit ETag-Caching
  • POST /jsonrpc – JSON-RPC-Endpunkt (alle 11 v1-Operationen; siehe Abdeckung der Operationen)
  • 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 jedoch einen SSE-Stream zurück
GetTaskEine Aufgabe anhand ihrer ID abrufen
CancelTaskEine laufende Aufgabe abbrechen
ListTasksAufgaben mit Filterung und Paginierung auflisten
SubscribeToTaskAufgabenaktualisierungen über SSE abonnieren
CreateTaskPushNotificationConfigEinen Webhook für Push-Benachrichtigungen registrieren
GetTaskPushNotificationConfigEine Konfiguration für Push-Benachrichtigungen abrufen
ListTaskPushNotificationConfigsPush-Konfigurationen für eine Aufgabe auflisten
DeleteTaskPushNotificationConfigEine Konfiguration für Push-Benachrichtigungen entfernen
GetExtendedAgentCardErweiterte Agentenkarte 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-Datenstrom 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

Mehrere Gesprächsrunden

Wenn eine Aufgabe den Zustand INPUT_REQUIRED erreicht, senden Sie eine Folgenachricht mit derselben 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 in Working, fügt die neue Nachricht zum Verlauf hinzu und setzt die Verarbeitung fort.

Idempotenz

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

Authentifizierung von 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-Zugangsdaten enthält
  • a2a-notification-token: <token> — wenn das Feld token vorhanden ist

Beide Header können gleichzeitig gesetzt werden. Der SSRF-Schutz validiert URLs des Webhooks anhand privater IP-Bereiche und des Localhost.

Eingabevalidierung

Alle eingehenden Anfragen werden vor der Verarbeitung validiert:

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

Einen Remote-Agent konsumieren

Verwenden Sie RemoteA2aAgent, um mit einem Remote-A2A-Agenten zu kommunizieren:

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 JSON-RPC-Codes als auch HTTP-Statuscodes zugeordnet:

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 Beispielagenten für A2A 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: die Erkennung der Agentenkarte, SendMessage (beide Agenten mit echtem LLM), GetTask, ListTasks, den CancelTask-Fehlerpfad, SendStreamingMessage, CRUD für Push-Benachrichtigungen, GetExtendedAgentCard, die Versionsaushandlung und Fehlerpfade.

Best Practices

  1. Fähigkeiten korrekt deklarieren — streaming und pushNotifications basierend darauf festlegen, was Ihr Agent tatsächlich unterstützt
  2. Streaming für lange Vorgänge verwenden — SendStreamingMessage liefert Clients Fortschritt in Echtzeit
  3. Mehrstufige Abläufe verarbeiten — contextId verwenden, um den Konversationsstatus über mehrere Nachrichten hinweg beizubehalten
  4. Webhook-URLs validieren — der SSRF-Schutz ist integriert, aber in der Produktion HTTPS verwenden
  5. Geeignete Zeitüberschreitungen festlegen — Zeitüberschreitungen für Anfragen an entfernte Agenten konfigurieren
  6. Idempotenz verwenden — Clients können SendMessage mit derselben messageId sicher wiederholen

Zurück: ← Server | Weiter: Evaluierung →

Abdeckung der Operationen

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

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

SubscribeToTask ist eine Momentaufnahme

Die Operation gibt die Aufgabe und ihren aktuellen Status zurück und schließt anschließend den Stream. Sie liefert keine nachfolgenden Aktualisierungen, daher darf ein Client nicht auf Fortschrittsmeldungen warten.

Eine erneute Live-Verbindung erfordert eine aufgabenbezogene Ereigniswarteschlange, die über die ursprüngliche Anfrage hinaus bestehen bleibt. Die Referenzimplementierungen beziehen diese aus ihren A2A SDKs — sowohl adk-python als auch adk-go delegieren tasks/resubscribe vollständig an den Warteschlangenmanager von SDK und implementieren dies nicht im ADK-Code. Dieser Server ist auf a2a-protocol-types eigenständig implementiert, das Wire-Typen statt einer Server-Laufzeitumgebung bereitstellt; daher existiert die Warteschlange noch nicht.

Verwenden Sie SendStreamingMessage, wenn Live-Aktualisierungen erforderlich sind.

Streaming-Ereignisvertrag

SendStreamingMessage übersetzt Agentenereignisse, sobald sie eintreffen:

Ereignis des AgentsA2A Ereignis
zuerst, vor der AusgabeTask, dann TaskStatusUpdateEvent — Working
Inhalt, partial = trueTaskArtifactUpdateEvent — append, nicht der letzte Chunk
Inhalt, partial = falseTaskArtifactUpdateEvent — letzter Abschnitt
Stream endetTaskStatusUpdateEvent — Completed
Stream-FehlerTaskStatusUpdateEvent — Failed

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

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