Pular para o conteúdo principal
Todos os Posts
AI8 min de leitura

Expondo Agentes Spring AI via o Protocolo A2A: O Que a Interoperabilidade Realmente Te Dá

A integração A2A do lado servidor no Spring AI já está estável o suficiente para colocar em produção, mas o protocolo é mais útil em fronteiras organizacionais, não como substituto de RPC interno. Este post percorre o que de fato muda em uma codebase Spring AI, onde ainda existem arestas afiadas, e um framework prático de decisão entre A2A, MCP e REST puro.

Tiarê Balbi BonaminiEngenheiro de Software · Vancouver
2/4

O protocolo Agent2Agent foi para a Linux Foundation em 2025, e o spring-ai-a2a ganhou autoconfiguração de primeira classe no começo de 2026. Se você roda um stack JVM, agora tem uma forma padrão de publicar um agente para que algo que você não é dono — um time diferente, um runtime diferente, uma empresa diferente — consiga descobri-lo e conversar com ele sem uma integração sob medida.

Esse é o pitch. A realidade é mais estreita do que o pitch. A2A não é um gRPC melhor e não é um substituto para a tooling de agente que você já tem dentro do seu próprio service mesh. É um protocolo de fronteira, e tratá-lo como tal é a diferença entre um rollout limpo e seis meses de abstrações vazando.

Este post é sobre o que de fato muda em uma codebase Spring AI quando você expõe um agente via A2A, onde o protocolo ainda tem arestas, e como decidir quando é a ferramenta certa. Vou manter o exemplo mínimo e Kotlin-first, porque é onde mora a maior parte do meu trabalho com Spring AI.

O que A2A está realmente fazendo

Tire o marketing e A2A é três coisas. Primeiro, um contrato de descoberta: todo servidor A2A publica um AgentCard em /.well-known/agent-card.json descrevendo sua identidade, suas skills e suas capabilities. Segundo, um protocolo de troca de mensagens sobre HTTP+JSON para enviar prompts estruturados, receber respostas estruturadas e fazer streaming de resultados parciais. Terceiro, um compromisso deliberado com opacidade: o chamador não sabe se o outro lado é um agente Spring AI, um agente LangChain em Python, ou um humano com uma velocidade de digitação muito alta. Só o AgentCard e o contrato wire importam.

O AgentCard é onde mora a maior parte do valor. Uma skill é a unidade de capability, e uma declaração de capability diz aos chamadores que inputs uma skill aceita e o que ela retorna. Na implementação do Spring AI, isso é emitido automaticamente a partir do seu agente apoiado em ChatClient, o que é agradável até você perceber que o AgentCard não é um teste de contrato. Ele diz ao chamador que formato enviar; não promete que o agente vai se comportar do mesmo jeito semana que vem. Você ainda precisa da sua própria disciplina de compatibilidade em cima.

Um servidor Spring AI A2A mínimo em Kotlin

Aqui está o menor exemplo realista — uma aplicação Spring Boot de um único arquivo que expõe um agente "summarize-release-notes" via A2A. Compila e roda como está com Spring Boot 3.4 e Spring AI 1.0.x com o starter spring-ai-starter-a2a-server no classpath.

kotlin
package com.example.releaseagent

import org.springframework.ai.a2a.server.A2aAgent
import org.springframework.ai.a2a.server.A2aSkill
import org.springframework.ai.chat.client.ChatClient
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean

@SpringBootApplication
class ReleaseAgentApplication {

    @Bean
    fun releaseAgent(chatClient: ChatClient.Builder): A2aAgent =
        A2aAgent.builder()
            .name("release-notes-summarizer")
            .description("Summarizes raw release notes into a 3-bullet executive digest.")
            .version("1.0.0")
            .skill(
                A2aSkill.builder()
                    .id("summarize")
                    .description("Takes markdown release notes; returns a 3-bullet digest.")
                    .inputSchema(mapOf("type" to "object",
                        "properties" to mapOf("notes" to mapOf("type" to "string")),
                        "required" to listOf("notes")))
                    .handler { input ->
                        val notes = input["notes"] as String
                        chatClient.build()
                            .prompt()
                            .system("Return exactly 3 bullets. No preamble.")
                            .user(notes)
                            .call()
                            .content()
                    }
                    .build()
            )
            .build()
}

fun main(args: Array<String>) {
    runApplication<ReleaseAgentApplication>(*args)
}

Rode com ./gradlew bootRun. A linha interessante é a lambda handler — é uma função Kotlin pura que recebe o mapa de input decodificado e retorna uma string. Tudo em volta dela (o envelope JSON, os hooks de streaming, a geração do AgentCard) é tratado por autoconfiguração. Acesse http://localhost:8080/.well-known/agent-card.json e você verá o card que o Spring AI construiu a partir da definição da skill.

A armadilha nesse snippet é sutil: version("1.0.0") no agente é apenas indicativo. Nada te impede de mudar o formato do output da skill summarize entre releases enquanto deixa a string de versão intocada. Isso é problema seu, não do A2A.

A assimetria que ninguém menciona

O suporte do lado servidor A2A do Spring AI está em forma de produção. O lado cliente — onde você consome um agente A2A de outro time de dentro da sua app Spring — ainda está correndo atrás. Descoberta funciona, invocação funciona, mas a ergonomia em torno de streaming, falhas parciais e invocação de tool no meio da chamada está evoluindo mês a mês.

O que isso significa na prática: se você está fazendo rollout de A2A, exponha primeiro, consuma depois. Coloque seus próprios agentes atrás de A2A para que sistemas externos consigam alcançá-los com um contrato estável, mas não reescreva seus caminhos de código de agent-chama-agent internos para passar por A2A a menos que o outro lado esteja fora da sua fronteira de confiança. Internamente, continue usando injeção direta de beans ChatClient ou qualquer transporte que você já estivesse usando. Você revisita a adoção do cliente em seis meses quando as APIs se estabilizarem.

Auth é a coisa que você precisa vigiar

A spec A2A define como declarar requisitos de auth no AgentCard mas é deliberadamente fina sobre impor um modelo de confiança específico. Você pode dizer "esta skill requer um bearer token"; a spec não diz nada sobre quem emite esse token, como ele é rotacionado, ou como o isolamento multi-tenant é imposto dentro do agente.

Para um agente interno isso tudo bem — encaixe o que você já usa, OAuth2 resource server, mTLS, um API gateway. Para um agente cross-organization não está tudo bem. Duas empresas trocando agentes vão descobrir rapidamente que precisam de um identity provider compartilhado, um audience claim acordado, uma história de rate-limiting, e um plano para o que acontece quando o agente remoto começa a alucinar e queimar tokens na sua conta. O A2A não te dá nada disso. Reserve tempo real de design para isso.

A2A vs MCP vs REST direto

Expondo Agentes Spring AI via o Protocolo A2A: O Que a Interoperabilidade Realmente Te Dá

Comparação de A2A, MCP e REST: agent-to-agent vs agent-to-tool vs service-to-service

O erro mais fácil é tratar A2A e MCP como alternativas. Eles não são. MCP é como um agente consome tools e data sources — o agente é o cliente, a tool é o servidor. A2A é como um agente é alcançado como um par — o agente é o servidor, e outro agente é o cliente. Um sistema bem construído usa os dois: seu agente Spring AI expõe uma superfície A2A para fora, e internamente usa MCP para alcançar seus próprios tools de retrieval, busca e ação.

REST direto ainda vence quando o outro lado não precisa dos affordances de planejamento ou raciocínio de um agente. Se você está pedindo a um serviço o saldo atual de uma conta, você quer um endpoint REST, não um agente com uma skill que resume o saldo em linguagem natural. No momento em que você coloca A2A na frente de um workflow determinístico, você paga por não-determinismo, custo de tokens e latência que não precisava.

A regra de decisão que uso: A2A quando o outro lado é um agente e vive em um domínio de confiança diferente. MCP quando preciso de tools dentro do meu agente. REST quando a chamada é determinística e o outro lado pode ser determinístico também. Uma fila de mensagens quando o trabalho pode ser assíncrono e backpressure importa mais do que imediatismo.

O que dá errado em produção

Três modos de falha aparecem repetidamente. O primeiro é drift do AgentCard: sua assinatura de skill muda, o card atualiza automaticamente, e o código do chamador quebra silenciosamente porque ele cacheou o schema antigo. Mitigue com versionamento explícito no id da skill (summarize-v2), não só na string de versão do agente, e com uma janela de depreciação em que as duas versões são servidas.

O segundo é explosão de custo do lado do chamador. Quando você expõe um agente via A2A, você não tem visibilidade sobre quão agressivamente o outro lado vai chamá-lo. Um cliente externo mal escrito pode fazer loop no seu agente, e cada loop é uma chamada de modelo na sua conta. Coloque cotas por chamador na frente dos endpoints A2A antes de publicar, não depois.

O terceiro é lacunas de observabilidade. Traces de mensagens A2A e spans internos de raciocínio de tool-call vivem em planos diferentes a menos que você os costure juntos. No Spring AI você pega isso quase de graça se já tem tracing do Micrometer ligado, mas só se propagar o taskId do A2A como um atributo de span. Sem isso, um workflow cross-agent que falhou parece dois incidentes não relacionados em dois serviços diferentes.

Quando eu de fato usaria

Use A2A quando você está publicando um agente através de uma fronteira organizacional e se importa mais com interoperabilidade do que com otimizar um chamador específico. Use quando quer que seu agente apareça em um registry externo ou seja descobrível por agentes que você nunca conheceu. Use quando o custo ergonômico de manter um setup REST-mais-OpenAPI-mais-auth feito à mão em sincronia com um segundo time é maior do que o custo de adotar um padrão.

Evite-o para chamadas agent-to-agent internas dentro de um único service mesh. Evite-o quando o lado remoto é uma API determinística fantasiada de agente. Evite-o para caminhos quentes sensíveis a latência onde o overhead do protocolo e a invocação do modelo vão estourar seu SLO.

Conclusões

  • Exponha o lado servidor primeiro; adie a adoção do cliente até a spec estabilizar.
  • Versione skills no id da skill, não só na versão do agente. O AgentCard não vai te salvar.
  • Coloque rate limits e caps de custo por chamador na frente de qualquer endpoint A2A antes de publicar.
  • A2A e MCP são complementares. Use A2A na fronteira externa e MCP para tools dentro do agente.
  • Propague o taskId do A2A para seus spans de tracing para que falhas cross-agent sejam debugáveis.
  • Trate declarações de auth do AgentCard como documentação, não como imposição. Traga seu próprio modelo de confiança.

Use nas fronteiras. Evite como cola. Entregue atrás de cotas.

Isso foi útil?

Deixe uma avaliação ou uma nota rápida — me ajuda a melhorar.