Ir al contenido
IA

Protocolo A2A: cómo diseñar un agente al que llaman otros agentes

A2A v1.0 salió en marzo de 2026. Seis pasos para diseñar un servidor A2A: Agent Card, skills por resultado, ocho estados de tarea, bindings, auth y versiones.

6 de octubre de 2026 · 10 min de lectura

Three dark tiles on the left, the middle one lit amaranth, linked to a card of skill rows pinned to a large closed dark box on the right; an amaranth line runs from the lit tile to the lit skill row, and a row of small fading squares runs back to a lit tile beside it

Al terminar esta guía tendrás el diseño de un servidor A2A para un agente que ya tienes en producción: una Agent Card publicada, skills entre las que el modelo de otro agente sabe elegir, un ciclo de vida de tareas alineado con los estados de tus trabajos, un canal de actualizaciones que encaja con tu infraestructura, autorización en cada llamada y un plan de versiones. El protocolo Agent2Agent (A2A) es un estándar abierto con el que un agente de IA encuentra a otro, le delega una tarea y recoge el resultado, sin que ninguno vea los prompts, la memoria ni las herramientas del otro.

A2A llegó a su primera versión estable, la 1.0, en marzo de 2026. Google lo anunció en abril de 2025 y lo cedió a la Linux Foundation en junio de 2025. En agosto de 2026 pasó a ser un proyecto alojado por la Agentic AI Foundation, la misma casa neutral que MCP. La Linux Foundation cuenta más de 150 organizaciones que lo respaldan y despliegues en producción dentro de Azure AI Foundry y Amazon Bedrock AgentCore. El protocolo es lo bastante estable para diseñar sobre él. Las decisiones de diseño siguen siendo tuyas, y de ellas trata esta guía.

¿Dónde encaja A2A frente a MCP?

La documentación de A2A describe MCP como vertical y A2A como horizontal. MCP le da a un agente más herramientas: una base de datos, la API de un calendario, un almacén de archivos. A2A conecta ese agente con otros agentes que no controlas. Un agente de planificación dentro de tu SaaS usa MCP para leer el servicio de calendario y A2A para recibir trabajo del agente de compras de un cliente. Casi todos los productos que exponen un agente acabarán usando los dos.

Para el diseño, lo que cuenta es la opacidad. Un cliente MCP llama a tus herramientas una por una y ve cada resultado. Un cliente A2A te entrega un objetivo y recibe una tarea, actualizaciones de estado y artifacts. Los prompts, la cadena de herramientas y el razonamiento intermedio quedan en privado. Si aún no tienes la parte MCP, empieza por qué hace un servidor MCP por un SaaS.

Qué necesitas antes de empezar

  • Un agente que ya completa un trabajo de principio a fin dentro del producto, con sus propias herramientas.
  • Un almacén de trabajos que sobreviva a los reinicios, una tabla o una cola. Una tarea A2A puede durar horas, y quien la pidió la vuelve a leer por su id.
  • Un proveedor de identidad que hable OAuth 2.0 u OpenID Connect. Las Agent Cards declaran esquemas con API key, autenticación HTTP, OAuth 2.0, OpenID Connect y TLS mutuo.
  • Uno de los SDK oficiales: Python, Go, Java, JavaScript, C#/.NET o Rust. La especificación exige a los SDK que gestionen la negociación de versión, que es justo la parte que menos conviene escribir a mano.

Paso 1: describe cada skill como el resultado que busca quien llama

Las skills son lo que lee el modelo del agente que llama para decidir si te delega el trabajo. Cada skill de la Agent Card lleva un id, un name, una description, tags, examples y los formatos que acepta y devuelve. Ese texto es un prompt para un modelo que no conoces. Escríbelo como documentación para un desconocido: qué hace la skill, qué necesita y qué devuelve.

Agrupa las skills por resultado. «Concilia una factura con su orden de compra» es una skill. Las funciones que hay detrás (recuperar la factura, cruzar las líneas, marcar la diferencia) son tu implementación, y la ejecución opaca de A2A las deja fuera de la card. Mantén la lista corta: cada skill de más es otra opción en la que el modelo de quien llama puede equivocarse. Dale a cada skill al menos un ejemplo en lenguaje natural y, cuando la entrada sea estructurada, otro en JSON, como hace la card de ejemplo de la propia especificación.

Paso 2: publica la Agent Card en la dirección well-known

Un servidor A2A tiene que publicar una Agent Card. El sitio estándar es https://tu-dominio/.well-known/agent-card.json; también valen los registros y la configuración directa. Una card mínima en v1.0, con una sola skill, tiene este aspecto:

{
  "name": "Invoice Reconciliation Agent",
  "description": "Matches supplier invoices against purchase orders and flags variances.",
  "version": "2.1.0",
  "provider": { "organization": "Example SaaS", "url": "https://example.com" },
  "supportedInterfaces": [
    { "url": "https://agents.example.com/a2a/v1", "protocolBinding": "JSONRPC", "protocolVersion": "1.0" }
  ],
  "capabilities": { "streaming": true, "pushNotifications": true, "extendedAgentCard": true },
  "securitySchemes": {
    "oidc": { "openIdConnectSecurityScheme": { "openIdConnectUrl": "https://auth.example.com/.well-known/openid-configuration" } }
  },
  "securityRequirements": [{ "schemes": { "oidc": { "list": ["openid"] } } }],
  "defaultInputModes": ["application/json", "text/plain"],
  "defaultOutputModes": ["application/json"],
  "skills": [
    {
      "id": "reconcile-invoice",
      "name": "Reconcile an invoice",
      "description": "Compares one supplier invoice with its purchase order. Returns matched lines, variances and a pass or hold decision.",
      "tags": ["finance", "invoices", "reconciliation"],
      "examples": ["Reconcile invoice INV-2291 against PO-7781."],
      "inputModes": ["application/json", "application/pdf"],
      "outputModes": ["application/json"]
    }
  ]
}

Cuatro campos cargan con casi todo el peso del diseño.

  • supportedInterfaces enumera los endpoints por orden de preferencia. El cliente coge el primero que sabe usar, así que pon arriba el binding que más has probado.
  • capabilities tiene que decir la verdad. Si un cliente llama a una función opcional que la card no declara, el servidor debe responder con un error. Declara el streaming solo cuando el streaming funcione.
  • securitySchemes le dice a quien llama cómo conseguir un token antes de la primera petición.
  • extendedAgentCard deja corta la card pública y muestra las skills propias de cada tenant solo a quien se ha autenticado, con GetExtendedAgentCard.

Sirve la card con Cache-Control y un ETag derivado de su versión, como recomienda la especificación. Cuando quien llama necesite comprobar de dónde viene la card, fírmala con JSON Web Signature sobre su forma JSON canónica (RFC 7515 y RFC 8785).

Paso 3: haz corresponder los estados de tus trabajos con el ciclo de vida de la tarea

En A2A v1.0 una tarea pasa por ocho estados. Antes de escribir un solo handler, apunta qué estado interno de tus trabajos cae en cada uno.

  • Submitted y working: aceptada y, después, en curso.
  • Input-required: el agente necesita más información. Quien llama responde con un mensaje nuevo que lleva el mismo id de tarea.
  • Auth-required: el agente necesita una credencial o la aprobación de una persona para seguir.
  • Completed, failed, canceled y rejected: estados finales. Una tarea en estado final no acepta más mensajes.

Tres reglas de la especificación marcan el resto. Los id de las tareas los genera el servidor, y un cliente no puede crear una tarea con un id propio. Los resultados van en los artifacts; los mensajes llevan conversación y estado, así que quien busca la salida lee los artifacts. Y una respuesta rápida que no necesita seguimiento puede volver como un único mensaje, sin tarea. Usamos rejected para el trabajo que el agente decide no hacer (fuera de alcance, contra una norma) y reservamos failed para el trabajo que se ha roto por un error, porque el siguiente paso de quien llama cambia: probar en otro sitio o volver a intentarlo más tarde.

El contextId agrupa las tareas relacionadas en una misma conversación. Un agente puede hacer caducar los contextos, y la especificación pide documentar esa política. Escribe la caducidad en las descripciones de las skills o en la documentación enlazada, para que quien llama sepa cuánto tiempo sigue siendo válida una petición de seguimiento.

Paso 4: elige un binding y un canal de actualizaciones

La v1.0 define tres bindings que deben comportarse igual: JSON-RPC 2.0, gRPC y HTTP+JSON. Las operaciones se llaman igual en los tres: SendMessage, SendStreamingMessage, GetTask, ListTasks, CancelTask, SubscribeToTask. En el binding REST se convierten en rutas como POST /message:send y GET /tasks/{id}. Elige el binding que tu gateway y tu sistema de observabilidad ya manejan. En un stack web suele ser JSON-RPC o HTTP+JSON sobre HTTPS.

Quien llama puede seguir una tarea de tres formas: consultando GetTask cada cierto tiempo, con streaming o con notificaciones push a un webhook que registra. Nuestra opción por defecto es el streaming para las tareas que terminan mientras quien llama espera, y el push para las que duran lo suficiente como para que se corte la conexión. En el push la especificación es muy concreta en seguridad:

  • Envía las credenciales configuradas en cada llamada al webhook.
  • Corta las peticiones al webhook a los 10-30 segundos y reintenta con backoff exponencial.
  • Rechaza las URL de webhook que apunten a localhost, a direcciones link-local o a redes privadas como 10.0.0.0/8 y 192.168.0.0/16. Sin ese control, el agente se convierte en una herramienta de request forgery contra su propia red.
  • En el lado que recibe, procesa las notificaciones de forma idempotente: los duplicados están previstos.

Paso 5: autoriza cada llamada según quién llama

La especificación exige un control de autorización en cada operación, antes de cualquier consulta que pueda revelar si un recurso existe. ListTasks devuelve solo las tareas que quien llama puede ver, aunque la petición no lleve filtros. GetTask sobre la tarea de otro tenant responde como si la tarea no existiera. Saca el tenant del token de quien llama a la entrada y pásalo a cada lectura del almacén.

Usa el estado auth-required para los permisos que el agente todavía no tiene: un token OAuth hacia un servicio posterior, o el visto bueno de una persona antes de un reembolso por encima de un umbral. La tarea se detiene, quien llama consigue la credencial y el trabajo continúa.

Trata como no fiable cada parte que llega. El texto de un agente que te delega trabajo llega a tu modelo, así que valen las mismas defensas contra la prompt injection que aplicas a la entrada de los usuarios. Las referencias a archivos se validan antes de descargarlas, por el mismo riesgo de request forgery que los webhooks. Los controles que describimos para blindar un servidor MCP para empresas (SSO, registro de auditoría, un gateway delante) sirven casi sin cambios.

Paso 6: separa la versión del protocolo de la versión de la card

En un despliegue A2A conviven dos números de versión. La versión del protocolo es Major.Minor: el cliente la envía en la cabecera A2A-Version y la card la declara para cada interfaz. El campo version de la card, en cambio, es la release de tu agente, y cambia cada vez que cambian las skills.

Una petición con la cabecera A2A-Version vacía se trata como 0.3, y una versión que la interfaz no sirve recibe VersionNotSupportedError. Importa porque la v1.0 rompió la compatibilidad con la 0.3: message/send pasó a ser SendMessage, los valores de los enum pasaron a SCREAMING_SNAKE_CASE y protocolVersion se movió de la card a cada interfaz. Si ya tienes clientes 0.3, sirve las dos versiones como interfaces separadas y retira la 0.3 en una fecha que publiques.

Cómo comprobar que funciona

  1. Descarga la card con curl. Esperas un 200, JSON válido, Cache-Control y un ETag.
  2. Envía un mensaje sin token. Esperas un error de autenticación, no una tarea.
  3. Envía un mensaje válido. Esperas una tarea con un id generado por el servidor, en estado submitted o working.
  4. Lee esa tarea con el token de otro tenant. Esperas un error de tarea no encontrada.
  5. Cancela la misma tarea dos veces. Las dos llamadas deben dejar el mismo estado.
  6. Abre un stream y comprueba que se cierra cuando la tarea llega a un estado final.
  7. Envía A2A-Version: 9.9. Esperas VersionNotSupportedError.
  8. Registra un webhook en http://127.0.0.1. Esperas un rechazo.

Errores frecuentes y cómo corregirlos

  • Skills con el nombre de funciones internas. Quien llama elige la equivocada, o ninguna. Renómbralas según el resultado y añade ejemplos.
  • Salida dentro de los mensajes de estado. Quien lee los artifacts no encuentra nada. Mueve los resultados a los artifacts y deja los mensajes para preguntas y avances.
  • Capabilities declaradas antes de que funcionen. Una card con streaming: true sin un SubscribeToTask que funcione hace fallar a los clientes que se fían de ella. Activa el flag el día en que la función esté en producción.
  • Nombres de la 0.3 y la 1.0 mezclados en el mismo endpoint. El cliente negocia una versión por interfaz. Separa las interfaces.
  • contextId usado como identidad. Agrupa conversaciones y no dice nada de quien llama. La autorización sale del token.

Para ir más allá

A2A cubre la conversación entre agentes; cada agente sigue necesitando sus propias herramientas y sus propias reglas. Para la gobernanza, lee qué cambió para MCP con la Linux Foundation, y para lo que falla cuando varios agentes se reparten el trabajo, los 12 errores de los equipos en su primer sistema multiagente.

Preguntas frecuentes

Artículos relacionados

Studio

Empieza un proyecto.

Escribimos sobre lo que construimos. Cuéntanos qué quieres construir tú.