A Discord bot that brings Pi into your server. Mention the bot or use slash commands to run Pi with full tool access, persistent sessions, and optional project extensions.
How it works: A detached daemon listens for Discord mentions, DMs, and slash commands. Each channel gets its own persistent Pi session, so follow-up questions remember earlier conversation. When a message comes in, the daemon calls session.prompt(), subscribes to the response stream, and live-updates the Discord reply as text streams back. Operator runs /discord start|stop|status from Pi to control it.
Requires a bot token and application id from the Discord Developer Portal.
/discord setup
/discord start
# In Discord
/pi ask text:"Summarize the errors in the latest deploy"
@your-bot check the logs and tell me what failed
/discordcommands in Pi for setup, start, stop, status, logs, config- detached daemon that stays connected to Discord independently of Pi
- durable per-route state: registry, queue, journal, sessions, memory
- routing scoped to channel or thread by Discord ids
- headless Pi sessions via
createAgentSession() - slash commands and @mention ingress
discord_uploadtool for posting files back to Discorddiscord_reacttool for adding emoji reactions to messages- emoji reaction awareness — user reactions appear as passive context on the bot's next turn
- throttled message updates with details-thread fallback
- DM allowlisting and admin controls for stop/reset
pi install npm:pi-discordThen restart Pi so it discovers the extension.
Inside Pi:
/discord setup
/discord start
/discord status
That writes the runtime config, optionally syncs Discord slash commands, and launches the detached daemon.
Once the bot is in your server, you can talk to it with either:
/pi ask text:"Check the repo status and summarize"
or a direct mention:
@your-bot inspect the latest error screenshot
To stop the helper process:
/discord stop
To inspect logs:
/discord logs 120
This is the part that usually goes wrong. Here's a quick checklist, followed by detailed steps.
- Developer Portal → Create Application
- General Information → Copy
Application ID - Bot → Create Bot → Copy
Token - Bot → Enable
Message Content Intent✓ - OAuth2 → URL Generator:
- Scopes:
bot,applications.commands - Permissions: View Channels, Send Messages, Create Public Threads, Send Messages in Threads, Read Message History, Attach Files, Embed Links
- Scopes:
- Copy invite URL → Add bot to your server
- Discord Settings → Enable Developer Mode → Right-click server → Copy Server ID
Go to the Discord Developer Portal at https://discord.com/developers/applications and create a new application. Give it a name that matches the bot you want to run.
Open the application's General Information page. Copy the Application ID. You will put that into the applicationId field in the pi-discord config. You can ignore the Public Key for now because this package currently talks to Discord through the gateway rather than receiving webhook interactions directly.
Open the Bot page and create a bot user if Discord has not done that already. Reset the token if needed, then copy the bot token. Put that into botToken.
Still on the Bot page, decide whether you need the Message Content Intent.
You should enable Message Content Intent if you want any of the following:
- mention-triggered prompts that rely on full message content
- ambient recent-channel context in normal guild channels
- attachment ingestion from ordinary message events
- a more natural “talk to the bot in chat” flow rather than slash-command-only usage
If your bot is or becomes a verified bot in 100+ servers, Discord may require privileged-intent approval for full behavior in ordinary guild traffic. Without that approval, the safest expectation is degraded mode built around slash commands, DMs, and direct mentions.
Go to OAuth2 → URL Generator.
Select these scopes:
botapplications.commands
Select permissions that let the bot operate in channels and threads. At minimum you will usually want:
View ChannelsSend MessagesCreate Public ThreadsSend Messages in ThreadsRead Message HistoryAttach FilesEmbed Links
If you want private-thread behavior in a locked-down server, add the thread permissions your server policy requires. Keep permissions minimal; the bot does not need broad moderation powers.
Use the generated invite URL to add the bot to your server.
After the bot is in the server, collect the guild ids you want to allow. In Discord, enable Developer Mode in advanced settings, then right-click the server and copy the server id. Add those to allowedGuildIds if you want an explicit allowlist.
The easiest path is:
/discord setup
That prompts for the bot token, application id, and a comma-separated guild allowlist, then writes the JSON config file at:
~/.pi/agent/pi-discord/config.json
You can also edit the config directly with:
/discord open-config
Or re-sync slash commands manually with:
/discord sync-commands
The runtime config lives at:
~/.pi/agent/pi-discord/config.json
Current fields:
botToken: Discord bot token from the Bot pageapplicationId: Discord application id from General InformationallowedGuildIds: optional guild allowlist. Empty means any guild the bot joins is acceptedadminUserIds: Discord user ids allowed to stop active runs and reset routesdmAllowlistUserIds: Discord user ids allowed to use the bot in DMscommandName: slash-command root. Defaults topi, which creates/pi ask,/pi status,/pi stop, and/pi resetregisterCommandsGlobally: iftrue, registers commands globally instead of guild-scopedsyncCommandsOnStart: iftrue,/discord startsyncs slash commands before starting the daemonworkspaceMode:dedicatedorsharedsharedExecutionRoot: execution root to use whenworkspaceModeissharedrouteOverrides: per-route overrides for execution root or workspace modeallowProjectExtensions: iftrue, bot sessions load discovered extensions in addition to the built-in helper extension. This is less safe in headless modeenableImageInput: iffalse, image attachments stay on disk and are described in text instead of being sent as model image inputenableDetailsThreads: iftrue, the daemon will try to open and reuse a details thread for tool chatter and uploadsglobalConcurrency: max routes processed at oncequeueLeaseMs: queue lease duration before abandoned work is recoveredprimaryFlushMs: cadence for throttled primary-message edits while the assistant is streamingdefaultModel: optionalprovider/model-idfor new routesdefaultThinkingLevel: Pi thinking level for new routes
Inside Discord, the package currently supports these slash subcommands under whatever commandName is configured:
/pi ask text:"..."/pi status/pi stop/pi reset
In addition, a direct mention in a guild channel or a DM from an allowlisted user will enqueue work for the current route.
Once a route already exists, ordinary non-mention guild messages in that same surface are journaled as ambient context instead of immediately triggering the agent.
Inside Pi, the extension exposes:
/discord setup/discord open-config/discord sync-commands/discord start/discord stop/discord status/discord logs [lines]/discord help
The package code stays in the extension directory. Mutable runtime state lives separately under:
~/.pi/agent/pi-discord
That workspace contains:
config.jsonlogs/daemon.logrun/status.jsonrun/daemon.pidand lock stateroutes/with per-route manifests, journals, attachments, queue state, and route session filesworkspaces/with dedicated execution roots for dedicated-mode routes
This separation is intentional. Runtime state should not be mixed into the extension package itself.
A route is keyed by Discord identity, not by an arbitrary chat title. The route key is built from:
- guild id or
dm - channel id
- optional thread id
Each route owns:
- a route manifest
- a durable queue
- an append-only transport journal
- a route memory file
- a session storage directory
- inbound and outbound attachment folders
- an execution root
By default, each route gets a persistent Pi session. The route manifest keeps the stable mapping between the route key and the current session file, plus message/thread ids used for outbound rendering.
Bot sessions are created with the Pi SDK, not with a separate custom context store. The daemon uses createAgentSession() and binds a small helper extension so it can:
- inject route memory and bounded recent Discord context at request time
- expose a
discord_uploadtool for explicit file egress back into Discord - expose a
discord_reacttool for adding emoji reactions to the triggering message - keep the session headless-safe by avoiding interactive UI assumptions
Raw Discord text is sent through session.prompt(..., { expandPromptTemplates: false, source: "extension" }) so normal Discord content beginning with / stays literal unless it came from an explicit Discord slash command.
Inbound attachments are downloaded into the route workspace before the run starts.
If an attachment is an image and the selected model supports image input, the daemon passes it through as Pi image content. Otherwise the saved file path is still included in the prompt context so the model can reason about the file as a normal artifact.
For outbound files, the bot session can use the discord_upload tool to post a generated file back into the current Discord surface or details thread.
The daemon writes structured JSON log lines to:
~/.pi/agent/pi-discord/logs/daemon.log
The current extension surfaces those logs with:
/discord logs 200
Daemon health is summarized in:
/discord status
which reports whether the daemon is running, its pid, known route count, and currently active runs.
Queueing: Each route has a durable queue that survives daemon restarts. Work items are leased with expiration timestamps, so if the daemon crashes mid-run, abandoned work is automatically recovered and retried on the next startup.
Journaling: An append-only journal records all inbound messages, edits, deletions, and assistant responses for each route. This provides bounded recent context for new prompts and supports post-hoc debugging without relying on Discord message history.
Route registry: A central registry tracks known routes by their Discord identity (guild/channel/thread). Each route maintains its own manifest with pointers to the current session file, primary message id, and details thread id.
Restart recovery: On startup, the daemon iterates known routes, recovers any expired queue leases, and backfills recent channel messages into the journal as ambient context. This means the bot can pick up where it left off even after an unexpected exit.
Detached daemon: The Discord gateway connection runs in a separate long-lived process (pi-discord-daemon) rather than inside Pi's runtime. This lets the bot stay online independently of any interactive Pi session.
A few constraints are deliberate.
DMs are deny-by-default and only open to ids listed in dmAllowlistUserIds.
Stop and reset controls are restricted to ids listed in adminUserIds.
Project extensions are off by default in bot sessions because many extensions assume an interactive TUI and human supervision.
The package stores route state separately from the extension package so updates or reinstalls do not trample mutable bot data.
No launchd or systemd service generation yet. No UI for editing route overrides. No full setup wizard beyond /discord setup. Some config fields exist for architectural reasons but aren't fully exercised. Gateway-only for now, no webhook ingestion.
If /discord start fails immediately, run:
/discord status
/discord logs 200
If slash commands do not appear, check these in order:
applicationIdis correct- the bot token belongs to the same application
- the bot was invited with
applications.commands - the bot is in one of the
allowedGuildIds, orregisterCommandsGloballyis enabled - command sync did not fail during
/discord start
If mentions do nothing, check:
- the bot can read the channel
Message Content Intentis enabled where needed- the daemon is running
- the guild is allowlisted if you are using
allowedGuildIds
If DMs do nothing, check that the sender id is listed in dmAllowlistUserIds.
If stop buttons or the /pi reset command refuse to work, check that the requester id is in adminUserIds.
Install dependencies and run the test suite:
npm install
npm testThe current test coverage focuses on config validation and recovery paths, route identity and initialization, queue/registry/manifest persistence hardening, interaction scoping, daemon status handling, and core authorization behavior.

