mentionable.dev

A2A Agent Card — Mentionable v0.1

Status: v0.1 (planned for Phase 4)

This document defines how a Mentionable host publishes a domain-level A2A agent card at /.well-known/agent-card.json so that A2A-aware tooling (Google ADK, Claude.ai connectors, third-party A2A clients) can discover and invoke the host without prior knowledge of any specific @agent@domain address.

Mentionable’s per-agent agent-card.md is the resolution target of @local@domain via WebFinger. This document defines the adjacent surface: a hub agent card at the standard A2A discovery path, with mention-based routing inside the host so multiple agents stay reachable from a single A2A endpoint.

A2A spec assumes one domain = one agent. Mentionable assumes one domain = many. The hub model bridges those without breaking either side.


1. The hub model

A2A client ─────────► GET https://<domain>/.well-known/agent-card.json

                       ▼  (single A2A AgentCard, hub-shaped)

A2A client ─────────► POST https://<domain>/a2a   (the hub endpoint)


                 ┌────────────┐
                 │ hub router │
                 └────────────┘

       ┌───────────────┼───────────────┐
       ▼               ▼               ▼
   text starts     text starts     no @ mention
   with @lean      with @gamebld   OR unknown handle
       │               │               │
       ▼               ▼               ▼
   lean@domain    gamebuilder@      defaultAgent
                  domain

The hub is one A2A agent card describing the host as a whole, with a single A2A endpoint. Inbound messages are dispatched by parsing the leading @<handle> mention from the message text:

Follow-up messages in an A2A conversation inherit contextId and route to the same agent that handled the first turn — no need to re-mention. (Same shape as Slack-reference’s follow-up handler from #154; the implementation is shared in spirit.)

1.1 Why mention routing

Most first-message scenarios already carry the address explicitly in the user-visible text:

“@lean — what’s the difference between Lean FIRE and Coast FIRE?”

A user typing this into a Claude.ai connector or an ADK chat panel writes the mention naturally. The router does the same parsing the rest of Mentionable already does (Pattern A in slack-connector, the shared parser in @mentionable/core). Without a mention, a default agent is the least-surprising fallback — an A2A client that just discovered the domain and asked “hello?” gets a coherent reply rather than an error.

1.2 Why a hub instead of one card per agent

A2A’s discovery contract pins /.well-known/agent-card.json (singular). An A2A client that GETs that path expects exactly one agent card. We can’t return an array there without breaking the contract. So we describe the host as a single hub agent that happens to dispatch internally.

Per-agent agent cards (one per registered Mentionable agent) stay reachable at /.well-known/agent-card/<local> — the canonical Mentionable resolution path that WebFinger already advertises (see agent-card.md and webfinger.md). The hub is additional discovery, not a replacement.


2. Payload

The card MUST conform to the A2A AgentCard shape. Mentionable-specific fields are added as JSON-LD-namespaced properties so A2A-vanilla parsers ignore them as unknown fields.

2.1 Single-agent host

When the host has exactly one registered agent, the hub IS that agent. The card inherits the agent’s name, description, and skills directly from its agent-card.md representation; the only material difference is the url (now the hub endpoint, not the per-agent endpoint) and the optional Mentionable extension.

{
  "@context": "https://a2a-protocol.org/2025-06-18",
  "name": "Lean FIRE Manager",
  "description": "Financial independence coach.",
  "url": "https://firemanager.info/a2a",
  "skills": [
    {
      "id": "chat",
      "name": "chat",
      "description": "Natural-language chat with an LLM-backed agent.",
      "input_modes": [{ "kind": "text", "mime": "text/plain" }],
      "output_modes": [{ "kind": "text", "mime": "text/plain" }]
    }
  ],
  "https://mentionable.dev/ns/v1#defaultAgent": "lean",
  "https://mentionable.dev/ns/v1#agents": [
    {
      "handle": "lean",
      "name": "Lean FIRE Manager",
      "card_url": "https://firemanager.info/.well-known/agent-card/lean"
    }
  ]
}

2.2 Multi-agent host

When the host has more than one registered agent, the hub describes itself as a router. name is the org’s hub identity; description MUST clearly state the routing convention (Mention @<handle> to address...). skills SHOULD inherit the default agent’s skills so an A2A client can drive skills[0] against the hub URL and get a sensible response without having to learn the routing convention up front.

{
  "@context": "https://a2a-protocol.org/2025-06-18",
  "name": "Verse8",
  "description": "Mentionable router. Mention @<handle> in messages to address a specific agent (assistant, gamebuilder). Without a mention, messages route to assistant.",
  "url": "https://verse8.io/a2a",
  "skills": [
    {
      "id": "chat",
      "name": "chat",
      "description": "Natural-language chat. Inherited from @assistant@verse8.io.",
      "input_modes": [{ "kind": "text", "mime": "text/plain" }],
      "output_modes": [{ "kind": "text", "mime": "text/plain" }]
    }
  ],
  "https://mentionable.dev/ns/v1#defaultAgent": "assistant",
  "https://mentionable.dev/ns/v1#agents": [
    {
      "handle": "assistant",
      "name": "Assistant",
      "card_url": "https://verse8.io/.well-known/agent-card/assistant"
    },
    {
      "handle": "gamebuilder",
      "name": "Gamebuilder",
      "card_url": "https://verse8.io/.well-known/agent-card/gamebuilder"
    }
  ]
}

2.3 Field reference

FieldRequiredNotes
@contextRECOMMENDEDA2A’s JSON-LD context. Tooling that doesn’t follow JSON-LD ignores it.
nameRequired by A2AOrg hub name (multi-agent) or default agent name (single).
descriptionRequired by A2AFor multi-agent hosts MUST describe the routing convention so a confused client + the user picking up the message can self-orient.
urlRequired by A2AThe hub A2A endpoint, e.g. https://<domain>/a2a. Per-agent endpoints stay at https://<domain>/a2a/<handle> and are discovered separately via WebFinger.
skillsRequired by A2AInherits the default agent’s skills. The hub itself has no skills of its own — it’s a router.
versionRecommendedSemVer of the hub agent card. Bump when the routing semantics or extension shape changes.
protocol_versionRecommended'0.1' for Mentionable v0.1 publishers.
https://mentionable.dev/ns/v1#defaultAgentRequiredHandle (slug, no @) of the agent that receives messages without a routable mention. MUST appear in mentionable:agents.
https://mentionable.dev/ns/v1#agentsRequiredArray of public agents on the host. Each item: handle (slug, required), name (display name, required), card_url (per-agent agent-card URL, recommended).
https://mentionable.dev/ns/v1#routerTypeOptional"logic" (v1 default — first-wins per §3.2) or "llm" (v0.2 reserved). Absent = "logic". See §3.6.
Other A2A fieldsAs per A2A specauth, capabilities, input_modes, output_modes, etc. Inherit defaults from the hub’s underlying agent. capabilities.extensions[] (per agent-card.md §1.2) advertises PolicyPart v0.1 and other spec extensions; the hub SHOULD union the underlying agents’ declared extensions.

3. Routing semantics

3.1 Mention parsing

The hub router parses the first @<handle> token from the inbound message’s text content. The grammar:

mention = "@" handle
handle  = [a-z0-9_-]{1,30}     ; lowercase only on the wire; handles in the
                               ; agents[] list MAY use mixed case but the
                               ; router lowercases before matching

Reuses the bare-mention parser from #178 / Pattern A — minus Slack-specific entity masking, which doesn’t apply to A2A messages. Implementations SHOULD share one parser between Slack-reference and the A2A router so behaviour stays consistent.

3.2 Match precedence

For each inbound message:

  1. Extract the first @<handle> from the leading text part.
  2. Lowercase the handle and look it up in the runtime’s registered agents.
  3. If hit → deliver to that agent.
  4. If miss OR no mention → fall through to §3.3.

The router scans for the first mention only. Subsequent @handle tokens in the same message text are NOT a routing signal — they are preserved verbatim in the message body so the chosen agent sees the full user intent and can decide what to do (acknowledge other agents, suggest a follow-up to them, attempt a relay; see §3.5).

v1 is deliberately “first wins”. Multi-agent fan-out as a hub-side responsibility is out of scope for v1 — see §3.5 for why and §8 for v0.2 directions.

3.3 Conversation continuity (mention-priority)

A2A’s contextId carries the conversation across turns. The router maintains a (contextId → agentHandle) mapping that the first turn populates. Mention presence on a follow-up overrides the sticky mapping:

Follow-up message contains…Routing decision
A routable @<handle>Route to that handle (and update the sticky mapping).
No routable mentionUse the sticky mapping (contextId → agentHandle).
Sticky mapping absent + no mentionRoute to defaultAgent.

This makes “first-message routing, follow-up inheritance, explicit re-mention to switch” the common path. The user’s mental model: address an agent the first time, follow up freely, re-address explicitly if you want to switch.

When the conversation switches agents mid-thread via re-mention, the new agent does NOT inherit the prior agent’s history — that history belongs to a different conversation by another party. The router treats the switch as a fresh first turn for the new agent. (Cross-agent context propagation is intentionally out of scope; see normalized-message.md for NormalizedMessage.history’s ownership model.)

Implementations MAY use a TTL on the sticky mapping; v1 RECOMMENDS the same 7-day idle timeout as ThreadStateStore.

3.4 Missing default

When defaultAgent is absent from mentionable:agents, the host is misconfigured. Routers MUST reject the publish at validation time (this is enforced by the validator in @mentionable/core).

When the defaultAgent field itself is missing from the card, A2A clients still see a valid hub card and can attempt to invoke skills[0] against url. The router MAY return a structured error (no_default_agent_configured) or fall back to the first agent in mentionable:agents. Implementations choose; this spec requires defaultAgent to be set so this case shouldn’t arise.

3.5 Multi-agent composition (when one message names two agents)

When a single message addresses more than one agent — e.g. @lean what would @gamebuilder say about this? — A2A’s request/response model gives one delivery and one response. Three patterns can compose multi-agent interactions on top:

(A) Caller-side fan-out. The A2A client splits the message and issues two message/send calls, one per agent. Each gets its own contextId. The client UI presents the two responses side-by-side. A2A spec aligns with this naturally — most general-purpose clients (a future Claude.ai connector with multi-agent intent, custom orchestration agents) SHOULD work this way. The hub doesn’t need to know.

(B) Hub-side fan-out (out of scope for v1). The hub fans out internally to the named agents, awaits both responses, and returns a single composite response on the original A2A request. Adds timeout + partial-failure surface to the hub. Reserved for v0.2 (see §8); the spec leaves room for this via the https://mentionable.dev/ns/v1#routerType field below.

(C) Agent-side relay (recommended for the reference implementation). The first-mentioned agent receives the full message text, sees the additional mentions, and decides what to do — acknowledge the others, suggest the user follow up with them, or itself open A2A connections to the named siblings and weave their replies into a single response. The hub’s mentionable:agents list is exactly the affordance an agent needs to discover its own siblings. A reference implementation (examples/llm-agent-vercel) ships with a system-prompt scaffold encouraging this behaviour, but the policy is the agent’s, not the hub’s.

For v1, the spec mandates only the hub’s “first wins” routing (§3.2). Patterns A and C are RECOMMENDED for clients and agents respectively; pattern B is a clearly-marked v0.2 extension point.

3.6 Optional routerType declaration (v0.2 hint)

The hub MAY declare its routing strategy via:

https://mentionable.dev/ns/v1#routerType : "logic" | "llm"

A2A-vanilla clients ignore the field. Mentionable-aware clients SHOULD treat absence as "logic".


4. Endpoint coexistence

Decision recorded for Phase 4: per-agent endpoints (/a2a/<handle>) stay live. The hub endpoint (/a2a) is additional, not a replacement.

PathAudienceStatus
https://<domain>/a2a/<handle>A2A clients that already have a specific addressExisting, unchanged
https://<domain>/a2a (the hub)A2A clients that just discovered the domainNew (this spec)
https://<domain>/.well-known/agent-card/<handle>Mentionable per-agent card resolutionExisting, unchanged
https://<domain>/.well-known/agent-card.jsonA2A standard discovery (the hub)New (this spec)
https://<domain>/.well-known/mentionable-agents.jsonMentionable-aware discovery (Layer 2)Existing (#188 / #205)

Operators who want to deprecate per-agent endpoints can do so at their own pace; nothing in this spec requires it.


5. Static hosting friendliness

The card at /.well-known/agent-card.json is single representation, JSON only — same constraint as mentionable-agents.json from agent-directory.md. An operator running the agent runtime elsewhere can serve a checked-in JSON file from S3, CloudFront, nginx try_files, or a static-site generator and still answer A2A discovery correctly.

The hub endpoint (/a2a) of course requires a function runtime — it’s where the router lives — but the discovery surface stays static-hostable. Mentionable is spec-first; the publish path remains accessible to deployments that split runtime from static hosting.


6. Mentionable extension shape

JSON-LD-namespaced fields:

https://mentionable.dev/ns/v1#defaultAgent : string             // handle slug
https://mentionable.dev/ns/v1#agents       : MentionableAgentRef[]

Where:

type MentionableAgentRef = {
  /** Local part of the @<handle>@<domain> address. Lowercase. */
  handle: string
  /** Display name. Per-agent, so a multi-agent host's hub can render a list. */
  name: string
  /** Optional per-agent Mentionable agent-card URL (the WebFinger target). */
  card_url?: string
  /** Optional one-paragraph description, mirrors per-agent card.description. */
  description?: string
}

A2A-vanilla parsers ignore the namespaced properties as unknown JSON-LD fields. JSON-LD-aware parsers reach the same fields via the term mentionable:agents if they pre-register the context, but most A2A tooling doesn’t, hence the fully-qualified URI shape.

The choice of JSON-LD namespacing (vs. an unprefixed extensions.mentionable object) follows the agent-directory.md precedent — Schema.org ContactPoint already accepts our mentionable: namespaced fields the same way.


7. Conformance

A publisher is conformant for v0.1 if:

  1. https://<domain>/.well-known/agent-card.json returns 200 with Content-Type: application/json.
  2. The body satisfies the A2A spec for an AgentCard (name, description, url, skills).
  3. https://mentionable.dev/ns/v1#defaultAgent is set and matches a handle in https://mentionable.dev/ns/v1#agents.
  4. Each mentionable:agents entry has handle and name.
  5. The url endpoint accepts inbound A2A messages and applies the routing rules in §3.

A consumer is conformant for v0.1 if:

  1. It treats the card as a valid A2A AgentCard regardless of whether it understands the mentionable: extension.
  2. It addresses follow-up messages to the same url and trusts the hub to route by contextId.
  3. (Mentionable-aware) it can list and address sibling agents via mentionable:agents without separate WebFinger lookups.

8. Non-goals for v0.1


9. References