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:
| Recurso | Seção da especificação | Status |
|---|---|---|
| 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 ETagPOST /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-Versionem todas as rotas
Operações JSON-RPC
Todas as 11 operações A2A v1.0.0 são compatíveis:
| Método | Descrição |
|---|---|
SendMessage | Envia uma mensagem, cria/retoma uma tarefa |
SendStreamingMessage | Igual a SendMessage, mas retorna o fluxo SSE |
GetTask | Recuperar uma tarefa por ID |
CancelTask | Cancelar uma tarefa em execução |
ListTasks | Listar tarefas com filtragem e paginação |
SubscribeToTask | Inscrever-se para receber atualizações da tarefa via SSE |
CreateTaskPushNotificationConfig | Registrar um webhook para notificações push |
GetTaskPushNotificationConfig | Recuperar uma configuração de notificação push |
ListTaskPushNotificationConfigs | Listar configurações push para uma tarefa |
DeleteTaskPushNotificationConfig | Remover uma configuração de notificação push |
GetExtendedAgentCard | Recuperar 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:
- O primeiro evento é um objeto
Taskcompleto (de acordo com a especificação §3.1.2) - Os eventos subsequentes são
TaskStatusUpdateEvent(Working, Completed etc.) - 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 campoauthenticationcontém credenciais bearera2a-notification-token: <token>— quando o campotokenestá 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ção | Erro |
|---|---|
| Mensagem com zero partes | InvalidParams (-32602) |
| messageId vazio ou contendo apenas espaços em branco | InvalidParams (-32602) |
| messageId excedendo 256 caracteres | InvalidParams (-32602) |
| taskId vazio ou contendo apenas espaços em branco | InvalidParams (-32602) |
| taskId excedendo 256 caracteres | InvalidParams (-32602) |
| Metadados excedendo 64 KB | InvalidParams (-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:
| Erro | JSON-RPC Código | HTTP Status |
|---|---|---|
| TaskNotFound | -32001 | 404 |
| TaskNotCancelable | -32002 | 409 |
| PushNotificationNotSupported | -32003 | 400 |
| UnsupportedOperation | -32004 | 400 |
| ContentTypeNotSupported | -32005 | 415 |
| InvalidAgentResponse | -32006 | 502 |
| VersionNotSupported | -32009 | 400 |
| InvalidParams | -32602 | 400 |
| MethodNotFound | -32601 | 404 |
| Interno | -32603 | 500 |
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
- Declare os recursos com precisão — defina
streaming,pushNotificationscom base no que seu agente realmente oferece - Use streaming para operações longas —
SendStreamingMessagefornece aos clientes progresso em tempo real - Lide com fluxos de várias etapas — use
contextIdpara manter o estado da conversa entre mensagens - Valide URLs do webhook — a proteção contra SSRF é integrada, mas use HTTPS em produção
- Defina tempos limite apropriados — configure tempos limite de solicitação para chamadas a agentes remotos
- Use idempotência — os clientes podem tentar novamente
SendMessagecom segurança usando o mesmomessageId
Relacionado
- LlmAgent — Criando agentes
- Sistemas multiagente — Subagentes e hierarquias
- Implantação do servidor — Executando agentes como servidores HTTP
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ção | Status |
|---|---|
SendMessage | Conduz o agente e registra sua saída como um artefato |
SendStreamingMessage | Conduz o agente e transmite partes do artefato à medida que são produzidas |
GetTask, ListTasks | Completo |
CancelTask | Completo |
SubscribeToTask (tasks/resubscribe) | Somente instantâneo — veja abaixo |
CreateTaskPushNotificationConfig, GetTaskPushNotificationConfig, ListTaskPushNotificationConfigs, DeleteTaskPushNotificationConfig | Completo |
GetExtendedAgentCard | Completo |
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 agente | evento A2A |
|---|---|
| primeiro, antes da saída | Task, depois TaskStatusUpdateEvent — Working |
conteúdo, partial = true | TaskArtifactUpdateEvent — append, não é o último bloco |
conteúdo, partial = false | TaskArtifactUpdateEvent — trecho final |
| fluxo termina | TaskStatusUpdateEvent — Completed |
| erros no fluxo | TaskStatusUpdateEvent — 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.