App

Azimuth

Azimuth

Version 1.00Source on GitHub

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

  1. 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`)
  2. 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`.
  3. 3The LLM worker (`cmd/llmworker`) subscribes to `msg.in.*`, unmarshals the message, and calls rose via `llm.RoseClient.Complete(content)`.
  4. 4The worker marshals a `model.Response` and publishes it to `msg.out.<kind>` (e.g. `msg.out.discord`).`)
  5. 5The bot subscribes to `msg.out.discord` and posts the response content back to the originating Discord channel with `ChannelMessageSend`.
  6. 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.
How it works, step by step — each step as the project's own documentation states it.

CONCEPTS

DISCORD MESSAGE → JETSTREAM → WORKER → ROSE → REPLYDISCORD CHANNELa user posts — bot authors are ignoredbot publishes model.MessageNATS JETSTREAM — msg.in.discordworker subscribes to msg.in.*LLM WORKER — cmd/llmworkerunmarshals the message · calls roseROSE — LOCAL LLM SERVERRoseClient.Complete(content) · mTLS to ROSE_URLmodel.ResponseNATS JETSTREAM — msg.out.discordBOT SERVICE — cmd/botChannelMessageSend — reply to the originating channelcompose services: nats · postgres · rose · bot — Postgres storage andPrometheus / Grafana / Loki observability are in the architecture diagram
Azimuth's bus architecture, as the shipped Discord code implements it: the bot publishes each non-bot Discord message to NATS JetStream subject msg.in.discord, the LLM worker (subscribed to msg.in.*) completes it against the local rose server and publishes the response to msg.out.discord, and the bot posts the reply to the originating channel. Postgres storage and Prometheus/Grafana/Loki observability appear in the project's architecture diagram; the email and SMS gateways are skeleton placeholders.

CONFIGURATION

OptionDefaultWhat it does
DISCORD_TOKENnone — requiredDiscord bot token; `Load()` returns `ErrMissingDiscordToken` (“DISCORD_TOKEN is required”) if unset/empty
`DISCORD_BOT_TOKEN` (standalone bot only)none — requiredToken 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_CAnone — all three required for the mTLS clientCertificate 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 sBounds 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 / usersnats:2.10-alpine` (`-js`, user `65532:65532`), `postgres:16-alpine` (user `70:70`), `bot` user `1000:1000` with `no-new-privileges:trueContainer deployment defaults; `rose` builds from `../rose` with `runtime: nvidia`

CONTROLS

ActionControl
Ask the standalone bot a questionPost `!ask <question>` in a Discord channel the bot can read; anything else (including bare `!ask` or `!askfoo`) is ignored
Ask via the bus stackPost 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

  1. 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.
  2. Discord developer documentation — Gateway eventsThe Discord gateway event model the bot consumes — the message events it converts to model.Message and publishes to JetStream.