An agent card is the only artifact in an A2A deployment that a complete stranger is guaranteed to be able to read before trusting anything else about you. It is fetched cold over HTTPS by a client that has never spoken to your agent, and everything downstream - which skill is invoked, with what payload, under which credential, against which endpoint - derives from what that one document said at the moment it was read. This article treats the card as architecture rather than as a schema: why the indirection exists, where the document is served from, how it is cached and invalidated, what a signature buys, and what breaks when it is wrong. For the field-by-field reference, see .

Why an agent publishes a card at all

The alternative to a card is out-of-band integration, and at small scale it works. A human reads your documentation, hardcodes the endpoint and the skill name, ships. That survives three integrations. It does not survive three hundred, and it does not survive a caller that is itself a model choosing at runtime among forty agents.

The card moves that knowledge from integration time to runtime. It is a late-binding mechanism: endpoint, skill names, argument shapes and credential requirements are resolved on the fly from a document at a predictable address, not from a wiki page transcribed into a constant six months ago. Three properties make it work: self-describing at a stable location, so no prior relationship is needed; machine-readable, so a router can choose without a human in the loop; versioned, so change is an observable event rather than a surprise.

The indirection is not free. You have added a network dependency to the critical path of every cold start, and a class of bug in which the implementation is correct and the advertisement is not. You are trading a human coordination problem for a distributed consistency problem, and the rest of this article is about that trade.

Where the card lives, and who is allowed to see it

By convention the card is served from a well-known path at the agent's origin - historically /.well-known/agent.json, more recently /.well-known/agent-card.json. Clients supporting both should try the newer path first and cache the negative result, so no session pays two round trips. covers the resolution rules.

The architectural consequence is that a well-known path is origin-scoped: one host, one agent. A fleet behind a single hostname needs path-scoped cards or a registry that maps an agent identifier to a card URL. What does not work is one card carrying the union of every agent's skills - you then cannot version, sign, or deprecate any of them independently.

Visibility is the other axis. The public card is what anyone may know, served without credentials. Many deployments also serve an extended card behind auth, listing privileged skills only entitled callers should be aware of; the public card advertises the auth scheme and points at it. Two rules follow: the public card must be fetchable unauthenticated, with permissive CORS if browser-resident clients exist, and it should be served from the same origin as the endpoint it advertises. A card on origin A whose endpoint points at origin B has the exact shape of a redirect-to-attacker.

Advertisement

The four layers of a card, and their different change rates

Read the diagram below as four layers rather than a flat bag of keys, because the layers change at different speeds and that mismatch causes most card operations pain.

Identity. Who the agent is, who runs it, where to complain, which terms apply. Changes almost never - a rebrand or a transfer of ownership.

Capability. What it can do, at two granularities: coarse protocol capabilities (streaming, push notifications) and fine business skills with descriptions, tags, and input and output schemas. Changes on roughly every feature release.

Contract. How to reach it - base URL, transport, auth scheme and required scopes, implementation version. Changes on migrations: rare, high blast radius.

Trust. The signature and the material a verifier needs to check it. Changes on key rotation, on a schedule unrelated to the product.

Because all four ship as one document under one ETag, a routine key rotation invalidates every client's cached skill list. That is tolerable when revalidation is cheap and intolerable when clients treat any card change as a reason to re-approve a counterparty. If card changes require human approval, diff the layers, not the bytes.

This section stops at the shape of the document. Key names, which fields are required, and the nested structure of a skill entry belong to .

Agent Ownerpublishes cardWell-Known URL/.well-known/agent.jsonDiscoveryclients fetchCard Contentsname, desc, capabilitiesAuth RequirementsOAuth scopes, mTLSSkills / Toolstyped schemasEndpointsservice address + transportVersioningsemver + compatSigningprovider signs cardRegistrypublic / private catalogCards are how one agent discovers what another can do
A2A agent card architecture: owner publishes at well-known URL, card lists capabilities/skills/endpoints/auth/versioning, signed by provider, registered in catalog.

From card to call - how a client turns an advertisement into an invocation

A client arrives with an intent and a card and must produce a request. Think of it as a three-stage funnel, because each stage fails differently.

Filter is the cheap, deterministic pass over hard constraints: is the transport one I speak, is the version inside my accepted range, can I obtain the scopes it demands, does it stream if my caller needs incremental output. Failures here are disqualified before a model is involved; this stage should never be probabilistic.

Rank is the semantic pass: which surviving skill matches a free-form intent. Here the card's prose is load-bearing. A skill description is prompt input for whatever router reads it, not marketing copy. State what the skill does, what inputs it expects, and what it does not do - negative scope ("text only; does not translate scanned images") eliminates more misroutes than another sentence of positive scope adds. Tags are the cheap prefilter that keeps a model off the hot path when the match is obvious. Scoring, thresholds and tie-breaking belong to skill matching; the card's job is to supply signal good enough to work on.

Bind is validating arguments against the advertised input schema locally, before anything crosses the network. Skipping it turns a client-side type error into a remote task that is accepted, queued, dispatched and only then failed - a round trip and an error path on someone else's system, for a mistake a local check catches in microseconds.

Caching a card, and the invalidation problem underneath it

Fetching the card per request is not viable: it adds a round trip to every interaction and makes the card host a hard availability dependency - if the document is unreachable, every new session fails even though the agent endpoint is healthy. So clients cache, and the moment they do you own an invalidation problem.

Use the HTTP layer rather than inventing a mechanism. Cache-Control: max-age states an intended propagation delay, not a performance knob; ETag with If-None-Match makes revalidation nearly free; stale-while-revalidate turns a slow card host into a background refresh instead of a failed session.

Cache-Control: public, max-age=600, stale-while-revalidate=3600
ETag: "card-2.4.1-9f3c1a"

The TTL tradeoff has real numbers at both ends. Sixty seconds means the card host serves traffic proportional to the session rate of every client in the ecosystem. Twenty-four hours means a day of skew after a deploy. Five to fifteen minutes with conditional revalidation is the usual settling point, and only safe alongside a publishing discipline: within a minor version a card may gain skills, never lose them. Additive-only change makes a stale reader out of date rather than wrong.

The mechanism that actually saves you is event-driven. Any response meaning "unknown skill", "version gone", or "scope no longer accepted" should trigger exactly one forced refetch and one retry, demoting the TTL from a correctness mechanism to a performance one. Single-flight the refetch per origin so a thousand concurrent sessions do not stampede the card host.

Advertisement

Trust - what a signature on a card does and does not prove

Signing a card - typically a detached JWS over a canonicalised serialisation - proves two narrow things: the document came from the holder of a particular key, and it has not been modified since, including by a registry or proxy re-serving a cached copy. The second property matters most once cards are mirrored in catalogs.

It proves nothing else. A signature does not say the agent behaves as described, that its skills are safe to invoke, or that the key belongs to the brand whose name appears in the name field. Signature is provenance; competence is a reputation problem and authorisation is an auth problem, and conflating either with a valid signature is how a verified card ends up authorising something nobody intended.

Key discovery is the hard part, and every option is a trust anchor in disguise. A JWKS on the same origin as the card is circular - whoever controls the origin controls both - so it protects only against tampering in transit. DNS-based discovery moves the anchor to your registrar; a vouching registry moves it to the registry operator. Internal deployments bottom out in an organisational CA; cross-organisation ones usually bottom out in trust-on-first-use with pinning: record origin and key fingerprint at onboarding, alert on change, require human approval. That catches the realistic attack - a hijacked subdomain re-serving a card whose endpoint points somewhere new.

One rule admits no exceptions: a client that falls back to accepting an unsigned card when verification fails has no security property, only a slower failure path. Fail closed, or do not sign. See trust models for how this composes across organisations.

End-to-end discovery flow

Trace one discovery from cold. Agent A needs a translation capability and holds no prior knowledge of a counterparty. It queries its registry with the intent "translation"; the registry returns candidates including Agent B at agentb.example.com.

Agent A fetches Agent B's well-known card URL. The registry's cached copy is a hint; the origin is authoritative. The response carries an ETag and a ten-minute max-age, both stored. Agent A verifies the detached signature against the key fingerprint pinned for that origin at onboarding. The relevant contents:

name:     TranslateAgent
version:  2.4.1
skills:   translate(text, target_lang) -> translated
          detect_language(text)        -> lang
auth:     OAuth 2.0, scope "translate:invoke"
url:      https://agentb.example.com/a2a

Filter passes: 2.4.1 satisfies the accepted range ^2.4, the transport is supported, the required scope is obtainable. Rank selects translate over detect_language. Bind validates the two string arguments against the advertised schema. Only then does Agent A open a session, complete the auth handshake with a scoped token, and invoke the skill.

Ten days later Agent B ships 3.0.0 with a breaking change to the output shape. Nothing notifies Agent A: its cached card expires, revalidation returns a new body rather than a 304, and the version no longer satisfies ^2.4. What happens next must be a policy decided in advance - continue against a pinned 2.4.x endpoint maintained through a published grace period, or stop routing to that skill and surface a capability gap. The failure mode to design against is the third option: noticing nothing and parsing a response shape that changed underneath.

When the card is wrong - failure modes and their signatures

Card defects are silent on the publisher's side. Every symptom appears in somebody else's logs, which is why they surface as vague integration complaints days after the deploy that caused them.

SymptomRoot causeMitigation
Callers get "unknown skill" for a skill the card listsCard advertises what the running build no longer registersGenerate the card from the handler registry; fail CI on divergence
Calls succeed but produce wrong results for a class of inputDescription drifted from behaviour; no schema change caught itAttach golden examples to each skill and test them against the live agent
Auth fails for new clients only; existing sessions fineScope renamed at the identity provider, card still advertises the old stringTreat scope strings as public contract - deprecate, never rename in place
Traffic keeps arriving at a decommissioned endpointLong TTL plus a registry copy nobody refreshedShorten TTL before a migration; serve 410 pointing at the new card
Intermittent misrouting across a client fleetDeploy skew - replicas serve different cardsServe one versioned artifact; alert on card hash divergence

The instrumentation that makes these tractable belongs to the publisher: log every card fetch with the caller identity and the If-None-Match presented. That tells you how many distinct clients hold which version and how long a change takes to propagate - the number you need before you can safely remove anything.

Operational lifecycle - publish a card like an API contract

Hand-written cards drift within a sprint. The card should be a projection of the handler registry, emitted by the same build that produces the agent, so adding a skill and advertising it are one action. Then gate it: fail the build when a registered handler has no card entry or a card entry has no handler, and when a skill identifier disappears without a major version bump.

Ordering around a deploy is asymmetric, and reversing it is the most common self-inflicted outage here. Adding a skill: deploy the code first, publish the card second - a client that learns about a skill before the handler exists will call it and fail. Removing a skill: drop it from the card first, wait one full max-age plus margin for stale-while-revalidate, then delete the code. The card leads on removal and trails on addition.

Deprecation deserves API treatment: mark the skill deprecated while continuing to serve it, and use the fetch telemetry above to watch usage decay to zero rather than guessing a window. And keep environments honest - a staging card advertising a production endpoint is the most common cross-environment leak here, because url is the field people copy-paste.

Treat the card as a published contract with a cache in front of it, not as a config file. Its value is late binding - a stranger can discover, filter, rank and bind against you with no prior relationship. Its cost is consistency: every client holds a copy minutes or hours old, and a card that disagrees with the running code fails silently in someone else's logs. Generate it from the same source as your handlers, keep change additive within a version, order removals card-first and additions code-last, and remember a signature proves provenance - never competence, never authorisation.