Discord
The Discord adapter discd connects one bot account to your Discord server and sends the messages to the agents of your groups. Your route table decides whether each channel has its own agent or the whole server shares one.
A Discord message passes five steps
discdreceives the message through the Discord Gateway WebSocket. It converts the message to an inbound message with a typed JID.routdstores the message and reads the route table. The first matching route selects the group folder.routdstarts the turn.runedstarts a KVM guest with the agent of that group and gives it the message over vsock.- The agent runs, calls MCP tools, and then calls
replyorsend. discdsends the answer of the agent to the Discord channel.
A thread uses the JID of its parent channel
Route rules match against these JIDs:
| chat type | JID format | example |
|---|---|---|
| server channel | discord:<guild_id>/<channel_id> | discord:123456789/987654321 |
| server thread | the JID of the parent channel, with the thread ID as the topic | discord:123456789/987654321 |
| DM | discord:dm/<channel_id> | discord:dm/111222333 |
To see the IDs, open Settings → Advanced in Discord and enable Developer Mode. Then right-click a channel or a server and select Copy Channel ID or Copy Server ID.
The verb of a message tells what happened
Each inbound message has a verb. A route can match on verb= to select the events that start a turn:
| verb | when |
|---|---|
| empty | a normal message in a server channel or a DM |
mention | the message mentions the bot, or it is a reply to a bot message |
like | a reaction that is not in the negative list |
dislike | a negative reaction, for example 👎 |
discd uses a bot token or a user token
| mode | env var | use |
|---|---|---|
| bot token | DISCORD_BOT_TOKEN | shared deployments, servers that you do not own, production |
| user token | DISCORD_USER_TOKEN | personal use with your own account, servers where you cannot add a bot |
discd uses the user token. If neither is set, discd stops at start.Step 1: make a Discord application
Skip this step if you use a user token.
- Open discord.com/developers/applications and click New Application.
- Give it a name. This is the name of the app, not of the bot user.
- Open Bot in the left sidebar. Click Add Bot if Discord asks.
- Under Privileged Gateway Intents, enable Message Content Intent. Without it,
discdgets messages with no text. Server Members Intent is necessary to read member names in DMs. Presence Intent is optional. - Click Reset Token and copy the bot token. Discord shows it only once.
.env file of the instance, never in code or in a chat.Step 2: invite the bot to your server
Skip this step if you use a user token, because your account is already in your servers.
- In the application, open OAuth2 → URL Generator.
- Select the scope
bot. - Select the permissions. View Channels, Read Message History and Send Messages are necessary. Attach Files sends files and voice. Add Reactions lets the agent use the
liketool. Manage Messages lets the agent use thedeletetool, and it is optional. - Open the generated URL in a browser, select your server and click Authorize.
Step 3: configure the instance
Add the token to /srv/data/arizuko_<instance>/.env:
# bot mode
DISCORD_BOT_TOKEN=your_bot_token_here
# user mode, personal use
# DISCORD_USER_TOKEN=your_user_token_here
Select the package and restart. The existing package selections stay:
./arizuko package add -i <instance> catalog:discd
sudo systemctl restart arizuko_<instance>
Read the log to see that the adapter is connected:
sudo journalctl -u arizuko_<instance> --since "30s ago" --no-pager \
| grep -E "discord connected|channel registered"
discd logs discord connected, and routd logs channel registered.
Step 4: add a route
Without a route, routd stores the Discord messages and starts no turn. Add at least one route. The CLI is the shortest way:
./arizuko route -i <instance> add 'platform=discord' main
The operator dashboard has the same function at /dash/routes/. You can also ask the root agent in a chat: "add a route that sends all Discord messages to main". The agent calls the add_route MCP tool. A new route applies at once, with no restart.
The first route that matches decides the folder
routd reads the routes in seq order, the lowest first. The match field is a list of key=glob predicates with spaces between them. All predicates must match. In a glob, * does not match /.
| match | what it routes |
|---|---|
platform=discord | all Discord messages, server channels and DMs |
chat_jid=discord:<guild>/<channel> | one channel |
platform=discord room=<guild>/* | all channels of one server, no DMs |
platform=discord verb=mention | only mentions and replies to the bot |
| empty | every message, as the last route |
One agent for all servers
./arizuko route -i <instance> add 'platform=discord' main
Every message from every channel of every server where the bot is goes to the main group.
One agent for each channel
./arizuko route -i <instance> add 'chat_jid=discord:123456789/111111111' engineering --seq 10
./arizuko route -i <instance> add 'chat_jid=discord:123456789/222222222' marketing --seq 20
./arizuko route -i <instance> add 'platform=discord' main --seq 30
The last route takes the messages of all other channels.
Mention-only channels
To make the agent answer only on a mention, use two routes. The first route starts a turn on a mention. The second route uses #observe mode: it stores every other message in the same folder as context and starts no turn.
seq match target
10 platform=discord room=123456789/* verb=mention main
20 platform=discord room=123456789/* main#observe
Route 10 starts the agent on mentions and on replies to the bot. Route 20 stores every other message of the server under main, so the agent has the full context at its next turn. DMs do not match these routes. Add a separate route for them.
After the agent replies in a thread, that channel and thread is engaged for 30 minutes by default. During that time, each message from a person in the thread starts a turn without a mention, although route 20 says #observe. This is by design: an agent that stops in the middle of a conversation is worse than the extra turns. ENGAGEMENT_TTL on routd sets the time.
Routing modes lists all target fragments.
A thread is a separate conversation
When a message arrives in a thread, discd uses the JID of the parent channel and sets the topic to the thread ID. The agent sees each thread as its own conversation, and a reply to a thread stays in that thread.
The agent uses the same tools as on every platform
In the guest, the agent calls MCP tools:
# reply in the same channel or thread
reply text:="Here is your answer."
# send to one channel by JID
send chatJid:="discord:123456789/987654321" text:="Scheduled report ready."
# add a reaction; targetId is the platform_id of the message
like chatJid:="discord:123456789/987654321" targetId:="1234567890" reaction:="👍"
# send a file
send_file chatJid:="discord:123456789/987654321" \
filepath:="/home/node/tmp/report.pdf" filename:="report.pdf"
The agent can also edit and delete its own messages, and quote a message with a comment.
post does not work on Discord. Discord has no broadcast feed. Use send to a channel.The health endpoint has three states
discd serves GET /health on its internal port :8080:
| status | HTTP | meaning |
|---|---|---|
| ok | 200 | connected and receives messages |
| stale | 200 | connected, but no inbound message for 5 minutes; a quiet server is normal |
| disconnected | 503 | the WebSocket is not open; check the token and the network |
./arizuko status -i <instance>
Six problems have a known cause
- The bot is in the server and does not answer
- Check that a route exists with
./arizuko route -i <instance> list. Without a route, no turn starts. - Messages arrive with no text in bot mode
- Message Content Intent is off. Open the Discord Developer Portal, then Bot, then Privileged Gateway Intents, and turn it on. User mode needs no intents.
discdreports "disconnected"- The token is wrong or was reset. In bot mode, make a new token in the Developer Portal and update
DISCORD_BOT_TOKEN. In user mode, checkDISCORD_USER_TOKEN, because a password change or a forced logout cancels a user token. Then restart the instance. - Replies go to the wrong channel
- The agent calls
sendwith a fixed JID. Usereply, which answers in the chat of the message. - Only mentions start the agent
- The matching route has
verb=mention, or the last route is in#observemode. Remove the verb predicate or the#observefragment, so that all messages start a turn. - The bot does not see the messages of a channel
- Give the bot the View Channel permission for that channel in Discord.
discd reads five variables
| variable | required | default | notes |
|---|---|---|---|
DISCORD_BOT_TOKEN | one of two | — | from the Discord Developer Portal, under Bot |
DISCORD_USER_TOKEN | one of two | — | the token of a personal account; discd uses it when both are set |
LISTEN_ADDR | no | :8080 in the fragment | internal HTTP address for health and file serving |
ASSISTANT_NAME | no | — | replaces <@bot_id> mentions with a readable name in stored messages |
MEDIA_MAX_FILE_BYTES | no | 20971520, 20 MiB | the largest attachment that discd serves |