Go rewrite of Mellow, an AI mental health companion for Discord. Runs with zero privileged intents: Guilds, GuildMessages, DirectMessages only. Turn off Message Content, Server Members, and Presence intents in the Discord dev portal.
- Discord:
disgo - Database: PostgreSQL via
pgx/v5, queries generated bysqlc, migrations bygolang-migrate - AI: Anthropic Claude (
anthropic-sdk-go), default modelclaude-haiku-4-5, params read from theMellowDB row - HTTP API:
chi, OpenAPI spec at/openapi.json, Scalar docs at/docs
Brand links come from config: WEBSITE_URL (default https://mellow.codemeapixel.dev)
derives /docs, /support, /invite, each overridable with DOCS_URL /
SUPPORT_URL / INVITE_URL / SOURCE_URL. Embed footer icons use the bot's own
avatar, resolved at gateway ready, so there is no external logo URL to maintain.
cp .env.example .envand fill it in.go build ./...make migrate-up(or the bot runs migrations itself on start).make run
If ENCRYPTION_KEY is set, the sensitive free-text columns (journal, mood notes,
ghost letters, crisis details, conversation content, feedback, reports) are
AES-256-GCM encrypted at rest, wire-compatible with the JS bot. If unset, those
columns are stored as plaintext.
Public: checkin, insights, coping (breathing, grounding, affirmations,
challenge, distraction, music, gratitude, journal, plan, streaks, toolbox),
ghostletter, crisis (analyze, resources, history), preferences, timemode,
profile, context, feedback, report, joke, compliment, trivia,
wouldyourather, wordgame, memegen, guildsettings, guildcontext,
guilddebug, info commands (ping, about, help, invite, support, privacy, docs,
source, version, stats). Plus the "Check on this message" context-menu command.
Owner-only (deployed to PRIVATE_GUILD_ID, gated by OWNER_IDS / Mellow.owners
/ User.role): mellow, user, guild, debug, feedback-manage,
report-manage, tools, changelog.
DM and @mention replies run through the AI. Background: check-in reminder loop, rotating presence, status poster, word-game sweeper, system log writes.
A message is screened by a keyword/pattern pass first (no AI cost when nothing matches). If something matches, one deterministic (temperature 0) classifier call grades it. Response by tier:
- high / critical (clear intent, plan, or means): a fixed, human-reviewed safety message with verified resources (988, 741741, findahelpline.com). No free-form AI. A CrisisEvent is logged and a mod alert fans out.
- medium (crisis language, not imminent): the normal AI reply proceeds (its system prompt carries a standing safety instruction); the CrisisEvent is logged quietly, no mod alert, no resource dump. Deliberately non-intrusive.
- low / none: nothing.
Every AI sentence on a crisis path is passed through a method-content filter; if it
trips, the fixed template is used instead. Hotline numbers are never AI-generated.
/crisis analyze and the "Check on this message" context menu append the verified
resource block when support is indicated.
/healthz, /v1/stats, /v1/testimonials, /v1/chat, /v1/feedback,
/openapi.json, and Scalar docs at /docs. If API_TOKEN is set, /v1/chat and
/v1/feedback require Authorization: Bearer <token>; the read endpoints stay open.
The running version is baked in at build time:
go build -ldflags "-X main.version=$(git describe --tags --always --dirty)" ./cmd/mellow,
docker build --build-arg VERSION=... ., or Railway/Railpack via railpack.json.
CI (.github/workflows/ci.yml) derives it from the git tag. /version also shows
the latest GitHub release and whether the running build is behind it; a mismatch
is logged to the ops feed at startup.
If OMNIPLEX_TOKEN is set (with CLIENT_ID as the bot ID), the bot posts server
/ user / shard counts to spider.omniplex.gg every 10 min and on start, and syncs
its documented command list (only PUTs when it changed). /changelog (owner-only)
publishes an entry from internal/changelog/PATCHNOTES.md to the listing:
publish [version] [force], list, delete id:, preview. PATCHNOTES.md is the
user-facing changelog (newest ## v<version> - <title> entry at the top); override
the file at runtime with PATCHNOTES_PATH.
The gateway runs through a shard manager with auto-scaling, so shard count follows
Discord's recommendation and grows automatically. Global slash commands are only
re-registered when the command set changed (set MELLOW_FORCE_DEPLOY=1 to force);
the dev/support guild commands (PRIVATE_GUILD_ID) are pushed every start.
Guilds are synced to the Guild table on GuildReady, lazily on each
interaction/message, and by a background job 20s after start and every 30 min
after (SyncGuilds). This backfills servers the bot was already in.
Operational events are written to SystemLog and posted as embeds to
LOG_CHANNEL_ID (a dedicated ops channel) and the PRIVATE_GUILD_ID server's
systemChannelId if set:
- shard connected / reconnected / resumed / disconnected, "guilds loaded" per shard
- guild joined / left (with the running server count)
- bot started / shutting down
- crisis signals (also to the origin guild's
systemChannelId)
Command usage is persisted but not pushed to the feed (too noisy). Browse
everything in Discord with /tools logs [type].
cmd/mellow entrypoint
internal/config env config
internal/logger slog setup
internal/crypto AES-256-GCM field encryption (JS-compatible)
internal/db pool, migrations, sqlc output, encrypting Store
internal/ai Claude client, prompts, crisis, coping, wordgame, meme
internal/github public repo info client
internal/discord gateway, router, commands, component/modal handling
internal/services syslog, reminder, presence, statusposter
internal/server chi HTTP API + OpenAPI
Regenerate query code after editing internal/db/queries/*.sql: make sqlc.
Copyright (C) 2026 Pixelated (CodeMeAPixel), https://codemeapixel.dev
Licensed under the GNU Affero General Public License v3.0. See LICENSE.