Tutoriel
Agent Card JSON A2A 1.0 en pratique : valider les capacités déclarées, la forme des skills et les données inter-agents
Quand la validation échoue, nommez d’abord la couche : ça parse, ça a passé le schéma généré, ou ça a collé au tableau requis de la spec.
Le billet précédent, la marche de découverte, a fini well-known, catalogue, puis Card. Comment écrire les champs : le tutoriel des champs Agent Card. Aujourd’hui on ne re-remplit pas, on ne re-marche pas les sauts. Vous tenez déjà une carte, ou vous venez de la GET. La question : comment affirmer qu’elle est légale, et la Task envoyée sera-t-elle traitée comme un déchet ? Le texte est dans la spec A2A 1.0 §4.4 et §3.3.4. Le a2a.json du site le dit lui-même : un bundle JSON Schema non normatif extrait du proto. Le passage 0.3 → 1.0 est sur les nouveautés de la v1.0.
Vert n’est pas un passage
Jeter le bundle généré dans Ajv, pointer la racine, voir du vert — le faux passage le plus fréquent en revue. La racine n’est pas une Card. AgentCard, AgentSkill, Task et Message vivent sous $defs. Valider la racine du bundle ne valide rien. Le bon pointeur est #/$defs/AgentCard. Le trafic utilise #/$defs/Task ou #/$defs/Message. N’enveloppez pas une Task dans le schéma Card.
Même avec le bon pointeur, le bundle généré omet souvent required. La copie septembre 2026 de AgentCard et AgentSkill sur le site pose additionalProperties: false sans lister les clés requises du tableau spec. Un name vide, un skill sans tags, peuvent rester verts. La source du requis est le tableau spec : name, description, version, supportedInterfaces, capabilities, defaultInputModes, defaultOutputModes, skills. Chaque skill a encore besoin de id / name / description / tags.
Donc au moins deux passes. Première : ça parse, et l’AgentCard généré n’explose pas sur une clé en trop. Seconde : le tableau spec affirme le non-vide. La première seule attrape une url 0.3 de premier niveau via additionalProperties: false ; un skill sans tags passe. La seconde seule, sans bundle, rate un inputSchema égaré. Il faut les deux.
| Cette couche | Ce qu’elle attrape | Ce qu’elle rate |
|---|---|---|
| JSON.parse | Syntaxe cassée, troncature, virgules finales | Si la forme est juste |
#/$defs/AgentCard généré | url 0.3 de premier niveau, inputSchema sur un skill | Si les clés requises par la spec ont été écrites |
| Tableau requis §4.4 | name vide, tags manquants, skills vide | Si les drapeaux collent à l’opération que vous allez envoyer |
Quatre couches, une classe d’erreur chacune
La troisième couche, ce sont les drapeaux de capacité. La spec §3.3.4 est nette : streaming faux et vous souscrivez quand même — l’agent DOIT renvoyer UnsupportedOperationError. pushNotifications faux et un webhook quand même : PushNotificationNotSupportedError. extendedAgentCard faux et vous tirez encore la carte étendue : même classe. Schéma vert, clés requises là, le saut suivant peut encore rebondir. La fixture doit écrire « carte légale » et « cette opération est permise par les drapeaux » en deux étapes.
La quatrième couche est le trafic. Vous envoyez une Task ou un Message. Vous ne POSTEZ pas la Card à nouveau. InvalidAgentResponseError concerne la forme de la réponse, pas la carte de visite. Le MIME doit coller à defaultInputModes ou aux inputModes du skill ; un écart est ContentTypeNotSupportedError. Ne contrôlez pas un skill A2A avec un inputSchema MCP. Les indices 1.0 sont examples et les MIME.
Le bundle généré porte aussi des patternProperties snake_case (supported_interfaces, default_input_modes). Le JSON spec est camelCase. Les cartes neuves suivent le tableau spec. Si la CI accepte les deux jeux de clés, le Diff se salit d’abord. Ne copiez pas les noms proto sur une carte publique, sauf couche de compatibilité assumée et dite dans la fixture.
La Card : pointer AgentCard, pas la racine du bundle
Épinglez une copie de a2a.json dans le dépôt, alignée sur une version de bundle publiée. La CI ne doit pas frapper une URL latest flottante. Ajv (ou toute implémentation 2020-12) reçoit le pointeur #/$defs/AgentCard. L’instance est la carte tout juste GET, ou agent-card.json du dépôt. Émettez path et keyword. Si le chemin ne colle pas à la fixture, suspectez le pointeur avant la carte.
Le rejet le plus utile de la première passe est une clé en trop. En 1.0 le point d’entrée est dans supportedInterfaces. Une url, protocolVersion ou supportsAuthenticatedExtendedCard de premier niveau restante passe au rouge sous additionalProperties: false. C’est du 0.3, pas « plus de champs, plus sûr ». Le tutoriel des champs a déjà couvert le déménagement. Aujourd’hui : le validateur doit marquer ces clés comme erreurs, pas les ignorer.
Chaque interface demande trois contrôles : url de prod en HTTPS absolu (gRPC est host:port), protocolBinding que le client parle, protocolVersion une version de protocole du type 1.0 — pas la version de l’agent. Les deux s’appellent version. La fixture doit les affirmer à part. La première entrée est préférée. Pas de liaison commune, pas d’appel.
Skills : la spec exige des champs que le schéma saute souvent
L’AgentSkill généré n’a souvent pas non plus de required. Un skill sans tags peut rester vert dans Ajv et avoir une surface de recherche vide — déjà dit dans le billet découverte. Assertion du jour : chaque skill a un id, name, description non vides, et au moins un tag. Un tableau skills vide échoue au tableau spec. Une entrée NO_SPEC hôte seul doit rougir avant l’enregistrement, pas après un saut mot-clé dans le vide.
Un skill ne doit pas porter inputSchema. additionalProperties: false le traite comme clé en trop. Ça appartient aux outils MCP ; voir le texte Schema du site. Un skill 1.0 montre examples et MIME. Verser une table de paramètres de fonction dans la carte : la première passe doit échouer. Échouer à la validation coûte moins cher que clarifier après la Task.
La carte ci-dessous est faite pour rougir au premier ou au second passage. Ce n’est pas un brouillon « presque utilisable ». Elle mélange une url 0.3 de premier niveau, un skill sans tags, et un inputSchema façon MCP. Diff à côté de Returns Specialist dans le dépôt. Les trois rouges doivent tomber sur trois chemins.
{
"name": "Returns Specialist",
"description": "Classifies return requests.",
"version": "1.0.3",
"url": "https://agents.example.com/returns/a2a",
"protocolVersion": "1.0",
"supportedInterfaces": [
{
"url": "https://agents.example.com/returns/a2a",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
}
],
"capabilities": {
"streaming": true
},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"],
"skills": [
{
"id": "classify-return",
"name": "Classify a return",
"description": "Decide the request type.",
"inputSchema": {
"type": "object",
"required": ["orderId"],
"properties": {
"orderId": { "type": "string" }
}
}
}
]
}
| Contrôle | À quoi ressemble l’échec | Saut suivant |
|---|---|---|
| Parse + pointeur AgentCard | Virgule finale ; url de premier niveau ; inputSchema de skill | Réparer le JSON / jeter les clés 0.3 |
| Requis spec / non vide | name vide ; skill sans tags ; skills vide | Remplir les champs depuis §4.4 |
| Drapeaux vs opération | S’abonner alors que streaming est faux ; tirer une carte étendue non déclarée | Changer le client, ou les drapeaux de la Card |
Trafic : une Task n’est pas une carte de visite
Après le passage de la Card seulement, message/send. Le corps suit la liaison — JSON-RPC, gRPC ou HTTP+JSON. L’objet métier dedans est un Message ou une Task, pas une AgentCard. Valider une requête contre la définition Card échoue sans rien apprendre. Le bundle généré a des defs séparées Task, Message, Part, Artifact. Les fixtures de trafic changent de pointeur. Ne réutilisez pas la ligne Card.
Validez aussi la réponse. La spec plie une réponse d’agent non conforme dans InvalidAgentResponseError. La machine d’états est submitted / working / completed / failed / canceled / rejected, plus les états interrompus input-required et auth-required. Traiter completed comme seul succès transforme « une phrase de plus » en incident. Ces clés ne sont pas sur la Card. Découverte réussie, appel raté — le ticket doit nommer la couche.
Le JSON ci-dessous est une fixture de rapport, pas un RPC officiel de catalogue ou d’A2A. Il plie les quatre couches en un objet pour le diff à côté de la Card du dépôt et d’une requête message/send. Les contrôles de signature n’affirment que la forme : chaque item de signatures[] a besoin de protected et signature (JWS RFC 7515). Les vraies clés et la vraie vérif restent à une revue de sécurité. Pas de clé privée dans la fixture.
{
"kind": "card-validation-report",
"note": "CI/review fixture — not an official A2A RPC",
"target": "agent-card.json",
"schema": {
"bundle": "https://a2a-protocol.org/latest/spec/a2a.json",
"pointer": "#/$defs/AgentCard",
"normative": false
},
"parse": true,
"schemaPass": false,
"schemaErrors": [
{ "path": "/url", "keyword": "additionalProperties" },
{ "path": "/skills/0/inputSchema", "keyword": "additionalProperties" }
],
"specChecks": [
{ "id": "required-name", "pass": true },
{ "id": "skills-tags-nonempty", "pass": false },
{ "id": "no-top-level-url", "pass": false }
],
"capability": {
"streaming": true,
"clientWillStream": true,
"ok": true
},
"next": "fix-card"
}
Signatures, fixtures, diffs locaux
La spec autorise signatures au format RFC 7515. Si le tableau est là, affirmez d’abord les deux chaînes requises non vides, puis décidez de vérifier. Pas de tableau ne rend pas la carte illégale — le champ est optionnel. Écrivez la carte publique comme si on allait la tirer. Pas de secret statique ni de mot de passe intranet dans la Card. Une carte étendue suit la session. Ne partagez pas un cache « déjà validé » avec la carte publique.
Ne versez pas un SKILL.md de Plugin ni un tools/list MCP dans le même contrôle Card. Les skills dans la boîte sont pour un agent de code de ce dépôt. L’auto-description de l’autre équipe est la Card. Les deux dans un capability.json, et trois couches ratées atterrissent sur la même ligne de ticket.
Dans le navigateur : Formater du JSON pour voir si la carte et le rapport parsent ; Valider un schéma JSON pour pointer le bundle officiel sur AgentCard, puis changer de pointeur pour une Task ; JSON Diff pour comparer la carte légale et la carte rouge, attraper clés en trop / tags manquants. Les données restent sur cette machine. Pour aller plus loin : le tutoriel des champs Agent Card, et la marche de découverte. Qu’est-ce qu’un catalogue : le tour Registry.
Liens : A2A Agent Card JSON Schema, Comment les agents se trouvent, Google Agent Registry.
FAQ
Ajv est vert sur a2a.json. Faut-il encore une seconde passe ?
Oui. Le bundle du site se dit non normatif, et les defs générées omettent souvent required. Le tableau spec est la source du requis. Vert veut seulement dire : pas de clés en trop ni de types heurtés.
Un seul schéma peut-il valider Card et Task ?
Non. Changez de pointeur. AgentCard et Task sont deux $defs. Envelopper une requête dans la Card, et le message d’échec enverra le saut suivant dans le mur.
Un skill peut-il accrocher un JSON Schema en entrée ?
Un skill 1.0 ne prend pas inputSchema. C’est un outil MCP. Le bundle généré le traite comme clé en trop. Les paramètres déterministes restent au saut outil. Ne les écrivez pas dans la carte.
Une carte sans signatures est-elle illégale ?
Non. signatures est optionnel. Si le tableau est là, affirmez les deux chaînes JWS requises. S’il est absent, courez quand même le requis §4.4 et les drapeaux.
À retenir et la suite
En 2026, « valider une Agent Card » se plie en quatre couches : parser, pointeur de bundle, tableau requis de la spec, puis drapeaux et trafic. Catalogue et découverte sont la porte. La validation est le garde qui décide si vous pouvez déléguer.
Ordre de mise en prod : épingler a2a.json, pointeur AgentCard ; courir les asserts non-vides du tableau spec ; vérifier drapeaux et MIME avant d’envoyer une Task ; basculer les fixtures de trafic sur Task / Message. Écrire les champs : tutoriel. Comment vous avez trouvé la carte : billet découverte. Diff de la carte légale, de la carte rouge et du rapport dans JSONVue.