Problem / Motivation
Microsoft Teams (320M+ monthly active users) is the dominant enterprise communication platform. EDDI's multi-agent orchestration capabilities - especially group discussions with debate, peer review, and round table styles - are a natural fit for enterprise Teams channels.
WARNING: Teams has the most complex authentication model of all target platforms. The Bot Framework uses Azure AD JWT tokens for incoming message verification, requiring OpenID metadata fetching, key rotation handling, and multi-claim validation. Plan for this complexity.
Key Teams constraints:
- No threading in Group Chats - Threads (reply chains) are only supported in Channels. Group Chats are flat message streams. Multi-agent debates MUST deploy in Channels.
- @mention-gated by default - Bots only receive messages when explicitly @mentioned unless Resource-Specific Consent (RSC) is configured with
ChannelMessage.Read.Group permission.
- Adaptive Cards (v1.5) - Teams uses Adaptive Cards for rich formatting. Schema version 1.5 is the maximum supported by Teams.
- Azure AD registration required - Each bot needs an Azure AD app registration with
appId + appPassword.
Architecture Principle
No platform SDKs. All adapters use java.net.http.HttpClient + Jackson for raw HTTP/JSON - no Bot Framework SDK. The Bot Framework REST API is well-documented enough for direct integration, and 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
Create ai.labs.eddi.integrations.teams package:
| Class |
Responsibility |
Slack Equivalent |
RestTeamsWebhook |
JAX-RS @POST /integrations/teams/messages. Receives Bot Framework Activity payloads, validates JWT, dispatches async. |
RestSlackWebhook |
TeamsTokenVerifier |
Validates incoming Bot Framework JWT tokens against Azure AD OpenID metadata. Fetches signing keys from https://login.botframework.com/v1/.well-known/openidconfiguration. Caches keys, handles rotation. |
SlackSignatureVerifier |
TeamsEventHandler |
Routes Teams activities to EDDI via ChannelTargetRouter. Handles message, conversationUpdate activity types. Strips <at> mention tags. Maps Teams conversations -> EDDI conversations. |
SlackEventHandler |
TeamsApiClient |
Posts messages/Adaptive Cards back via Bot Framework REST API (POST /v3/conversations/{id}/activities). Acquires access tokens from login.microsoftonline.com. Converts Markdown -> Adaptive Card JSON (v1.5) for rich responses, plain text for simple ones. |
SlackWebApiClient |
TeamsGroupDiscussionListener |
Implements GroupDiscussionEventListener. Posts agent contributions as reply chain activities in the channel. Uses replyToId for threading. |
SlackGroupDiscussionListener |
TeamsDeliveryException |
Retryable delivery failure. |
SlackDeliveryException |
Configuration Model
{
"name": "Engineering Teams Channel",
"channelType": "teams",
"platformConfig": {
"channelId": "19:abc123@thread.tacv2",
"appId": "${vault:teams-app-id}",
"appPassword": "${vault:teams-app-password}",
"tenantId": "your-tenant-id",
"serviceUrl": "https://smba.trafficmanager.net/teams/"
},
"defaultTargetName": "assistant",
"targets": [...]
}
Key Design Decisions
- Channel-only deployment - Document clearly that Teams Group Chats do NOT support threading and are incompatible with multi-agent debates. Channels are mandatory.
- JWT verification complexity - This is the hardest verification of all platforms. Recommended: full validation - verify JWT signature against Bot Framework's published keys (OpenID discovery), validate audience (
appId), issuer, expiration, serviceUrl claim. Cache OpenID metadata with refresh on 401.
- Adaptive Card rendering - For rich responses (tables, structured data, code blocks), generate Adaptive Card JSON (v1.5). For simple text responses, use plain text with basic HTML formatting (
<b>, <i>, <code>). Teams does NOT support standard Markdown in bot messages.
- RSC permissions - Document required manifest entry for ambient listening without @mentions. Without RSC, the bot only receives @mentioned messages (which may be sufficient for trigger-keyword use cases).
- Outgoing auth - Bot must acquire access tokens from
https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token using client credentials flow. Cache tokens until expiry.
- Message size - ~28KB for text, separate limits for Adaptive Card payloads. Implement chunking for long agent responses.
- Loop prevention - Filter activities where
activity.from.id matches the bot's appId.
Deliverables
Alternatives Considered
- Microsoft Bot Framework SDK for Java - Adds heavy dependency; EDDI's approach is raw HTTP (no SDK for Slack either), consistent with the no-SDK principle. The Bot Framework REST API is well-documented enough for direct integration.
- Azure Bot Service as proxy - Adds infrastructure complexity and cost. Direct webhook is simpler and keeps EDDI self-contained.
Additional Context
Acceptance Criteria
Problem / Motivation
Microsoft Teams (320M+ monthly active users) is the dominant enterprise communication platform. EDDI's multi-agent orchestration capabilities - especially group discussions with debate, peer review, and round table styles - are a natural fit for enterprise Teams channels.
Key Teams constraints:
ChannelMessage.Read.Grouppermission.appId+appPassword.Architecture Principle
Proposed Solution
Create
ai.labs.eddi.integrations.teamspackage:RestTeamsWebhook@POST /integrations/teams/messages. Receives Bot Framework Activity payloads, validates JWT, dispatches async.RestSlackWebhookTeamsTokenVerifierhttps://login.botframework.com/v1/.well-known/openidconfiguration. Caches keys, handles rotation.SlackSignatureVerifierTeamsEventHandlerChannelTargetRouter. Handlesmessage,conversationUpdateactivity types. Strips<at>mention tags. Maps Teams conversations -> EDDI conversations.SlackEventHandlerTeamsApiClientPOST /v3/conversations/{id}/activities). Acquires access tokens fromlogin.microsoftonline.com. Converts Markdown -> Adaptive Card JSON (v1.5) for rich responses, plain text for simple ones.SlackWebApiClientTeamsGroupDiscussionListenerGroupDiscussionEventListener. Posts agent contributions as reply chain activities in the channel. UsesreplyToIdfor threading.SlackGroupDiscussionListenerTeamsDeliveryExceptionSlackDeliveryExceptionConfiguration Model
{ "name": "Engineering Teams Channel", "channelType": "teams", "platformConfig": { "channelId": "19:abc123@thread.tacv2", "appId": "${vault:teams-app-id}", "appPassword": "${vault:teams-app-password}", "tenantId": "your-tenant-id", "serviceUrl": "https://smba.trafficmanager.net/teams/" }, "defaultTargetName": "assistant", "targets": [...] }Key Design Decisions
appId), issuer, expiration,serviceUrlclaim. Cache OpenID metadata with refresh on 401.<b>,<i>,<code>). Teams does NOT support standard Markdown in bot messages.https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/tokenusing client credentials flow. Cache tokens until expiry.activity.from.idmatches the bot'sappId.Deliverables
ai.labs.eddi.integrations.teams"teams"toREGISTERED_CHANNEL_TYPESdocs/teams-integration.mdAlternatives Considered
Additional Context
Acceptance Criteria
"teams"added toREGISTERED_CHANNEL_TYPESinRestChannelIntegrationStore./mvnw testpasses with zero failuresdocs/teams-integration.mdexists (followingdocs/slack-integration.mdstructure)