Agent card
core-agent can publish a /.well-known/agent-card.json descriptor from its attach-mode listener so external discovery systems — most notably Google Cloud Agent Registry — can index the binary’s name, description, and skills without a parallel registration channel. The endpoint is opt-in: a binary built without the card config behaves exactly like today’s binary plus a card-shaped 404 at the well-known path.
The card is discovery metadata, not a transport promise. core-agent does not speak the A2A JSON-RPC transport — the url field on the card points at the attach listener, and an A2A client that POSTs JSON-RPC at it gets a 404. GCP Agent Registry, the actual consumer of this endpoint, only reads the card; it never drives the URL as A2A. If you need a non-A2A-shaped registration, GCP also supports NO_SPEC which doesn’t require a card at all — see the Agent Registry register-agents docs.
Enabling the endpoint
Section titled “Enabling the endpoint”Four layers, applied in order: .agents/config.json’s agent.description → .agents/agent-card.json on disk → CLI flag overrides → library API for embedders. The endpoint registers as soon as description is set from any layer.
.agents/config.json’s agent.description is the recommended source — set it once and it flows to both the card AND the ADK system prompt (via agent.WithDescription). The card-only layers (.agents/agent-card.json description field, --agent-card-description flag) override just the card, not the LLM system prompt — use them when the public-facing wording should differ from what you tell the model.
The card’s url field is derived per-request from the caller’s Host header (with X-Forwarded-Proto / X-Forwarded-Host taking precedence behind ingress) — the operator never has to know their own external address. By definition, the consumer fetching /.well-known/agent-card.json already knows the URL they used; the handler just echoes it back. Same convention as OIDC discovery and most other well-known endpoints. Set external_url only when you want to publish a canonical URL different from the fetch URL (rare — typically when serving on multiple addresses but wanting one canonical answer).
.agents/agent-card.json (recommended)
Section titled “.agents/agent-card.json (recommended)”Lives next to .agents/config.json and .agents/mcp.json (same version: 1 envelope). Gets checked into the repo with the .agents/skills/ bundles it describes:
{ "version": 1, "name": "core-agent", "description": "Production-incident response agent for the platform fleet.", "agent_version": "v2.2.0", "documentation_url": "https://example.internal/platform/runbooks/core-agent", "provider": { "organization": "Platform Team", "url": "https://example.internal/platform" }, "extra_skills": [ { "id": "rollback-deploy", "name": "Rollback a deploy", "description": "Curated: revert a Cloud Deploy release to the previous revision.", "tags": ["curated", "deploy"], "examples": ["rollback the most recent payments-api release"] } ]}A missing file is not an error — the binary just runs without the card endpoint unless flags supply description. A file that only sets extra_skills (no description) is rejected: the card is the public-discovery surface, not a skill-library side-channel. external_url is optional; set it only to override the per-request URL with a canonical alternative.
CLI flags
Section titled “CLI flags”Override individual fields. Useful when running under Cloud Run / Helm where mounting a file alongside the binary is awkward and per-pod values come in via env vars:
| Flag | Overrides |
|---|---|
--agent-card-description=<text> | description — required to enable the endpoint |
--agent-card-external-url=<url> | external_url — optional; overrides the per-request URL derivation with a canonical value |
--agent-card-name=<text> | name (defaults to first registrant’s AppName, else core-agent) |
--agent-card-version=<text> | agent_version (defaults to the build version) |
--agent-card-provider-org=<text> | provider.organization |
--agent-card-provider-url=<url> | provider.url |
--agent-card-docs-url=<url> | documentation_url |
--agent-card-config=<path> | path to the JSON file (default: .agents/agent-card.json); - skips file loading entirely |
There is no flag for curated skills. Skill descriptions are multi-sentence and tags/examples are arrays — they live in the file (or the library API), reviewable in code review and versioned with the binary.
Library API
Section titled “Library API”Embedders building their own daemon populate attach.Options.AgentCard directly:
srv, err := attach.NewServer(attach.Options{ Registry: reg, // Non-loopback binds require auth (Auth.BearerToken / mTLS / // enforced multi-session) — see #376. Loopback shown here. Addr: "127.0.0.1:7777", AgentCard: attach.AgentCardConfig{ Name: "my-agent", Description: "Does the thing.", // ExternalURL omitted — handler derives url from each // request's Host / X-Forwarded-* headers. Provider: attach.AgentCardProvider{Organization: "Acme", URL: "https://acme.example.com"}, ExtraSkills: []attach.AgentCardSkill{ {ID: "do-thing", Name: "Do the thing", Description: "Executes the thing."}, }, },})Where the skills come from
Section titled “Where the skills come from”Two sources, merged. Curated wins on id collision:
- Auto-derived from every
.agents/skills/bundle the binary loaded at startup (the same set surfaced through the in-TUI/skillsview). Each getstags: ["skill"]and liftsname/descriptionfrom the SKILL.md frontmatter. Internal tools (report_done, MCP tools, the inbox primitives, etc.) are never auto-included — they’re implementation detail, not skills. - Curated extras from
extra_skillsin the file (orExtraSkillsin the library API). Defaulttagsare["curated"]if unset.
Newly-dropped SKILL.md bundles surface on the next card fetch without restart — the card handler re-reads the skills snapshot per request, same as GET /sessions/<id>/skills.
Auth and security
Section titled “Auth and security”The card endpoint is always unauthenticated, even when the rest of the attach listener requires mTLS + bearer auth — public discoverability is the point. The card’s securitySchemes / security fields describe the auth required for other endpoints, so a discovery system can tell what credentials it would need to actually drive the agent:
Attach Auth config | Emitted securitySchemes | Emitted security |
|---|---|---|
| (none) | omitted | omitted |
BearerToken set | {bearer: {type: "http", scheme: "Bearer"}} | [{bearer: []}] |
ClientCAFile set | {mtls: {type: "mutualTLS"}} | [{mtls: []}] |
| both set | both schemes | [{bearer: [], mtls: []}] (AND — both required) |
Registering with Google Cloud Agent Registry
Section titled “Registering with Google Cloud Agent Registry”Once the card serves cleanly:
# Verify the card.curl https://my-agent.example.com:7777/.well-known/agent-card.json | jq
# Register as A2A_AGENT_CARD (gets the skills indexed for keyword search).gcloud alpha agent-registry agents create my-agent \ --location=us-central1 \ --agent-spec-url=https://my-agent.example.com:7777/.well-known/agent-card.jsonThe Registry fetches /.well-known/agent-card.json once at registration; subsequent operator searches hit the Registry’s index, not the binary. If your external_url is publicly resolvable, you’re done. If it lives behind a private VPC, see the Registry docs on setting up Private Service Connect for indexing reach.
Validation
Section titled “Validation”The card builder validates every emission against the vendored A2A v0.3.0 JSON Schema (pkg/attach/testdata/agentcard.schema.json) in TestAgentCardSchemaValidation. The vendored file gets bumped in a dedicated commit each time the A2A spec issues a minor-version bump — see pkg/attach/testdata/AGENTCARD_SCHEMA_NOTICE.md for the refresh policy.
Design background
Section titled “Design background”See docs/agent-card-design.md for the rationale and trade-offs. Future work — full A2A JSON-RPC transport (attach/a2a.go) — stays deferred per docs/attach-mode-design.md until a concrete consumer surfaces.