A2A
Simule um agente remoto de logística que precisa receber destino antes de concluir a análise. Depois, conecte um consumidor e um servidor A2A em ambiente de desenvolvimento, registrando suas versões. O laboratório deve mostrar descoberta de interface, uma tarefa que pede informação e um artefato final. Os dados são fictícios, e não haverá compra ou contratação de transporte.
A evidência local é o estado da tarefa e a seleção de interface. A evidência integrada depende de mensagens reais no binding escolhido. Não use a saída do simulador como prova de interoperabilidade de rede. O caso também exige testar falha de compatibilidade e acesso indevido a uma tarefa, porque agentes em linguagens diferentes ainda precisam respeitar identidade e escopo.
JSONBashMCPAPIsAgentesAo terminar esta aula
- Compatibilidade deve ser selecionada antes de iniciar trabalho remoto.
- Guarde o identificador da tarefa para acompanhar sem duplicar.
- Estado concluído e artefato válido são verificações complementares.
Antes de continuar: Leitura: A2A
Selecione uma interface compatível
FundamentosSalve o exemplo da leitura como tarefas.cjs e execute. A resolução integrada será executada depois como a2a.mjs. Identifique URL, binding e protocolo no objeto que representa a Card. Mude a versão para0.3 e confirme que o consumidor atual recusa. Mude o binding para um não implementado e observe a mesma falta de compatibilidade. Esses testes impedem escolher qualquer URL disponível como se todas oferecessem o mesmo contrato. Registre também a versão do software separadamente, para não confundi-la com a revisão do protocolo durante a implantação.
No servidor real, disponibilize uma Agent Card conforme a especificação da versão escolhida, por configuração direta ou descoberta suportada. Confira formatos, skills e requisitos de segurança. Use um endpoint de desenvolvimento verificado, não uma URL produzida pelo modelo a partir de conteúdo externo. Se houver Card assinada, aplique a verificação correspondente; se não houver, documente a origem de confiança usada. O nome apresentado não deve determinar permissões nem liberar acesso a dados privados.
Crie trabalho e forneça a informação faltante
FundamentosInicie uma solicitação de rota sem destino e capture o estado de informação necessária. Em seguida, forneça o destino ligado à tarefa ou ao contexto conforme o contrato real. O trabalho deve continuar em vez de criar uma análise independente sem correlação. No simulador, completar altera a tarefa existente. Na integração, use o binding documentado e capture messageId, task id e contextId quando presentes. Explique a finalidade de cada identificador e evite tratá-los como valores intercambiáveis.
Confira que a conclusão inclui um artefato com a rota e sua origem. Modifique o servidor de teste para indicar conclusão sem artefato e faça o consumidor marcar resultado incompleto. Um estado terminal pode encerrar acompanhamento, mas seu produto ainda precisa da saída esperada. Acrescente ao contrato campos como destino, premissas e data para validar correspondência. Uma rota para outra cidade deve ser recusada mesmo que tenha estrutura válida e esteja anexada a uma tarefa concluída.
Teste desconexão e acesso
AvaliaçãoInterrompa o acompanhamento depois que o servidor criar a tarefa e tente consultar pelo identificador conhecido. Verifique se o trabalho continua e se o consumidor recupera o estado. Não crie uma nova tarefa automaticamente em toda desconexão. Para a primeira tentativa sem identificador recebido, documente como o servidor deduplica ou como o consumidor trata o resultado desconhecido. A política precisa ser específica; um timeout não prova que o trabalho nunca iniciou.
Crie uma identidade diferente e tente consultar a tarefa anterior. O servidor deve negar sem revelar detalhes privados do trabalho. Teste também cancelamento enquanto a tarefa está ativa e depois de concluída, observando o comportamento do binding e runtime. Cancelamento de acompanhamento é diferente de cancelamento de trabalho, e nenhum deles promete desfazer um efeito externo já realizado. Guarde os estados e eventos em vez de inferir o resultado somente pelo fechamento da conexão.
Compare a fronteira com MCP
MCPDesenhe o controlador consultando um catálogo por MCP e delegando a análise por A2A. Indique quais dados cruzam cada fronteira e que identidade os autoriza. Se usar ADK como consumidor, registre a revisão A2A suportada pelo adaptador instalado e execute o caso compatível. A conclusão deve mostrar o benefício de não compartilhar código ou memória com o agente remoto, junto dos custos de rede, autenticação e acompanhamento. Declare os cenários não executados sem atribuir ao protocolo garantias que dependem da implementação.
↗ Agent2Agent Protocol Specification v1.0.1↗ Architecture — MCP 2026-07-28
Resolução integrada, ambiente e evidência
FundamentosSalve como a2a.mjs e execute com Node.js 24.19.0, sem pacotes externos. O arquivo inicia um servidor HTTP real somente em 127.0.0.1 numa porta efêmera e um consumidor que faz fetch. Ele implementa um subconjunto HTTP+JSON conforme A2A 1.0.1: Agent Card, SendMessage em /message:send, GetTask em /tasks/{id} e CancelTask em /tasks/{id}:cancel, com protocolo1.0, ROLE_USER, estados TASK_STATE e artefato textual. Credenciais Bearer fictícias mapeiam ana e bia; não são JWT nem OAuth. O cliente fornece informação à mesma tarefa, solicita returnImmediately, interrompe um GET e depois recupera o resultado sem criar outra tarefa. Os asserts verificam uma única tarefa, artefato para Cuiaba,401 sem identidade,404 para outro usuário e400 para revisão incompatível. O cancelamento de uma tarefa WORKING conserva CANCELED mesmo depois do prazo do callback; o callback consulta o estado antes de publicar conclusão. Mensagens para tarefas terminais são recusadas. O validador do consumidor lança exceção quando falta o artefato esperado, e assert.throws confirma essa falha. Esses pedidos loopback foram executados. O exemplo não implementa streaming, catálogo, assinatura de Card, persistência após reinício nem todas as operações A2A; é uma implementação do recorte estudado, e não um servidor completo de produção. Uma implementação em outra linguagem deve reproduzir esse contrato; interoperabilidade cross-language requer testar essa segunda implementação, não foi inferida do teste JavaScript.
node a2a.mjsExercício aplicado
Um agente de compras solicita análise de rota a um agente remoto. Descubra uma interface compatível, forneça destino quando solicitado e valide o artefato sem criar tarefas duplicadas após desconexão.
- Execute seleção de interface e uma versão incompatível.
- Observe informação necessária e conclua a tarefa com destino.
- Implemente a troca em um binding A2A compatível e capture identificadores.
- Teste artefato ausente, consulta por outra identidade e desconexão.
Abrir resolução comentada
Salve como a2a.mjs e execute com Node.js 24.19.0, sem pacotes externos. O arquivo inicia um servidor HTTP real somente em 127.0.0.1 numa porta efêmera e um consumidor que faz fetch. Ele implementa um subconjunto HTTP+JSON conforme A2A 1.0.1: Agent Card, SendMessage em /message:send, GetTask em /tasks/{id} e CancelTask em /tasks/{id}:cancel, com protocolo1.0, ROLE_USER, estados TASK_STATE e artefato textual. Credenciais Bearer fictícias mapeiam ana e bia; não são JWT nem OAuth. O cliente fornece informação à mesma tarefa, solicita returnImmediately, interrompe um GET e depois recupera o resultado sem criar outra tarefa. Os asserts verificam uma única tarefa, artefato para Cuiaba,401 sem identidade,404 para outro usuário e400 para revisão incompatível. O cancelamento de uma tarefa WORKING conserva CANCELED mesmo depois do prazo do callback; o callback consulta o estado antes de publicar conclusão. Mensagens para tarefas terminais são recusadas. O validador do consumidor lança exceção quando falta o artefato esperado, e assert.throws confirma essa falha. Esses pedidos loopback foram executados. O exemplo não implementa streaming, catálogo, assinatura de Card, persistência após reinício nem todas as operações A2A; é uma implementação do recorte estudado, e não um servidor completo de produção. Uma implementação em outra linguagem deve reproduzir esse contrato; interoperabilidade cross-language requer testar essa segunda implementação, não foi inferida do teste JavaScript.
O simulador da leitura seleciona JSONRPC 1.0 e conserva T7 enquanto recebe destino. A resolução acima escolhe HTTP+JSON na Card e usa requisições reais. Nos dois casos, identificação da tarefa e validação do artefato são obrigações diferentes: receber uma resposta HTTP não torna o resultado completo.
No servidor real, a retomada consulta a tarefa conhecida e mantém autorização por identidade. O acesso indevido é recusado antes de devolver status ou conteúdo privado. A integração entre linguagens fica comprovada por troca real no contrato escolhido, com versões e mensagens registradas.
// Subconjunto HTTP+JSON A2A1.0.1, Node24; rede loopback real, sem LLM.
import { createServer } from 'node:http';
import { randomUUID } from 'node:crypto';
import assert from 'node:assert/strict';
const tasks=new Map(),owners=new Map(),tokens=new Map([['token-ana','ana'],['token-bia','bia']]);
const wait=ms=>new Promise(r=>setTimeout(r,ms));let base;
function reply(res,status,value){
const headers={'content-type':'application/a2a+json'};
if(status===401)headers['www-authenticate']='Bearer';
if(status>=400)value={...value,code:value.code===9?9:({400:3,401:16,404:5,413:3}[status]||13)};
res.writeHead(status,headers);res.end(JSON.stringify(value));
}
const server=createServer(async(req,res)=>{
try{
if(req.url==='/.well-known/agent-card.json')return reply(res,200,{name:'LogisticaCurso',description:'Analise ficticia de rota.',version:'1.0.0',supportedInterfaces:[{url:base,protocolBinding:'HTTP+JSON',protocolVersion:'1.0'}],capabilities:{},defaultInputModes:['text/plain'],defaultOutputModes:['text/plain'],skills:[{id:'rota',name:'Rota',description:'Analisa destino ficticio.',tags:['logistica']}],securitySchemes:{bearer:{httpAuthSecurityScheme:{scheme:'bearer'}}},securityRequirements:[{schemes:{bearer:{list:[]}}}]});
const user=tokens.get((req.headers.authorization||'').replace(/^Bearer /,''));
if(!user)return reply(res,401,{code:401,message:'Nao autenticado'});
if(req.headers['a2a-version']!=='1.0')return reply(res,400,{code:400,message:'Versao nao suportada'});
if(req.method==='POST'&&req.url==='/message:send'){
let raw='';for await(const chunk of req){raw+=chunk;if(raw.length>8192)return reply(res,413,{code:413,message:'Corpo grande'});}
const {message:m,configuration}=JSON.parse(raw);
if(!m||m.role!=='ROLE_USER'||!m.messageId||!Array.isArray(m.parts))return reply(res,400,{code:400,message:'Mensagem invalida'});
let t;
if(m.taskId){t=tasks.get(m.taskId);if(!t||owners.get(t.id)!==user)return reply(res,404,{code:404,message:'Tarefa nao acessivel'});if(['TASK_STATE_COMPLETED','TASK_STATE_CANCELED','TASK_STATE_FAILED','TASK_STATE_REJECTED'].includes(t.status.state))return reply(res,400,{code:9,message:'Tarefa terminal nao pode continuar'});}
else{t={id:randomUUID(),contextId:randomUUID(),status:{state:'TASK_STATE_INPUT_REQUIRED',timestamp:new Date().toISOString()}};tasks.set(t.id,t);owners.set(t.id,user);}
const destino=m.parts.find(p=>p.text?.startsWith('destino:'))?.text.slice(8);
if(destino){t.status={state:'TASK_STATE_WORKING',timestamp:new Date().toISOString()};setTimeout(()=>{if(t.status.state!=='TASK_STATE_WORKING')return;t.status={state:'TASK_STATE_COMPLETED',timestamp:new Date().toISOString()};t.artifacts=[{artifactId:randomUUID(),name:'rota',parts:[{text:'Rota ficticia para '+destino}]}];},60);}
if(destino&&!configuration?.returnImmediately)await wait(70);
return reply(res,200,{task:t});
}
const cancelId=req.url?.match(/^\/tasks\/([^/:]+):cancel$/)?.[1];
if(req.method==='POST'&&cancelId){const t=tasks.get(cancelId);if(!t||owners.get(cancelId)!==user)return reply(res,404,{code:404,message:'Tarefa nao acessivel'});if(['TASK_STATE_COMPLETED','TASK_STATE_CANCELED','TASK_STATE_FAILED','TASK_STATE_REJECTED'].includes(t.status.state))return reply(res,400,{code:9,message:'Tarefa terminal nao cancelavel'});t.status={state:'TASK_STATE_CANCELED',timestamp:new Date().toISOString()};return reply(res,200,t);}
const id=req.url?.match(/^\/tasks\/([^?]+)$/)?.[1];
if(req.method==='GET'&&id){const t=tasks.get(id);if(!t||owners.get(id)!==user)return reply(res,404,{code:404,message:'Tarefa nao acessivel'});await wait(25);return reply(res,200,t);}
return reply(res,404,{code:404,message:'Operacao nao implementada'});
}catch(e){reply(res,400,{code:400,message:'Entrada invalida'});}
});
await new Promise(r=>server.listen(0,'127.0.0.1',r));base='http://127.0.0.1:'+server.address().port;
const headers={'content-type':'application/a2a+json','a2a-version':'1.0',authorization:'Bearer token-ana'};
async function send(message){const r=await fetch(base+'/message:send',{method:'POST',headers,body:JSON.stringify({message,configuration:{returnImmediately:true}})});assert.equal(r.status,200);return r.json();}
try{
const card=await (await fetch(base+'/.well-known/agent-card.json')).json();assert.equal(card.supportedInterfaces[0].protocolVersion,'1.0');console.log('card',card);
const initial=await send({role:'ROLE_USER',messageId:randomUUID(),parts:[{text:'Preciso de rota.'}]});assert.equal(initial.task.status.state,'TASK_STATE_INPUT_REQUIRED');
const id=initial.task.id;
const working=await send({taskId:id,role:'ROLE_USER',messageId:randomUUID(),parts:[{text:'destino:Cuiaba'}]});assert.equal(working.task.id,id);assert.equal(working.task.status.state,'TASK_STATE_WORKING');
const abort=new AbortController();const request=fetch(base+'/tasks/'+id,{headers,signal:abort.signal});setTimeout(()=>abort.abort(),5);await assert.rejects(()=>request);
await wait(80);
const done=await (await fetch(base+'/tasks/'+id,{headers})).json();assert.equal(done.status.state,'TASK_STATE_COMPLETED');assert(done.artifacts[0].parts[0].text.includes('Cuiaba'));
assert.equal((await fetch(base+'/tasks/'+id,{headers:{...headers,authorization:'Bearer token-bia'}})).status,404);
assert.equal((await fetch(base+'/tasks/'+id,{headers:{'a2a-version':'1.0'}})).status,401);
assert.equal((await fetch(base+'/tasks/'+id,{headers:{...headers,'a2a-version':'0.3'}})).status,400);
assert.equal(tasks.size,1);console.log('tarefa recuperada',done,'quantidade',tasks.size,'auth e versao recusadas');
assert.equal((await fetch(base+'/tasks/'+id+':cancel',{method:'POST',headers})).status,400);
const outra=await send({role:'ROLE_USER',messageId:randomUUID(),parts:[{text:'Nova analise sem destino.'}]});
const cancelada=await(await fetch(base+'/tasks/'+outra.task.id+':cancel',{method:'POST',headers})).json();assert.equal(cancelada.status.state,'TASK_STATE_CANCELED');
const ativa=await send({role:'ROLE_USER',messageId:randomUUID(),parts:[{text:'destino:Cuiaba'}]});assert.equal(ativa.task.status.state,'TASK_STATE_WORKING');
await fetch(base+'/tasks/'+ativa.task.id+':cancel',{method:'POST',headers});await wait(90);
const permanece=await(await fetch(base+'/tasks/'+ativa.task.id,{headers})).json();assert.equal(permanece.status.state,'TASK_STATE_CANCELED');assert.equal(permanece.artifacts,undefined);
const reabrir=await fetch(base+'/message:send',{method:'POST',headers,body:JSON.stringify({message:{taskId:id,role:'ROLE_USER',messageId:randomUUID(),parts:[{text:'destino:Outra'}]}})});assert.equal(reabrir.status,400);assert.equal(tasks.get(id).status.state,'TASK_STATE_COMPLETED');
console.log('cancelamento de WORKING permanece apos90ms; tarefa terminal nao reabre');
function validarResultado(t){if(t.status.state!=='TASK_STATE_COMPLETED'||!t.artifacts?.some(a=>a.parts.some(p=>p.text?.includes('Cuiaba'))))throw new Error('artefato incompleto ou destino incorreto');return t;}
validarResultado(done);assert.throws(()=>validarResultado({...done,artifacts:[]}),/artefato incompleto/);console.log('resultado incompleto sem artefato rejeitado pelo consumidor');
}finally{server.closeAllConnections();await new Promise(r=>server.close(r));}
Como conferir seu resultado
- O consumidor rejeita binding ou versão não suportados.
- A informação adicional continua o trabalho identificado.
- Artefatos e status privados não são entregues a outra identidade.
- Cancelar WORKING conserva CANCELED após 90 ms; mensagens adicionais não reabrem tarefa terminal.
- Artefato ausente ou destino incorreto gera exceção no validador do consumidor.
Aplique em um problema novo
Primeiro resolva sem consultar a resposta. Explique suas decisões e guarde a evidência. A conclusão de leitura é independente desta autoavaliação.
Confira seus pré-requisitos
- Ler Card/binding/tarefa/artefato.
- Separar desconexão e cancelamento.
Servidor loopback: iniciar rota, cancelar WORKING e esperar 90 ms; tentar reabrir terminal e validar concluída sem artefato.
Conferir raciocínio e critérios de domínio
A tarefa WORKING cancelada deve permanecer CANCELED após 90 ms, sem artefato publicado pelo callback tardio.
Mensagem para tarefa terminal é recusada; conhecer id não permite consultar tarefa de outro usuário. Cancelar acompanhamento e cancelar trabalho são operações diferentes.
Resultado COMPLETED sem artefato esperado falha no validador. A evidência é a exceção/recusa do consumidor, não uma mensagem dizendo que foi rejeitado.
Evidências para autoavaliação ou revisão por pares
- Cancelamento terminal: A tarefa WORKING cancelada deve permanecer CANCELED após 90 ms, sem artefato publicado pelo callback tardio.
- Estado e autorização: Mensagem para tarefa terminal é recusada; conhecer id não permite consultar tarefa de outro usuário. Cancelar acompanhamento e cancelar trabalho são operações diferentes.
- Validação do artefato: Resultado COMPLETED sem artefato esperado falha no validador. A evidência é a exceção/recusa do consumidor, não uma mensagem dizendo que foi rejeitado.
Um erro frequente
Estado terminal garante entrega suficiente.
Conclusão operacional ainda exige o artefato esperado e sua validação.
Teste sua compreensão
Responda com suas palavras antes de abrir o comentário. Saber explicar uma decisão é parte do domínio.
1. A2A 1.0.1 exige somente JSON-RPC?
Não. A especificação define múltiplos bindings.
A Card informa interfaces e o consumidor escolhe uma compatível.
2. Mensagem e artefato são equivalentes?
Não. Mensagem comunica; artefato representa saída do trabalho.
Separar ambos facilita acompanhar e validar a tarefa.
3. MCP e A2A são substitutos obrigatórios?
Não. Podem compor fronteiras diferentes no mesmo sistema.
Um agente remoto pode usar MCP internamente e expor trabalho via A2A.
↗ Agent2Agent Protocol Specification v1.0.1↗ Architecture — MCP 2026-07-28
Seu progresso fica salvo neste navegador. Concluir a leitura não substitui demonstrar o domínio nos exercícios.
Referências e aprofundamento
Documentação oficial e trabalhos originais. As referências registram o escopo e as limitações para você conferir o que sustentam.
- Agent2Agent Protocol Specification v1.0.1
A2A Project / Linux Foundation • consulta: 2026-10-06
APIsAgent Cards, discovery, tarefas, mensagens, artefatos, interoperabilidade, segurança, comparação MCP e bindings JSON-RPC/gRPC/HTTP+JSON.
Limites: Fixar versão do protocolo/SDK no laboratório; JSON-RPC é um binding, não a única opção.
- Base protocol — MCP 2026-07-28
Model Context Protocol • consulta: 2026-10-06
MCPJSON-RPC, schema, versão/capabilities em _meta e JSON Schema2020-12.
Limites: Fonte aberta contém versão por requisição; compatibilidade com revisões antigas exige implementação explícita.
- Architecture — MCP 2026-07-28
Model Context Protocol • consulta: 2026-10-06
MCPInfraestruturaHost, clients e servers, fronteiras de segurança e requisições stateless com versão/capabilities.
Limites: Difere das revisões baseadas em initialize; SDKs e clientes antigos podem usar a arquitetura legada.
- A2A Quickstart: Consuming
Google ADK • consulta: 2026-10-06
Google ADKAPIsConsumir agente remoto A2A e uso de Agent Card; integração ADK.
Limites: A interoperabilidade depende da versão A2A e das capacidades concretas do SDK e servidor.