Service registry
Every service is independently deployable for zero-downtime rollouts. Go for the data and worker services, Elixir/OTP for the Twitch ingress, SvelteKit (SSR) for the console. The only inter-service transport is NATS: subject-based pub/sub for events, request-reply for RPC. No service reads another service’s database.
Registry
Section titled “Registry”| Service | Repo path | Language | Owns / does | Exposes |
|---|---|---|---|---|
| Twitch Ingress | app/ingress/ |
Elixir (OTP 27+) | EventSub Conduit + WebSocket shards; per-shard supervision; tenant OAuth; filter-and-normalize events | twitch.ingress.event.*, twitch.ingress.status.*, twitch.ingress.admin.shards.get |
| Sesame | app/sesame/ |
Go | Core engine and command processor; consumes ingress events, runs module handlers and commands, routes to outgress | consumes twitch.ingress.event.*; publishes to twitch.outgress.* |
| Outgress | app/outgress/ |
Go | Sends to Twitch; per-broadcaster rate limit (Valkey); channel registry; app/user token lifecycle; kill switch | consumes twitch.outgress.*; serves bagel.rpc.outgress.* |
| Projector | app/projector/ |
Go | Builds the Valkey settings projection on stream-online; serves broadcaster tier lookups | bagel.rpc.broadcaster.status.get |
| Users | app/users/ |
Go | User accounts, status (free/paid/vip), active toggle, Twitch OAuth tokens | bagel.rpc.dashboard.*, bagel.rpc.admin.user.*, bagel.rpc.internal.projection.users.get, bagel.rpc.internal.tokens.*; emits data.users.* |
| Commands | app/commands/ |
Go | Custom chat commands | bagel.rpc.commands.*, bagel.rpc.internal.projection.commands.get; emits data.commands.changed |
| Modules | app/modules/ |
Go | Per-broadcaster feature modules | bagel.rpc.internal.projection.modules.get; emits data.modules.changed |
| Notifications | app/notifications/ |
Go | Dashboard notifications, admin announcements, and TTL cleanup | bagel.rpc.notifications.*, bagel.rpc.admin.notifications.* |
| Transactions | app/transactions/ |
Go | Tebex purchase records | consumes data.transactions.recorded |
| Admin (legacy) | app/admin/ |
Go + templ | Read-only operator window over NATS (shard fleet, users) | Operators only, over the tailnet |
| Console | console/ |
SvelteKit SSR | dashboard (broadcaster self-serve) + admin (operator); arctic OAuth |
HTTPS via cloudflared / tailnet; talks to services only over NATS RPC |
| Web | web/ |
Astro | Public marketing site and documentation | Public HTTPS |
See System state for the data plane, the bus, and the end-to-end request flow, and RPC contracts for the full request-reply surface.
Service-to-service shape
Section titled “Service-to-service shape”Two traffic classes on NATS:
- Events (fire-and-forget pub/sub). Domain change events
data.*(emitted by the owning data service, consumed by projector and peers), ingress eventstwitch.ingress.event.*, shard statustwitch.ingress.status.*, outgress send lanestwitch.outgress.*, and the cache invalidation broadcastbagel.cache.invalidate.broadcaster. - RPC (request-reply). Everything under
bagel.rpc.*plus the ingress shard snapshottwitch.ingress.admin.shards.get. Each handler subscribes in a queue group, so any replica can answer and load spreads across the fleet.
A data service is the only writer of its schema. Any other service that needs
that data asks for it over RPC; it never opens the database. Writes are
write-behind: the caller gets an optimistic reply, the DB updates asynchronously,
and a bagel.cache.invalidate.broadcaster publish reconverges cached readers.
Inter-service authentication
Section titled “Inter-service authentication”- Transport perimeter: services run inside the cluster on the tailnet; there is no public NATS listener. Public ingress is the outbound cloudflared tunnel only.
- NATS authorization: subjects carrying secrets (the bot-account token verbs
under
bagel.rpc.internal.tokens.*) are restricted by NATS account/permission so only outgress and users may use them. Plaintext tokens never transit any other subject. - Mesh: Linkerd native-sidecar provides mTLS between meshed services. The legacy admin tool is intentionally not meshed and is reachable only over Tailscale, with tailnet ACLs as its access control (see Networking).
- OAuth: the console authenticates end users with arctic (Twitch OAuth);
broadcaster grants are persisted by the users service via
grant_save.
