Protocolo de agente a agente (A2A)
ADK-Rust implementa el A2A Protocol v1.0.0 para la comunicación de agentes entre redes. La implementación se encuentra en adk-server detrás del indicador de funcionalidad a2a-v1 y abarca las 11 operaciones JSON-RPC, los enlaces REST, el descubrimiento de tarjetas de agente y la negociación de versiones. Consulta Cobertura de operaciones para conocer la única operación cuya semántica es más limitada de lo que permite la especificación. Los tipos de protocolo son proporcionados por a2a-protocol-types: el A2A SDK de Rust verificado por la Foundation creado por @tomtom215 (a2a-rust).
Descripción general
A2A resulta útil para:
- Integrarse con servicios de agentes de terceros
- Crear arquitecturas de microservicios con agentes especializados
- Habilitar la comunicación entre agentes escritos en distintos lenguajes (cualquier lenguaje con un cliente A2A)
- Aplicar contratos formales entre sistemas de agentes
Para una organización interna sencilla, utiliza subagentes locales en lugar de A2A para obtener un mejor rendimiento.
Cumplimiento de v1.0.0
La implementación cumple plenamente con la especificación del A2A Protocol v1.0.0:
| Función | Sección de la especificación | Estado |
|---|---|---|
| Tarjeta del agente con declaración de capacidades | §8 | ✅ |
| Marcas de tiempo RFC 3339 en todos los cambios de estado de la tarea | §5.6.1 | ✅ |
Idempotencia del ID del mensaje para SendMessage | §3.3.1 | ✅ |
| Autenticación de notificaciones push (Bearer + token) | §13.2 | ✅ |
| Flujo de reanudación de varios turnos de INPUT_REQUIRED | §3.4.3 | ✅ |
| Validación de entradas (partes, ID, tamaño de metadatos) | §3.3 | ✅ |
Content-Type: application/a2a+json en las respuestas | §9 | ✅ |
| Objeto de tarea como primer evento de transmisión de SSE | §3.1.2 | ✅ |
| Búsqueda de tareas con alcance de contexto para múltiples turnos | §3.4.1 | ✅ |
Negociación de versiones (encabezado A2A-Version) | §9.1 | ✅ |
| Validación de la máquina de estados (estados terminales) | §4.1.3 | ✅ |
Tarjetas de agentes
Cada agente A2A expone una tarjeta de agente en /.well-known/agent-card.json que describe sus capacidades, habilidades e interfaces compatibles.
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 tarjeta del agente incluye:
- Nombre, descripción y versión del agente
- Interfaces compatibles con enlace de protocolo y versión
- Capacidades:
streaming,pushNotifications,extendedAgentCard - Habilidades derivadas de la configuración del agente
- Modos de entrada/salida predeterminados
Las capacidades ahora se declaran explícitamente mediante el parámetro AgentCapabilities — ya no hay valores predeterminados codificados.
Exponer un agente mediante A2A v1
Construye un servidor A2A v1.0.0 completo con integración de 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?;
Esto expone:
GET /.well-known/agent-card.json— Tarjeta del agente con almacenamiento en caché ETagPOST /jsonrpc— Endpoint JSON-RPC (las 11 operaciones de v1; consulta Cobertura de operaciones)- Rutas REST para todas las operaciones
- Negociación del encabezado
A2A-Versionen todas las rutas
Operaciones JSON-RPC
Se admiten las 11 operaciones de A2A v1.0.0:
| Método | Descripción |
|---|---|
SendMessage | Enviar un mensaje, crear o reanudar una tarea |
SendStreamingMessage | Igual que SendMessage, pero devuelve un flujo de SSE |
GetTask | Recuperar una tarea por ID |
CancelTask | Cancelar una tarea en ejecución |
ListTasks | Enumerar tareas con filtrado y paginación |
SubscribeToTask | Suscribirse a las actualizaciones de tareas mediante SSE |
CreateTaskPushNotificationConfig | Registrar un webhook para notificaciones push |
GetTaskPushNotificationConfig | Recuperar una configuración de notificaciones push |
ListTaskPushNotificationConfigs | Enumerar las configuraciones push de una tarea |
DeleteTaskPushNotificationConfig | Eliminar una configuración de notificaciones push |
GetExtendedAgentCard | Recuperar la tarjeta extendida del agente |
SendMessage
{
"jsonrpc": "2.0",
"id": 1,
"method": "SendMessage",
"params": {
"message": {
"messageId": "msg-123",
"role": "ROLE_USER",
"parts": [{"text": "Research quantum computing"}]
}
}
}
La respuesta incluye un objeto Task con estado, historial y artefactos. La respuesta utiliza Content-Type: application/a2a+json.
SendStreamingMessage
El mismo formato de solicitud que SendMessage. Devuelve un flujo SSE donde:
- El primer evento es un objeto
Taskcompleto (según la especificación §3.1.2) - Los eventos posteriores son
TaskStatusUpdateEvent(En progreso, Completado, etc.) - Los eventos de artefactos son
TaskArtifactUpdateEvent
Conversaciones de varios turnos
Cuando una tarea alcanza el estado INPUT_REQUIRED, envía un mensaje de seguimiento con el mismo contextId para reanudarla:
{
"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"}]
}
}
}
El controlador encuentra automáticamente la tarea existente mediante contextId, la cambia de INPUT_REQUIRED a Working, añade el nuevo mensaje al historial y continúa el procesamiento.
Idempotencia
Las solicitudes SendMessage duplicadas con el mismo messageId devuelven la tarea creada anteriormente sin volver a procesarla. Esto se aplica tanto a SendMessage como a SendStreamingMessage.
Autenticación de notificaciones push
Cuando un cliente registra un webhook mediante CreateTaskPushNotificationConfig, el servidor incluye encabezados de autenticación en las entregas del webhook:
Authorization: Bearer <credentials>— cuando el campoauthenticationcontiene credenciales de portadora2a-notification-token: <token>— cuando el campotokenestá presente
Ambos encabezados pueden establecerse simultáneamente. La protección contra SSRF valida URLs del webhook frente a rangos de IP privadas y localhost.
Validación de entradas
Todas las solicitudes entrantes se validan antes del procesamiento:
| Validación | Error |
|---|---|
| Mensaje sin partes | InvalidParams (-32602) |
| messageId vacío o compuesto únicamente por espacios en blanco | InvalidParams (-32602) |
| messageId con más de 256 caracteres | InvalidParams (-32602) |
| taskId vacío o compuesto únicamente por espacios en blanco | InvalidParams (-32602) |
| taskId con más de 256 caracteres | InvalidParams (-32602) |
| Metadatos de más de 64 KB | InvalidParams (-32602) |
Consumir un agente remoto
Usa RemoteA2aAgent para comunicarte con un 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 la comunicación directa a nivel de 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?;
Gestión de errores
Los errores de A2A se asignan tanto a códigos JSON-RPC como a códigos de estado HTTP:
| Error | JSON-RPC Código | HTTP Estado |
|---|---|---|
| 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 |
Ejecución de los ejemplos
Se incluyen dos agentes de ejemplo completos de A2A v1.0.0:
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
El cliente valida: el descubrimiento de la tarjeta del agente, SendMessage (ambos agentes con LLM reales), GetTask, ListTasks, la ruta de error de CancelTask, SendStreamingMessage, la CRUD de notificaciones push, GetExtendedAgentCard, la negociación de versiones y las rutas de error.
Prácticas recomendadas
- Declara las capacidades con precisión — establece
streamingypushNotificationssegún lo que tu agente admita realmente - Usa streaming para operaciones largas —
SendStreamingMessageproporciona a los clientes información del progreso en tiempo real - Gestiona los flujos de varios turnos — usa
contextIdpara mantener el estado de la conversación entre mensajes - Valida URLs de webhook — la protección contra SSRF está integrada, pero usa HTTPS en producción
- Establece tiempos de espera adecuados — configura tiempos de espera para las solicitudes a agentes remotos
- Usa la idempotencia — los clientes pueden reintentar de forma segura
SendMessagecon el mismomessageId
Relacionado
- LlmAgent — Creación de agentes
- Sistemas multiagente — Subagentes y jerarquías
- Implementación del servidor — Ejecución de agentes como servidores HTTP
Anterior: ← Servidor | Siguiente: Evaluación →
Cobertura de operaciones
Las 11 operaciones de JSON-RPC v1 se envían y se implementan.
| Operación | Estado |
|---|---|
SendMessage | Dirige al agente y registra su salida como un artefacto |
SendStreamingMessage | Dirige al agente y transmite fragmentos del artefacto a medida que se producen |
GetTask, ListTasks | Completo |
CancelTask | Completo |
SubscribeToTask (tasks/resubscribe) | Solo instantánea — consulta abajo |
CreateTaskPushNotificationConfig, GetTaskPushNotificationConfig, ListTaskPushNotificationConfigs, DeleteTaskPushNotificationConfig | Completo |
GetExtendedAgentCard | Completo |
SubscribeToTask es una instantánea
La operación devuelve la tarea y su estado actual, y luego cierra el flujo. No entrega actualizaciones posteriores, por lo que el cliente no debe esperar progreso de ella.
Una reconexión en vivo requiere una cola de eventos por tarea que sobreviva a la solicitud
original. Las implementaciones de referencia obtienen esto de su A2A SDKs: tanto
adk-python como adk-go delegan por completo tasks/resubscribe en el gestor de colas de SDK, y ninguna
lo implementa en código ADK.
Este servidor está implementado manualmente sobre a2a-protocol-types, que proporciona tipos de protocolo
en lugar de un entorno de ejecución del servidor, por lo que la cola aún no existe.
Usa SendStreamingMessage cuando se requieran actualizaciones en vivo.
Contrato de eventos de transmisión
SendStreamingMessage traduce los eventos del agente a medida que llegan:
| Evento del agente | evento A2A |
|---|---|
| primero, antes de la salida | Task, luego TaskStatusUpdateEvent — Working |
contenido, partial = true | TaskArtifactUpdateEvent — append, no es el último fragmento |
contenido, partial = false | TaskArtifactUpdateEvent — fragmento final |
| el flujo termina | TaskStatusUpdateEvent — Completed |
| errores del flujo | TaskStatusUpdateEvent — Failed |
Todos los fragmentos de una respuesta comparten un ID de artefacto. El texto unido se conserva, por lo que una GetTask posterior devuelve lo que se transmitió. Esto coincide con el contrato que adk-python y adk-go implementan sobre su SDKs.