Protocolo Agent-to-Agent (A2A)

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

Visão geral

A2A é útil para:

  • Integrar-se a serviços de agentes de terceiros
  • Criar arquiteturas de microsserviços com agentes especializados
  • Habilitar a comunicação entre agentes em diferentes linguagens (qualquer linguagem com um cliente A2A)
  • Estabelecer contratos formais entre sistemas de agentes

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

Conformidade com a v1.0.0

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

RecursoSeção da especificaçãoStatus
Cartão do agente com declaração de capacidades§8✅
Timestamps RFC 3339 em todas as alterações de status da tarefa§5.6.1✅
Idempotência do ID da mensagem para SendMessage§3.3.1✅
Autenticação de notificações push (Bearer + token)§13.2✅
Fluxo de retomada de múltiplos turnos 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 de tarefa como primeiro evento de streaming SSE§3.1.2✅
Consulta de tarefas 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 agentes

Todo agente A2A expõe um cartão de agente em /.well-known/agent-card.json que descreve seus recursos, habilidades e interfaces compatíveis.

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 do agente inclui:

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

Os recursos agora são declarados explicitamente por meio do parâmetro AgentCapabilities — não há mais padrões codificados diretamente.

Expondo um agente via A2A v1

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

Isso expõe:

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

Operações JSON-RPC

Todas as 11 operações A2A v1.0.0 são compatíveis:

MétodoDescrição
SendMessageEnvia uma mensagem, cria/retoma uma tarefa
SendStreamingMessageIgual a SendMessage, mas retorna o fluxo SSE
GetTaskRecuperar uma tarefa por ID
CancelTaskCancelar uma tarefa em execução
ListTasksListar tarefas com filtragem e paginação
SubscribeToTaskInscrever-se para receber atualizações da tarefa via SSE
CreateTaskPushNotificationConfigRegistrar um webhook para notificações push
GetTaskPushNotificationConfigRecuperar uma configuração de notificação push
ListTaskPushNotificationConfigsListar configurações 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 de tarefa com status, histórico e artefatos. A resposta usa Content-Type: application/a2a+json.

SendStreamingMessage

Mesmo formato de solicitação que SendMessage. Retorna um fluxo SSE em que:

  1. O primeiro evento é um objeto Task completo (de acordo com a especificação §3.1.2)
  2. Os eventos subsequentes são TaskStatusUpdateEvent (Working, Completed etc.)
  3. Os eventos de artefato são TaskArtifactUpdateEvent

Conversas de Vários Turnos

Quando uma tarefa atinge 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, faz a transição de INPUT_REQUIRED para Working, acrescenta a nova mensagem ao histórico e continua o processamento.

Idempotência

Solicitações SendMessage duplicadas com o mesmo messageId retornam a tarefa criada anteriormente sem realizar um novo processamento. Isso se aplica tanto a SendMessage quanto a SendStreamingMessage.

Autenticação de Notificações Push

Quando um cliente registra um webhook por meio de CreateTaskPushNotificationConfig, o servidor inclui cabeçalhos 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 cabeçalhos podem ser definidos simultaneamente. A proteção contra SSRF valida URLs do webhook em relação a intervalos de IP privados e ao localhost.

Validação de Entrada

Todas as solicitações 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 A2A remoto:

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

Os erros de A2A correspondem tanto aos códigos JSON-RPC quanto aos 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 do 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 real), GetTask, ListTasks, caminho de erro de CancelTask, SendStreamingMessage, CRUD de notificação push, GetExtendedAgentCard, negociação de versão e caminhos de erro.

Práticas recomendadas

  1. Declare os recursos com precisão — defina streaming, pushNotifications com base no que seu agente realmente oferece
  2. Use streaming para operações longas — SendStreamingMessage fornece aos clientes progresso em tempo real
  3. Lide com fluxos de várias etapas — use contextId para manter o estado da conversa entre mensagens
  4. Valide URLs do webhook — a proteção contra SSRF é integrada, mas use HTTPS em produção
  5. Defina tempos limite apropriados — configure tempos limite de solicitação para chamadas a agentes remotos
  6. Use idempotência — os clientes podem tentar novamente SendMessage com segurança usando o mesmo messageId

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

Cobertura das operações

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

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

SubscribeToTask é um instantâneo

A operação retorna a tarefa e seu status atual e, em seguida, encerra o fluxo. Ela não entrega atualizações subsequentes, portanto um cliente não deve aguardar progresso por meio dela.

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

Use SendStreamingMessage quando forem necessárias atualizações ao vivo.

Contrato de eventos de streaming

SendStreamingMessage traduz os eventos do agente conforme eles chegam:

Evento do agenteevento A2A
primeiro, antes da saídaTask, depois TaskStatusUpdateEvent — Working
conteúdo, partial = trueTaskArtifactUpdateEvent — append, não é o último bloco
conteúdo, partial = falseTaskArtifactUpdateEvent — trecho final
fluxo terminaTaskStatusUpdateEvent — Completed
erros no fluxoTaskStatusUpdateEvent — Failed

Todos os blocos de uma resposta compartilham um ID de artefato. O texto unido é persistido, portanto uma chamada posterior a GetTask retorna o que foi transmitido. Isso corresponde ao contrato que adk-python e adk-go implementam sobre SDKs.

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