A2A em produção: delegar trabalho não transfere confiança

A2A em produção: delegar trabalho não transfere confiança

Read this article in English

O agente encaminha uma solicitação para outro agente, a conexão cai e ninguém sabe se o trabalho terminou. Repetir pode duplicar uma ação; desistir pode abandonar uma tarefa em andamento. Mesmo quando a resposta chega, permanece a pergunta mais importante: quem autorizou aquele especialista a acessar o dado? Conectar dois agentes é fácil. Operar a delegação exige um contrato para o trabalho e outro para a confiança.

A fronteira da delegação

O que é A2A?

Agent2Agent (A2A) é um protocolo aberto para comunicação entre agentes independentes, inclusive de fornecedores e frameworks diferentes. Eles expõem capacidades, trocam mensagens e acompanham trabalho sem revelar memória interna, raciocínio privado ou implementação das ferramentas. A especificação publicada é a 1.0.0: modelo de dados, operações e bindings como JSON-RPC, gRPC e HTTP/REST são camadas distintas.

MCP não é o protocolo que A2A veio substituir. A comparação oficial separa colaboração entre agentes de acesso a ferramentas e recursos:

Decisão MCP A2A
O que estou integrando? Ferramentas, recursos e prompts Um agente com capacidades próprias
O que exponho? Uma interface de uso dessas capacidades Um contrato de interação e trabalho
Exemplo Consultar uma fatura Investigar uma divergência de cobrança

Não reduza a diferença a “MCP é síncrono, A2A é assíncrono”. O MCP já documenta tarefas experimentais na revisão 2025-11-25. A decisão é sobre responsabilidade e contrato, não apenas duração.

O problema que resolve

Imagine um atendimento que precisa explicar uma cobrança. Um agente especialista reúne faturas, aplica regras e devolve um parecer. O coordenador não precisa conhecer cada consulta interna; precisa saber o que pode solicitar, como acompanhar e o que aceitar como resultado.

Sem esse contrato, cada integração inventa descoberta, estados, cancelamento e formato de resposta. A2A padroniza parte dessa conversa. Não escolhe o especialista correto, não garante a qualidade do parecer e não autoriza um reembolso. Essas decisões continuam pertencendo à aplicação e aos sistemas responsáveis.

Diagrama com o contrato estável à esquerda — Agent Card, identidade autorizada e critérios de aceite — e a execução à direita. Um coordenador delega via A2A a um agente especialista, que usa MCP para acessar ferramentas e dados sob outra verificação de autorização. Mensagem ou tarefa e seus artefatos retornam ao coordenador para validação; nenhuma seta transfere confiança ou permissões automaticamente.
A2A atravessa a fronteira entre agentes; MCP pode conectar o especialista às ferramentas. O resultado volta para validação, não para execução automática.

Como funciona — passo a passo

  1. Defina o resultado. Especifique escopo, dados permitidos, formato de saída, prazo e condição de aceite. “Resolva o problema” não é um contrato operacional.
  2. Descubra o especialista. Consulte a Agent Card pelo endereço conhecido, por um catálogo aprovado ou por configuração privada.
  3. Confira compatibilidade e identidade. Verifique versão, binding, modalidades, capacidades opcionais e autenticação. Uma skill anunciada não concede permissão para utilizá-la.
  4. Envie o mínimo necessário. Use uma Message com objetivo e contexto relevante. Não transporte segredos nem a conversa inteira por conveniência.
  5. Acompanhe a resposta certa. O servidor pode devolver uma Message direta ou uma Task com ciclo de vida. Para tarefas, registre identificadores e acompanhe pelos mecanismos suportados.
  6. Valide antes de continuar. Confira o estado final e os Artifacts, aplique critérios de aceite e registre a decisão. Só então entregue a resposta ou autorize outra ação.

Agent Card: descoberta não é aprovação

A Agent Card descreve identidade, skills, interfaces, requisitos de autenticação e capacidades. O caminho convencional de descoberta é /.well-known/agent-card.json, mas plataformas podem usar caminhos próprios.

Um catálogo curado é diferente de permitir que o modelo siga qualquer URL encontrada numa resposta. Em produção, aprove fornecedores e destinos, valide HTTPS e confira mudanças no card. Capacidade declarada é informação para seleção; não é evidência de confiabilidade nem política de autorização.

Streaming e notificações push também precisam ser anunciados e suportados. Um cliente não deve presumir todas as capacidades só porque encontrou “A2A” no endpoint. O protocolo tampouco prescreve uma API universal para os catálogos de descoberta.

A tarefa vive além da conexão

No ciclo de vida oficial, uma interação simples pode terminar numa Message, sem Task. Quando há trabalho rastreável, a tarefa recebe um identificador; contextId agrupa interações relacionadas, enquanto taskId identifica a tarefa em mensagens de continuidade.

Os nomes abaixo seguem o guia conceitual; a representação serializada depende da versão do esquema:

Estado ou sinal O que o coordenador deve fazer
submitted / working Acompanhar progresso; aceitação não significa conclusão
input-required Obter a informação faltante e continuar a tarefa
auth-required Resolver a autorização por um fluxo seguro, fora do texto da conversa
completed Conferir o resultado; conclusão não equivale a aprovação
failed, canceled, rejected Encerrar aquele trabalho e decidir uma nova ação

Uma tarefa em estado terminal não é reiniciada. Refinar o resultado exige outra interação, possivelmente uma nova tarefa no mesmo contexto. Cancelar também não deve ser tratado como rollback dos efeitos já produzidos no sistema de negócio.

Se o stream cair, recupere o estado com Get Task, quando aplicável, em vez de reenviar imediatamente o pedido. Para operações com efeito externo, mantenha uma chave de negócio e deduplicação na aplicação. Identificadores de protocolo não substituem a transação do sistema de origem.

Notificações push são opcionais e podem se repetir. A especificação exige autenticação e recomenda processamento idempotente. Verifique também se a notificação pertence a uma tarefa esperada.

A identidade precisa atravessar cada fronteira

Há três perguntas diferentes: quem chama o coordenador, quem autentica no especialista e quem executa a ferramenta. Compartilhar um contextId não responde nenhuma delas.

A especificação exige autorização em cada operação, inclusive consulta e listagem de tarefas. Conhecer um identificador não pode permitir acesso ao trabalho de outra pessoa. O controle deve existir antes de uma consulta revelar a existência do recurso.

No Microsoft Foundry, a autenticação A2A distingue identidade compartilhada de autenticação individual. Chave, identidade do agente ou identidade gerenciada do projeto não preservam, por si, as permissões de cada usuário. OAuth identity passthrough atende ao cenário individual, com consentimento e escopos.

Isso ainda não é uma autorização transitiva para toda ferramenta usada pelo especialista. Cada integração com dados precisa conferir a identidade efetiva e aplicar seu próprio controle.

O mesmo vale para a resposta: Artifact é saída de trabalho, não instrução confiável. Valide schema, origem e evidências; não transforme texto devolvido por outro agente em permissão para executar uma ação. É a continuidade prática dos guardrails e avaliações: proteger a entrada não dispensa avaliar o percurso.

No Foundry, separe protocolo de implementação

Existem dois lados: chamar um endpoint remoto com a ferramenta A2A e expor um agente Foundry para chamadas de entrada. Configurar um não substitui configurar o outro.

A documentação de incoming A2A registra estas condições:

Aspecto Endpoint de entrada do Foundry
Maturidade Preview, não recomendado para workloads de produção
Versões 1.0 e 0.3; novas integrações devem mirar 1.0
Transporte em 1.0 JSON-RPC; sem HTTP+JSON ou gRPC nessa versão
Modalidade Somente texto; sem arquivos e outras modalidades
Streaming A2A Não suportado
Agente Requer o protocolo responses; prompt agents o suportam

Não confunda streaming da resposta do coordenador com streaming na conexão A2A remota. São dois trechos diferentes.

Há ainda uma armadilha de compatibilidade: sem seleção de versão, o endpoint Foundry atende 0.3 por padrão. Para uma integração direta em 1.0, use o card em agentCard/v1.0 ou selecione A2A-Version: 1.0 conforme o cliente. Se também enviar a2a-version na query string, os valores precisam coincidir.

O acesso direto ao card e ao endpoint de entrada exige Microsoft Entra ID e permissão no destino, como Foundry Agent Consumer. Para menor privilégio, restrinja o papel ao agente necessário. O SDK Python ser GA não torna GA todo recurso que ele expõe: o exemplo da ferramenta usa A2APreviewTool.

O desenho conceitual é mais amplo que o endpoint de entrada do Foundry:

Coordenador -- A2A --> Especialista -- MCP --> Ferramentas
     ^                     |
     +-- resultado/estado -+
     |
validacao + aceite + decisao da proxima acao

Implante e teste um agente A2A no Microsoft Foundry

Este laboratório cria um prompt agent gerenciado, não um contêiner Python. O script configura o agente no Azure; o Foundry hospeda a execução. Usa somente dados fictícios, sem ferramentas financeiras. É preview e as chamadas ao modelo geram consumo.

1. Prepare projeto, modelo e ambiente

No Foundry novo, crie um projeto e implante um modelo em Discover → Models → Deploy. Confira disponibilidade regional e quota. Copie o endpoint do projeto e o nome do deployment, não apenas o nome do catálogo.

Use Python 3.11+, Azure CLI e Foundry User no projeto para criar o agente. Provisionar projeto/modelo exige permissões adicionais; esse papel não autoriza atribuir RBAC. Em uma pasta de laboratório, execute:

az login
az account set --subscription "<subscription-id>"
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install azure-ai-projects==2.3.0 azure-identity==1.25.3 a2a-sdk==1.0.2 httpx==0.28.1
$env:FOUNDRY_PROJECT_ENDPOINT = "https://<account>.services.ai.azure.com/api/projects/<project>"
$env:FOUNDRY_MODEL_NAME = "<nome-do-deployment>"
$env:FOUNDRY_AGENT_NAME = "a2a-demo-billing"

2. Salve o código completo

Salve como a2a_foundry.py. deploy cria uma versão e direciona 100% do endpoint para ela; reexecutá-lo cria outra versão. Use um nome exclusivo de laboratório. call autentica o usuário do Azure CLI, confere o card e envia uma mensagem A2A 1.0 sem streaming.

"""Create a managed prompt agent or call its preview A2A 1.0 endpoint."""

import argparse
import asyncio
import os
import re
from urllib.parse import urlsplit

import httpx
from a2a.client import A2ACardResolver, ClientConfig, create_client
from a2a.helpers import new_text_message
from a2a.types.a2a_pb2 import Role, SendMessageRequest, TaskState
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import (
    A2AProtocolConfiguration,
    AgentCard,
    AgentCardSkill,
    AgentEndpointConfig,
    FixedRatioVersionSelectionRule,
    PromptAgentDefinition,
    ProtocolConfiguration,
    ResponsesProtocolConfiguration,
    VersionSelector,
)
from azure.identity import AzureCliCredential
from google.protobuf.json_format import MessageToJson


def settings():
    endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"].rstrip("/")
    name = os.environ.get("FOUNDRY_AGENT_NAME", "a2a-demo-billing")
    url = urlsplit(endpoint)
    if (
        url.scheme != "https"
        or not (url.hostname or "").endswith(".services.ai.azure.com")
        or url.username or url.password or url.port not in (None, 443)
        or url.query or url.fragment
        or not re.fullmatch(r"/api/projects/[A-Za-z0-9_-]+", url.path)
    ):
        raise ValueError("Use the HTTPS project endpoint from public Azure Foundry.")
    if not re.fullmatch(r"a2a-demo-[a-z0-9-]+", name):
        raise ValueError("Use an isolated lab agent name starting with a2a-demo-.")
    return endpoint, name


def deploy(endpoint, name, model):
    if not model.strip():
        raise ValueError("FOUNDRY_MODEL_NAME must be a deployed model name.")
    with (
        AzureCliCredential() as credential,
        AIProjectClient(
            endpoint=endpoint,
            credential=credential,
            allow_preview=True,
            retry_total=0,
        ) as project,
    ):
        version = project.agents.create_version(
            agent_name=name,
            draft=False,
            definition=PromptAgentDefinition(
                model=model,
                instructions=(
                    "Explain fictional billing discrepancies and ask for missing "
                    "evidence. Do not claim to access accounts or issue refunds. "
                    "This demonstration has no tools or real customer data."
                ),
                tools=[],
            ),
        )
        print(f"Created agent version: {version.name}:{version.version}")
        agent = project.agents.update_details(
            agent_name=name,
            agent_endpoint=AgentEndpointConfig(
                version_selector=VersionSelector(
                    version_selection_rules=[
                        FixedRatioVersionSelectionRule(
                            agent_version=version.version,
                            traffic_percentage=100,
                        )
                    ]
                ),
                protocol_configuration=ProtocolConfiguration(
                    responses=ResponsesProtocolConfiguration(),
                    a2a=A2AProtocolConfiguration(),
                ),
            ),
            agent_card=AgentCard(
                version=version.version,
                description="Educational billing specialist; fictional data only.",
                skills=[
                    AgentCardSkill(
                        id="explain-billing",
                        name="Explain a fictional billing discrepancy",
                        description="Suggest checks; never execute financial actions.",
                    )
                ],
            ),
        )
        if agent.state != "enabled":
            raise RuntimeError(f"The agent endpoint is not enabled: {agent.state}")
        print(f"A2A base: {endpoint}/agents/{name}/endpoint/protocols/a2a")


def validate_card(card, base_url):
    expected = urlsplit(base_url)
    if not card.supported_interfaces:
        raise ValueError("The Agent Card has no supported interfaces.")
    for interface in card.supported_interfaces:
        target = urlsplit(interface.url)
        path = target.path.rstrip("/")
        if (
            interface.protocol_version != "1.0"
            or interface.protocol_binding != "JSONRPC"
            or target.scheme != expected.scheme
            or target.netloc != expected.netloc
            or not (path == expected.path or path.startswith(expected.path + "/"))
        ):
            raise ValueError("Unexpected Agent Card interface; no call was sent.")


async def call(endpoint, name):
    base_url = f"{endpoint}/agents/{name}/endpoint/protocols/a2a"
    with AzureCliCredential() as credential:
        token = credential.get_token("https://ai.azure.com/.default").token
        async with httpx.AsyncClient(
            headers={"Authorization": f"Bearer {token}", "A2A-Version": "1.0"},
            timeout=httpx.Timeout(120.0),
            follow_redirects=False,
        ) as http:
            card = await A2ACardResolver(
                httpx_client=http,
                base_url=base_url,
                agent_card_path="agentCard/v1.0",
            ).get_agent_card()
            validate_card(card, base_url)
            client = await create_client(
                agent=card,
                client_config=ClientConfig(
                    streaming=False,
                    httpx_client=http,
                    supported_protocol_bindings=["JSONRPC"],
                ),
            )
            try:
                request = SendMessageRequest(
                    message=new_text_message(
                        "Fictional example: an invoice rose from 100 to 120. "
                        "Which evidence should I check before drawing a conclusion?",
                        role=Role.ROLE_USER,
                    )
                )
                received = False
                async for event in client.send_message(request):
                    received = True
                    print(MessageToJson(event))
                    state = None
                    if event.HasField("task"):
                        state = event.task.status.state
                    elif event.HasField("status_update"):
                        state = event.status_update.status.state
                    if state in (
                        TaskState.TASK_STATE_FAILED,
                        TaskState.TASK_STATE_CANCELED,
                        TaskState.TASK_STATE_REJECTED,
                    ):
                        raise RuntimeError(f"A2A task ended: {TaskState.Name(state)}")
                if not received:
                    raise RuntimeError("The A2A endpoint returned no events.")
            finally:
                await client.close()


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("action", choices=("deploy", "call"))
    args = parser.parse_args()
    project_endpoint, agent_name = settings()
    if args.action == "deploy":
        deploy(project_endpoint, agent_name, os.environ["FOUNDRY_MODEL_NAME"])
    else:
        asyncio.run(call(project_endpoint, agent_name))

3. Implante e exponha o endpoint

.\.venv\Scripts\python.exe a2a_foundry.py deploy

O script habilita responses e a2a, publica a Agent Card e imprime o endereço. No modelo atual de agentes, não há uma Agent Application separada para criar. O seletor escolhe a versão servida; publicar em Teams/M365 é outro fluxo.

O SDK exige allow_preview=True. version no card acompanha a versão do agente; o protocolo é selecionado separadamente em agentCard/v1.0.

4. Autorize quem vai consumir

O autor com Foundry User já consegue testar. Para outro consumidor, um administrador com permissão para atribuir RBAC executa o trecho abaixo. Use o Resource ID do projeto e o object ID do usuário, não o client ID de uma aplicação.

$projectId = "<ARM-resource-id-do-projeto>"
$callerId = "<object-id-do-usuario-consumidor>"
az role assignment create `
  --assignee-object-id $callerId `
  --assignee-principal-type User `
  --role "eed3b665-ab3a-47b6-8f48-c9382fb1dad6" `
  --scope "$projectId/agents/$env:FOUNDRY_AGENT_NAME"

Esse é o papel Foundry Agent Consumer, restrito ao agente do laboratório. Atribuí-lo não remove permissões mais amplas já existentes.

5. Chame e interprete a resposta

Com o consumidor correto autenticado por az login, execute:

.\.venv\Scripts\python.exe a2a_foundry.py call

A saída é JSON do SDK, com Message ou Task. Uma tarefa working ainda não terminou; preserve seu ID para acompanhamento. O código interrompe em falha, cancelamento ou rejeição e não imprime tokens.

401 indica autenticação; 403, acesso ao destino; 404, nome/caminho ou A2A não habilitado. Confira também propagação do RBAC, deployment e versão. Ao terminar, exclua apenas o agente de laboratório e suas permissões desnecessárias; preserve recursos compartilhados.

O exemplo foi exercitado com SDKs reais e transportes locais simulados, sem provisionar ou executar workloads em uma assinatura Azure.

Boas práticas para produção

  • Comece por um especialista necessário, não por uma rede de agentes. Se uma ferramenta resolve, evite outra fronteira operacional.
  • Teste falhas de contrato: credencial revogada, tarefa de outro usuário, resposta fora do schema e conexão interrompida.
  • Limite a delegação: prazo, custo total, quantidade de agentes acionados e profundidade de encaminhamento. Impeça ciclos.
  • Proteja callbacks e referências de arquivo, quando suportados. Valide destinos contra SSRF e autentique remetentes.
  • Observe o trabalho inteiro: correlacione contexto, tarefa, chamada e artefato; meça tempo até resultado aceito, não só latência HTTP.
  • Defina retenção e recuperação. O servidor precisa documentar por quanto tempo tarefas e artefatos ficam disponíveis. Não registre tokens ou dados sensíveis indiscriminadamente.

Esses controles são decisões de implementação apoiadas nas orientações empresariais do A2A, não garantias entregues automaticamente pelo protocolo.

O que precisa estar de pé

Fundação

Os especialistas aprovados, seus responsáveis e os dados que podem acessar estão definidos. A identidade efetiva é conhecida em cada integração.

Está de pé quando uma credencial válida, mas sem permissão, não consegue ler a tarefa de outro usuário.

Produção com contexto

Objetivos têm escopo; saídas têm schema e evidências; interrupções e pedidos de entrada possuem tratamento explícito.

Está de pé quando uma queda de conexão não provoca uma segunda ação de negócio por acidente.

Escala e eficiência

Existem limites de custo, prazo, encaminhamento e retenção. O resultado aceito, incluindo retrabalho, é a unidade de medida.

Está de pé quando uma nova delegação precisa justificar seu custo e pode ser interrompida por política.

A ordem é causal, não cronológica.

Perguntas frequentes (FAQ)

Preciso de A2A para qualquer sistema multiagente?

Não. Uma orquestração interna com componentes do mesmo runtime pode ser suficiente. A2A faz sentido quando você precisa de um contrato interoperável entre agentes independentes.

A2A compartilha a memória dos agentes?

Não exige isso. O protocolo permite colaboração com execução opaca. Envie somente o contexto necessário e permitido para a tarefa.

Uma tarefa concluída pode pedir mais informação?

Não deve voltar a um estado ativo. Uma tarefa terminal permanece terminal; o aprofundamento inicia outra interação, relacionada pelo contexto.

Posso assumir todos os recursos A2A no Foundry?

Não. Confira direção da integração, versão, transporte e capacidades. O endpoint de entrada documentado está em preview, aceita texto e não oferece streaming A2A.

Conclusão

A2A torna a delegação compreensível entre sistemas; não a torna confiável por decreto. O card descreve, a identidade autoriza, a tarefa registra e a aplicação aceita. MCP pode continuar fazendo o trabalho de conectar ferramentas. Quando essas responsabilidades ficam explícitas, conectar agentes deixa de ser apenas encaminhar mensagens e passa a ser uma operação que alguém consegue governar.

Referências oficiais

👉 Se você está conectando agentes — especialmente entre equipes, plataformas e domínios de dados, trate a delegação como uma fronteira de produção.
Quer trocar ideia sobre A2A, MCP e governança de agentes? Me chama no LinkedIn.

Compartilhe:

Eron Cavalcante

Eron Cavalcante atua como Cloud Solution Architect na Microsoft, com foco em plataformas de dados, IA generativa e arquitetura de nuvem para o setor financeiro. Combina experiência técnica em Azure, Databricks e engenharia de dados com uma visão de negócio orientada a valor, apoiando clientes na adoção responsável de IA em escala.

Deixe um comentário