What it is
One Go service behind nginx on a single Hetzner box. It is deployed as two containers
(app, nginx) with the database and telemetry in a named volume, and it is the only
component that holds an agent API key — the browser never talks to Hermes.
The service exists to answer one question with an agent instead of a dashboard: "list the customers", "chart the log severities", "who signed in today" — asked in chat, answered from SQLite and the telemetry files, rendered by the model rather than by hand-written Go.
Shape
| Layer | Thing | Notes |
|---|---|---|
| Edge | nginx :443 |
master.nextrum-sy.de only · unknown Host → 444 · HSTS without includeSubDomains |
| App | Go, internal/web |
OAuth session → user; every route resolves a thread through GetChatSession(id, userID) |
| Run | RunRegistry in memory |
a turn is a detached goroutine, polled by the browser once a second |
| Store | SQLite, WAL | single writer; see the data model |
| Telemetry | OTLP JSONL ×3 | logs, traces, metrics — readable with jq, no collector |
| Interface | MCP POST /mcp |
thirteen tools; see MCP tools |
| Agent | Hermes on the host :8642 |
OpenAI-compatible, streaming; the app is a BFF in front of it |
Contract
Routes, as registered in internal/web:
GET /{$} · /chat · /chat/new · /chat/{id} chat surface (the only UI)
POST /ui/chat · /ui/sessions send a turn · new thread
POST /ui/sessions/{id}/archive · /unarchive lifecycle
GET /ui/runs/{id} live run state (polled)
GET /login · /auth/github · /auth/github/callback · /logout
GET /users allowlist administration
GET /healthz liveness
POST /mcp MCP over JSON-RPC 2.0
Environment that changes behaviour:
| Key | Default | Meaning |
|---|---|---|
NX_DATA_DIR |
./data |
SQLite + the three OTLP files |
NX_PORT |
8080 |
listen port inside the container |
MCP_TOKEN |
— | bearer for /mcp; the app refuses to start without it |
HERMES_API_URL |
http://host.docker.internal:8642 |
the agent |
HERMES_API_KEY |
— | required; the BFF's credential, never exposed to the browser |
HERMES_MODEL |
hermes-agent |
model name sent to the API |
NX_HISTORY_TURNS · _TOKENS · _MSG_CHARS |
24 · 6000 · 1200 |
context packing budget |
NX_DEV_AUTH_USER |
— | loopback/private bypass, for testing the UI without OAuth |
NX_AUTH_DISABLED |
— | break-glass: unauthenticated UI. Never a steady state |
Non-goals
- No dashboard. Observability is reached through MCP tools, not pages.
- No server-side rendering of answers. The agent chooses the presentation; the Go layer
only frames it safely (markdown, chart specs, sandboxed
htmlin an iframe withoutallow-same-origin). - No agent shell. The chat agent runs with
terminaldisabled on the API platform, so it reaches data through MCP rather than through a root shell.
See also
- Runbook for the operational path.
- Chat authorization for who sees which conversation.
Rendered from nx-master/service.md · OKF bundle · regenerate with npm run render