Hermes Provider
Hermes is the managed per-tenant agent runtime used for onyx-prod organisations such as Classical Visas. Opbox can talk to it through an OpenAI-compatible API, so the chat route reuses the OpenAI streaming loop with a tenant-specific base URL and bearer token.
Opbox is optional. A tenant can exist as standalone Onyx without any Opbox workspace; those users work through the OnyxOS/Hermes dashboard and local tenant skills. The protected /v1/* API still starts with a tenant bearer so the tenant can be linked to Opbox or another client later.
Endpoint Shape
Store the tenant base URL in AiCredential.hermesEndpointUrl:
https://gateway.opbox.app/tenant/<slug>
Opbox appends /v1/chat/completions internally. Do not store the host root (https://gateway.opbox.app) and do not store a URL that already includes /v1/chat/completions.
Required tenant routes:
GET /tenant/<slug>/v1/models
POST /tenant/<slug>/v1/chat/completions
GET /tenant/<slug>/health
If /v1/chat/completions returns text/html or the OnyxOS SPA, internal Caddy is missing the /v1/* route or the hermes-api supervisor program is not running.
Tenant Runtime
Each tenant container runs four supervised processes:
| Process | Listen address | Purpose |
|---|---|---|
onyx-system | 127.0.0.1:8768 | OnyxOS management daemon |
hermes-dashboard | 127.0.0.1:8888 | Tenant dashboard UI |
hermes-api | 127.0.0.1:8642 | OpenAI-compatible API server |
caddy | 0.0.0.0:9000 | Internal route mux |
Internal Caddy must route /v1/* and /health to 127.0.0.1:8642 before the SPA fallback.
Opbox-provisioned tenants receive their managed OPENROUTER_API_KEY and seed Hermes with an OpenRouter custom provider. Standalone Onyx tenants do not need Opbox; they can use GROQ_API_KEY from /etc/hermes/global.env or edit their tenant config.yaml to point at another provider.
Provisioning Contract
For Opbox-created tenants, the onyx provisioner generates a tenant API bearer, passes it to Docker as HERMES_API_SERVER_KEY, and returns it once to Opbox:
{
"slug": "classical-visas",
"username": "classical-visas",
"password": "<one-time dashboard password>",
"api_key": "<one-time Hermes API bearer>",
"dashboard_url": "https://gateway.opbox.app/tenant/classical-visas/"
}
Opbox encrypts api_key into a HERMES AiCredential, stores dashboard_url without a trailing slash as hermesEndpointUrl, and activates that credential on OrganizationAiConfig with chatProvider = "hermes".
For standalone Onyx tenants created with onyx-add-tenant, the script generates the API bearer when HERMES_API_SERVER_KEY is omitted and prints it once. No Opbox credential is written unless the tenant is later linked to an Opbox organisation.
Agent Access
There are three distinct paths:
| Direction | Mechanism | Required when |
|---|---|---|
| User -> Onyx | OnyxOS/Hermes dashboard, local tenant skills | Always available for standalone Onyx use |
| Opbox -> Onyx | HERMES AiCredential calling /v1/chat/completions | The Opbox assistant should run on the tenant Hermes runtime |
| Onyx -> Opbox | Opbox MCP cp_live_* key calling /api/mcp/* | The tenant agent needs to read or mutate Opbox workspace data |
Skills can package local tenant behavior, prompts, and workflows. They should not replace MCP for Opbox data access, because MCP is where Opbox enforces workspace scope, autonomy level, rate limits, and audit attribution.
Quick Checks
Unauthenticated route sanity:
curl -i https://gateway.opbox.app/tenant/<slug>/v1/models
Expected: JSON 401 or 403, not HTML.
Authenticated smoke test:
curl -N https://gateway.opbox.app/tenant/<slug>/v1/chat/completions \
-H "Authorization: Bearer $HERMES_API_SERVER_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"hermes-agent","messages":[{"role":"user","content":"ping"}],"stream":true}'
Expected: text/event-stream with data: events.