Agent Card
Scope. Beach-specific extension to A2A's Agent Card schema. The base Agent Card is an A2A standard; the
x-beachextension and conventions documented here are Beach's. Non-Beach agents reading a Beach agent card will see the standard A2A fields and ignore the extension.
The self-description every Beach-based agent publishes so other agents (and tooling) can discover its capabilities, transports, and conventions.
Location
An agent publishes its card at a well-known URL:
https://<agent-host>/.well-known/agent-card.json
The URL follows RFC 8615 well-known conventions. This is also the discovery convention adopted by A2A.
Purpose
- Discovery. Another agent or tool fetches the card to learn what the agent does and how to reach it.
- Capability negotiation. A peer agent inspects the card's
capabilitiesto decide which of its own features to use when talking to this agent. - Documentation. Humans read the card to understand what an agent offers without reading its source.
- Registry population. Beach's agent registry (in
@cool-ai/beach-transport) fetches Agent Cards to populate its list of known peers.
Schema
{
"$schema": "https://a2a-protocol.org/schema/v1.0/agent-card.json",
"schemaVersion": "1.0",
"id": "example-travel-agent",
"name": "ExampleTravel",
"displayName": "ExampleTravel — Holiday Planner",
"description": "Plans holidays, searches flights, hotels, cruises, and assembles packages. Produces structured data with analysis and visual surfaces.",
"provider": {
"name": "Example Provider",
"url": "https://example.com/"
},
"version": "1.0.0",
"capabilities": [
{
"id": "flight-search",
"description": "Search scheduled and charter flights by origin, destination, dates, and party composition.",
"examples": ["find me flights to Corfu in August", "cheapest direct to Rome from Gatwick"],
"requiredScopes": []
},
{
"id": "hotel-search",
"description": "Search hotels by destination, dates, and party composition.",
"requiredScopes": []
},
{
"id": "cruise-search",
"description": "Search cruises by region, ports, cruise type, and dates.",
"requiredScopes": []
},
{
"id": "destination-research",
"description": "Produce narrative research on a destination: culture, logistics, activities, sample itineraries.",
"requiredScopes": []
},
{
"id": "package-assembly",
"description": "Combine flights, hotels, and activities into a single-price package with basket management.",
"requiredScopes": []
},
{
"id": "booking",
"description": "Commit a booking against a selected package. Requires approval.",
"requiredScopes": ["booking:write"]
}
],
"transports": [
{
"protocol": "a2a",
"version": "1.0",
"endpoint": "https://example.com/travel/a2a/messages",
"auth": { "type": "api-key", "header": "X-Beach-Key" },
"requiredScopes": []
},
{
"protocol": "mcp",
"version": "stable",
"endpoint": "https://example.com/travel/mcp",
"transport": "http",
"tool": "ask_example_travel",
"requiredScopes": []
}
],
"envelope": {
"parts": [
"domain-data",
"llm-context",
"a2ui-surface",
"response",
"artifact",
"progress",
"approval-request",
"approval-response"
],
"consumes": ["domain-data", "a2ui-surface"],
"a2uiCatalog": "https://a2ui.org/specification/v0_9/basic_catalog.json",
"llmContextLanguage": "en"
},
"extensions": {
"beach": {
"version": "1.0",
"respondToolSchemaVersion": "1",
"turnStates": ["awaiting", "complete", "clarifying", "error", "suspended", "delegated", "passed"]
}
},
"contact": {
"email": "support@example.com"
},
"generatedAt": "2026-04-20T09:50:00Z"
}
(The example uses generic example.com placeholders — real agents populate provider, endpoint, and contact with their own values.)
Field reference
Required
| Field | Type | Purpose |
|---|---|---|
schemaVersion |
string | Version of the Agent Card schema the document follows. |
id |
string | Stable identifier for the agent, unique per provider. |
name |
string | Short canonical name. |
description |
string | One-sentence summary of what the agent does. |
version |
string (semver) | Version of this specific agent (not the protocol). |
capabilities |
array | What the agent can do — see below. |
transports |
array | How to reach the agent — see below. |
Optional
| Field | Type | Purpose |
|---|---|---|
displayName |
string | Human-friendly name including product branding. |
provider |
object | Who publishes the agent (name, URL). |
envelope |
object | Declares the envelope parts the agent produces and consumes. |
extensions |
object | Namespaced extension blocks. beach advertises Beach-specific features. |
contact |
object | Support contact information. |
generatedAt |
ISO datetime | When the card was generated (for cache invalidation). |
Capability entry
{
"id": "flight-search",
"description": "Search flights by origin, destination, dates.",
"examples": ["find me flights to Corfu in August"],
"inputSchema": { "$ref": "https://example.com/travel/schemas/flight-search-input.json" },
"outputSchema": { "$ref": "https://example.com/travel/schemas/flight-search-output.json" }
}
inputSchema and outputSchema are optional JSON Schema references. When present, peer agents can validate their requests and responses against the contract.
Transport entry
{
"protocol": "a2a",
"version": "1.0",
"endpoint": "https://agent.example.com/a2a/messages",
"auth": { "type": "api-key", "header": "X-Beach-Key" }
}
The protocol value is validated against @cool-ai/beach-core's TransportRegistry. Canonical core set (registered by @cool-ai/beach-transport):
a2a— A2A HTTP. Endpoint is the URL accepting POSTed A2A Messages.mcp— MCP. Endpoint is either astdio://command URI (for local subprocess access) or a StreamableHTTP URL. Atoolfield names the MCP tool that peers call to converse with the agent (e.g.ask_example_travel).sse,smtp,whatsapp,webhook,cron— primarily inbound channel transports; unusual in an Agent Card's peer-facing list but valid for agents that accept peer contact via these channels.
Consumers register additional protocols (grpc, websocket, etc.) via transports.register(...). An Agent Card's transports[].protocol entry is valid if it resolves against the registry.
The auth.type value is validated against @cool-ai/beach-core's AuthTypeRegistry. Canonical core set:
none— no auth (local dev, trusted LAN)api-key— API key in a named headeroauth2— OAuth 2.0 (scope and token endpoint in the auth object)mtls— mutual TLS (cert fingerprints in the auth object)
Consumers register additional auth types (e.g. hmac-signed, aws-sigv4) via authTypes.register(...). See design principle 3.2 (declarative configuration, open registries).
Envelope block
{
"parts": ["domain-data", "llm-context", "a2ui-surface", "response", "artifact", "progress", "approval-request", "approval-response"],
"consumes": ["domain-data", "a2ui-surface"],
"a2uiCatalog": "https://a2ui.org/specification/v0_9/basic_catalog.json",
"llmContextLanguage": "en"
}
parts declares what the agent produces. consumes declares what the agent understands when receiving envelopes from peers. Both are open sets — any registered partType can appear, including consumer-defined types (see below).
consumes matters for the llm-context optimisation: if a peer does not list llm-context in its consumes, the producing agent skips the Haiku-translator step for that peer and saves the cost.
The same suppression applies to any part type registered with requiresPeerConsumes: true. Consumer-defined part types (e.g. ta.itinerary-slot-state) use this flag so they are only delivered to peers that have declared the renderer. Peers without the renderer receive domain-data and llm-context only.
Consumer-defined types use a namespaced identifier (consumer-slug.type-name) to avoid registry collisions. Both the producing agent's parts array and any consuming peer's consumes array carry the namespaced identifier verbatim:
"envelope": {
"parts": ["domain-data", "llm-context", "a2ui-surface", "ta.itinerary-slot-state"],
"consumes": ["domain-data", "a2ui-surface", "ta.itinerary-slot-state"]
}
See ../../packages/protocol/README.md#consumer-defined-part-types for the full registration pattern.
a2uiCatalog is the URL of the catalog that surfaces reference; consumers use it to validate that the catalog matches what they can render. llmContextLanguage advertises the language of llm-context parts so peers can translate if needed.
Extensions — beach
The extensions.beach block advertises Beach-specific features:
{
"version": "1.0",
"respondToolSchemaVersion": "1",
"turnStates": ["awaiting", "complete", "clarifying", "error", "suspended", "delegated", "passed"]
}
version— the Beach protocol version the agent implements.respondToolSchemaVersion— the version of therespond()tool schema the agent uses internally. Peers that inspect tool-use traces (for audit or MCP-level integration) need this.turnStates— the set ofturnStatevalues the agent emits. The canonical set includessuspended(HITL),delegated(peer call in flight), andpassed(multi-actor handoff); agents that do not use HITL or multi-actor patterns need not advertise those. Future Beach versions may add states; consumers check this to handle them gracefully.
Publishing
@cool-ai/beach-protocol exports an Agent Card builder:
// Conceptual; types land in @cool-ai/beach-protocol once implementation begins.
const card = buildAgentCard({
id: "example-travel-agent",
name: "ExampleTravel",
// ...
});
@cool-ai/beach-transport ships a well-known route handler that serves the card:
// Conceptual.
app.use(wellKnownAgentCard(card));
// Serves GET /.well-known/agent-card.json
Discovering a peer
@cool-ai/beach-transport's agent registry fetches and caches peer Agent Cards:
// Conceptual.
const registry = new AgentRegistry({
peers: [
{ url: "https://travel.example.com/.well-known/agent-card.json" },
{ url: "https://organiser.example.com/.well-known/agent-card.json" }
]
});
await registry.refresh();
const travel = registry.byId("example-travel-agent");
// travel.card contains the full Agent Card
// travel.transports lets you call the agent via A2A or MCP
Versioning
- The Agent Card schema is versioned via
schemaVersion. Breaking schema changes bump the major version. Beach-based consumers should validateschemaVersionbefore parsing. - The agent is versioned via
version. Agents are independently semver'd. Consumers treat a minor bump as backward-compatible capability addition; a major bump as a potential breaking change in capability behaviour. - The Beach extension is versioned via
extensions.beach.version. Allows Beach-specific features to evolve independently of the Agent Card schema.
Caching
- The card is typically small (< 10KB) and fetched infrequently.
- Clients should respect HTTP cache headers and re-fetch at a reasonable interval (default: 1 hour).
- The
generatedAtfield allows cheap invalidation: a consumer cached bygeneratedAtneed not re-parse the card when the field is unchanged.
Related
- respond-tool.md — turn states advertised in
extensions.beach.turnStates. - envelope.md — parts declared in the
envelopeblock. - ../../packages/transport/ — the agent registry.
- ../design-principles.md — principle 3.2 ("declarative configuration") and principle 4.1 ("agents are peers").