Skip to content

fix(slack): channel user identity, observation mode, and proactive messaging #350

Description

@vybe

Problem

Slack channel behavior has three related issues (parallel to #349 for Telegram):

1. User Identity Bug

Agent cannot see who sent a message in channels. The adapter passes sender_id (Slack user ID like U123ABC) but not the user's display name or real name. The agent sees:

What do you think about this approach?

Instead of:

[Channel: #engineering]
[From: John Smith (@johndoe)]

What do you think about this approach?

Root cause: slack_adapter._parse_mention() and _parse_thread_reply() don't fetch user info or include channel name in the context.

2. No Observation Mode

Slack adapter only handles:

  • app_mention — @mentions in channels
  • message.im — DMs
  • Thread replies where bot already responded

There's no way for an agent to observe channel conversations without being explicitly @mentioned. This limits agents that should monitor discussions and contribute when relevant.

Slack API constraint: Observing all channel messages requires the channels:history and/or groups:history OAuth scopes, plus subscribing to message events (not just app_mention).

3. No Proactive Channel Messaging

Agent can wake up on schedule but cannot send messages to Slack channels. Issue #321 covers proactive DMs to users; this is about channel broadcasts.

Current state:

  • Channel bindings are stored in slack_channel_agents
  • Agent can send responses within the request/response flow
  • No MCP tool for outbound channel messages outside that flow

Proposed Solution

Phase 1: Fix User Identity

Fetch user info via Slack API and include in context:

async def _enrich_user_context(self, event: dict, team_id: str) -> dict:
    """Fetch user display name and channel name for context."""
    bot_token = db.get_slack_workspace_bot_token(team_id)
    user_id = event.get("user")
    channel_id = event.get("channel")
    
    context = {}
    if bot_token and user_id:
        user_info = await slack_service.get_user_info(bot_token, user_id)
        if user_info:
            context["display_name"] = user_info.get("real_name") or user_info.get("name")
            context["username"] = user_info.get("name")
    
    if bot_token and channel_id:
        channel_info = await slack_service.get_channel_info(bot_token, channel_id)
        if channel_info:
            context["channel_name"] = channel_info.get("name")
    
    return context

Update message_router.py to format this context (shared with Telegram, see #349).

Phase 2: Observation Mode

Add ability for agents to observe channel conversations:

Option A: Event subscription approach

  1. Add channels:history / groups:history scopes to OAuth
  2. Subscribe to message events for bound channels
  3. New observation_mode flag on slack_channel_agents
  4. When enabled, all messages (not just @mentions) route to agent
  5. Agent can respond with [NO_REPLY] to stay silent

Option B: Periodic polling (simpler)

  1. Scheduled job fetches recent messages from bound channels
  2. Agent receives batch context periodically
  3. Can choose to respond or not

Recommendation: Option A aligns with Telegram #349 and provides real-time awareness.

Database changes:

  • Add observation_mode boolean to slack_channel_agents
  • Add channel_messages table for history persistence (or reuse session pattern)

Phase 3: Proactive Channel Messaging

Share MCP tools with Telegram (#349):

// Discover channels agent is bound to
list_channel_groups(channel_type?: "telegram" | "slack") 
  → Array<{channel_type, channel_id, channel_name, ...}>

// Send to a channel
send_group_message(
  channel_type: "telegram" | "slack",
  channel_id: string,
  message: string,
  thread_ts?: string  // Slack: optionally post in existing thread
) → {sent: boolean, message_id?: string}

Slack-specific considerations:

  • Use chat.postMessage with the workspace's bot token
  • Respect channel-level rate limits
  • Consider unfurl_links: false for cleaner automated posts

Files to Modify

File Changes
src/backend/adapters/slack_adapter.py Add _enrich_user_context(), include in parsed messages
src/backend/adapters/message_router.py Shared group context formatting (with #349)
src/backend/services/slack_service.py Add get_user_info(), get_channel_info() if missing
src/backend/db/slack_channels.py Add observation_mode column, message history
src/backend/adapters/transports/slack_socket.py Subscribe to message events when observation enabled
src/mcp-server/src/tools/channels.ts Shared tools (with #349)

OAuth Scope Changes

Current scopes (likely):

  • chat:write, chat:write.customize
  • users:read, users:read.email
  • app_mentions:read

Additional scopes for observation mode:

  • channels:history — read public channel messages
  • groups:history — read private channel messages
  • channels:read — get channel info

Acceptance Criteria

  • Agent sees [Channel: #name] [From: Display Name (@username)] prefix
  • observation_mode flag enables seeing all channel messages
  • Agent can return [NO_REPLY] to skip responding
  • Observation mode includes recent message history
  • MCP tool list_channel_groups returns agent's Slack channels
  • MCP tool send_group_message delivers to Slack channels
  • Rate limiting prevents channel spam

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

complexity-mediumComplexity: medium (board points 5-8)priority-p1Critical paththeme-channelsTheme: Channelstype-epicParent epic issue (groups child sub-issues)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions