Discord tracker
Use Discord guild channels as a source of work. A message becomes an issue when it mentions the configured bot user or its bot-managed role, or when an allowed user chooses the message command Apps > Track with Lorenz. The message's native Discord thread carries status changes, rich Workpads, progress notes, and human follow-up. Discord Gateway events wake Lorenz immediately, while REST polling remains the authoritative source of truth and the recovery path for missed events.
The provider lives in extensions/discord-tracker and is selected with provider: discord.
Issue model
- A non-bot message in a configured channel becomes an issue when it mentions the configured bot user or a guild role Discord reports as managed by that bot, or the bot marks it through the Track with Lorenz message command.
- Lorenz resolves the managed bot role from the guild automatically. No separate role id is required.
userscan narrow issue creation and interactive status changes to specific Discord user ids.- The bot's
marker_emojireaction records ownership for a message selected through the context menu. A human reaction with the same emoji never creates work. - The issue id is
<channel-id>:<message-id>, for example223456789012345678:723456789012345678. - The issue identifier is
DSC-<channel-id>-<message-id>. It is a display label, not a tool input. - Hashtags become lowercase labels.
#route-backendtherefore works with Lorenz's route-label dispatch rules. - The first status is
Todounless the bot already owns a configured status reaction.
Discord creates a native thread from the source message on the first comment or status update. A thread created from a message has the same id as that source message, so Lorenz can recover the discussion after a restart without persisting a second identifier.
After Lorenz claims a new Todo issue, it starts a best-effort 👀 acknowledgement alongside
agent setup. The source message therefore shows activity without waiting for workspace preparation
or the agent's first tool call. The reaction provides the usual In Progress fallback until the
native thread has an authoritative status event. An acknowledgement failure is observable but never
fails the claimed run.
Create the Discord bot
- Create an application in the Discord Developer Portal and add a bot.
- On the bot page, enable the Message Content Intent. Messages that mention the bot are visible without it, but complete human discussion in issue threads requires the intent.
- Install the bot in the target guild with these channel permissions:
- View Channels
- Read Message History
- Send Messages
- Create Public Threads
- Send Messages in Threads
- Add Reactions
The
applications.commandsscope is included automatically when thebotscope is selected.
- Enable Developer Mode in Discord, then copy the guild, channel, bot user, and permitted requester ids.
- Export the token as
DISCORD_BOT_TOKEN. Keep it out of workflow files and source control.
The configured channels must be guild text or announcement channels that support threads. Direct messages, forum parents, and media parents are not tracker sources.
Configure the tracker
tracker:
kind: work
trackers:
work:
provider: discord
guild_id: $DISCORD_GUILD_ID
channels:
- $DISCORD_CHANNEL_ID
bot_user_id: $DISCORD_BOT_USER_ID
users:
- $DISCORD_REQUESTER_ID
emoji_states:
"👀": In Progress
"✅": Done
"❌": Cancelled
scan_lookback_days: 30
active_states: [Todo, In Progress]
terminal_states: [Done, Cancelled]
polling:
interval_ms: 60000
guild_id, every channels entry, bot_user_id, and every users entry must be a 17-20 digit
Discord snowflake. $VAR references are resolved during config parsing. Unresolved list entries are
dropped, and dispatch validation fails if that leaves no channel.
| Key | Default | Meaning |
|---|---|---|
api_key |
DISCORD_BOT_TOKEN |
Bot token used for REST and Gateway authentication. |
endpoint |
https://discord.com/api/v10 |
REST base URL. Override only for a controlled test proxy. |
guild_id |
DISCORD_GUILD_ID |
Guild that scopes Gateway events and message permalinks. Required. |
channels |
none | Guild text or announcement channel ids to scan. Required. |
bot_user_id |
DISCORD_BOT_USER_ID |
Bot id used for user and managed-role mentions. Required. |
users |
empty | Optional requester allowlist. Empty permits any non-bot author. |
emoji_states |
👀, ✅, ❌ |
Emoji-to-state overrides merged over the defaults. |
marker_emoji |
🤖 |
Bot-owned reaction that marks a context-menu tracked message. |
scan_lookback_days |
0 |
Fixed trailing candidate window. Zero is unbounded. |
assignee is rejected because Discord messages have no assignee field.
Status and discussion
Status lives in the native thread. The latest valid event wins:
discord_update_statuspostsstatus: <Name>as the bot.- A human can use
/status,/start,/done,/cancel, or/reopeninside the issue thread. - A human can select another workflow-configured status on a rich Workpad.
- A later allowed human mention continues the issue in
In Progress, including fromTodoor a terminal state. - When no thread status exists, only reactions owned by the bot can provide state. Human reactions never change an issue.
Lorenz acknowledges every command or button interaction immediately with a deferred private response, performs the authoritative status write, then replaces that response with a success or error card. Commands outside a tracked issue thread fail privately without changing state. Guild commands are registered when the Gateway watcher starts, so they are available without a global command propagation delay. A newly tracked context-menu message enters the next dispatch check directly after Discord confirms the marker and thread writes, so it does not wait for a historical channel scan. The ordinary REST scan still repairs missed events and process restarts.
The bot mirrors explicit status transitions onto its own source-message reaction. Polling also self-heals a stale mirror after a human command or bare re-mention. These heals run in a serialized background queue, so rate limits on reaction writes never delay issue discovery or dispatch. Mirror failures do not change the thread-derived authoritative state.
Rich Workpads
discord_workpad posts a native Components V2 card in the issue thread. It renders the environment,
plan, acceptance criteria, validation commands, and progress as separate Markdown sections inside
an accented container. Its action row derives every option from the configured active and terminal
states, so the same selector remains valid across later status changes. The selector supports
Discord's native limit of 25 options. Outbound mentions are disabled, and long sections are split
across bounded Text Display components.
Use discord_comment for incremental progress after the initial Workpad. Plain comments remain
appropriate for chronological notes, while the Workpad provides a compact structured starting
point and the issue's primary interactive controls.
Discord and Slack behavior
Both trackers keep thread state authoritative, use bot-owned reactions only as a visual mirror, and offer equivalent agent tools for status, comments, reads, queries, user lookup, and channel context. Discord adds native interaction surfaces while preserving the same recovery model.
| Behavior | Slack | Discord |
|---|---|---|
| Create work | Bot mention in a root or reply | Bot or managed-role mention, plus Track with Lorenz on any allowed source message |
| Human status control | Text commands in the thread | Slash commands and a Workpad status selector with private confirmations |
| Push path | Optional Socket Mode and a second app token | Gateway using the existing bot token |
| Workpad | Plain thread reply | Components V2 container with Markdown sections and a status selector |
| Conversation | Slack thread | Native Discord thread |
| Recovery | REST polling and authoritative thread read | REST polling and authoritative thread read |
The Discord path is more interactive without weakening correctness: the Gateway is only the
low-latency delivery path, every mutation writes the same authoritative status: <Name> thread
record used by tools and polling, and private interaction errors do not clutter the issue thread.
Agent tools
The tracker automatically mounts the discord tool pack:
| Tool | Purpose |
|---|---|
discord_update_status |
Post the authoritative status event and update the reaction mirror. |
discord_workpad |
Post a structured Components V2 Workpad with a native status selector. |
discord_comment |
Post a progress note in the native issue thread. |
discord_read_thread |
Read the source, resolved state, permalink, reactions, and thread. |
discord_query |
Query tracked messages with filtering, projection, ordering, and paging. |
discord_user_info |
Resolve a Discord user id. |
discord_channel_context |
Read a bounded context window around the source message. |
Every per-issue tool validates that the issue references a configured channel and a currently
tracked mention or bot-owned marker before reading or writing. discord_query intersects requested
channels with the configured channel allowlist.
Gateway and polling
The Gateway connection subscribes only to guild, guild-message, guild-reaction, and message-content intents. It filters events to configured source channels and already-known issue threads. A relevant event asks the runtime to poll immediately. It never becomes a second source of issue state.
The connection heartbeats, checks acknowledgements, resumes with the last sequence after a recoverable disconnect, and falls back to a fresh identify when the session cannot be resumed. Fatal authentication or disallowed-intent close codes stop push wake-ups, but interval polling continues.
REST requests retry HTTP 429 responses according to Discord's retry_after value. Idempotent reads
and reaction writes also retry bounded 5xx failures. Message posts are not retried after ambiguous
5xx responses, which prevents duplicate thread comments. Outbound comments disable mention parsing
so agent text cannot unexpectedly ping users, roles, or everyone.
Troubleshooting
No issues are discovered
- Confirm the message mentions the bot user or its bot-managed guild role, or use Apps > Track with Lorenz on the message.
- Confirm the author is present in
users, when that allowlist is non-empty. - Confirm the bot can view the channel and read message history.
- Increase
scan_lookback_daysor set it to0when the source message is older than the window.
Thread comments fail
Confirm the channel supports native threads and the bot has Create Public Threads, Send Messages in Threads, and Read Message History permissions.
Push wake-ups stop
Enable Message Content Intent in the Developer Portal. A Gateway close code 4014 means the bot
requested a privileged intent that is not enabled or approved. Polling continues as the fallback,
so the symptom is increased dispatch latency rather than lost work.