Provider Model Catalog
Daemon-owned model catalog — sources, refresh lifecycle, native HTTP/UDS endpoints, OpenAI-compatible projection, and extension model.source contract.
- Audience
- Operators running durable agent work
- Focus
- Agents guidance shaped for scanability, day-two clarity, and operator context.
The model catalog is the daemon-owned authority for pre-session provider model selection. The
new-session dialog, CLI, HTTP, UDS, Host API, web settings, and the OpenAI-compatible model list
all read the same projected rows. Active session controls keep flowing through ACP configOptions
once a session is running.
The catalog never blocks session creation. Every refresh is detached from the request lifetime, runs under an explicit deadline, falls back to stale rows when refresh fails, and exposes source health through structured status.
Why a separate catalog
ACP models.availableModels is observed only after session/new or session/load, which is too
late for the new-session dialog and for agents picking a model through CLI/HTTP/UDS. The catalog
splits two concepts that used to live together:
- Pre-session catalog: which provider models AGH knows about before creating a session.
- Active session config: which controls the running ACP session exposes right now.
The catalog is daemon-owned, persisted, refreshable, extensible, and agent-manageable. Active
session configOptions continue to govern the live session — catalog rows never override them.
Source priorities and merge
The merge key is (provider_id, model_id). Source rows are preserved separately and merged on read.
| Source kind | Priority | Origin |
|---|---|---|
config | 120 | [providers.<id>.models] operator config. |
provider_live | 110 | Live discovery sources for the provider account/runtime. |
extension | 100 | Extension model sources (capability model.source). |
models_dev | 50 | Cross-provider enrichment from models.dev (with stale fallback). |
builtin | 10 | Built-in defaults shipped with the daemon. |
acp_session | session-scoped | Observed during an ACP session; never rewrites global authority. |
Higher-priority non-empty fields win; lower-priority sources fill missing fields. Ties resolve by
fresher refreshed_at, then ascending source_id. models.dev and builtin rows can enrich
metadata but never prove account-level availability.
Merged availability
The merged projection exposes both nullable available and string availability_state so stale
live truth is visible instead of collapsed:
availability_state | Meaning | API available | API stale |
|---|---|---|---|
available_live | Live or extension row confirmed availability with fresh data. | true | false |
available_stale | Live or extension row confirmed availability but the row is stale. | true | true |
unavailable_live | Live or extension row denied availability with fresh data. | false | false |
unavailable_stale | Live or extension row denied availability but the row is stale. | false | true |
unknown | Only catalog/builtin/config metadata is known. | null | depends |
Manual model entry remains valid even when no source advertises the model. models.curated is
metadata, never an allowlist.
Native HTTP and UDS endpoints
| Method | Path | Transports | Description |
|---|---|---|---|
| GET | /api/providers/models | HTTP, UDS | List merged provider model catalog entries. |
| GET | /api/providers/{provider_id}/models | HTTP, UDS | List merged catalog entries for one provider. |
| POST | /api/providers/models/refresh | HTTP, UDS | Refresh sources across providers; returns source status. |
| POST | /api/providers/{provider_id}/models/refresh | HTTP, UDS | Refresh sources for one provider. |
| GET | /api/providers/models/status | HTTP, UDS | Source status across providers. |
| GET | /api/providers/{provider_id}/models/status | HTTP, UDS | Source status for one provider. |
List and status endpoints accept these query parameters:
provider_id: filter by AGH provider id (only on the cross-provider list).source_id: filter by catalog source id (config,models_dev,provider_live:<id>,extension:<slug>).refresh=true: refresh sources before listing.include_stale=true: include stale source rows in the merged projection.
Refresh requests accept an optional JSON body:
{
"source_id": "provider_live:codex",
"force": true,
"request_id": "rfsh-2026-05-07-abc"
}request_id (or a daemon-generated value) is the refresh_request_id correlation key surfaced in
logs and source status events. Refresh work runs under daemon-owned lifetime: the request's cancel
does not cancel refresh work, and the daemon joins outstanding refresh workers during shutdown.
The native list response shape is:
{
"models": [
{
"provider_id": "codex",
"model_id": "gpt-5.4",
"display_name": "GPT-5.4",
"sources": [
{
"source_id": "config",
"source_kind": "config",
"priority": 120,
"stale": false,
"refreshed_at": "2026-05-07T18:32:11Z"
},
{
"source_id": "models_dev",
"source_kind": "models_dev",
"priority": 50,
"stale": false,
"refreshed_at": "2026-05-07T03:00:00Z"
}
],
"available": true,
"availability_state": "available_live",
"stale": false,
"refreshed_at": "2026-05-07T18:32:11Z",
"context_window": 256000,
"max_output_tokens": 32000,
"supports_tools": true,
"supports_reasoning": true,
"reasoning_efforts": ["minimal", "low", "medium", "high", "xhigh"],
"default_reasoning_effort": "medium"
}
]
}Source status payloads carry source_id, provider_id, source_kind, refresh_state
(idle | refreshing | succeeded | failed), last_refresh, next_refresh, last_success,
row_count, stale, and a redacted last_error. Raw secrets, command lines, OAuth material, or
provider response bodies never appear in last_error.
The HTTP and UDS transports return canonical, byte-equal JSON for the same projection so cross- transport regression tests can compare daemon output directly.
Native tools
Managed agents can use the agh__provider_models toolset instead of shelling out to the CLI. The
native tools share the same daemon service and response payloads as HTTP, UDS, and
agh provider models.
| Native tool | Purpose |
|---|---|
agh__provider_models_list | List merged provider model projections. |
agh__provider_models_refresh | Refresh provider model catalog sources. |
agh__provider_models_status | Inspect source status and stale fallback. |
provider_id is optional on all three tools, matching the cross-provider HTTP/UDS routes.
source_id, refresh, include_stale, force, and request_id preserve the same meanings as the
CLI flags and native endpoint fields.
OpenAI-compatible projection
A list-only OpenAI-compatible endpoint is registered on HTTP only. UDS does not expose this route.
GET /api/openai/v1/models
GET /api/openai/v1/models?provider_id=codexAuthentication uses the same bearer-auth and middleware contract as the rest of /api/*. CORS and
rate-limit behavior follow HTTP defaults. Errors are wrapped in the OpenAI envelope shape
({"error": {...}}) but reuse AGH's normal status-code semantics. Refresh work is not
available through this endpoint; clients use the native catalog endpoints, the CLI, the Host API,
or the web for refreshes.
{
"object": "list",
"data": [
{
"id": "gpt-5.4",
"object": "model",
"created": 0,
"owned_by": "codex",
"agh": {
"provider_id": "codex",
"model_id": "gpt-5.4",
"display_name": "GPT-5.4",
"sources": ["config", "models_dev"],
"available": true,
"availability_state": "available_live",
"stale": false,
"supports_tools": true,
"supports_reasoning": true,
"reasoning_efforts": ["minimal", "low", "medium", "high", "xhigh"],
"context_window": 256000,
"max_output_tokens": 32000
}
}
]
}The agh extension key carries AGH-specific metadata. Generated OpenAPI/SDK contracts treat it as
a typed object (OpenAIModelAGHPayload), not a free-form blob.
Provider models CLI
The CLI surface lives under the singular provider namespace because the catalog is provider-
scoped and already neighbors agh provider auth. A top-level agh models … alias is intentionally
out of scope for the MVP to avoid forking the command contract before the first one is stable.
agh provider models list [provider] -o json
agh provider models list [provider] --source models_dev --refresh --include-stale
agh provider models refresh [provider] -o json
agh provider models refresh [provider] --source provider_live:codex --force --request-id rfsh-abc
agh provider models status [provider] -o jsonrefresh returns the same source-status payloads as the native HTTP/UDS refresh endpoints — not a
single success line — so CI scripts and agents can act on partial-source failures without parsing
stderr. JSON output is canonical: identical between agh provider models … and the daemon HTTP
response for the same projection.
See agh provider models for full flag and output
documentation generated from the cobra source.
Extension model.source contract
Extensions can provide model rows by declaring the manifest provide capability model.source. The
daemon validates rows, persists them, and applies the normal merge policy. Extensions cannot own
global catalog state; they only contribute source rows.
[capabilities]
provides = ["model.source"]
[actions]
requires = ["models/list", "models/refresh", "models/status"]
[security]
capabilities = ["model.read", "model.write"]
[subprocess]
command = "node"
args = ["dist/index.js"]Extensions that provide model.source must implement the AGH-to-extension service method
models/list. The daemon calls it with a deadline-bound context; the extension returns rows scoped
to provider IDs the extension declares.
| Method | Direction | Purpose |
|---|---|---|
models/list | AGH → extension | Extension returns provider model rows; daemon validates and persists them. |
models/list | Host API | Extension reads the daemon-owned merged projection. |
models/refresh | Host API | Extension triggers a daemon-owned source refresh. |
models/status | Host API | Extension reads daemon-owned source status. |
Capability grants follow the same area-based scheme as other Host API methods:
| Method | Area | Notes |
|---|---|---|
models/list | model.read | Returns the daemon-owned merged projection, not raw rows. |
models/status | model.read | Returns daemon-owned source status. |
models/refresh | model.write | Triggers daemon-owned refresh; rate-limited and serialized. |
Marketplace extensions are limited to read-oriented grants by policy, so a marketplace extension
can declare model.read and read the projection but must request model.write explicitly to
trigger refresh, and refresh grants stay subject to the marketplace policy review.
Extension source rows are always validated through internal/modelcatalog. Invalid rows produce a
recorded source status (with redacted error) instead of corrupting the merged projection.
Refresh lifetime and serialization
- Catalog list calls return cached rows immediately when present.
- Refresh detaches from request cancellation via
context.WithoutCancel(ctx)and re-attaches an explicit deadline throughcontext.WithDeadline. - Refresh work is serialized per
provider_idbefore any subprocess or provider-home work. - Concurrent refresh requests for the same provider coalesce behind the in-flight refresh and return identical source statuses when it finishes.
- Refreshes for different providers can run concurrently.
- The daemon joins outstanding refresh workers during shutdown.
Discovery never creates an ACP session. Live provider sources fail closed by recording source status; session creation is never blocked on a successful network refresh. Stale rows remain available as a fallback while the catalog labels them stale.
Observability
Catalog operations emit structured events with the following correlation keys:
refresh_request_idprovider_idsource_idsource_kindmodel_idfor row-scoped eventsextension_namefor extension sourcessession_idonly for ACP session config observations
Tracked events include refresh started/succeeded/failed, source row count changes, stale fallback usage, all-source failure, extension source denied/unavailable, and ACP config option captured/ updated transitions.
Related pages
- Providers covers
[providers.<id>.models]and the per-providermodels.discoveryshape. - config.toml documents
[model_catalog.sources.models_dev]defaults. agh provider modelsis the CLI generated from the cobra source.- Develop Extensions covers the
manifest provide capability
model.sourceand Host API model methods.