arizuko

arizuko › howto › Discord

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

  1. discd receives the message through the Discord Gateway WebSocket. It converts the message to an inbound message with a typed JID.
  2. routd stores the message and reads the route table. The first matching route selects the group folder.
  3. routd starts the turn. runed starts a KVM guest with the agent of that group and gives it the message over vsock.
  4. The agent runs, calls MCP tools, and then calls reply or send.
  5. discd sends 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 typeJID formatexample
server channeldiscord:<guild_id>/<channel_id>discord:123456789/987654321
server threadthe JID of the parent channel, with the thread ID as the topicdiscord:123456789/987654321
DMdiscord: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:

verbwhen
emptya normal message in a server channel or a DM
mentionthe message mentions the bot, or it is a reply to a bot message
likea reaction that is not in the negative list
dislikea negative reaction, for example 👎

discd uses a bot token or a user token

modeenv varuse
bot tokenDISCORD_BOT_TOKENshared deployments, servers that you do not own, production
user tokenDISCORD_USER_TOKENpersonal use with your own account, servers where you cannot add a bot
Set one token. If both are set, discd uses the user token. If neither is set, discd stops at start.
A user token runs the agent as your own account. The Discord terms of service limit automated use of user accounts. Use a user token only on an account that you own and can lose. For production and team deployments, use a bot token.

Step 1: make a Discord application

Skip this step if you use a user token.

  1. Open discord.com/developers/applications and click New Application.
  2. Give it a name. This is the name of the app, not of the bot user.
  3. Open Bot in the left sidebar. Click Add Bot if Discord asks.
  4. Under Privileged Gateway Intents, enable Message Content Intent. Without it, discd gets messages with no text. Server Members Intent is necessary to read member names in DMs. Presence Intent is optional.
  5. Click Reset Token and copy the bot token. Discord shows it only once.
A token is a password. Keep tokens only in the .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.

  1. In the application, open OAuth2 → URL Generator.
  2. Select the scope bot.
  3. 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 like tool. Manage Messages lets the agent use the delete tool, and it is optional.
  4. 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 /.

matchwhat it routes
platform=discordall 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=mentiononly mentions and replies to the bot
emptyevery 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:

statusHTTPmeaning
ok200connected and receives messages
stale200connected, but no inbound message for 5 minutes; a quiet server is normal
disconnected503the 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.
discd reports "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, check DISCORD_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 send with a fixed JID. Use reply, 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 #observe mode. Remove the verb predicate or the #observe fragment, 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

variablerequireddefaultnotes
DISCORD_BOT_TOKENone of two—from the Discord Developer Portal, under Bot
DISCORD_USER_TOKENone of two—the token of a personal account; discd uses it when both are set
LISTEN_ADDRno:8080 in the fragmentinternal HTTP address for health and file serving
ASSISTANT_NAMEno—replaces <@bot_id> mentions with a readable name in stored messages
MEDIA_MAX_FILE_BYTESno20971520, 20 MiBthe largest attachment that discd serves