Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Descubrimiento de agentes A2A

Esta implementación puede publicar sus agentes para que otra implementación, o cualquier cliente HTTP, pueda encontrarlos y llamarlos. A2A es el protocolo para que un agente llegue a otro agente, del mismo modo que una persona se conecta con un bot a través de una aplicación de chat. Esta página muestra exactamente qué escribir y exactamente qué devuelve.

Cada respuesta en esta página es una salida real de un daemon en ejecución. Nada aquí es ilustrativo.

Autenticación

Los dos GET de descubrimiento no requieren autenticación: la tarjeta de catálogo y la tarjeta de agente por alias son legibles sin token para que un par pueda descubrir tu superficie publicada antes de emparejarse. El POST de SendMessage es distinto. Ejecuta un turno completo de agente con herramientas habilitadas, por lo que está protegido por la autenticación de emparejamiento del gateway, como cualquier otra superficie de escritura. Cuando [gateway] require_pairing está activado (el valor por defecto), pasa un token bearer derivado del emparejamiento en el POST de la tarea:

curl -X POST http://localhost:42617/a2a/agent_alpha \
  -H "Authorization: Bearer $ZEROCLAW_TOKEN" \
  -H "A2A-Version: 1.0" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{...}}'

Un POST de tarea sin autenticación recibe 401, nunca un turno de agente. Los GET de descubrimiento a continuación no necesitan encabezado. Consulta la documentación de emparejamiento del gateway para obtener un token.

Todo en dos solicitudes

Solo necesitas dos solicitudes GET para descubrir un agente.

Primero, pregunta a la implementación qué agentes publica:

curl http://localhost:42617/.well-known/agents-card.json

Segundo, pregunta a uno de esos agentes qué puede hacer:

curl http://localhost:42617/a2a/agent_alpha/.well-known/agent-card.json

La primera solicitud te da una lista de URLs de agentes. La segunda te da las habilidades de un agente y la URL a la que envías el trabajo. Esa es toda la superficie de descubrimiento. El resto de esta página consiste simplemente en leer esas dos respuestas con cuidado.

Solicitar 1: listar los agentes

curl http://localhost:42617/.well-known/agents-card.json

Respuesta:

{
    "nombre": "ZeroClaw agents",
    "descripción": Catálogo de descubrimiento que enumera los agentes A2A publicados en esta instalación de ZeroClaw. No es un agente ejecutable; cada entrada a continuación sirve su propia tarjeta A2A y su propio endpoint. Las habilidades se agregan a partir de los agentes publicados, cada una etiquetada con su alias propietario.,
    "supportedInterfaces": [
        {
            "url": "http://localhost:42617/.well-known/agents-card.json",
            protocolBinding: catálogo,
            "protocolVersion": "1.0"
        },
        {
            "url": "http://localhost:42617/a2a/agent_beta",
            protocolBinding: JSONRPC,
            "protocolVersion": "1.0"
        },
        {
            "url": "http://localhost:42617/a2a/agent_alpha",
            protocolBinding: JSONRPC,
            "protocolVersion": "1.0"
        }
    ],
    versión: "0.8.6",
    capabilities: {
        streaming: false,
        pushNotifications: false,
        extendedAgentCard: false
    },
    "defaultInputModes": ["texto"],
    "defaultOutputModes": ["texto"],
    "skills": [
        {
            "id": "agent_beta/github-issue-triage",
            "nombre": "github-issue-triage",
            "descripción": "Agente de triaje de incidencias y gestión del ciclo de vida para ZeroClaw.",
            "tags": ["github", "issues", triage, "agent_beta"]
        },
        {
            "id": "agent_beta/github-pr-review-session",
            "nombre": "github-pr-review-session",
            "descripción": "Copiloto de revisión humana para las revisiones de PR de ZeroClaw.",
            "tags": ["github", "pull-requests", "review", "agent_beta"]
        },
        {
            "id": "agent_alpha/zeroclaw",
            "nombre": zeroclaw,
            "descripción": "Ayuda a los usuarios a operar e interactuar con su instancia del agente ZeroClaw.",
            "tags": [operaciones, cli, "gateway", "agent_alpha"]
        },
        {
            "id": "agent_alpha/skill-creator",
            "nombre": "skill-creator",
            "descripción": "Crear nuevas habilidades, modificar y mejorar las habilidades existentes, y medir el rendimiento de las habilidades.",
            "tags": ["skills", authoring, evaluación, "agent_alpha"]
        },
        {
            "id": "agent_alpha/changelog-generation",
            "nombre": "changelog-generation",
            "descripción": "Habilidad de generación de changelog para los lanzamientos de ZeroClaw.",
            "tags": [historial de cambios, "release", automatización, "agent_alpha"]
        }
    ]
}

Léelo así. supportedInterfaces enumera URLs. La marcada catalog es esta misma lista, ignórala. Las dos marcadas JSONRPC son los agentes: agent_alpha y agent_beta. Sus URLs son adonde enviarás el trabajo. skills agrega las habilidades de cada agente publicado, cada id con prefijo y con tags-etiquetado con el alias propietario, así que una sola lectura muestra toda la superficie de capacidades de la instalación y quién posee cada parte.

Solicitar 2: inspeccionar un agente

Toma una URL de la lista y añade la ruta de la tarjeta:

curl http://localhost:42617/a2a/agent_alpha/.well-known/agent-card.json

Respuesta:

{
    "nombre": "agent_alpha",
    "descripción": "ZeroClaw agente 'agent_alpha'.",
    "supportedInterfaces": [
        {
            "url": "http://localhost:42617/a2a/agent_alpha",
            protocolBinding: JSONRPC,
            "protocolVersion": "1.0"
        }
    ],
    versión: "0.8.6",
    capabilities: {
        streaming: false,
        pushNotifications: false,
        extendedAgentCard: false
    },
    "defaultInputModes": ["texto"],
    "defaultOutputModes": ["texto"],
    "skills": [
        {
            "id": zeroclaw,
            "nombre": zeroclaw,
            "descripción": "Ayuda a los usuarios a operar e interactuar con su instancia del agente ZeroClaw.",
            "tags": [operaciones, cli, "gateway"]
        },
        {
            "id": "skill-creator",
            "nombre": "skill-creator",
            "descripción": "Crear nuevas habilidades, modificar y mejorar las habilidades existentes, y medir el rendimiento de las habilidades.",
            "tags": ["skills", authoring, evaluación]
        },
        {
            "id": "changelog-generation",
            "nombre": "changelog-generation",
            "descripción": "Habilidad de generación de changelog para los lanzamientos de ZeroClaw.",
            "tags": [historial de cambios, "release", automatización]
        }
    ]
}

Ahora ya sabes tres cosas. El agente se llama agent_alpha. Tiene tres habilidades, zeroclaw, skill-creator y changelog-generation, con descripciones sencillas de lo que hace cada una. Y la única URL de interfaz JSONRPC, http://localhost:42617/a2a/agent_alpha, es la dirección a la que haces POST de una tarea.

La description de la tarjeta proviene del documento de identidad del alias cuando hay uno configurado: la biografía de una identidad de AIEOS aporta la línea, y si no, se usa el nombre de esa identidad. Cuando no hay identidad configurada, la tarjeta usa el valor neutro predeterminado ZeroClaw agent '<alias>'. mostrado arriba.

Lo que un agente elige mostrar

Un agente no tiene que publicar todas las habilidades que tiene. El agente agent_beta en esta misma implementación publica su propio conjunto seleccionado:

curl http://localhost:42617/a2a/agent_beta/.well-known/agent-card.json
{
    "nombre": "agent_beta",
    "descripción": "ZeroClaw agent 'agent_beta'.",
    "supportedInterfaces": [
        {
            "url": "http://localhost:42617/a2a/agent_beta",
            protocolBinding: JSONRPC,
            "protocolVersion": "1.0"
        }
    ],
    versión: "0.8.6",
    capabilities: {
        streaming: false,
        pushNotifications: false,
        extendedAgentCard: false
    },
    "defaultInputModes": ["texto"],
    "defaultOutputModes": ["texto"],
    "skills": [
        {
            "id": "github-issue-triage",
            "nombre": "github-issue-triage",
            "descripción": "Agente de triaje de incidencias y gestión del ciclo de vida para ZeroClaw."
        },
        {
            "id": "github-pr-review-session",
            "nombre": "github-pr-review-session",
            "descripción": "Copiloto de revisión humana para las revisiones de PR de ZeroClaw."
        }
    ]
}

La implementación eligió qué agentes publicar y qué habilidades expone cada uno. Ese es el propósito de publicar: decides por cada agente qué habilidades puede ver el mundo exterior.

Enviando una tarea

Una vez que tengas la URL de la interfaz de un agente y una skill, envía el trabajo como una petición POST de JSON-RPC SendMessage a esa URL, indicando la versión del protocolo A2A que utilizas en el encabezado A2A-Version (este servidor utiliza 1.0):

curl -X POST http://localhost:42617/a2a/agent_alpha \
  -H "Authorization: Bearer $ZEROCLAW_TOKEN" \
  -H "A2A-Version: 1.0" \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "SendMessage",
    "params": {
      "message": {
        "messageId": "msg-1",
        "role": "ROLE_USER",
        "parts": [{ "text": "Reply with PONG" }]
      }
    }
  }'

El agente ejecuta el turno y responde con una tarea completada. La respuesta es la parte de texto dentro del artefacto de la tarea; el resultado es un SendMessageResponse cuya rama task contiene el Task:

{
    "id": 1,
    "jsonrpc": "2.0",
    "result": {
        "task": {
            "id": "0ef19fcb-b5e4-4c26-afce-d80451c8861e",
            "contextId": "a2a_agent_alpha_06cb22f5-12bf-4b26-9ebc-9c063ab520a4",
            "status": { "state": "TASK_STATE_COMPLETED" },
            "artifacts": [
                {
                    "artifactId": "5346ae32-1b63-40c0-9aaa-345d815c792e",
                    "parts": [
                        { "text": "PONG" }
                    ]
                }
            ]
        }
    }
}

La URL de la interfaz es la misma para el descubrimiento y para las tareas; solo cambia la solicitud. El endpoint acepta únicamente SendMessage y únicamente A2A-Version: 1.0; cualquier otro method devuelve un error JSON-RPC -32601, un mensaje vacío devuelve -32602, una versión no compatible devuelve un error VERSION_NOT_SUPPORTED, y un cuerpo que no es JSON-RPC devuelve HTTP 400.

Exposición y el único filo afilado

El endpoint de tareas comparte los controles de habilitación y publicación de las tarjetas: solo responde cuando se establece [a2a.server] enabled y el alias está habilitado y publicado. Un POST de tarea a un alias no publicado o desconocido devuelve 404, igual que su tarjeta. Sin embargo, no comparte la postura de autenticación de las tarjetas: las tarjetas de descubrimiento siguen siendo públicas, mientras que la invocación de tareas requiere el token bearer de la puerta de enlace y devuelve 401 sin él.

Un detalle a tener en cuenta: la URL de la interfaz responde a un GET simple con el panel web, no con un agente, porque el gateway recurre a servir el panel para cualquier ruta que no reconoce:

curl -i http://localhost:42617/a2a/translator
HTTP/1.1 200 OK
content-type: text/html

Discovery (las rutas .well-known) y el POST de SendMessage son la superficie compatible. Un GET simple a la URL de la interfaz no forma parte del protocolo; lee la tarjeta en la ruta .well-known en su lugar.

Desde dónde sirven los agentes

Las tarjetas son servidas por la puerta de enlace web, en la misma dirección y puerto que todo lo demás. Si tu puerta de enlace está en localhost:42617, ahí es donde viven el catálogo y cada tarjeta de agente. No ejecutas un segundo servidor y no abres un segundo puerto.

Si colocas este despliegue detrás de un proxy inverso o un nombre de host público, las URL dentro de las tarjetas deben coincidir con la dirección que los clientes realmente alcanzan. La URL publicada se resuelve en este orden: una URL base pública explícita si estableces una, luego una anulación específica de host y puerto de A2A si la estableces, y después la propia dirección del gateway. La anulación existe para el caso de proxy; si no estás detrás de un proxy, no la tocas nunca y las tarjetas anuncian directamente la dirección del gateway.

Activándolo

Discovery está desactivado hasta que lo activas, y está desactivado de tres formas independientes para que nada se filtre por accidente:

  • El servidor A2A está deshabilitado para todo el despliegue de forma predeterminada.
  • Cada agente no está publicado de forma predeterminada, incluso con el servidor en marcha.
  • Un agente publicado expone solo las habilidades que nombras, nada más.

Habilitas el servidor una vez, marcas los agentes específicos que quieres que sean accesibles como publicados y enumeras las habilidades que expone cada uno. Un agente que está deshabilitado, o que no está publicado, no aparece en el catálogo y la ruta de su tarjeta devuelve 404. Un nombre de agente desconocido devuelve 404 también.

Una habilidad con nombre aparece en la tarjeta solo cuando se resuelve en una habilidad real que el agente realmente lleva: debe residir en uno de los paquetes de habilidades del agente y su SKILL.md debe tener frontmatter YAML válido. Un nombre que no se resuelve, o una habilidad en un paquete que el agente no declara, se omite silenciosamente en lugar de anunciarse.

La causa más común de un array vacío skills: [] es configurar a2a.exposed_skills en un agente que no declara ningún skill_bundles. exposed_skills solo restringe el conjunto de habilidades resuelto del agente; no carga habilidades por sí solo. Si no se declara ningún paquete, no hay nada que el filtro pueda conservar, así que todos los nombres se descartan y la tarjeta no anuncia nada. Añade el o los paquetes propietarios a agents.<alias>.skill_bundles. La validación de configuración expone este caso como una advertencia de inicio (a2a_exposed_skills_without_bundles).

Lo que realmente expone la publicación

Lea esto antes de publicar. Una vez que el servidor esté habilitado y se publique un alias, POST /a2a/{alias} ejecuta un turno completo del agente para ese alias: lo invoca a través de la misma ruta que usan las superficies de chat, con todo el conjunto de herramientas configurado del agente (shell, archivos, navegador y cualquier otra cosa que lleve ese alias).

Ese endpoint de tarea está detrás de la autenticación bearer/pairing de la pasarela, como cualquier otra superficie de escritura. Un llamador necesita un token bearer derivado del pairing para invocar un agente publicado; una solicitud no autenticada obtiene 401, nunca un turno del agente.

Las tarjetas de descubrimiento no están detrás de esa autenticación. El catálogo y las tarjetas por alias se pueden leer sin un token, así que una superficie publicada anuncia los nombres de sus agentes y las habilidades expuestas a cualquier cliente que pueda الوصولar al listener. Ese es el propósito del descubrimiento: un par lee la tarjeta antes incluso de emparejarse. También significa que publicar expone esos metadatos a cualquiera que pueda acceder al gateway, aunque invocar al agente siga requiriendo un token.

La publicación es una decisión de exposición en ambos ejes: los metadatos de la tarjeta son públicos, y cualquier poseedor de un token válido puede invocar un alias publicado con su conjunto completo de herramientas. Antes de activar los interruptores:

  • Delimite la postura de enlace. Enlace la puerta de enlace a una interfaz privada, o colóquela detrás de un proxy inverso, en lugar de exponer directamente el listener a una red no confiable. Esto también limita quién puede leer las tarjetas no autenticadas.
  • Publique solo los alias cuyo conjunto completo de herramientas esté dispuesto a permitir que invoque cualquier titular de tokens, y cuyos nombres y habilidades esté dispuesto a anunciar sin autenticación. Reduzca exposed_skills al mínimo que necesita la interoperabilidad.
  • Trata un alias publicado como una superficie de ejecución invocable de forma remota cuando decidas qué herramientas y paquetes de habilidades lleva ese alias.
  • La interoperabilidad entre despliegues comparte un token con el par que te llama; limita el ámbito y rota esa credencial como cualquier otra.

Cómo se conectan varias implementaciones

Discovery se compone a través de cualquier número de despliegues. Cada despliegue publica su propio catálogo en su propia dirección. Un cliente que conoce varias direcciones de despliegue recupera cada catálogo, lee los agentes y ahora tiene un mapa combinado de cada agente alcanzable en todos ellos. No hay registro ni servidor central: el cliente es lo único que necesita conocer las direcciones, y se comunica directamente con cada despliegue.

Una imagen ilustrativa. Ejecutas una implementación personal. Tu equipo ejecuta una compartida. Un equipo de datos ejecuta una tercera. Tu cliente recupera los tres catálogos:

curl http://personal.example:42617/.well-known/agents-card.json
curl http://team.example:42617/.well-known/agents-card.json
curl http://data.example:42617/.well-known/agents-card.json

Cada uno devuelve su propia lista de agentes. Tu cliente ahora ve, por ejemplo, un agente notes en personal, un agente deploy en team y un agente query en data. Para usar cualquiera de ellos, obtiene la tarjeta de ese agente y envía una tarea a la URL de ese agente, exactamente como se muestra arriba. No cambia nada por despliegue; son las mismas dos lecturas y un POST, apuntando a un host diferente.

Llamando la atención: el cliente A2A saliente

Todo lo anterior describe un despliegue que se descubre e invoca. La dirección inversa es saliente: un despliegue delega tareas en un agente A2A remoto y usa las respuestas en su propio trabajo. Ese es el cliente saliente, configurado en [a2a.client]. Está desactivado por defecto y no registra nada hasta que lo habilites, por lo que una instalación sin configurar no tiene ninguna huella saliente de A2A.

[a2a.client]
enabled = true                  # registra las herramientas a2a_*; por defecto false

[[a2a.client.peers]]
name = "team"                   # el argumento `peer` de cada herramienta a2a_*
base_url = "https://team.example.com"
token = "${TEAM_A2A_TOKEN}"     # se resuelve desde el entorno al momento de la llamada; vacío = anónimo
tags = ["production"]

Cada peer nombra un origen de servidor A2A remoto. El cliente deriva la ruta de la tarjeta well-known y la ruta de tareas JSON-RPC a partir de base_url; un agente que realiza la llamada nunca escribe una URL, solo el nombre de peer.

Las credenciales se tipan como secretos. Un token con valor ${VAR} se resuelve desde el entorno en el momento de la llamada, un valor literal se envía tal cual, y un valor vacío no envía ningún encabezado Authorization. Como el campo está marcado como secreto, las superficies de configuración y de esquema lo tratan como material cifrado de solo escritura y no como un valor legible. Es la misma postura de secreto que http_request usa para sus tokens de autenticación.

Las llamadas salientes son una superficie de SSRF, por lo que la política rechaza por defecto los pares en hosts privados, de bucle local (loopback) y de enlace local. Para un despliegue local o en la intranet que apunte a 127.0.0.1 o a un segmento RFC1918, establezca allow_private_hosts = true o agregue los hosts específicos a allowed_private_hosts; la lista de permitidos fija solo esos hosts sin relajar la postura global. La resolución de nombres de host queda fijada a las direcciones que superaron el filtro, las redirecciones están deshabilitadas, los cuerpos de respuesta están acotados por max_response_bytes y cada solicitud tiene un límite de request_timeout_secs.

Las cuatro herramientas difieren en su impacto. a2a_discover y a2a_get_task son operaciones de lectura y no se cuentan como acciones. a2a_send y a2a_cancel modifican el estado del par, por lo que son operaciones Act: requieren aprobación por defecto, y un modo de autonomía de solo lectura las deniega antes de cualquier E/S de red. Trata a2a_send como una concesión de ejecución remota a cualquiera de las skills que publique el par.

Desactivar el cliente saliente es una contención, no una reversión. Establecer [a2a.client] enabled = false impide que las herramientas a2a_* se registren, así que desactivarlo y reiniciar el daemon (reconstruyendo el registro) es la acción correcta para contener un incidente de egreso saliente. La contención no surte efecto de inmediato en un daemon que ya está en ejecución: sus tareas de agente conservan las herramientas a2a_* ya construidas hasta el reinicio o la reconstrucción del registro, por lo que un operador debe considerar que el egreso sigue activo hasta entonces. Desactivar el interruptor no deshace la migración del gateway entrante a la superficie SendMessage v1 ni el cambio del modelo de wire compartido, que se publican en la misma versión: un llamador externo que ya usa v1 seguirá funcionando, y un operador que quiera el comportamiento entrante previo a v1 debe revertir la versión completa en lugar de cambiar solo este interruptor. La negociación de versión del gateway, los nombres de método JSONRPC y las formas de los enums y de Part pasaron todos a v1 al mismo tiempo.

Casos de uso

Algunas razones concretas para vincular implementaciones entre sí.

Una implementación de investigación delega la búsqueda bibliográfica a una implementación de datos especializada. El agente de investigación descubre el agente search de la implementación de datos, le envía una consulta como una tarea y incorpora el resultado en su propio trabajo. El lado de investigación nunca posee las credenciales ni los índices del lado de datos; solo conoce la URL del agente.

Una implementación de guardia distribuye un incidente a implementaciones propiedad de cada equipo. Descubre un agente triage en la implementación de cada equipo y envía a cada uno el mismo incidente como una tarea, recopilando sus respuestas. Cada equipo controla lo que expone su agente de triage; del lado de guardia solo se leen tarjetas y se envían tareas.

Una implementación personal llama a los agentes verificados de una implementación de empresa sin compartir credenciales. Descubres el agente invoice de la empresa, le envías una solicitud preliminar y recibes un resultado. La empresa decide qué agentes y habilidades se publican; nunca obtienes una cuenta dentro de su implementación, solo el endpoint del agente.

A2A no es MCP

Estos resuelven problemas distintos y se complementan. MCP conecta un agente con sus herramientas y contexto: responde a qué puede llamar un único agente. A2A conecta un agente con otros agentes como pares: responde a qué otros agentes puede pasarles trabajo. Un agente al que accedes a través de A2A puede usar herramientas de MCP internamente para hacer el trabajo, y tú no lo ves ni te importa; la tarjeta muestra habilidades, no las herramientas detrás de ellas. Usa MCP para dotar a un agente de capacidades, usa A2A para permitir que los agentes deleguen entre sí.