code<spar>

Multi-Channel Setup

How to enable multiple messaging channels simultaneously, link user identities across channels, use cross-channel approvals, and understand message normalization.

Multi-Channel Setup

CodeSpar agents can operate on multiple messaging channels simultaneously. The same agent responds on WhatsApp, Slack, Discord, and Telegram with a unified experience. Users can start a conversation on one channel and continue on another.

Overview

                    ┌─────────┐
        Slack ──────┤         │
     WhatsApp ──────┤ CodeSpar├────── GitHub
      Discord ──────┤  Agent  │
     Telegram ──────┤         │
                    └─────────┘

All channels connect to the same agent instance. Commands, permissions, and context are shared.


How to Enable Multiple Channels Simultaneously

Enabling multiple channels is simply a matter of setting the environment variables for each channel. All enabled channels run in parallel within the same CodeSpar process.

Step 1: Set Environment Variables

Add the configuration for each channel you want to enable in your .env file:

# ============================================
# Channel Configuration — enable all four
# ============================================
 
# Slack
ENABLE_SLACK=true
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_SIGNING_SECRET=your-signing-secret
SLACK_APP_TOKEN=xapp-your-app-token
 
# WhatsApp
ENABLE_WHATSAPP=true
EVOLUTION_API_URL=https://evolution.example.com
EVOLUTION_API_KEY=your-evolution-key
EVOLUTION_INSTANCE=codespar
WHATSAPP_WEBHOOK_PORT=3001
WHATSAPP_BOT_MENTION=@codespar
 
# Discord
ENABLE_DISCORD=true
DISCORD_BOT_TOKEN=your-discord-bot-token
 
# Telegram
ENABLE_TELEGRAM=true
TELEGRAM_BOT_TOKEN=your-telegram-bot-token

Step 2: Start CodeSpar

npm run dev

On startup, CodeSpar initializes each enabled channel and reports its status:

[startup] ✅ ENABLE_SLACK: true (connected)
[startup] ✅ ENABLE_WHATSAPP: true (connected)
[startup] ✅ ENABLE_DISCORD: true (connected)
[startup] ✅ ENABLE_TELEGRAM: true (connected)

Step 3: Verify Channel Status

Check that all channels are connected:

@codespar status

Or via the API:

curl http://localhost:3000/api/channels
{
  "channels": [
    { "name": "slack", "platform": "slack", "connected": true },
    { "name": "whatsapp", "platform": "whatsapp", "connected": true },
    { "name": "discord", "platform": "discord", "connected": true },
    { "name": "telegram", "platform": "telegram", "connected": true }
  ]
}

Common Configurations

Slack + WhatsApp (most common for mixed teams):

ENABLE_SLACK=true
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...
 
ENABLE_WHATSAPP=true
EVOLUTION_API_URL=https://evolution.example.com
EVOLUTION_API_KEY=...
EVOLUTION_INSTANCE=codespar

Slack only (engineering-focused teams):

ENABLE_SLACK=true
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...

Discord + Telegram (open-source communities):

ENABLE_DISCORD=true
DISCORD_BOT_TOKEN=...
 
ENABLE_TELEGRAM=true
TELEGRAM_BOT_TOKEN=...

Message Normalization

Every incoming message from any channel is normalized to a common NormalizedMessage format before being processed by the agent. This is the foundation of cross-channel consistency.

NormalizedMessage Type

interface NormalizedMessage {
  id: string;                  // Unique message ID
  channelType: string;         // "slack", "whatsapp", "discord", "telegram"
  channelUserId: string;       // Platform-specific user ID
  channelDisplayName: string;  // User's display name on the platform
  text: string;                // Message text (with @mention prefix stripped)
  threadId?: string;           // Thread/conversation ID (if applicable)
  timestamp: string;           // ISO 8601 timestamp
  replyTo?: string;            // ID of the message being replied to
}

How Each Channel Normalizes

PlatformUser ID SourceDisplay Name Source@mention Handling
Slackevent.user (e.g., U1234ABCD)Slack user profile<@UBOTID> prefix stripped
WhatsAppPhone number (e.g., 5511999990000)WhatsApp profile nameText matching against WHATSAPP_BOT_MENTION
DiscordDiscord user ID (e.g., 123456789012345678)Discord username<@BOTID> prefix stripped
TelegramTelegram user ID (e.g., 987654321)Telegram first + last name/command or @botname prefix stripped

After normalization, the agent processes every message identically regardless of its source channel. This means the same command syntax works everywhere:

Slack:     @codespar deploy staging    → NormalizedMessage { text: "deploy staging" }
WhatsApp:  @codespar deploy staging    → NormalizedMessage { text: "deploy staging" }
Discord:   @codespar deploy staging    → NormalizedMessage { text: "deploy staging" }
Telegram:  @codespar deploy staging    → NormalizedMessage { text: "deploy staging" }

Identity Linking

By default, CodeSpar treats each channel user as a separate identity. To enable cross-channel features, users need to link their identities by registering with the same display name from each channel.

How Identity Linking Works

Step 1: Register from First Channel

From Slack:

@codespar register John Silva

CodeSpar creates an identity:

{
  "id": "identity-001",
  "displayName": "John Silva",
  "role": "read-only",
  "channels": [
    { "channelType": "slack", "channelUserId": "U1234ABCD" }
  ]
}

Step 2: Register from Second Channel

From WhatsApp (same person, same name):

@codespar register John Silva

CodeSpar finds the existing identity with display name "John Silva" and links the WhatsApp channel:

{
  "id": "identity-001",
  "displayName": "John Silva",
  "role": "read-only",
  "channels": [
    { "channelType": "slack", "channelUserId": "U1234ABCD" },
    { "channelType": "whatsapp", "channelUserId": "5511999990000" }
  ]
}

Step 3: Register from Additional Channels

From Discord:

@codespar register John Silva

The Discord channel is added to the same identity. All three channels are now linked.

Verify Identity Linking

Check your identity from any channel:

@codespar whoami
👤 Identity
─────────────────
Name: John Silva
Role: maintainer
Channels:
  • Slack: @john (U1234ABCD)
  • WhatsApp: +55 11 99999-0000
  • Discord: john#1234
Registered: 2024-01-15

Important Notes

  • Name matching is case-sensitive. John Silva and john silva are treated as different identities.
  • One identity per display name. If two different people register with the same name, their channels will be incorrectly linked. Use unique display names.
  • Registration is per-organization in multi-tenant mode. The same person must register separately in each organization.

Cross-Channel Features

Once identities are linked, several powerful cross-channel capabilities become available.

Cross-Channel Approval

Approve an action from a different channel than where it was requested:

# In Slack — developer requests deploy:
@alice: @codespar deploy staging

# CodeSpar responds in Slack:
@codespar: ⏳ Deploy to staging requires approval.
           Token: dp-a1b2c3
           Approve with: @codespar approve dp-a1b2c3

# In WhatsApp — team lead approves:
@bob: @codespar approve dp-a1b2c3

# Both channels receive confirmation:
# Slack:    ✅ Deploy to staging approved by Bob Silva. Deploying...
# WhatsApp: ✅ Deploy to staging approved by Bob Silva. Deploying...

This works because:

  1. The approval token (dp-a1b2c3) is global — not scoped to any channel
  2. Bob's identity is resolved from his WhatsApp channel ID
  3. Bob's RBAC role is checked against the required permission
  4. Status updates are broadcast to all connected channels

Unified Notifications

When an event occurs (build failure, PR opened, deploy completed), the notification is sent to all connected channels. Users with linked identities are not notified twice — they receive the notification on their primary channel.

Consistent Permissions

RBAC roles apply across all channels. If John is a maintainer on Slack, he has maintainer permissions on WhatsApp, Discord, and Telegram too. Role changes apply instantly across all channels.

Unified Audit Trail

All actions are attributed to the resolved identity, regardless of which channel was used:

curl "http://localhost:3000/api/audit?limit=5"
{
  "entries": [
    {
      "actorId": "identity-001",
      "actorType": "user",
      "action": "deploy",
      "metadata": {
        "channel": "whatsapp",
        "displayName": "John Silva"
      }
    },
    {
      "actorId": "identity-001",
      "actorType": "user",
      "action": "approve",
      "metadata": {
        "channel": "slack",
        "displayName": "John Silva"
      }
    }
  ]
}

Example Scenario: Cross-Channel Development Flow

Here is a realistic scenario showing how multi-channel works in practice.

Setup

  • Slack: Used by the engineering team for day-to-day development
  • WhatsApp: Used by the team lead and CTO for approvals on the go
  • Both channels are connected to the same CodeSpar agent

The Flow

1. Developer posts a task in Slack:

Slack / #engineering:
@dev: @codespar fix the rate limiter — it's blocking legitimate API requests

@codespar: 🔍 Searching for rate-limiting code...
           Found 3 relevant files.
           🤖 Generating fix with Claude Sonnet...

           ✅ Fix ready:
           → Branch: codespar/task-a1b2c3
           → PR #92: Fix rate limiter false positives
           → Files: 2 modified
           Review PR: https://github.com/acme/api/pull/92

2. CI runs and passes. CodeSpar notifies both channels:

Slack / #engineering:
@codespar: ✅ Build passed on codespar/task-a1b2c3 (PR #92)

WhatsApp / Engineering Group:
@codespar: ✅ Build passed on codespar/task-a1b2c3 (PR #92)

3. Developer requests deploy from Slack:

Slack / #engineering:
@dev: @codespar deploy staging

@codespar: ⏳ Deploy to staging requires approval.
           Token: dp-x4y5z6
           Approve with: @codespar approve dp-x4y5z6

4. Team lead approves from WhatsApp (on mobile):

WhatsApp / Engineering Group:
@lead: @codespar approve dp-x4y5z6

5. Both channels receive the deployment result:

Slack:    ✅ Deploy to staging approved by @lead. Deploying...
WhatsApp: ✅ Deploy to staging approved by @lead. Deploying...

[2 minutes later]

Slack:    ✅ Deploy to staging completed. Health check: passed.
WhatsApp: ✅ Deploy to staging completed. Health check: passed.

Same @mention Syntax

The same @codespar mention syntax works on every channel:

ChannelSyntaxNotes
Slack@codespar deploy stagingStandard Slack mention
WhatsApp@codespar deploy stagingConfigurable via WHATSAPP_BOT_MENTION
Discord@codespar deploy stagingUses Discord bot mention
Telegram@codespar deploy stagingUses Telegram bot username

In direct messages (DMs), the @codespar prefix is optional on all channels.


Channel-Specific Behavior

While the core experience is identical, some channels have minor differences:

FeatureSlackWhatsAppDiscordTelegram
Code blocksRendered with syntax highlightingMonospace textRendered with syntax highlightingMonospace text
Thread supportMessages in threadsFlat messagesMessages in threadsFlat messages
File attachmentsSupportedSupportedSupportedSupported
Max message length40,000 chars4,096 chars2,000 chars4,096 chars
Emoji reactionsSupportedNot supportedSupportedNot supported

For channels with shorter message limits (Discord at 2,000 chars), long outputs are automatically split into multiple messages. CodeSpar uses the channel's capabilities.maxMessageLength to determine where to split.


Reconnecting a Channel

If a channel disconnects, you can reconnect it without restarting CodeSpar:

curl -X POST http://localhost:3000/api/channels/discord/reconnect

Or check status and reconnect all disconnected channels:

# Check which channels are disconnected
curl http://localhost:3000/api/channels
 
# Reconnect specific channel
curl -X POST http://localhost:3000/api/channels/whatsapp/reconnect

Troubleshooting

Channel shows "disconnected"

  1. Check channel-specific environment variables are correctly set
  2. Verify the external service is running (Evolution API for WhatsApp, etc.)
  3. Try reconnecting via the API: POST /api/channels/:name/reconnect
  4. Check CodeSpar logs for connection errors

Identity not linking across channels

  1. Ensure the exact same display name is used on all channels (case-sensitive)
  2. Check identity resolution: @codespar whoami from each channel
  3. Verify via API: GET /api/identity?channelType=slack&channelUserId=U1234
  4. In multi-tenant mode, ensure you are registering in the same organization

Notifications received on wrong channel

  1. Check that the user's identity is properly linked
  2. Notifications are sent to all connected channels by default
  3. Channel preferences are not yet configurable (coming soon)

Approval token not recognized on another channel

  1. Approval tokens are global — they work on any channel
  2. Verify the token has not expired (30-minute default)
  3. Verify the approving user has the required RBAC role
  4. Check that self-approval is not the issue (same identity requesting and approving)

Next Steps