Skip to content

spec/adoption: the profile MUSTs an Agent Card declaration that nothing implements #92

Closed

Description

@imran-siddique

The gap

docs/spec/profile.md states a normative requirement:

An agent supporting this profile MUST declare the extension URI in its Agent Card, and a client opting in MUST signal it per the A2A extension mechanism (the A2A-Extensions header for HTTP and JSON-RPC bindings, or the equivalent binding metadata).

There is no Agent Card anywhere in the implementation. The only .well-known path we serve is our own:

$ grep -rn "agent-card\|AgentCard\|well-known" src/
src/cmcp_runtime/... (none)
src/ca2a_runtime/transport/server.py:  CHANNEL_PATH = "/.well-known/ca2a/channel"

A2A serves the card at /.well-known/agent-card.json. We neither publish one nor read one. So:

  • An adopter cannot comply with a MUST in our own spec using our own runtime.
  • The reference server is, by our spec's own wording, not a conforming cA2A agent.
  • Discovery does not work in the direction that matters: a client has no way to find out that a peer speaks the profile before it tries. Today the only way to discover cA2A support is to send a cA2A request and see whether it is understood.

docs/spec/transport.md also says the Signed Agent Card "remains the A2A identity anchor" and that cA2A "treats it as the anchor the delegation credential's subject and the peer's attestation measurement are checked against", with the check "not yet wired". That is a second, larger unimplemented claim resting on the same missing piece.

Scope

Minimum, to make the MUST satisfiable:

  • Publish an Agent Card from the reference server that declares the cA2A extension URI, at the path A2A specifies.
  • Read a peer's card on the client side and confirm it declares the extension before sending a cA2A request, rather than discovering support by failure.
  • Send the A2A-Extensions opt-in header on outbound requests, and decide what the server does when it is absent but cA2A metadata is present (today it just parses it).

Deliberately not in the minimum:

  • Verifying the card's signature and binding a delegation subject or an attestation measurement to it. That is the identity-anchor claim, it is a real design question, and conflating it with "publish a card" would stall the small fix behind the big one. It should get its own issue once the card exists.

What needs deciding

  • Whether the card is generated from PeerNode state or supplied as config. Generated means it cannot drift from what the node actually enforces; supplied means an operator controls what is published, which is what most deployments will want.
  • Whether a missing or non-declaring peer card is a refusal or a warning on the client side. Per the pattern used everywhere else in this stack it should be recorded and configurable, defaulting to permissive, because most peers today have no card at all.
  • Whether to weaken the spec's MUST to a SHOULD until the card is implemented, or leave the MUST and treat the implementation as the thing that is behind. Leaving a MUST that our own reference violates is the sort of thing a reviewer at graduation will find.

Acceptance

  • The reference server publishes a card declaring the extension URI, fetched and asserted in a test.
  • A client can decide whether a peer speaks cA2A from its card alone, without sending a task.
  • profile.md and transport.md say exactly which parts of the Agent Card story are implemented and which are not.

Related: #91 (SDK interop — the SDK has its own card handling, so the two should not invent different ones), #18 (profile stability with AAIF/Google), #47.

Activity

  1. imran-siddique commented on Aug 10, 2026

    @imran-siddique
    MemberAuthor

    Decisions taken 2026-08-10, so this starts from a settled shape.

    Advertise the requirement in the card. The SDK's AgentExtension carries uri, description, required and a params Struct, so the profile declaration has somewhere to state what the callee demands of callers:

    extensions: [{
      uri: "https://agentrust-io.com/extensions/ca2a/v0.1",
      required: false,
      params: { require_caller_attestation: "any" }
    }]

    That closes the discovery gap this issue names. Today the only way for a caller to learn a callee requires attestation is to send a request and be refused, which wastes a round trip on a path that already does a handshake.

    Generate the card from PeerNode state, not from config. Chosen over operator-supplied so the card cannot claim a requirement the node does not enforce. require_caller_attestation and caller_verifier are already on the node after #90, so the value is there to read.

    required: false. The profile is an overlay and a non-cA2A peer must be able to ignore it. Declaring the extension required would break plain-A2A callers, which is the opposite of what this is for.

    What #95 already gives this

    The SDK bridge landed, and two pieces of it are directly reusable:

    • a2a.extensions.common.find_extension_by_uri(card, uri) — the client-side check "does this peer speak cA2A", so discovery needs no new parsing.
    • ca2a_runtime.transport.a2a_sdk.opted_in(header_values) — the A2A-Extensions header half of the opt-in.

    AgentCard.capabilities.extensions is the field to populate.

    Still to decide when this is picked up

    • Whether a peer card that does not declare the extension is a client-side refusal or a recorded warning. Per the pattern used everywhere else in this stack it should be recorded and configurable, defaulting to permissive, since most peers today have no card at all.
    • Whether the reference server publishes at /.well-known/agent-card.json itself or leaves card serving to the SDK and only supplies the AgentExtension to merge in. The second is less code and avoids two card implementations.

    Explicitly still out of scope, and worth its own issue once the card exists: verifying the card's signature and binding a delegation subject or an attestation measurement to it. transport.md claims the card is "the anchor the delegation credential's subject and the peer's attestation measurement are checked against" with the check "not yet wired", and that is a real design question that should not stall publishing a card.

  2. imran-siddique commented on Aug 24, 2026

    @imran-siddique
    MemberAuthor

    Implemented on main

    The Agent Card declaration and discovery path landed in #130 and is on main at a921d15. Closing against the code rather than against the changelog entry.

    What exists now, in src/ca2a_runtime/transport/a2a_sdk.py:

    • agent_extension_for_node (line 113) derives the cA2A AgentExtension from a live PeerNode, carrying its require_caller_attestation value, with required=false.
    • merge_agent_card (line 131) contributes that declaration into a copy of an operator-owned card. It does not invent identity, interfaces, skills, a serving URL, or a signature, which was the right call: the card belongs to the operator, not to us.
    • inspect_agent_card (line 166) returns a recordable discovery result for a remote card. A missing or malformed declaration is a warning for deployment policy rather than an implicit trust decision.

    Verified locally on main today: tests/unit/test_a2a_sdk_bridge.py is 34 passed, and the full suite is 540 passed, 2 skipped. The tests use the real a2a-sdk protobuf models and assert that existing card fields and unrelated extensions survive the merge.

    So the specific gap this issue described is closed. An adopter can satisfy the profile's MUST using this runtime, and the reference server is no longer non-conforming by our own spec's wording.

    One thing this deliberately does not do, and it is worth stating so nobody reads more into the fix than is there: cA2A still does not serve a card at /.well-known/agent-card.json on the reference transport. That is correct, because the reference transport is explicitly not part of the profile and the card is the operator's to serve. Discovery in the other direction is covered by inspect_agent_card.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions