Summary
Add a Discord IUserFrontend implementation so RockBot can push real-time notifications to the user (and accept replies) via a Discord bot, initially scoped to a private channel with a single trusted user — i.e. just you and rockbot.
This gives us push-to-mobile notifications (which email-to-self doesn't, for most people's phones) without building a full multi-tenant auth layer.
Motivation
Currently the agent has no way to proactively alert the user in real time. Email-from-self works but isn't a great push/alert channel. Discord bots support mobile push natively, and the existing UserProxy / IUserFrontend abstraction is the right seam to plug into — we already have CLI (Spectre) and Blazor frontends, Discord is a third.
Design
New projects:
src/RockBot.UserProxy.Discord/ — implements IUserFrontend
- Owns the Discord gateway connection and bot token (single point of Discord credential/library exposure)
- Outbound: receives
AgentReply from UserProxyService, posts to the configured channel
- Inbound: receives user messages from the configured channel, forwards to
UserProxyService as user input
src/RockBot.UserProxy.Discord.Host/ (or merge into Discord project) — hostable entry point, mirrors the CLI/Blazor hosts
Configuration (UserProxyOptions or a Discord-specific options type)
BotToken — via user-secrets locally, Kubernetes Secret in deployment
GuildId + ChannelId — restrict the bot to a single server + channel
AuthorizedUserId — Discord user ID allowed to interact; all other authors are ignored
Least-privilege / "nothing trusts the LLM"
- Only this process has the Discord token and a Discord library dependency
- Messages from any Discord user other than
AuthorizedUserId are dropped before they hit the bus
- Even in private-channel mode, outbound events carry
source: discord + principal: <discord-user-id> so we don't have to rework the bus contract when/if we add shared-channel support later
- If the Discord frontend crashes or is compromised, blast radius is one process and one credential
Deployment
- New Helm subchart or deployment in
deploy/helm/rockbot/ — runs as its own pod alongside the agent, similar to how Blazor frontend is deployed
- New Dockerfile:
deploy/Dockerfile.userproxy-discord
Out of scope (future issues)
- Shared/public channels — requires per-principal identity mapping, ACLs, rate limiting, denial-of-wallet protection, context isolation. File separately when we actually want it.
- Signal frontend — Signal has no official bot API;
signal-cli is unofficial and fragile. Separate investigation if ever needed.
- Rich Discord features — slash commands, threads, reactions, role management. Start with plain message send/receive.
Acceptance criteria
Open questions
- Which Discord .NET library — Discord.Net, DSharpPlus, or just the REST+gateway API directly for a minimal surface?
- Gateway connection (supports inbound events) vs. webhook-only (outbound alerts only, much simpler). The full bidirectional experience wants the gateway; a webhook-only "alerts" mode might be a useful stepping stone.
- Should this and a future Signal/Slack/etc. frontend share a "chat-transport" abstraction, or is
IUserFrontend already that abstraction?
Summary
Add a Discord
IUserFrontendimplementation so RockBot can push real-time notifications to the user (and accept replies) via a Discord bot, initially scoped to a private channel with a single trusted user — i.e. just you and rockbot.This gives us push-to-mobile notifications (which email-to-self doesn't, for most people's phones) without building a full multi-tenant auth layer.
Motivation
Currently the agent has no way to proactively alert the user in real time. Email-from-self works but isn't a great push/alert channel. Discord bots support mobile push natively, and the existing
UserProxy/IUserFrontendabstraction is the right seam to plug into — we already have CLI (Spectre) and Blazor frontends, Discord is a third.Design
New projects:
src/RockBot.UserProxy.Discord/— implementsIUserFrontendAgentReplyfromUserProxyService, posts to the configured channelUserProxyServiceas user inputsrc/RockBot.UserProxy.Discord.Host/(or merge into Discord project) — hostable entry point, mirrors the CLI/Blazor hostsConfiguration (
UserProxyOptionsor a Discord-specific options type)BotToken— via user-secrets locally, Kubernetes Secret in deploymentGuildId+ChannelId— restrict the bot to a single server + channelAuthorizedUserId— Discord user ID allowed to interact; all other authors are ignoredLeast-privilege / "nothing trusts the LLM"
AuthorizedUserIdare dropped before they hit the bussource: discord+principal: <discord-user-id>so we don't have to rework the bus contract when/if we add shared-channel support laterDeployment
deploy/helm/rockbot/— runs as its own pod alongside the agent, similar to how Blazor frontend is deployeddeploy/Dockerfile.userproxy-discordOut of scope (future issues)
signal-cliis unofficial and fragile. Separate investigation if ever needed.Acceptance criteria
RockBot.UserProxy.Discordproject implementingIUserFrontendsource+principalmetadata, even in single-user modeOpen questions
IUserFrontendalready that abstraction?