Protocolo Agent-to-Agent (A2A)

ADK-Rust implementa o A2A Protocol v1.0.0 para comunicação entre agentes em diferentes redes. A implementação fica em adk-server, atrás da flag de recurso a2a-v1, e cobre todas as 11 operações JSON-RPC, ligações REST, descoberta de cartão do agente e negociação de versão. Consulte Cobertura de operações para a única operação cuja semântica é mais restrita do que a especificação permite. Os tipos de wire são fornecidos por a2a-protocol-types — a implementação Rust de A2A SDK verificada pela Foundation, por @tomtom215 (a2a-rust).

Visão geral

A2A é útil quando:

  • Integrando com serviços de agentes de terceiros
  • Construindo arquiteturas de microsserviços com agentes especializados
  • Habilitando comunicação entre agentes em diferentes linguagens (qualquer linguagem com um cliente A2A)
  • Aplicando contratos formais entre sistemas de agentes

Para organização interna simples, use subagentes locais em vez de A2A para melhor desempenho.

Conformidade com v1.0.0

A implementação está totalmente em conformidade com a especificação A2A Protocol v1.0.0:

RecursoSeção da EspecificaçãoStatus
Cartão do agente com declaração de capacidades§8
Timestamps RFC 3339 em todas as mudanças de status da tarefa§5.6.1
Idempotência do ID da mensagem para SendMessage§3.3.1
Autenticação de notificação push (Bearer + token)§13.2
Fluxo de retomada multi-turno de INPUT_REQUIRED§3.4.3
Validação de entrada (partes, IDs, tamanho dos metadados)§3.3
Content-Type: application/a2a+json nas respostas§9
Objeto task como primeiro evento de streaming SSE§3.1.2
Busca de task com escopo de contexto para múltiplas interações§3.4.1
Negociação de versão (cabeçalho A2A-Version)§9.1
Validação da máquina de estados (estados terminais)§4.1.3

Cartões de Agente

Todo agente A2A expõe um cartão de agente em /.well-known/agent-card.json descrevendo suas capacidades, habilidades e interfaces suportadas.

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

O cartão de agente inclui:

  • Nome do agente, descrição e versão
  • Interfaces suportadas com binding de protocolo e versão
  • Capacidades: streaming, pushNotifications, extendedAgentCard
  • Habilidades derivadas da configuração do agente
  • Modos padrão de entrada/saída

As capacidades agora são declaradas explicitamente por meio do parâmetro AgentCapabilities — nada mais de padrões codificados.

Expondo um Agente via A2A v1

Crie um servidor completo A2A v1.0.0 com integração 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?;

Isso expõe:

  • GET /.well-known/agent-card.json — Cartão de agente com cache ETag
  • POST /jsonrpc — endpoint JSON-RPC (todas as 11 operações v1; veja Cobertura de operações)
  • rotas REST para todas as operações
  • negociação de cabeçalho A2A-Version em todas as rotas

Operações JSON-RPC

Todas as 11 operações v1.0.0 A2A são suportadas:

MétodoDescrição
SendMessageEnviar uma mensagem, criar/retomar uma tarefa
SendStreamingMessageIgual a SendMessage, mas retorna fluxo SSE
GetTaskRecuperar uma tarefa por ID
CancelTaskCancelar uma tarefa em execução
ListTasksListar tarefas com filtragem e paginação
SubscribeToTaskAssinar atualizações de tarefa via SSE
CreateTaskPushNotificationConfigRegistrar um webhook para notificações push
GetTaskPushNotificationConfigRecuperar uma configuração de notificação push
ListTaskPushNotificationConfigsListar configurações de push para uma tarefa
DeleteTaskPushNotificationConfigRemover uma configuração de notificação push
GetExtendedAgentCardRecuperar o cartão estendido do agente

SendMessage

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

A resposta inclui um objeto Task com status, history e artifacts. A resposta usa Content-Type: application/a2a+json.

SendStreamingMessage

Mesmo formato de request que SendMessage. Retorna um stream SSE onde:

  1. O primeiro evento é um objeto Task completo (conforme a spec §3.1.2)
  2. Os eventos subsequentes são TaskStatusUpdateEvent (Working, Completed, etc.)
  3. Os eventos de artifact são TaskArtifactUpdateEvent

Conversas de Múltiplas Interações

Quando uma tarefa alcança o estado INPUT_REQUIRED, envie uma mensagem de acompanhamento com o mesmo contextId para retomá-la:

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

O handler encontra automaticamente a tarefa existente por contextId, a transiciona de INPUT_REQUIRED para Working, anexa a nova mensagem ao histórico e continua o processamento.

Idempotência

Requests duplicadas de SendMessage com o mesmo messageId retornam a tarefa criada anteriormente sem reprocessamento. Isso se aplica tanto a SendMessage quanto a SendStreamingMessage.

Autenticação de Notificações Push

Quando um cliente registra um webhook via CreateTaskPushNotificationConfig, o servidor inclui headers de autenticação nas entregas do webhook:

  • Authorization: Bearer <credentials> — quando o campo authentication contém credenciais bearer
  • a2a-notification-token: <token> — quando o campo token está presente

Ambos os headers podem ser definidos simultaneamente. A proteção contra SSRF valida o webhook URLs em relação a intervalos de IP privados e localhost.

Validação de Entrada

Todas as requests recebidas são validadas antes do processamento:

ValidaçãoErro
Mensagem com zero partesInvalidParams (-32602)
messageId vazio ou contendo apenas espaços em brancoInvalidParams (-32602)
messageId excedendo 256 caracteresInvalidParams (-32602)
taskId vazio ou contendo apenas espaços em brancoInvalidParams (-32602)
taskId excedendo 256 caracteresInvalidParams (-32602)
Metadados excedendo 64 KBInvalidParams (-32602)

Consumindo um Agente Remoto

Use RemoteA2aAgent para se comunicar com um agente remoto 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()?;

Cliente A2A

Para comunicação direta no nível do protocolo:

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

Tratamento de Erros

Erros A2A mapeiam para códigos JSON-RPC e códigos de status HTTP:

ErroJSON-RPC CódigoHTTP Status
TaskNotFound-32001404
TaskNotCancelable-32002409
PushNotificationNotSupported-32003400
UnsupportedOperation-32004400
ContentTypeNotSupported-32005415
InvalidAgentResponse-32006502
VersionNotSupported-32009400
InvalidParams-32602400
MethodNotFound-32601404
Interno-32603500

Executando os Exemplos

Dois agentes de exemplo completos A2A v1.0.0 estão incluídos:

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

O cliente valida: descoberta do cartão do agente, SendMessage (ambos os agentes com LLM reais), GetTask, ListTasks, caminho de erro de CancelTask, SendStreamingMessage, CRUD de notificação por push, GetExtendedAgentCard, negociação de versão e caminhos de erro.

Melhores Práticas

  1. Declare as capacidades com precisão — defina streaming, pushNotifications com base no que seu agente realmente suporta
  2. Use streaming para operações longasSendStreamingMessage fornece aos clientes progresso em tempo real
  3. Lide com fluxos de múltiplas interações — use contextId para manter o estado da conversa entre mensagens
  4. Valide URLs de webhook — a proteção contra SSRF é integrada, mas use HTTPS em produção
  5. Defina timeouts apropriados — configure timeouts de requisição para chamadas a agentes remotos
  6. Use idempotência — os clientes podem tentar novamente com segurança SendMessage com o mesmo messageId

Anterior: ← Servidor | Próximo: Avaliação →

Cobertura de operações

Todas as 11 operações JSON-RPC da v1 são encaminhadas e implementadas.

OperaçãoStatus
SendMessageAciona o agente, registra sua saída como um artefato
SendStreamingMessageAciona o agente, transmite partes do artefato à medida que são produzidas
GetTask, ListTasksCompleto
CancelTaskCompleto
SubscribeToTask (tasks/resubscribe)Somente snapshot — veja abaixo
CreateTaskPushNotificationConfig, GetTaskPushNotificationConfig, ListTaskPushNotificationConfigs, DeleteTaskPushNotificationConfigCompleto
GetExtendedAgentCardCompleto

SubscribeToTask é um snapshot

A operação retorna a tarefa e seu status atual, depois fecha o stream. Ela não entrega atualizações subsequentes, então um cliente não deve esperar que ela forneça progresso.

Uma nova reanexação ao vivo exige uma fila de eventos por tarefa que sobreviva à solicitação original. As implementações de referência obtêm isso de suas A2A SDKs — adk-python e adk-go delegam tasks/resubscribe inteiramente ao gerenciador de filas do SDK, e nenhuma delas o implementa em código de ADK. Este servidor foi feito manualmente em a2a-protocol-types, que fornece tipos de wire em vez de um runtime de servidor, então a fila ainda não existe.

Use SendStreamingMessage quando forem necessárias atualizações em tempo real.

Contrato de eventos de streaming

SendStreamingMessage traduz os eventos do agente à medida que eles chegam:

Evento do agenteA2A evento
primeiro, antes da saídaTask, depois TaskStatusUpdateEventWorking
conteúdo, partial = trueTaskArtifactUpdateEventappend, não o último trecho
conteúdo, partial = falseTaskArtifactUpdateEvent — fragmento final
fluxo terminaTaskStatusUpdateEventCompleted
erros do fluxoTaskStatusUpdateEventFailed

Todos os chunks de uma resposta compartilham um ID de artefato para que um cliente possa remontá-los. O texto unido é persistido, então uma chamada posterior GetTask retorna o que foi transmitido. Isso corresponde ao contrato adk-python e adk-go implementam sobre seu SDKs.

Protocolo Agent-to-Agent (A2A) - Documentação ADK-Rust | ADK-Rust