HIP-1322: Skills — The Discovery Catalogue. Status Final. Hanzo architectural specification.
/.well-known/agent-skills/ is the catalogue an AI client reads to learn what this deployment can do. It is implemented in hanzoai/cloud at apps/skills (HIP-0106) and serves two public GET routes from a catalogue embedded in the one cloud binary.
This HIP states why the catalogue is generated rather than authored (§2), which brand a request is answered for (§3), and why these two routes sit at the root rather than under /v1 (§4).
An agent that must be told what a platform offers is an agent someone has to keep telling. The discovery convention answers that once, at a fixed address, in a document the agent can fetch before it holds any credential.
The catalogue's danger is not that it is missing but that it is confidently wrong. A skill document is read as live instruction: it tells an agent an address answers and what to send there. An authored catalogue drifts from the router the moment either moves, and drift here does not degrade gracefully — it hands an agent an address that 404s and a reason to keep trying.
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.
Two routes, public, GET only:
| Route | Answer | |---|---| | GET /.well-known/agent-skills/index.json | the brand's master catalogue | | GET /.well-known/agent-skills/{skill}/SKILL.md | one skill document |
The discovery surface carries no secrets and MUST NOT require a bearer. Neither route can be a typed op, so both declare their prose through the registry the router projection consults — which is why they render only while they are served, and why the generated SDKs and the spec-derived CLI carry the same sentence the document does.
The catalogue is generated by plugin/gen-skills from each app's own plugin/<app>/openapi.json — the app binary describing its own live router, which is the same input the fleet catalogue and openapi.yaml are built from — and embedded as catalog/.
This package MUST NOT re-derive skills at request time. It serves the embedded bytes, so the sha256 digests in index.json match the served SKILL.md exactly, and a catalogue cannot describe a route the binary does not serve.
The rule earns its place from a measured failure: the lane that built this catalogue in hanzoai/openapi projected authored intent rather than served routes and had no liveness filter, so it shipped skills for /v1/balancers and /v1/builds — which production 404s — as live instructions to an agent. The generator moved to cloud, beside the routes, for that reason.
The brand is decided per request from the Host header, mirroring getWhiteLabelBrand: api.hanzo.ai serves the Hanzo catalogue, api.lux.network the Lux one, api.zoo.ngo the Zoo one. Hanzo branding MUST NOT appear on a Lux or Zoo surface.
A Host resolving to a brand with no embedded catalogue falls back to the deployment brand (CLOUD_BRAND), then to hanzo. Falling back is not guessing — every fallback is a catalogue that exists.
These two routes live at the root because /.well-known/ is registered by RFC 8615 and the discovery convention names the path. HIP-0139 §3 permits an app to answer at an address not named for it exactly where an outside convention fixes the address, and this is such a case: an agent looks under /.well-known/agent-skills/ or it does not find us.
Registration order matters and is part of the contract: these routes are mounted at order 8, BEFORE IAM's /.well-known/ catch-all at order 50 and before the console's /, so a first-match scan reaches them.
toolsauthenticated and scoped to an org.
call.
A skill document MUST NOT be the only place a capability's contract is written; it points at the served document, which HIP-1030 specifies.
skills was briefly treated as having folded into tools, and the corpus recorded it as no longer a capability. It had not folded: apps/skills is its own package with its own row in manifest/apps.go and its own two served routes, and it answers a question tools does not — what exists, to a caller who has not authenticated. The record is corrected here.
The catalogue is public by design, so it MUST carry nothing an unauthenticated reader may not have: no org names, no tenant addresses, no credentials, no internal hostnames. Because it is generated from each app's own router description rather than from a tenant's state, it has no tenant state to leak.
The digest in index.json is the integrity claim a client can check, and it holds only because the same bytes are embedded and served. A future lane that re-derived a skill at request time would break that claim silently, which is why §2 forbids it.
Released under CC0 1.0 Universal Public Domain Dedication.