Skip to main content
Discover every Discord and Slack channel your bot can address, without waiting for someone to say @bot first.

Quick Start

1

Run an agent on the gateway

Nothing to enable — if Discord or Slack is configured, its channels are auto-listed on every refresh. Your agent can address every visible channel without a prior inbound message:
from praisonaiagents import Agent

agent = Agent(
    name="Announcer",
    instructions="Post the daily standup summary to every team channel.",
    model="gpt-4o-mini",
)

agent.start("Post 'Standup at 10:00 GMT' to every channel in the directory.")
2

Configure Discord or Slack

Enumeration works out of the box — no new keys are required:
# gateway.yaml
channels:
  discord:
    platform: discord
    token: "${DISCORD_BOT_TOKEN}"
    # Guilds intent is already enabled by the adapter.

  slack:
    platform: slack
    bot_token: "${SLACK_BOT_TOKEN}"
    # Paginates public + private channels your bot user can access.
Channels the bot has joined but that have never messaged it are now addressable. Previously a channel became reachable only after its first inbound message.

How It Works

ChannelDirectory.refresh_from_adapters asks each adapter for its channels, then merges them into the observed set. Each adapter returns a list of ChannelRef descriptors. Only .id is consumed by the directory; name and type are for logging and UI.
FieldTypeDefaultDescription
idstrPlatform-native channel identifier (required)
namestr""Human-readable name, "" when unknown
typestr""Platform-native type tag ("text", "channel", "im")
Adapters implement list_channels() optionally. refresh_from_adapters looks it up via getattr(adapter, "list_channels", None) and skips any adapter that doesn’t provide it — Telegram, custom webhook, and IRC descriptor-only adapters stay observed-only, so nothing regresses.

Behavior Details

The directory degrades gracefully — a slow or empty adapter yields fewer entries, never an exception.
SituationResult
Discord client not readyReturns [] while client.is_ready() is False
Slack timeout or SDK errorReturns []; the refresh loop continues
Adapter without list_channels()Skipped — stays observed-only
Workspace over 10 000 channelsOnly the first 10 000 land in the directory (surfaced in the operator log)

Slack pagination and timeout

Slack’s async client is driven from the synchronous refresh loop by a sync-safe wrapper — asyncio.run(...) when no loop is running, or a short-lived worker thread when a loop is already active. This works whether the gateway starts inside an existing asyncio.run or from a plain sync entrypoint.
ConstantWhereDefaultDescription
_MAX_LIST_CHANNELS_PAGESbots/slack.py50Pagination cap × 200 per page → 10 000-channel ceiling
_LIST_CHANNELS_TIMEOUTbots/slack.py30.0Seconds before the Slack coroutine driver degrades to []
Discord guilds intentbots/discord.pyalready onRequired for the guild → text-channels cache. No user action
These constants are not YAML options — they are documented so operators can reason about failure modes. On startup the directory may transiently return []; write agent code that tolerates an empty directory on the first tick.

Best Practices

Channels come and go. Iterate the auto-populated directory on every turn instead of caching channel ids in your agent.
On workspaces over 10 000 conversations, add a channel-name prefix or topic-tag filter in your agent — the directory alone stops at the pagination cap.
list_channels() may return [] transiently while a client warms up. Guard agent code so an empty first tick is harmless.
For a custom platform adapter, list_channels() is opt-in. Skip it if enumeration is prohibitively expensive on that platform — the adapter stays observed-only.

Gateway

Top-level gateway and channel configuration

Channel Descriptor

How a plugin channel declares its config

Per-Chat Session Scope

The scope model channel routing feeds into

Messaging Bots

Connect agents to Discord, Slack, Telegram, and more