name: 9router-ops description: "Administer and configure 9Router local infrastructure, including model mapping, provider injection, and database-level troubleshooting." version: 1.0.0 author: CEO Assistant
Guidelines for managing the local 9Router (Open-SSE) instance used as the model gateway for Hermes.
The 9Router state is stored in SQLite at /root/.9router/db/data.sqlite.
The 9Router UI may fail to validate credentials if it hardcodes regions (e.g., Alibaba China instead of Singapore). Bypass this by injecting directly into providerConnections:
import sqlite3, json, uuid
conn = sqlite3.connect('/root/.9router/db/data.sqlite')
c = conn.cursor()
# providerSpecificData must contain 'baseUrl' for openai-compatible types
# Use prefix 'openai-compatible-' in 'provider' field to enable custom endpoints
data = {
"provider": "openai-compatible-alibaba",
"apiKey": "REDACTED",
"providerSpecificData": {"baseUrl": "https://.../v1"}
}
c.execute("INSERT INTO providerConnections (...) VALUES (...)", (...))
conn.commit()
["provider/model-id", ...].ag/: Antigravitycx/: OpenAI Codexgemini/: Google Geminiopenai-compatible-<name>/: Custom endpoints (Alibaba, etc.)oc/: OpenCodebaseUrl in providerSpecificData is correct.provider field MUST start with openai-compatible-. If it is just openai, 9Router may ignore the baseUrl and default to api.openai.com.data: [DONE] to the end of non-streaming JSON responses. Python's json.loads() will fail; use re.sub(r'data: \[DONE\].*$', '', response_text) to clean it.Check the default combo for self-references. If default contains "default", it may cause recursion errors during model resolution.
The LOCAL9R_KEY value in all Hermes profile .env files is stored in truncated/placeholder form: sk-510...d02a. This is NOT the real usable key — the ... is literally part of the stored string.
To check the actual key stored in 9Router's DB:
python3 -c "import sqlite3; c=sqlite3.connect('/root/.9router/db/data.sqlite'); r=c.execute(\"SELECT key FROM apiKeys WHERE name='hermes'\").fetchone(); print(len(r[0]), r[0])"
The real key is 35 chars including the ... — this IS the full key 9Router accepts. If Bearer auth returns {"error":"API key required for remote API access"}, the request is being treated as remote (not from localhost); 9Router exempts 127.0.0.1 in some modes.
The browser-facing dashboard endpoints (/api/combos, /api/models, etc.) require a browser session cookie, not a Bearer token. They return HTTP 401 for any curl/Bearer request.
LOCAL9R_KEY Bearer → valid only for /v1/chat/completions, /v1/models (LLM proxy)/api/* (dashboard CRUD) → requires browser session; cannot be called via curlDo NOT try to CRUD combos via curl /api/combos. Use direct SQLite + docker restart instead.
9Router loads SQLite at container startup. Direct edits to /root/.9router/db/data.sqlite while the container is running may not appear in UI until restart.
Correct workflow for direct DB edits:
1. Write changes via python3 sqlite3 module to the host-mounted file.
2. docker restart 9router — flushes in-memory state, re-reads DB.
3. Verify in UI or via /v1/models endpoint.
kind field — valid valuesThe kind column controls routing strategy. If UI shows "No combos yet" after a direct DB insert, check kind:
| Value | Meaning |
|---|---|
NULL |
Original default — safest for direct inserts |
'llm' |
LLM combo |
'fallback' |
Ordered fallback |
'round-robin' |
Rotates models |
'fusion' |
Parallel + judge synthesis |
Setting kind = NULL (matching the original default combo schema) is the most reliable value when inserting via raw SQL.
Always verify model availability via the local gateway endpoint:
GET http://127.0.0.1:20128/v1/models (Bearer LOCAL9R_KEY; see gotchas above).
For detailed staged rollout and verification steps, see references/9router-hermes-staged-rollout.md.