> ## Documentation Index
> Fetch the complete documentation index at: https://www.plungeai.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Discovery

> Discovery is the read-only catalog behind every other plane: agents, skills, plugins, MCP servers, connectors, models, providers and (once you have saved some) workflows. Four routes, all ozk_-authenticated, all backed by the registry service (orchestration/api-gateway/routes/discovery.ts → …


Discovery is the read-only catalog behind every other plane: agents, skills, plugins, MCP servers, connectors, models, providers and (once you have saved some) workflows. Four routes, all `ozk_`-authenticated, all backed by the registry service (`orchestration/api-gateway/routes/discovery.ts` → `orchestration/registry/`).

## 8.1 Search — `GET /v1/discovery/search`

| Param | Values | Notes |
|---|---|---|
| `q` | text | natural-language or keyword query; with `q` the registry runs **hybrid** search (semantic + keyword), without it a plain listing |
| `kind` | `agents` · `skills` · `plugins` · `mcp-servers` · `connectors` · `models` · `providers` · `workflows` · `experts` · `backgrounds` · `personas` (= `digital-twins`) | the plural catalog groups (`registry.ts` `KIND_GROUPS`) |
| `type` | singular storage type (`agent`, `skill`, `mcp_server`, …) | alternative to `kind` |
| `category` | e.g. `search`, `google-workspace` | exact card category |
| `status` | `active` (default) | the public catalog shows callable capabilities only; other statuses need an explicit value |
| `tier` | card tier tag | e.g. `verified` |
| `mode` | `hybrid` (default with `q`) · `keyword` · `vector` | force one search leg |
| `fields` | `summary` · `full` | `full` adds parameters, demonstrations and `prompt_format` |
| `limit`, `offset` | ints | `limit` is capped by the registry; page with `offset` |
| `include` | `quality` | attaches a `quality` object per agent card — `{ score, success_rate, runs }` or `null` when the evaluation service has no data (it was `null` for every card on 2026-09-18) |

Response: `{ "cards": [...], "count": <cards in this page>, "total": <every match>, "searchMethod": "text" | "hybrid" | "vector", "query": { …the normalised query… } }`. `count` is the page size; `total` is every card the filters (and, for `q`, the keyword predicate) match, ignoring `limit` and `offset` — page with `offset` until you have seen `total`. For a `q` search the vector leg can add cards the keyword predicate did not match, so `total` is a floor, never smaller than `offset + count`.

```bash
curl -s "https://api.plungeai.com/v1/discovery/search?q=web+search&kind=agents&limit=3" \
  -H "Authorization: Bearer $PLUNGE_API_KEY"
```

```bash
curl -s "https://api.plungeai.com/v1/discovery/search?q=llm&kind=agents&limit=2&include=quality" \
  -H "Authorization: Bearer $PLUNGE_API_KEY"
```

Catalog sizes on 2026-09-18 (`limit=100`, first page): agents **92**, skills **12**, plugins **5**, providers **11**, mcp-servers **100+**, models **100+**, connectors **802** as of 2026-09-29 (9 pages of 100 — read `total`), workflows 0 for a fresh key (saved workflows are per user), experts / backgrounds / personas 0 (capability cards are created by users and missions — see [Skills, plugins & capability injection](/one-api/skills-plugins-capabilities)).

## 8.2 The card object

Summary cards (`fields=summary`, the default for the agents/tools lists) carry: `id`, `type`, `name`, `version`, `description`, `category`, `best_for[]`, `not_for[]`, `tags[]`, `status`, `icon`, `color`, `created_at`, `updated_at`. Full cards add `parameters[]`, `demonstrations[]`, `prompt_format`, `output_type`, `output`. Connector cards add a `connector` object (`auth`, `provider_key`, `key_placeholder`, `help_text`); model cards add pricing and capability columns from the rate card.

Tags encode the operational facts the planes act on:

| Tag | Meaning |
|---|---|
| `status:active` | callable — the only status the tools/agents planes accept |
| `gated:<op>` | the trust fence refuses `<op>` unattended (403) — [Tools plane](/one-api/tools) |
| `tier:verified` | reviewed card |
| `auth:oauth_static` · `auth:api_key` · `auth:oauth_dcr` | how a connector is connected — [Connectors](/one-api/connectors) |
| `serves:agent` | a connector some tool agent uses |

## 8.3 Recommend — `POST /v1/discovery/recommend`

Body `{ "type": "agent" | "skill" | … (plural kinds accepted as aliases), "task": "<what you want done>", "status"?: "<status>" }`. **Since 2026-09-25 recommend rides on the Jev resolve ([§8.4](#84-resolve--post-v1discoveryresolve)) and works again** — from 2026-09-18 it answered `502 upstream_error` for every request. `status` defaults to `active`. Response: `{ "recommended": "<card id>" | null, "card": { …the card… } | null, "score": <0–1>, "resolve": { …the full resolve decision… } }` — `recommended` is `null` and `score` is `0` when nothing fits; `score` is also `0` on the keyword fallback (no confidence). Only the types in the discovery index can be recommended — `agent`, `skill`, `connector`, `mcp_server`, `workflow`, `plugin`, `expert`, `digital_twin`; any other valid type (`model`, `provider`, `background`, `component`, `theme`, …) answers `400 type_not_discoverable` (legacy aliases such as `ai_agent` are normalised first), an unknown type `400 invalid_request`.

## 8.4 Resolve — `POST /v1/discovery/resolve`

One step from a task to a call: which tool, which operation, what command, and how sure the platform is. The registry shortlists cards per kind (BM25 over id, name, description, `best_for` and tags), one decision request to the Jev model (`typesafe-provider`) picks a card per kind or none, and the answer carries that card's operation, its required parameters, an example CNL task and a calibrated confidence (`orchestration/registry/resolve.ts`).

```bash
curl -s -X POST "https://api.plungeai.com/v1/discovery/resolve" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"task": "current weather conditions for Berlin", "kinds": ["agents"]}'
```

The body is whitelisted — any other field is dropped; you never pass an owner or a debug flag, so only public cards are considered.

| Field | Values | Notes |
|---|---|---|
| `task` | text, required | what you want done, in plain language; missing or empty → `400 invalid_json` |
| `kinds` | array of card types: `agent` · `workflow` · `connector` · `mcp_server` · `skill` · `plugin` · … | plural kinds (`agents`, `mcp-servers`, …) are accepted aliases; deduplicated, at most 8; default: `agent`, `workflow`, `connector`, `mcp_server`, `skill`, `plugin` |
| `category` | e.g. `search` | exact card category |
| `status` | `active` (default) · `ready` · `any` · a literal status | same default as search: callable capabilities only |

Response fields:

| Field | Meaning |
|---|---|
| `outcome` | `match` · `uncertain` · `none` — see below |
| `tool` | the chosen card: `{ id, kind, type, name, description, category, status }`; absent on `none` |
| `operation` | `{ name, purpose?, gated }` — absent when the card has several operations and none was decided; `gated: true` means the [trust fence](/one-api/tools) needs an approval |
| `call` | `{ required_params[], example_cnl? }` — the example is a CNL task you can adapt ([workflows](/one-api/workflows)) |
| `confidence` | 0–1, calibrated; `null` on the keyword fallback |
| `alternatives[]` | up to 5 runners-up `{ id, kind, name, p }` |
| `gate` | advisory signals from the same request: `acts_on_user_system`, `needs_external_service`, `prose_suffices` (0–1) and a suggested `handler` |
| `method` | `all` (every card of each kind was shown to the model) · `bm25` (a keyword shortlist) · `cache` (a stored decision) · `lexical-fallback` (the decision model was unavailable) · `rate-limited` (a public caller exceeded the per-isolate Jev budget — the pick is a keyword match with `confidence: null`) |
| `pass` | `1`, or `2` when a second, closer comparison of the top three was needed |
| `mode` | `shadow` · `act` — how the platform's own agents use resolve (in `shadow` they log the decision and keep showing the plain search list); it does not change your answer |
| `index_version`, `timings` | the catalog snapshot the decision used, and the server-side legs in ms |

What each outcome means for a caller:

- **`match`** (confidence ≥ 0.8) — call `tool.id` with `operation.name`; fill `call.required_params` (the card at [§8.5](#85-one-card--get-v1discoverycardstypeid) has every parameter). Ask your user first when `operation.gated` is `true`.
- **`uncertain`** — the best guess is below 0.8, or `method` is `lexical-fallback` or `rate-limited` and the pick is a keyword match (`confidence: null`). Treat `tool` and the first `alternatives` as a shortlist: read their cards and choose, or ask your user.
- **`none`** — no card in the catalog fits this task, or the task needs no tool at all (plain text answers it). Do not invent an id; answer without a tool or tell your user nothing fits.

Decisions for the same task, kinds, status and category are cached (10 minutes in memory, 6 hours in KV) until the catalog snapshot changes.

## 8.5 One card — `GET /v1/discovery/cards/:type/:id`

Returns the **full card as markdown with YAML frontmatter** (`Content-Type: text/markdown`) — the same document the platform's own agents read before calling a tool. `:type` is the singular storage type (`agent`, `skill`, `plugin`, `mcp_server`, `connector`, `model`, `provider`, `workflow`, `expert`, `background`, `digital_twin`); the plural `kind` spellings (`agents`, `mcp-servers`, …) are accepted as aliases.

```bash
curl -s "https://api.plungeai.com/v1/discovery/cards/agent/brave-agent" \
  -H "Authorization: Bearer $PLUNGE_API_KEY"
```

Unknown card → `404 not_found`. A 4xx from the registry is normalised into the One API error envelope; a registry 5xx never reaches you raw (`502 upstream_error`).

## 8.6 From an id to a call

| Card `type` | Use it as |
|---|---|
| `agent` | `GET /v1/tools/<id>` + `POST /v1/tools/<id>/execute` ([tools](/one-api/tools)); `POST /v1/agents/<id>/execute` ([agents](/one-api/agents)); `agent: <id>` in a CNL task ([workflows](/one-api/workflows)) |
| `model` | `"model": "<id>"` on `POST /v1/chat/completions` — ids are `provider/model` slugs ([models](/one-api/models)); also `model:` + `provider:` on an agent-plane call |
| `provider` | the `provider/` prefix of model slugs; `provider:` on agent-plane calls |
| `skill` · `plugin` · `expert` · `background` · `digital_twin` | `skills: [<id>]`, `plugins: [<id>]`, `experts: [<id>]`, `backgrounds: [<id>]`, `persona: <id>` on a `type: harness` task ([capability injection](/one-api/skills-plugins-capabilities)) |
| `mcp_server` | `POST /v1/mcp/runs` with the server id ([MCP plane](/one-api/mcp)); `mcp: [<id>]` on a harness mission |
| `connector` | connect it in Studio → Integrations; then the agents that `serves:agent` it stop returning `needs_connection` ([connectors](/one-api/connectors)) |
| `workflow` | `POST /v1/workflows/<id>/execute` ([workflows](/one-api/workflows)) |

Search results are cached briefly on the platform side (KV + LRU, ~200 ms per uncached call); agents that discover at run time — `concierge`, the harness loop — hit this same route, so what you see is what they see.
