Protocole Agent-Ă -Agent (A2A)

ADK-Rust implĂ©mente le A2A Protocol v1.0.0 pour la communication entre agents sur plusieurs rĂ©seaux. L’implĂ©mentation se trouve dans adk-server derriĂšre le drapeau de fonctionnalitĂ© a2a-v1 et couvre les 11 opĂ©rations JSON-RPC, les liaisons REST, la dĂ©couverte de carte d’agent et la nĂ©gociation de version. Voir Couverture des opĂ©rations pour l’opĂ©ration dont la sĂ©mantique est plus restreinte que ce que la spĂ©cification autorise. Les types de transport sont fournis par a2a-protocol-types — le A2A SDK Rust vĂ©rifiĂ© par la Fondation par @tomtom215 (a2a-rust).

Vue d’ensemble

A2A est utile lorsque :

  • IntĂ©gration avec des services d’agents tiers
  • Construction d’architectures de microservices avec des agents spĂ©cialisĂ©s
  • Activation de la communication entre agents dans plusieurs langages (tout langage avec un client A2A)
  • Application de contrats formels entre systĂšmes d’agents

Pour une organisation interne simple, utilisez des sous-agents locaux plutĂŽt que A2A pour de meilleures performances.

Conformité v1.0.0

L’implĂ©mentation est entiĂšrement conforme Ă  la spĂ©cification A2A Protocol v1.0.0 :

FonctionnalitéSection de spécificationStatut
Carte d'agent avec dĂ©claration des capacitĂ©s§8✅
Horodatages RFC 3339 sur tous les changements de statut de tñche§5.6.1✅
Idempotence de l’ID du message pour SendMessage§3.3.1✅
Authentification des notifications push (Bearer + token)§13.2✅
INPUT_REQUIRED flux de reprise multi-tours§3.4.3✅
Validation des entrĂ©es (parties, ID, taille des mĂ©tadonnĂ©es)§3.3✅
Content-Type: application/a2a+json sur les rĂ©ponses§9✅
Objet Task comme premier Ă©vĂ©nement de streaming SSE§3.1.2✅
Recherche de tĂąche Ă  portĂ©e de contexte pour les Ă©changes multi-tours§3.4.1✅
NĂ©gociation de version (en-tĂȘte A2A-Version)§9.1✅
Validation de la machine Ă  Ă©tats (Ă©tats terminaux)§4.1.3✅

Fiches d’agent

Chaque agent A2A expose une fiche d’agent Ă  /.well-known/agent-card.json dĂ©crivant ses capacitĂ©s, ses compĂ©tences et les interfaces prises en charge.

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

La fiche d’agent inclut :

  • Nom, description et version de l’agent
  • Interfaces prises en charge avec l’association de protocole et la version
  • CapacitĂ©s : streaming, pushNotifications, extendedAgentCard
  • CompĂ©tences dĂ©rivĂ©es de la configuration de l’agent
  • Modes d’entrĂ©e/sortie par dĂ©faut

Les capacitĂ©s sont dĂ©sormais dĂ©clarĂ©es explicitement via le paramĂštre AgentCapabilities — plus de valeurs par dĂ©faut codĂ©es en dur.

Exposer un agent via A2A v1

CrĂ©ez un serveur A2A v1.0.0 complet avec l’intĂ©gration LLM :

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

Cela expose :

  • GET /.well-known/agent-card.json — Fiche d’agent avec mise en cache ETag
  • POST /jsonrpc — point de terminaison JSON-RPC (les 11 opĂ©rations v1 ; voir Operation coverage)
  • Routes REST pour toutes les opĂ©rations
  • NĂ©gociation d’en-tĂȘte A2A-Version sur toutes les routes

Opérations JSON-RPC

Les 11 opérations A2A v1.0.0 sont prises en charge :

MéthodeDescription
SendMessageEnvoyer un message, créer/reprendre une tùche
SendStreamingMessageIdentique Ă  SendMessage mais renvoie un flux SSE
GetTaskRécupérer une tùche par ID
CancelTaskAnnuler une tĂąche en cours d’exĂ©cution
ListTasksLister les tĂąches avec filtrage et pagination
SubscribeToTaskS’abonner aux mises à jour des tñches via SSE
CreateTaskPushNotificationConfigEnregistrer un webhook pour les notifications push
GetTaskPushNotificationConfigRécupérer une configuration de notification push
ListTaskPushNotificationConfigsLister les configurations push pour une tĂąche
DeleteTaskPushNotificationConfigSupprimer une configuration de notification push
GetExtendedAgentCardRécupérer la fiche agent étendue

SendMessage

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

La réponse inclut un objet Task avec status, history et artifacts. La réponse utilise Content-Type: application/a2a+json.

SendStreamingMessage

MĂȘme format de requĂȘte que SendMessage. Renvoie un flux SSE oĂč :

  1. Le premier événement est un objet Task complet (selon la spec §3.1.2)
  2. Les événements suivants sont TaskStatusUpdateEvent (Working, Completed, etc.)
  3. Les Ă©vĂ©nements d’artifact sont TaskArtifactUpdateEvent

Conversations multi-tours

Lorsqu’une tĂąche atteint l’état INPUT_REQUIRED, envoyez un message de suivi avec le mĂȘme contextId pour la reprendre :

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

Le gestionnaire retrouve automatiquement la tñche existante par contextId, la fait passer de INPUT_REQUIRED à Working, ajoute le nouveau message à l’historique et poursuit le traitement.

Idempotence

Les requĂȘtes SendMessage en double avec le mĂȘme messageId renvoient la tĂąche créée prĂ©cĂ©demment sans retraitement. Cela s’applique Ă  la fois Ă  SendMessage et SendStreamingMessage.

Authentification des notifications push

Lorsqu’un client enregistre un webhook via CreateTaskPushNotificationConfig, le serveur inclut des en-tĂȘtes d’authentification lors des livraisons de webhook :

  • Authorization: Bearer <credentials> — lorsque le champ authentication contient des identifiants bearer
  • a2a-notification-token: <token> — lorsque le champ token est prĂ©sent

Les deux en-tĂȘtes peuvent ĂȘtre dĂ©finis simultanĂ©ment. La protection SSRF valide la URLs du webhook par rapport aux plages d’IP privĂ©es et Ă  localhost.

Validation des entrées

Toutes les requĂȘtes entrantes sont validĂ©es avant traitement :

ValidationErreur
Message avec zéro partieInvalidParams (-32602)
messageId vide ou contenant uniquement des espacesInvalidParams (-32602)
messageId dépassant 256 caractÚresInvalidParams (-32602)
taskId vide ou composé uniquement d'espacesInvalidParams (-32602)
taskId dépassant 256 caractÚresInvalidParams (-32602)
Métadonnées dépassant 64 KBInvalidParams (-32602)

Consommer un agent distant

Utilisez RemoteA2aAgent pour communiquer avec un agent A2A distant :

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

Client A2A

Pour une communication directe au niveau du protocole :

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

Gestion des erreurs

Les erreurs A2A se mappent Ă  la fois aux codes JSON-RPC et aux codes d’état HTTP :

ErreurJSON-RPC CodeHTTP Statut
TaskNotFound-32001404
TaskNotCancelable-32002409
PushNotificationNotSupported-32003400
UnsupportedOperation-32004400
ContentTypeNotSupported-32005415
InvalidAgentResponse-32006502
VersionNotSupported-32009400
InvalidParams-32602400
MethodNotFound-32601404
Interne-32603500

Exécution des exemples

Deux agents exemples complets A2A v1.0.0 sont inclus :

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

Le client valide : la dĂ©couverte de la carte de l’agent, SendMessage (les deux agents avec de vraies LLM), GetTask, ListTasks, le chemin d’erreur CancelTask, SendStreamingMessage, les notifications push CRUD, GetExtendedAgentCard, la nĂ©gociation de version et les chemins d’erreur.

Bonnes pratiques

  1. DĂ©clarer les capacitĂ©s avec prĂ©cision — dĂ©finissez streaming, pushNotifications en fonction de ce que votre agent prend rĂ©ellement en charge
  2. Utiliser le streaming pour les opĂ©rations longues — SendStreamingMessage fournit aux clients une progression en temps rĂ©el
  3. GĂ©rer les flux Ă  plusieurs tours — utilisez contextId pour conserver l’état de la conversation entre les messages
  4. Valider le webhook URLs — la protection SSRF est intĂ©grĂ©e, mais utilisez HTTPS en production
  5. DĂ©finir des dĂ©lais d’expiration appropriĂ©s — configurez des dĂ©lais d’expiration des requĂȘtes pour les appels Ă  des agents distants
  6. Utiliser l’idempotence — les clients peuvent rĂ©essayer sans risque SendMessage avec le mĂȘme messageId

PrĂ©cĂ©dent : ← Serveur | Suivant : Évaluation →

Couverture des opérations

Les 11 opérations v1 JSON-RPC sont toutes distribuées et implémentées.

OpĂ©rationÉtat
SendMessagePilote l’agent, enregistre sa sortie comme un artefact
SendStreamingMessagePilote l’agent, diffuse les fragments d’artefact au fur et à mesure de leur production
GetTask, ListTasksComplet
CancelTaskComplet
SubscribeToTask (tasks/resubscribe)InstantanĂ© uniquement — voir ci-dessous
CreateTaskPushNotificationConfig, GetTaskPushNotificationConfig, ListTaskPushNotificationConfigs, DeleteTaskPushNotificationConfigComplet
GetExtendedAgentCardComplet

SubscribeToTask est un instantané

L’opĂ©ration renvoie la tĂąche et son Ă©tat actuel, puis ferme le flux. Elle ne fournit pas les mises Ă  jour ultĂ©rieures, donc un client ne doit pas l’attendre pour suivre la progression.

Une rĂ©-attache en direct nĂ©cessite une file d’évĂ©nements par tĂąche qui survit Ă  la requĂȘte d’origine. Les implĂ©mentations de rĂ©fĂ©rence obtiennent cela Ă  partir de leur A2A SDKs — adk-python et adk-go dĂ©lĂšguent toutes deux tasks/resubscribe entiĂšrement au gestionnaire de files de SDK, et aucune ne l’implĂ©mente dans le code ADK. Ce serveur est rĂ©alisĂ© Ă  la main sur a2a-protocol-types, qui fournit des types de protocole plutĂŽt qu’un runtime de serveur, donc la file n’existe pas encore.

Utilisez SendStreamingMessage lorsque des mises Ă  jour en direct sont requises.

Contrat des événements en flux

SendStreamingMessage traduit les Ă©vĂ©nements d’agent au fur et Ă  mesure de leur arrivĂ©e:

ÉvĂ©nement de l'agentA2A Ă©vĂ©nement
d'abord, avant la sortieTask, puis TaskStatusUpdateEvent — Working
contenu, partial = trueTaskArtifactUpdateEvent — append, pas le dernier bloc
contenu, partial = falseTaskArtifactUpdateEvent — dernier segment
fin du fluxTaskStatusUpdateEvent — Completed
erreurs du fluxTaskStatusUpdateEvent — Failed

Tous les fragments d’une rĂ©ponse partagent un identifiant d’artefact afin qu’un client puisse les rĂ©assembler. Le texte assemblĂ© est conservĂ©, donc une requĂȘte ultĂ©rieure GetTask renvoie ce qui a Ă©tĂ© diffusĂ©. Cela correspond au contrat adk-python et adk-go implĂ©mentent sur leur SDKs.

Protocole Agent-Ă -Agent (A2A) - Documentation ADK-Rust | ADK-Rust