Agent Card

Scope. Beach-specific extension to A2A's Agent Card schema. The base Agent Card is an A2A standard; the x-beach extension 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 capabilities to 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 a stdio:// command URI (for local subprocess access) or a StreamableHTTP URL. A tool field 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 header
  • oauth2 — 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 the respond() tool schema the agent uses internally. Peers that inspect tool-use traces (for audit or MCP-level integration) need this.
  • turnStates — the set of turnState values the agent emits. The canonical set includes suspended (HITL), delegated (peer call in flight), and passed (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 validate schemaVersion before 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 generatedAt field allows cheap invalidation: a consumer cached by generatedAt need 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 envelope block.
  • ../../packages/transport/ — the agent registry.
  • ../design-principles.md — principle 3.2 ("declarative configuration") and principle 4.1 ("agents are peers").