App

Azimuth
OVERVIEW
Azimuth is Qompass AI's AI navigator: a Go chatbot stack in which Discord messages (and, by design, email/SMS gateways) travel over NATS JetStream to an LLM worker that answers via the local rose LLM server, with Postgres storage and Prometheus/Grafana/Loki observability in the architecture diagram. The shipped code implements the Discord path end-to-end; the email and SMS gateways are skeleton placeholders.
HOW IT WORKS
- 1Configure the environment — `DISCORD_TOKEN` is required (hard error if unset); `NATS_URL` and `ROSE_URL` fall back to deployment defaults — and start the services (`nats`, `postgres`, `rose`, `bot`) from `discord/compose.yaml` on the external `quantum-net` network.`; `discord/compose.yaml`)
- 2A user posts in Discord; the bot service (`cmd/bot`) ignores bot authors, converts the event to a `model.Message`, and publishes it to NATS JetStream subject `msg.in.discord`.
- 3The LLM worker (`cmd/llmworker`) subscribes to `msg.in.*`, unmarshals the message, and calls rose via `llm.RoseClient.Complete(content)`.
- 4The worker marshals a `model.Response` and publishes it to `msg.out.<kind>` (e.g. `msg.out.discord`).`)
- 5The bot subscribes to `msg.out.discord` and posts the response content back to the originating Discord channel with `ChannelMessageSend`.
- 6Alternatively, the standalone `discord/main.go` bot answers the single command form `!ask <question>` directly: it POSTs to rose's `/api/generate` and posts the answer back, ignoring all other messages.
CONCEPTS
CONFIGURATION
| Option | Default | What it does |
|---|---|---|
DISCORD_TOKEN | none — required | Discord bot token; `Load()` returns `ErrMissingDiscordToken` (“DISCORD_TOKEN is required”) if unset/empty |
`DISCORD_BOT_TOKEN` (standalone bot only) | none — required | Token env var read by `discord/main.go`; process exits fatally (“DISCORD_BOT_TOKEN not set”) if empty — a different name from the `cmd/*` config |
NATS_URL | `nats://nats:4222` (`DefaultNATSURL`) | NATS JetStream bus URL |
ROSE_URL | `https://rose:11434` (`DefaultRoseURL`) | Rose LLM server base URL used by the mTLS client in `internal/llm` |
Rose endpoint (standalone bot) | `http://localhost:11434/api/generate` (`roseEndpoint` const) | Direct HTTP endpoint used by `discord/main.go` `queryRose` — differs from the `ROSE_URL` default above |
TLS_CERT` / `TLS_KEY` / `TLS_CA | none — all three required for the mTLS client | Certificate pair + CA bundle for mutual TLS to rose; TLS 1.3 minimum, verification on, X25519 only |
Prompt / response caps (worker client) | MaxPromptBytes = 32 KiB`, `MaxResponseBytes = 1 MiB`, `RequestTimeout = 120 s | Bounds for one rose completion in `internal/llm` (standalone bot: same caps, `roseTimeout = 30 s`) |
NATS subjects | `msg.in.discord` (in), `msg.in.*` (worker sub), `msg.out.discord` / `msg.out.<kind>` (out) | JetStream routing between gateways and the LLM worker; kinds are `discord` / `email` / `sms` |
Compose images / users | nats:2.10-alpine` (`-js`, user `65532:65532`), `postgres:16-alpine` (user `70:70`), `bot` user `1000:1000` with `no-new-privileges:true | Container deployment defaults; `rose` builds from `../rose` with `runtime: nvidia` |
CONTROLS
| Action | Control |
|---|---|
| Ask the standalone bot a question | Post `!ask <question>` in a Discord channel the bot can read; anything else (including bare `!ask` or `!askfoo`) is ignored |
| Ask via the bus stack | Post any (non-bot) message in a Discord channel the `cmd/bot` service reads — it publishes every such message to `msg.in.discord` (no prefix required in that code path) |
| Stop the bot | `Ctrl-C` / SIGINT or SIGTERM — both entry points wait on a signal channel, then close the Discord session |
| Run the stack | `docker compose` services `nats`, `postgres`, `rose`, `bot` defined in `discord/compose.yaml` (bot `depends_on: [nats, rose, postgres]`) |
NOT COVERED
- The GitHub README is Qompass boilerplate beyond the “Your AI navigator” header — no usage, config, or command documentation; all rows above trace to the primo clone under `discord/`. - Two bot implementations coexist and differ: standalone `discord/main.go` (direct HTTP, `!ask` only, `DISCORD_BOT_TOKEN`, 30 s timeout) vs. the bus stack `cmd/bot` + `cmd/llmworker` (NATS + mTLS, `DISCORD_TOKEN`, 120 s timeout). Sources do not state which is the production entry point. - Email (`cmd/email`) and SMS (`cmd/smsgw`) gateways are TODO skeletons (“Implement IMAP/SMTP polling and NATS publishing” / “Implement Twilio/Vonage webhook → NATS bridge”) — the architecture diagram's Email/SMS → NATS path and the Postgres write path are drawn but not implemented in the files read. No Discord slash commands, admin commands, or user-facing settings were found.
REFERENCES
- NATS JetStream — concepts documentationThe durable streaming layer whose subjects (msg.in.discord, msg.in.*, msg.out.<kind>) route messages between Azimuth's gateways and its LLM worker.
- Discord developer documentation — Gateway eventsThe Discord gateway event model the bot consumes — the message events it converts to model.Message and publishes to JetStream.