A2A Agent Identity: Verify the Peer Before You Collaborate
Before two agents collaborate over A2A, the caller still has to answer a narrower question: is this Agent Card the peer I meant, on the interface I meant, for the skill I am about to send? A2A agent identity is that pre-task check. It is not the protocol tutorial, and it is not a file of copy-paste manifests.
A2A already defines messages, tasks, artifacts, and the Agent Card. The A2A 1.0.0 specification says identity is handled at the protocol layer, not as a field inside the task, and that clients should check the server's TLS certificate. That is necessary. It does not tell you which operator published the card, or which URL is the one your policy allows.
A general handoff, with no protocol attached, is the agent handshake. This page stays on A2A.
The card you verify
An A2A 1.0 Agent Card tells you who provides the peer, which interfaces it serves, and which skill you are allowed to call. Fetch that card, often with GET /.well-known/agent-card.json, and read it before you send a task.
The endpoint list is supportedInterfaces. The first entry is the one the agent prefers. Every entry has its own url, a protocolBinding of JSONRPC, GRPC, or HTTP+JSON, and a protocolVersion. On a current interface that version is 1.0. The card's own version field is the agent's release string. Do not send that string as the protocol version.
Send an A2A-Version header on every request, set to the protocolVersion of the interface you selected. If that interface does not support the version, the agent returns VersionNotSupportedError. Confirm the match before SendMessage, not after the first error.
Optional signatures are RFC 7515 JWS values over the card. No signature means you cannot treat the card as signed. A signature you cannot verify is not a pass. Read an authenticated extra card only when capabilities.extendedAgentCard is true, and only after you authenticate with a scheme the public card declares.
Four checks, in order
| What you check | What you read | When you stop |
|---|---|---|
| Who publishes the card | Read provider.organization, provider.url, the serving host, and any signatures you can verify. Compare provider with the operator behind the name you started from. |
Stop if the provider, the host, and the name you resolved do not name the same operator. If the card is signed, verify at least one signature with a key you trust. If it is unsigned, say so in the log and do not call the card signed. |
| Which interface you call | Read supportedInterfaces, including the protocolBinding and protocolVersion on the entry you will call. Use the first entry only when your policy allows it. Otherwise pick a later entry you implement, and send A2A‑Version set to that entry's protocolVersion. |
Stop if you are about to call a URL that is not on that list, or a version that entry does not advertise. If the interface does not support the version, the agent returns VersionNotSupportedError. Confirm the match before SendMessage. |
| Which skill is allowed | Read skills, securitySchemes, securityRequirements, and a later TASK_STATE_AUTH_REQUIRED if the task pauses for a credential. Match the task to one skills[].id, and read that skill's requirements separately from the card defaults. |
Stop if the skill you want is missing, or the scheme is broader than the action you intend. A pause for a credential is not proof the peer is the right peer. It only means the call needs a credential after you already chose the endpoint. |
| Why the call was allowed | Read the card URL, the interface you selected, the version you will send, the signature result, the skill id, the time, and the decision. Write that down before SendMessage. |
Stop if you cannot reconstruct why the task was allowed. |
Write the decision down before SendMessage. A compact log is enough:
{
"source": "buyer.agent",
"counterparty_name": "support.agent",
"agent_card": "https://support.agent/.well-known/agent-card.json",
"interface_url": "https://api.support.agent/a2a",
"protocol_binding": "HTTP+JSON",
"protocol_version": "1.0",
"a2a_version_header": "1.0",
"signature": "verified",
"skill_id": "quote_status",
"decision": "send"
}
Attach the A2A task id after the call returns. The log is a verification note. It is not an agent.json manifest. Worked manifest blocks live on agent.json examples.
Where a portable name fits
Start from a name you already trust, resolve the operator and the card URL, then run the four checks. Do not start from a URL pasted into a chat and hope the card matches. How agents find a name in the first place is agent discovery. Which protocol to pick when the job is a tool call instead of a peer task is MCP vs A2A.
If the collaboration is a purchase, stop and use the buyer checks in verify an agent before paying it. Paying is a different decision from sending a task.
FAQ
Does A2A 1.0 already prove who the agent is?
It gives you a card, optional JWS signatures, TLS, and security schemes. You still compare the provider and the interface URL with the operator you meant to reach. A valid TLS certificate proves the host, not the business relationship.
What if the card has no signatures?
Treat it as unsigned. Signatures are optional. Do not invent a pass.
Should I trust an extended card more than the public one?
Only after you authenticate the way the public card's security schemes describe, and only if capabilities.extendedAgentCard is true. The extended card can add skills. It does not replace the ownership check.
Is this the same check as an agent handshake?
No. A handshake is the protocol-neutral decision to proceed, stop, or ask a human. These four checks are what you read on an A2A 1.0 card before that decision.
Give the peer a name you can resolve, then verify the card before you send the task.