OpenRouter and Nous Portal curated picker lists now resolve via a JSON manifest served by the docs site, falling back to the in-repo snapshot when unreachable. Lets us update model lists without shipping a release. Live URL: https://hermes-agent.nousresearch.com/docs/api/model-catalog.json (source at website/static/api/model-catalog.json; auto-deploys via the existing deploy-site.yml GitHub Pages pipeline on every merge to main). Schema (v1) carries id + optional description + free-form metadata at manifest, provider, and model levels. Pricing and context length stay live-fetched via existing machinery (/v1/models endpoints, models.dev). Config (new model_catalog section, default enabled): model_catalog.url master manifest URL model_catalog.ttl_hours disk cache TTL (default 24h) model_catalog.providers.<name>.url optional per-provider override Fetch pipeline: in-process cache -> disk cache (fresh < TTL) -> HTTP fetch -> disk-cache-on-failure fallback -> in-repo snapshot as last resort. Never raises to callers; at worst returns the bundled list. Changes: - website/static/api/model-catalog.json initial manifest (35 OR + 31 Nous) - scripts/build_model_catalog.py regenerator from in-repo lists - hermes_cli/model_catalog.py fetch + validate + cache module - hermes_cli/models.py fetch_openrouter_models() + new get_curated_nous_model_ids() - hermes_cli/main.py, hermes_cli/auth.py Nous flows use the helper - hermes_cli/config.py model_catalog defaults - website/docs/reference/model-catalog.md + sidebars.ts - tests/hermes_cli/test_model_catalog.py 21 tests (validation, fetch success/failure, accessors, disabled, overrides, integration)
3.8 KiB
| sidebar_position | title | description |
|---|---|---|
| 11 | Model Catalog | Remotely-hosted manifest driving curated model picker lists for OpenRouter and Nous Portal. |
Model Catalog
Hermes fetches curated model lists for OpenRouter and Nous Portal from a JSON manifest hosted alongside the docs site. This lets maintainers update picker lists without shipping a new hermes-agent release.
When the manifest is unreachable (offline, network blocked, hosting failure), Hermes silently falls back to the in-repo snapshot that ships with the CLI. The manifest never breaks the picker — worst case you see whatever list was bundled with your installed version.
Live manifest URL
https://hermes-agent.nousresearch.com/docs/api/model-catalog.json
Published on every merge to main via the existing deploy-site.yml GitHub Pages pipeline. The source of truth lives in the repo at website/static/api/model-catalog.json.
Schema
{
"version": 1,
"updated_at": "2026-04-25T22:00:00Z",
"metadata": {},
"providers": {
"openrouter": {
"metadata": {},
"models": [
{"id": "moonshotai/kimi-k2.6", "description": "recommended", "metadata": {}},
{"id": "openai/gpt-5.4", "description": ""}
]
},
"nous": {
"metadata": {},
"models": [
{"id": "anthropic/claude-opus-4.7"},
{"id": "moonshotai/kimi-k2.6"}
]
}
}
}
Field notes:
version— integer schema version. Future schemas bump this; Hermes refuses manifests with versions it doesn't understand and falls back to the hardcoded snapshot.metadata— free-form dict at the manifest, provider, and model level. Any keys. Hermes ignores unknown fields, so you can annotate entries ("tier": "paid","tags": [...], etc.) without coordinating a schema change.description— OpenRouter-only. Drives picker badge text ("recommended","free", or empty). Nous Portal doesn't use this — free-tier gating is determined live from the Portal's pricing endpoint.- Pricing and context length are NOT in the manifest. Those come from live provider APIs (
/v1/modelsendpoints, models.dev) at fetch time.
Fetch behavior
| When | What happens |
|---|---|
/model or hermes model |
Fetches if disk cache is stale, else uses cache |
| Disk cache fresh (< TTL) | No network hit |
| Network failure with cache | Silent fallback to cache, one log line |
| Network failure, no cache | Silent fallback to in-repo snapshot |
| Manifest fails schema validation | Treated as unreachable |
Cache location: ~/.hermes/cache/model_catalog.json.
Config
model_catalog:
enabled: true
url: https://hermes-agent.nousresearch.com/docs/api/model-catalog.json
ttl_hours: 24
providers: {}
Set enabled: false to disable remote fetch entirely and always use the in-repo snapshot.
Per-provider override URLs
Third parties can self-host their own curation list using the same schema. Point a provider at a custom URL:
model_catalog:
providers:
openrouter:
url: https://example.com/my-openrouter-curation.json
The overriding manifest only needs to populate the provider block(s) it cares about. Other providers continue to resolve against the master URL.
Updating the manifest
Maintainers:
# Re-generate from the in-repo hardcoded lists (keeps manifest in sync after
# editing OPENROUTER_MODELS or _PROVIDER_MODELS["nous"] in hermes_cli/models.py).
python scripts/build_model_catalog.py
Then PR the resulting change to website/static/api/model-catalog.json to main. The docs site auto-deploys on merge and the new manifest is live within a few minutes.
You can also hand-edit the JSON directly for fine-grained metadata changes that don't belong in the in-repo snapshot — the generator script is a convenience, not the single source of truth.