A2A Protocol
Guia completo do protocolo Agent-to-Agent — do funcionamento interno à integração com LangGraph e API Gateway de produção.
O que é o A2A?
O Agent-to-Agent (A2A) é um protocolo aberto criado pelo Google em abril de 2025 e posteriormente transferido para a Linux Foundation. Ele padroniza a comunicação entre agentes de IA de diferentes frameworks e fornecedores, sem que nenhum agente precise expor seu estado interno, memória ou lógica.
Problema que resolve
Sem um padrão, cada par de agentes exigia um conector customizado. O A2A cria uma linguagem comum — permitindo que um agente do LangGraph colabore com um do CrewAI, Semantic Kernel ou qualquer framework compatível.
JSON publicado em /.well-known/agent.json descrevendo capacidades, endpoint e autenticação do agente.
Unidade fundamental de trabalho, com lifecycle próprio e ID único. Pode ser síncrona ou assíncrona.
Turno de comunicação entre cliente e agente remoto, composto por Parts (Text, File, Data).
Saída tangível gerada pelo agente: documento, dataset, imagem — entregue em chunks via SSE.
Primitivas do protocolo
Papéis na comunicação
| Papel | Descrição | Responsabilidade |
|---|---|---|
| Client Agent | Agente que inicia e delega a tarefa | Envia JSON-RPC, monitora status, consome artefatos |
| Remote Agent | Agente que recebe e executa a tarefa | Processa, atualiza lifecycle, retorna resultado |
Types de Parts
| Type | Uso |
|---|---|
| TextPart | Texto simples — instrução, resposta, pergunta |
| FilePart | Arquivo binário (base64 inline ou URL referenciada) |
| DataPart | JSON estruturado — parâmetros, resultados tabelados |
Transporte
Todo o tráfego A2A usa HTTP + JSON-RPC 2.0 como base. Para respostas em tempo real, o protocolo usa Server-Sent Events (SSE). A partir da v0.3, há suporte opcional a gRPC para deployments de alta performance.
Ciclo de uma Task
Cada task percorre um estado bem definido. O contextId agrupa tasks relacionadas ao longo de múltiplas interações.
contextId e taskId — o servidor os gera e retorna. Em todas as mensagens subsequentes, inclua-os para manter a continuidade da conversa.
Integração com LangGraph
O LangGraph Server expõe o endpoint A2A automaticamente em /a2a/{assistant_id} quando você roda via langgraph dev ou faz deploy em produção. Não é necessário implementar o protocolo manualmente.
/a2a/<assistant_id>.
Iniciando os servidores
# Terminal 1 — Agente A
langgraph dev --port 2024
# Terminal 2 — Agente B
langgraph dev --port 2025
Definindo o agente
Cada agente é um StateGraph do LangGraph. O servidor expõe automaticamente o endpoint A2A.
from langgraph.graph import StateGraph, MessagesState
from langchain_anthropic import ChatAnthropic
# Instancia o LLM
llm = ChatAnthropic(model="claude-sonnet-4-20250514")
# Nó de processamento
def call_model(state: MessagesState):
response = llm.invoke(state["messages"])
return {"messages": [response]}
# Constrói o grafo
builder = StateGraph(MessagesState)
builder.add_node("agent", call_model)
builder.set_entry_point("agent")
builder.set_finish_point("agent")
graph = builder.compile()
Comunicação A2A entre agentes
O orquestrador envia mensagens JSON-RPC 2.0 para o endpoint de cada agente e mantém o contextId ao longo das rodadas.
import asyncio, aiohttp, uuid
async def send_message(session, url, message,
context_id=None, task_id=None):
payload = {
"jsonrpc": "2.0",
"method": "message/send",
"id": str(uuid.uuid4()),
"params": {
"message": {
"role": "user",
"parts": [{"type": "text", "text": message}],
# Inclui contextId após primeira mensagem
**({"contextId": context_id} if context_id else {}),
**({"taskId": task_id} if task_id else {}),
}
}
}
async with session.post(url, json=payload) as resp:
data = await resp.json()
result = data["result"]
text = result["artifacts"][0]["parts"][0]["text"]
return text, result.get("contextId"), result.get("id")
async def run_conversation():
agent_a = "http://localhost:2024/a2a/<AGENT_A_ID>"
agent_b = "http://localhost:2025/a2a/<AGENT_B_ID>"
message = "Olá, vamos colaborar numa tarefa!"
context_id = task_id = None
async with aiohttp.ClientSession() as session:
for _ in range(3):
message, context_id, task_id = await send_message(
session, agent_a, message, context_id, task_id)
message, context_id, task_id = await send_message(
session, agent_b, message, context_id, task_id)
asyncio.run(run_conversation())
Arquitetura com Gateway
Como A2A usa HTTP puro, um API gateway é o ponto natural de controle para todo o tráfego entre agentes — autenticação, roteamento, rate limiting e observabilidade.
Configuração Kong (YAML)
services:
- name: agente-a
url: http://langgraph-agent-a:2024
routes:
- name: a2a-agente-a
paths: ["/agentes/a"]
strip_path: true
plugins:
- name: jwt
config:
key_claim_name: iss
claims_to_verify: [exp]
- name: rate-limiting
config:
minute: 60
policy: local
Autenticação no Gateway
O Agent Card declara os esquemas de auth suportados, no mesmo formato do OpenAPI. O gateway valida as credenciais antes de encaminhar ao agente.
| Scheme | Header HTTP | Uso típico |
|---|---|---|
| apiKey | API-Key: <valor> | Agentes internos |
| http Bearer | Authorization: Bearer <token> | JWT/OAuth2 |
| oauth2 | Bearer + token endpoint | Agentes terceiros |
| openIdConnect | Bearer + OIDC discovery | Enterprise SSO |
| mTLS | Certificado cliente | Alta segurança |
Configurando SSE no Gateway
O A2A usa Server-Sent Events para respostas em streaming. O gateway não pode fazer buffering das respostas, caso contrário o cliente só recebe a resposta completa ao final.
NGINX
location /agentes/ {
proxy_pass http://langgraph_upstream;
# Desabilita buffering — essencial para SSE
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 300s;
# Headers para SSE
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding on;
}
Proxy A2A com FastAPI
Um gateway leve que combina autenticação JWT, roteamento dinâmico por agente e proxy com suporte a SSE.
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import StreamingResponse
import httpx, jwt
app = FastAPI()
AGENTES = {
"planejador": "http://agente-planejador:2024/a2a/assistente-abc",
"executor": "http://agente-executor:2025/a2a/assistente-xyz",
}
@app.post("/a2a/{agente}")
async def proxy_a2a(agente: str, request: Request):
# 1. Autenticação JWT
token = request.headers.get("Authorization", "").replace("Bearer ", "")
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Token inválido")
# 2. Roteamento
if agente not in AGENTES:
raise HTTPException(status_code=404, detail="Agente não encontrado")
# 3. Proxy com suporte a SSE
body = await request.json()
async def stream():
async with httpx.AsyncClient() as client:
async with client.stream("POST", AGENTES[agente], json=body) as r:
async for chunk in r.aiter_bytes():
yield chunk
return StreamingResponse(stream(), media_type="text/event-stream")
Logging e rastreamento
O campo contextId é o identificador de sessão do A2A — use-o para correlacionar todas as mensagens de uma conversa nos seus logs.
async def a2a_middleware(request, call_next):
body = await request.json()
# Extrai metadados do JSON-RPC
method = body.get("method") # ex: "message/send"
context_id = (
body.get("params", {})
.get("message", {})
.get("contextId")
)
logger.info({
"protocol": "a2a",
"method": method,
"context_id": context_id,
"agent": request.url.path,
"timestamp": datetime.utcnow().isoformat(),
})
return await call_next(request)
A2A vs MCP
Os dois protocolos são complementares — um agente usa MCP para acessar ferramentas e A2A para delegar subtarefas a outros agentes.
| Dimensão | A2A | MCP |
|---|---|---|
| Para quê | Agente ↔ Agente | Agente ↔ Ferramentas/APIs |
| Modelo | Peer-to-peer, colaborativo | Host-client-server |
| Estado | Stateful (lifecycle de tasks) | Stateless (chamadas discretas) |
| Descoberta | Agent Card com skills | Lista de tools com schema |
| Streaming | SSE nativo + push webhooks | Opcional por implementação |
| Quem criou | Google → Linux Foundation | Anthropic |
O que o Gateway entrega para A2A
-
1Autenticação centralizada
JWT, OAuth2, mTLS e API Key validados antes de qualquer tráfego chegar ao agente LangGraph.
-
2Roteamento por skill/agente
O gateway roteia para o agente correto com base no path, header ou Agent Card discovery — os agentes nunca ficam expostos diretamente.
-
3Rate limiting máquina-a-máquina
Tráfego A2A é mais intenso que tráfego humano — limite por agente/cliente usando headers como
X-Agent-Role. -
4Proxy SSE sem buffering
proxy_buffering offno NGINX ou HTTP APIs no AWS — essencial para streaming funcionar. -
5Observabilidade via contextId
Logue o
contextIde omethodJSON-RPC em cada request para correlacionar toda a conversa entre agentes.