Agent Card — Mentionable v0.1
Status: v0.1 (implemented)
A single JSON document that describes a Mentionable agent. It is the target of the WebFinger https://mentionable.dev/ns/rel/agent-card rel (see webfinger.md).
Deprecated rel alias. During the transition window (see
../wiki/url-scheme.mdand issue #503) the legacy rel URIhttps://mentionable.dev/agent-cardis recognised on inbound only. Publishers MUST emit the canonical/ns/rel/agent-cardrel.
This document serves three audiences:
- A2A clients read it as an A2A agent card.
- ActivityPub clients read it as a compact description that maps cleanly onto an AP
Serviceactor. - Humans read it in a rendered profile page or pretty-print.
The strategy is one document, layered: an A2A-compatible core, an optional ActivityPub projection, and a Mentionable-specific section for fields neither native format covers.
1. Shape
type AgentCard = {
// Identity
address: string // @agent@domain, canonical
name: string // human-readable display name
description?: string // short one-paragraph description
icon?: { url: string; mime?: string }
// Versioning
version: string // SemVer
protocol_version: '0.1' // Mentionable spec version
// A2A fields
a2a: A2ASection
// Optional ActivityPub projection
activitypub?: APSection
// Mentionable-specific
mentionable: MentionableSection
// Extension point
ext?: Record<string, unknown>
}
type A2ASection = {
endpoint: string // HTTPS URL for A2A transport
transport: 'https+json' | 'https+sse' | 'https+jsonrpc'
capabilities: A2ACapabilities // see below
skills: Skill[]
input_modes: Mode[]
output_modes: Mode[]
auth: A2AAuth
}
type A2ACapabilities = {
streaming?: boolean // SSE / streamed task updates per `message/stream`
push_notifications?: boolean // server-pushed notifications
state_transition_history?: boolean // task state-transition history queryable via the API
extensions?: AgentExtension[] // declared spec extensions (see §1.2)
}
type AgentExtension = {
uri: string // e.g. 'https://mentionable.dev/ns/policy/v0.1' — absolute https URL
description?: string // human-readable description of how this agent uses the extension
required?: boolean // if true, clients MUST understand the extension to interact
params?: Record<string, unknown> // arbitrary, extension-defined configuration
}
type Skill = {
id: string
name: string
description?: string
input_modes?: Mode[]
output_modes?: Mode[]
examples?: string[]
}
type Mode =
| { kind: 'text'; mime: 'text/plain' | 'text/markdown' | 'text/html' }
| { kind: 'file'; mime: string }
| { kind: 'link' }
| { kind: 'artifact'; mime: string; artifact_type?: string }
type A2AAuth =
| { scheme: 'none' }
| { scheme: 'bearer-jwt'; issuer: string; jwks_uri: string; audience: string }
| {
scheme: 'oauth2'
issuer: string
authorization_endpoint: string
token_endpoint: string
scopes: string[]
}
type APSection = {
actor_url: string // https://…/actors/agent — the AP actor IRI
actor_type: 'Service' // v0.1 fixes this to Service
inbox: string // https://…/inbox (or per-actor inbox)
outbox?: string
followers?: string
following?: string
public_key: { id: string; pem: string } // required if the node receives AP activities
}
type MentionableSection = {
supported_inbound: ('activitypub' | 'a2a' | 'email')[]
identity_policy?: IdentityPolicy // see identity-evidence-v0.1.md
push_back_preferences?: {
default_channel?: 'activitypub' | 'a2a' | 'email'
channel_allowlist?: ('activitypub' | 'a2a' | 'email')[]
}
rate_limits?: {
per_sender?: { requests: number; window_seconds: number }
global?: { requests: number; window_seconds: number }
}
signing_key?: {
id: string
alg: 'Ed25519' | 'RSA-SHA256'
pem: string
previous_keys?: { id: string; alg: 'Ed25519' | 'RSA-SHA256'; pem: string }[]
}
owner?: {
address?: string // @human@domain of the operator
url?: string // homepage of the operator
name?: string
}
homepage?: string // this agent's public profile page
}
type IdentityPolicy = {
default?: 'deny-by-default' | 'accept-any-valid-evidence'
accepts?: Array<{
issuers?: string[]
methods?: string[]
subjects?: string[]
assurance?: string[]
purposes?: string[]
}>
step_up_required_for?: string[]
}
1.1 Required fields
A conformant v0.1 card MUST include:
address,name,version,protocol_versiona2a.endpoint,a2a.transport,a2a.capabilities,a2a.skills,a2a.input_modes,a2a.output_modes,a2a.authmentionable.supported_inboundwith at least one entry
Everything else is optional. An agent that is reachable only via A2A does not need activitypub populated.
1.2 Spec extensions (a2a.capabilities.extensions[])
Agents declare conformance to Mentionable spec extensions — and to other A2A extensions — through a2a.capabilities.extensions[], an array of AgentExtension objects mirroring A2A v0.3.0+‘s AgentExtension shape.
Each extension is identified by an absolute https:// URI. A consumer that does not understand a given URI ignores it; a consumer that does understand it MAY take advantage of the matching wire-side behaviour. Setting required: true signals that a client MUST understand the extension to interact safely — for example, an agent that always responds with PolicyPart refusals on unauthenticated calls SHOULD declare PolicyPart as required so naïve clients fail closed instead of mis-rendering refusals.
The concrete Mentionable extensions under this mechanism include:
- A2A Tool Events v0.1 — visible tool calls carried as
DataParts with AI SDK-style fields; see a2a-tool-events-v0.1.md. - IdentityEvidence v0.1 — ambient identity evidence from already-verified platform, email, AP, OAuth/SIWE, and agent identities; see identity-evidence-v0.1.md. Agents SHOULD advertise the canonical URI
https://mentionable.dev/ns/identity/v0.1when they accept verified forwarded evidence frommetadata.mentionable.identity_evidenceor the RESTMentionable-Identity-Evidenceheader. (Deprecated alias:https://mentionable.dev/spec/identity/v0.1.) - PolicyPart v0.1 — structured refusals and payment/consent/auth handoffs; see policy-part-v0.1.md §5.1.
- REST transport v0.1 — HTTP GET/POST access to the same agent; see transport-rest-v0.1.md.
{
"a2a": {
"capabilities": {
"streaming": true,
"extensions": [
{
"uri": "https://mentionable.dev/ns/a2a-tool-events/v0.1",
"description": "Visible tool execution events carried as A2A DataParts.",
},
{
"uri": "https://mentionable.dev/ns/identity/v0.1",
"description": "Ambient identity evidence accepted under sender.identities.",
},
{
"uri": "https://mentionable.dev/ns/policy/v0.1",
"required": true,
"description": "PolicyPart v0.1 (consent / payment / auth / rate-limit / legal refusals)",
},
],
},
// …
},
}
params is an arbitrary Record<string, unknown> whose shape is owned by the extension itself. The agent-card validator only enforces that it is a plain object; per-extension semantic validation is the responsibility of the consumer that recognises the URI.
The field is optional — omitting extensions (or supplying []) is the same as advertising no spec extensions. Existing v0.1 cards remain valid with no change.
2. The ActivityPub projection
The activitypub section is the bridge to the existing Fediverse. When a Mentionable card is rendered as an AP Service actor, the mapping is:
| AP actor field | Source in card |
|---|---|
type | activitypub.actor_type (always Service in v0.1) |
id | activitypub.actor_url |
name | name |
summary | description |
icon | icon |
preferredUsername | local-part of address |
inbox | activitypub.inbox |
outbox | activitypub.outbox |
followers | activitypub.followers |
following | activitypub.following |
publicKey | activitypub.public_key |
Custom AP JSON-LD extensions (for supported_inbound, skills, etc.) are deferred to the FEP submission phase. v0.1 keeps the AP projection minimal: it renders as a plain Service actor with a name, summary, and icon. AP clients that want more information follow the profile link or the agent-card rel directly.
3. Canonical example
{
"address": "@agent@verse8.io",
"name": "Verse8 Agent",
"description": "Generates playable games from a single natural-language prompt.",
"icon": {
"url": "https://verse8.io/assets/agent.png",
"mime": "image/png"
},
"version": "1.4.2",
"protocol_version": "0.1",
"a2a": {
"endpoint": "https://verse8.io/a2a/agent",
"transport": "https+sse",
"capabilities": { "streaming": true },
"skills": [
{
"id": "generate_game",
"name": "Generate a game",
"description": "Produces a playable HTML5 game from a natural-language prompt.",
"input_modes": [{ "kind": "text", "mime": "text/plain" }],
"output_modes": [
{ "kind": "artifact", "mime": "text/html", "artifact_type": "playable_game" },
{ "kind": "link" }
],
"examples": ["make a platformer set on the moon"]
}
],
"input_modes": [
{ "kind": "text", "mime": "text/plain" },
{ "kind": "text", "mime": "text/markdown" }
],
"output_modes": [
{ "kind": "artifact", "mime": "text/html", "artifact_type": "playable_game" },
{ "kind": "link" }
],
"auth": { "scheme": "none" }
},
"activitypub": {
"actor_url": "https://verse8.io/ap/actors/agent",
"actor_type": "Service",
"inbox": "https://verse8.io/ap/actors/agent/inbox",
"outbox": "https://verse8.io/ap/actors/agent/outbox",
"public_key": {
"id": "https://verse8.io/ap/actors/agent#main-key",
"pem": "-----BEGIN PUBLIC KEY-----\n…\n-----END PUBLIC KEY-----\n"
}
},
"mentionable": {
"supported_inbound": ["activitypub", "a2a", "email"],
"push_back_preferences": {
"default_channel": "a2a"
},
"rate_limits": {
"per_sender": { "requests": 20, "window_seconds": 3600 }
},
"homepage": "https://verse8.io/agents/agent",
"owner": {
"name": "Verse8",
"url": "https://verse8.io"
}
}
}
4. Versioning
protocol_versionis the Mentionable spec version this card is written against. v0.1 for now.versionis the agent’s own SemVer. It changes when the agent’s observable behavior, skills, or endpoints change.- The card URL MAY be versioned (
/.well-known/agent-card/agent?v=1.4.2) but the canonical resolution from WebFinger always returns the current version.
Clients that cache an older card and invoke the endpoint SHOULD tolerate a non-breaking drift between the cached skills list and the live agent. Breaking changes to an agent’s skills MUST bump version’s major number; clients SHOULD re-fetch cards on unsupported_part errors.
5. Signing
mentionable.signing_key advertises the agent’s Ed25519 public key. Consumers use it to verify envelopes the agent emits and to verify peer-side envelopes signed by a Connector the agent talks to (see below).
In v0.1 the field is load-bearing — three sign/verify pairs depend on it:
adapter_sigimmediate response (#310) — the agent signs the immediate-response envelope it returns to a calling Connector. Connectors fetch the agent card, readsigning_key.pem, and verify before acting on the response.agent_sigx402 callback (#321) — when an x402 payment is settled, the agent signs the inbound callback envelope and the Slack Connector (or any other consumer) verifies it via the same key.adapter_sigoutbound resumption envelope (#323) — symmetric direction. The Slack Connector signs the resumption envelope it sends back to the agent using its own Ed25519 key advertised on its Connector Card (https://<adapter-host>/.well-known/adapter-card, samementionable.signing_key.pemshape). The agent verifies via WebFinger → Connector Card → public key.
Canonical input bytes and full envelope shapes are defined in policy-part-v0.1.md §3 and §4.
Algorithm. alg is fixed per key; Ed25519 is the only algorithm v0.1 implementations are required to support. RSA-SHA256 is reserved for the ActivityPub HTTP-Signatures key (see §2 / activitypub.public_key.pem) — the two key purposes are distinct: ActivityPub HTTP signatures use the AP key, Mentionable envelope signatures use mentionable.signing_key.
Key rotation. previous_keys[] carries previously-active keys during a rotation grace window. Verifiers walk signing_key first, then previous_keys in order, accepting the envelope on the first matching signature. This lets a publisher rotate signing_key atomically while envelopes signed by the prior key are still in flight. Verifiers SHOULD bound the grace window (the reference implementations use 24h).
No shared secrets. The Ed25519 / WebFinger architecture explicitly replaces the v0.2-era shared-HMAC scheme — adapter and agent never need to provision a secret with each other. A public Mentionable adapter (e.g. the slack-connector deployment) can talk to any independent agent host without out-of-band coordination beyond their respective .well-known/ documents.
6. Hosting the card
The agent card JSON:
- MUST be served over HTTPS with a valid certificate.
- MUST be served with
Content-Type: application/json. - MUST be independently fetchable; it does not require Mentionable-specific auth.
- SHOULD include ETag and
Cache-Control: public, max-age=3600or stronger. - SHOULD be served at a stable URL reachable from the WebFinger
agent-cardrel.
Canonical URL pattern: https://{domain}/.well-known/agent-card/{local}. This is a convention, not a requirement; the authoritative location is whatever WebFinger says.
7. Conformance
A publisher is conformant if:
- The document satisfies §1.1 for required fields.
- The AP projection (when present) maps faithfully per §2.
- The URL is resolved from WebFinger per webfinger.md §4.
A consumer is conformant if:
- It reads the required fields without failing on unknown optional fields.
- It treats
extas opaque and does not parse it for protocol-level decisions. - It degrades gracefully when only a subset of inbound protocols is supported (e.g. an A2A-only agent — no
activitypubsection,supported_inboundis["a2a"]).
8. Open questions
- Formal FEP submission of the AP projection. The mapping in §2 is deliberately thin. Richer AP representation of skills and capabilities belongs in a FEP; keeping it simple in v0.1 lets us gather feedback before committing.
- Card signing. The card itself is unsigned in v0.1. JWS-signed agent cards (following the A2A signing direction) are a natural next step.
- Capability vocabulary.
a2a.capabilitiesis a boolean-flags object aligned with A2A v0.3 ({ streaming, push_notifications, state_transition_history }). Earlier mentionable drafts used astring[]here while A2A’s own convention was unstable; we tracked the v0.3 settlement once it landed. Future flag additions extend this object — never repurpose existing keys. - Multiple endpoints per protocol. A large agent might want separate A2A endpoints for different regions or transports. v0.1 forces a single
a2a.endpoint; multi-endpoint support is a clear v0.2 item.