# Skills

URL: https://docs.hanzo.ai/docs/openapi/skills

> Skills — reached at /.well-known/agent-skills/; not in the public REST contract.

**Skills** ships in every Hanzo cloud. It carries no operations in the public API document, so it has no generated REST reference — it is reached another way.

> [All capabilities →](https://docs.hanzo.ai/docs/openapi) · [How the four surfaces line up →](https://docs.hanzo.ai/docs/guides)

|                            |                                                                              |
| -------------------------- | ---------------------------------------------------------------------------- |
| **Reached at**             | /.well-known/agent-skills/ — the Agent Skills discovery convention, GET-only |
| **In the public contract** | no — see below                                                               |

## Why there is no reference here

This reference is generated from the public API document, one page per operation. This capability contributes no operation to that document — which is a fact about its address, not about whether it is finished. It is GA and it runs in every deployment. It is reached at /.well-known/agent-skills/ — the Agent Skills discovery convention, GET-only.

## Specification

> **HIP-1322 · Skills — The Discovery Catalogue** — Draft · [read the specification →](https://github.com/hanzoai/hips/blob/7ed737180339cd05b5f2935eafe7f7b83d2449a2/HIPs/hip-1322-skills-the-discovery-catalogue.md)

`/.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).

### Motivation

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.

### Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted
as in RFC 2119.

#### §1 The surface

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.

#### §2 Generated from the router, never authored

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.

#### §3 White-label: the brand is the Host

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.

#### §4 The address is the convention's, not ours

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.

#### §5 The boundary against `tools`

- **tools** (HIP-1213) is the per-tenant tool plane an agent _calls through_,
  authenticated and scoped to an org.
- **skills** is the unauthenticated _description_ of what exists, read before any
  call.

One is the shelf, the other is the sign on the door. 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.

### Rationale

`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.

### Security Considerations

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.

---

[All Hanzo capabilities](https://docs.hanzo.ai/docs/openapi) · [Interactive reference](https://docs.hanzo.ai/docs/openapi/reference)
