Problem / Motivation
Discord (200M+ monthly active users) is a major platform for developer communities, open-source projects, and increasingly enterprise teams. EDDI's multi-agent group discussions map naturally to Discord's channel + thread model.
WARNING: Critical architectural difference from Slack: Discord's HTTP-based Interactions API only receives slash commands and component interactions (button clicks, select menus). Regular channel messages and thread replies require a persistent WebSocket Gateway connection. This fundamentally differs from Slack's pure HTTP Events API model. The recommended approach is a phased implementation.
Architecture Principle
No platform SDKs. All adapters use java.net.http.HttpClient + Jackson for raw HTTP/JSON - no JDA, no Discord4J. Every messaging platform's API is just REST under the hood. Raw HTTP keeps the dependency tree lean, the single-JAR deployment simple, and gives full control over retry logic, error handling, and message formatting. See the existing SlackWebApiClient (245 lines, zero external dependencies) as the reference pattern.
Proposed Solution
Phase 1 - Slash Commands via Interactions API (HTTP)
MVP that matches EDDI's existing HTTP webhook architecture:
| Class |
Responsibility |
Slack Equivalent |
RestDiscordWebhook |
JAX-RS @POST /integrations/discord/interactions. Verifies Ed25519 signature, handles Discord's ping verification, dispatches interactions async. |
RestSlackWebhook |
DiscordSignatureVerifier |
Ed25519 signature verification using Discord's public key. Uses JDK's java.security.Signature.getInstance("Ed25519") (available since JDK 15; EDDI runs on JDK 25). Requires reconstructing the public key from Discord's hex-encoded publicKey. |
SlackSignatureVerifier |
DiscordEventHandler |
Routes Discord slash command interactions to EDDI via ChannelTargetRouter. Creates threads for responses. Maps Discord interactions -> EDDI conversations. |
SlackEventHandler |
DiscordApiClient |
java.net.http.HttpClient-based client calling Discord REST API (/channels/{id}/messages, /interactions/{id}/{token}/callback). Standard Markdown (Discord's native format). Handles X-RateLimit-* headers. |
SlackWebApiClient |
DiscordGroupDiscussionListener |
Implements GroupDiscussionEventListener. Creates a thread for each group discussion, posts agent contributions as thread messages. |
SlackGroupDiscussionListener |
DiscordDeliveryException |
Retryable delivery failure. |
SlackDeliveryException |
Phase 1 limitations:
- Users must invoke via
/eddi <message> slash command (no @mention in regular messages)
- Thread follow-ups require the user to use the slash command again within the thread
- No ambient channel listening (proactive responses, observe mode)
Phase 2 - Gateway for Ambient Listening (Future Enhancement)
Add WebSocket Gateway connection for MESSAGE_CREATE events:
- Enables
@mention triggering (like Slack's app_mention)
- Enables thread reply continuity without slash commands
- Requires: heartbeat management, reconnection logic, intent subscriptions (
MESSAGE_CONTENT privileged intent)
- This is a significant architecture extension and should be a separate follow-up issue
Configuration Model
{
"name": "Dev Community AI Channel",
"channelType": "discord",
"platformConfig": {
"guildId": "123456789",
"channelId": "987654321",
"botToken": "${vault:discord-bot-token}",
"publicKey": "${vault:discord-public-key}",
"applicationId": "111222333"
},
"defaultTargetName": "assistant",
"targets": [...]
}
Key Design Decisions
- Phase 1 = HTTP only - No WebSocket Gateway in the initial implementation. Keeps the adapter architecturally consistent with Slack's pure-HTTP model. Gateway support is a natural Phase 2.
- Slash command registration - Provide documentation for registering slash commands via Discord's API (
PUT /applications/{id}/commands). Consider a setup MCP tool or REST endpoint.
- Deferred response pattern - Discord requires initial interaction acknowledgment within 3 seconds (like Slack). Use
DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE (type 5) to show "thinking...", then follow up with the actual response via webhook.
- Threads for group discussions - Discord threads map well to the Slack pattern. Each agent's contribution -> thread message.
- Ed25519 verification - More involved than Slack's HMAC-SHA256: requires hex-decoding the public key, constructing an
EdDSAPublicKey, and verifying the signature of timestamp + body. JDK 25 supports this natively via java.security.Signature - no external libraries needed.
- Rate limits - Discord uses per-route rate limiting with
X-RateLimit-* response headers. Implement header-driven pacing (respect X-RateLimit-Remaining and X-RateLimit-Reset-After). Channel message sending: 5 requests per 5 seconds per channel.
- Message size limit - 2000 characters (smallest of all platforms). Implement aggressive line-break-aware chunking.
- Loop prevention - Filter interactions with
member.user.bot == true; Discord slash command interactions are always from users (bots can't invoke slash commands), but component interactions need filtering.
Deliverables
Alternatives Considered
- JDA (Java Discord API) - Full-featured library but forces WebSocket Gateway model and adds a large dependency tree. Raw HTTP keeps EDDI lightweight and consistent with the no-SDK principle.
- Gateway-first approach - More feature-complete but architecturally divergent from EDDI's HTTP webhook model. Phased approach is lower risk.
Additional Context
Acceptance Criteria
Problem / Motivation
Discord (200M+ monthly active users) is a major platform for developer communities, open-source projects, and increasingly enterprise teams. EDDI's multi-agent group discussions map naturally to Discord's channel + thread model.
Architecture Principle
Proposed Solution
Phase 1 - Slash Commands via Interactions API (HTTP)
MVP that matches EDDI's existing HTTP webhook architecture:
RestDiscordWebhook@POST /integrations/discord/interactions. Verifies Ed25519 signature, handles Discord's ping verification, dispatches interactions async.RestSlackWebhookDiscordSignatureVerifierjava.security.Signature.getInstance("Ed25519")(available since JDK 15; EDDI runs on JDK 25). Requires reconstructing the public key from Discord's hex-encodedpublicKey.SlackSignatureVerifierDiscordEventHandlerChannelTargetRouter. Creates threads for responses. Maps Discord interactions -> EDDI conversations.SlackEventHandlerDiscordApiClientjava.net.http.HttpClient-based client calling Discord REST API (/channels/{id}/messages,/interactions/{id}/{token}/callback). Standard Markdown (Discord's native format). HandlesX-RateLimit-*headers.SlackWebApiClientDiscordGroupDiscussionListenerGroupDiscussionEventListener. Creates a thread for each group discussion, posts agent contributions as thread messages.SlackGroupDiscussionListenerDiscordDeliveryExceptionSlackDeliveryExceptionPhase 1 limitations:
/eddi <message>slash command (no@mentionin regular messages)Phase 2 - Gateway for Ambient Listening (Future Enhancement)
Add WebSocket Gateway connection for
MESSAGE_CREATEevents:@mentiontriggering (like Slack'sapp_mention)MESSAGE_CONTENTprivileged intent)Configuration Model
{ "name": "Dev Community AI Channel", "channelType": "discord", "platformConfig": { "guildId": "123456789", "channelId": "987654321", "botToken": "${vault:discord-bot-token}", "publicKey": "${vault:discord-public-key}", "applicationId": "111222333" }, "defaultTargetName": "assistant", "targets": [...] }Key Design Decisions
PUT /applications/{id}/commands). Consider a setup MCP tool or REST endpoint.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE(type 5) to show "thinking...", then follow up with the actual response via webhook.EdDSAPublicKey, and verifying the signature oftimestamp + body. JDK 25 supports this natively viajava.security.Signature- no external libraries needed.X-RateLimit-*response headers. Implement header-driven pacing (respectX-RateLimit-RemainingandX-RateLimit-Reset-After). Channel message sending: 5 requests per 5 seconds per channel.member.user.bot == true; Discord slash command interactions are always from users (bots can't invoke slash commands), but component interactions need filtering.Deliverables
ai.labs.eddi.integrations.discord(Phase 1 scope)"discord"toREGISTERED_CHANNEL_TYPESdocs/discord-integration.mdAlternatives Considered
Additional Context
Acceptance Criteria
SlackEventHandlerTestpattern - pure unit tests, no CDI container)"discord"added toREGISTERED_CHANNEL_TYPESinRestChannelIntegrationStore./mvnw testpasses with zero failuresdocs/discord-integration.mdexists (followingdocs/slack-integration.mdstructure)DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE) followed by agent reply