Discord channel plugin for Molecule
discord-channel-molecule is a supervised kind: channel plugin. It connects
one Discord bot to one host workspace through Discord Gateway v10 and Molecule's
public channel API v1 Unix-socket contract. It does not import Molecule Core or
private runtime modules.
Status: the implementation and hermetic end-to-end suite are complete, but the plugin is not yet canary-proven, tagged, or released. Install only an exact reviewed commit in a nonproduction workspace until the canary and manual release gate are complete.
Supported behavior
| Surface | Behavior |
|---|---|
| Direct messages | Accepted only when DMs are explicitly enabled and the user and DM channel IDs are allowlisted. |
| Guild messages | Accepted only when the user, guild, and channel IDs are allowlisted and Discord's structured mentions array contains the exact configured bot ID. Mention-looking text alone does not activate the bot. |
| Replies | Reply to the originating message, suppress all generated mentions, emit typing indicators, and split text losslessly at Discord's 2,000-character limit. |
| Delivery | Persist the authorized payload as queued before the in-memory queue. A restart safely restores queued work. Immediately before A2A, atomically transition to dispatching and discard the payload; a recovered dispatch or ambiguous host/provider result is never replayed. Only definitively delivered/failed tombstones are prunable. |
| Gateway | Persist session_id, sequence, and resume_gateway_url; heartbeat, RESUME, bound the event queue, and use capped reconnect backoff. |
Text messages are the v0.1 scope. Attachments, slash-command Interactions, unsolicited outbound notifications, threads as a distinct conversation model, and webhook-only operation are not implemented.
Architecture and trust boundary
Discord Gateway MESSAGE_CREATE
-> exact bot/user/guild/channel policy
-> owner-only atomic message journal
-> SDK 0.5.4 channel API v1 over authenticated private UDS
-> host workspace agent
-> Discord REST reply (single attempt per chunk)
The runtime injects MOLECULE_CHANNEL_API_VERSION,
MOLECULE_CHANNEL_A2A_SOCKET, MOLECULE_CHANNEL_A2A_TOKEN, and
MOLECULE_CHANNEL_PLUGIN_ID. The bot token and allowlists are workspace
configuration owned by this plugin. Neither set belongs in Core configuration,
an org template, plugin.yaml, a prompt, or a git repository.
Discord application setup
- Create an application and bot in the Discord Developer Portal.
- Enable the privileged
MESSAGE_CONTENTGateway intent. The plugin requests only guild messages, direct messages, and message content. Discord closes a connection that requests this intent without portal authorization. - Generate an OAuth2 bot invite for the nonproduction server. Grant only the channel permissions needed for the canary: View Channel, Send Messages, and Read Message History.
- Enable Developer Mode in Discord and copy the bot user ID, allowed human user IDs, server/guild ID, and exact channel IDs.
- Copy the bot token once into the workspace secret surface. Never paste it into chat, commit it, put it in a URL, or include it in a support log.
Discord's current Gateway, privileged-intent, resume, and message constraints are linked in PROVENANCE.md.
Install an immutable plugin revision
Use the platform's authenticated plugin-management UI or management client to install a reviewed commit, not a moving branch. The immutable source value is:
gitea://molecule-ai/discord-channel-molecule#<reviewed-commit>
Do not place a workspace token in a URL, command argument, shell history, or support conversation. Follow the current platform plugin-install documentation for the credential-safe invocation supported by that client.
Plugin installation restarts the workspace. Configure all values below through the workspace Secrets UI or the authenticated workspace secret-management surface, preferably in one update so the daemon sees a consistent set on its next start.
Configuration
| Name | Required | Meaning |
|---|---|---|
DISCORD_BOT_TOKEN |
yes | Discord bot token; secret and redacted from configuration/log representations. |
DISCORD_BOT_USER_ID |
yes | Exact numeric bot user ID; READY must report the same identity. |
DISCORD_ALLOWED_USER_IDS |
yes | Comma-separated numeric user IDs. Empty or malformed values fail startup. |
DISCORD_ALLOWED_CHANNEL_IDS |
yes | Comma-separated numeric guild or DM channel IDs. |
DISCORD_ALLOWED_GUILD_IDS |
guild use | Comma-separated guild IDs. Empty means every guild message is denied. |
DISCORD_ALLOW_DMS |
no | Exact true enables allowlisted DMs; default false. |
DISCORD_STATE_DIR |
no | Dedicated owner-only journal directory. Defaults to discord-channel-molecule below the runtime's plugin state directory. Existing directories/files must already be owned by the daemon and mode 0700/0600; symlinks, nonregular files, and unsafe parents fail startup. |
DISCORD_QUEUE_SIZE |
no | Bounded inbound queue, 1–1024; default 64. |
DISCORD_MAX_STATE_MESSAGES |
no | Dedup/journal retention, 10–10,000; default 10,000. Queued, dispatching, and ambiguous entries are never pruned. |
DISCORD_HTTP_TIMEOUT_SECONDS |
no | Bounded Discord REST timeout; default 15. |
DISCORD_HOST_TIMEOUT_SECONDS |
no | Bounded workspace turn timeout; default 600. |
DISCORD_RECONNECT_MAX_SECONDS |
no | Reconnect backoff ceiling; default 60. |
DISCORD_API_BASE_URL and DISCORD_ALLOW_INSECURE_TEST_ENDPOINTS exist only
for the hermetic loopback suite. Production accepts only
https://discord.com/api/v10 for REST and wss://gateway.discord.gg for the
Gateway, so a configuration override cannot redirect the bot token.
Canary checklist
Do not release or replace the native path from unit/CI evidence alone.
- Install an exact reviewed commit into one disposable nonproduction workspace.
- Confirm the daemon reaches READY with no secret values in its logs.
- From an allowed user and channel, verify one DM (if enabled) and one exact guild mention each produce one agent reply.
- Verify an unlisted user, unlisted channel, unlisted guild, bot-authored message, webhook-authored message, and mention-looking text without a real Discord mention produce no workspace turn.
- Restart before a controlled queued message crosses A2A and verify it is recovered once. Restart after a controlled dispatch begins and verify the retained ambiguous tombstone prevents a duplicate host turn.
- The human release approver, signed in as Gitea user
hongming, records the exact full commit SHA in one evidence comment oninternal#1071under the same repository owner as this plugin (currentlymolecule-ai) using exactly these two lines once each:discord-channel-molecule canary commit: <full-sha>anddiscord-channel-molecule non-production canary: PASS. - Only then manually dispatch
.gitea/workflows/release.ymlon exactmainwith that commit, evidence URL, and confirmation phrase.
The release workflow never runs on a push or tag. It re-runs all tests, validates
the SDK-vendored client, builds the source/wheel artifacts, verifies that the
exact evidence comment names the exact release SHA, rechecks live main, and
creates v<plugin.yaml version> idempotently through the release-bot token.
The preexisting-tag path, the HTTP 201 body, and the authoritative GET after
every tag POST outcome must all agree on the exact tag name, target SHA, and
canonical message binding that evidence URL; a same-SHA tag without that
provenance fails closed.
Infisical and release credentials reach authenticated HTTP clients only through
stdin/config file descriptors, never process arguments; every authenticated
curl invocation disables ambient curl configuration before reading that input.
The non-mutating
preflight requires authenticated repository push permission; the single tag
attempt plus authoritative reconciliation remains the exact tag-write proof.
Repository administration must protect v* tags from direct pushes.
Migration from the retired native Discord channel
The old Core subsystem used webhook_url and chat_id for outbound Incoming
Webhook delivery plus a Discord Application public_key and platform
Interactions endpoint for inbound slash commands. This plugin instead uses one
bot token and a persistent Gateway connection for conversational DMs and exact
mentions.
There is no automatic credential or behavior conversion:
- create a bot token and explicit allowlists; an Incoming Webhook URL cannot be converted into a bot token;
- do not restore or dual-run the retired Core subsystem for rollback; disable this plugin if the canary fails and leave Discord disconnected while fixing or rolling back to an earlier reviewed plugin commit;
- migrate slash-command users to DM or exact-mention conversations; slash commands require a separately reviewed plugin feature;
- validate inbound and reply continuity at the pinned plugin commit; and
- after the cutover evidence is accepted, revoke the old webhook token and remove the retired Interactions endpoint/public key and legacy configuration.
This is deliberately not described as parity before the real canary.
Development and verification
The repository started from the published molecule-ai-sdk==0.5.4
kind: channel scaffold. channel_sdk.py remains byte-identical to that
release.
python3.11 -m venv .venv
.venv/bin/pip install --require-hashes -r requirements-dev.lock
.venv/bin/ruff check .
.venv/bin/pytest -q -m "not e2e"
.venv/bin/pytest -q -m e2e
.venv/bin/bandit -q -r . -x ./.venv,./tests,./channel_sdk.py
.venv/bin/pip-audit --require-hashes --disable-pip -r requirements.lock
Validate the plugin and SDK vendor boundary with SDK 0.5.4:
molecule-plugin validate plugin .
molecule-plugin channel-sdk check .
CI runs quality, unit, hermetic E2E, SDK drift, dependency audit, and static
security jobs on every pull request and push. The advisory meta-CI consumer also
derives the canonical skill-lint bundle from repo-meta.yaml. No test requires
Discord or Molecule credentials.