DocsChannelsChannels Overview

Ryvos communicates through multiple channels simultaneously. In daemon mode, all configured channels are active at once, and the agent maintains context across them.

Available Channels

ChannelProtocolDM PolicyApproval UIRich Formatting
TelegramBot API (teloxide)allowlist/open/disabledInline buttonsMarkdown, 4096 char split
DiscordGateway (serenity)allowlist/open/disabledButton componentsRich embeds
SlackSocket Modeallowlist/open/disabledBlock Kit UIBlock Kit, slash commands
WhatsAppCloud API (Meta)allowlist/open/disabledText-basedPlain text, 4096 char limit
Web UIHTTP + WebSocketAPI key authBrowser dialogFull HTML, streaming
TUITerminalLocal onlyTerminal promptANSI colors, panels
CLI/REPLstdin/stdoutLocal onlyTerminal promptPlain text, streaming
WebhooksHTTP POSTAPI key authN/AJSON

Channel Architecture

All channels implement the ChannelAdapter trait:

trait ChannelAdapter {
    fn name(&self) -> &str;
    async fn start(&self) -> Result<()>;
    async fn send(&self, session_id: &str, message: &str) -> Result<()>;
    async fn send_approval(&self, request: &ApprovalRequest) -> Result<()>;
    async fn broadcast(&self, message: &str) -> Result<()>;
    async fn stop(&self) -> Result<()>;
}

The ChannelDispatcher routes messages between channels and the agent:

Telegram ──┐
Discord ───┤
Slack ─────┼──→ ChannelDispatcher ──→ AgentRuntime
WhatsApp ──┤                    ←──── Response routed back
Web UI ────┘                          to originating channel

DM Policies

Each channel supports three DM (Direct Message) policies:

PolicyBehavior
allowlistOnly messages from users in the allowed_users list are processed. Others are ignored silently.
openMessages from any user are processed. Use with caution.
disabledDMs are completely disabled. Only group/channel messages (if supported) work.
[channels.telegram]
enabled = true
bot_token = "${TELEGRAM_BOT_TOKEN}"
dm_policy = "allowlist"
allowed_users = [123456789, 987654321]

:::caution Never use dm_policy = "open" on public-facing bots unless you have budget limits configured. Anyone who discovers your bot could interact with it, consuming your LLM API budget. :::

Message Flow

When a message arrives on any channel:

  1. Channel adapter receives the raw message
  2. DM policy check — is this user allowed?
  3. ChannelDispatcher creates or resumes a session
  4. AgentRuntime processes the message through the ReAct loop
  5. Response is streamed back to the originating channel
  6. Approval requests (if any) are sent to the same channel with interactive UI

Approval Routing

When the agent needs approval for a tool call, the approval request is sent to the channel where the conversation started:

Telegram

Inline keyboard buttons:

🔒 Approval needed: bash
Command: git push origin main
Tier: T3 (High)

[✅ Approve] [❌ Deny]

Discord

Button components on an embed:

┌─────────────────────────────────┐
│ 🔒 Approval Required            │
│                                  │
│ Tool: bash                       │
│ Command: git push origin main    │
│ Tier: T3 (High)                  │
│                                  │
│ [Approve] [Deny]                 │
└─────────────────────────────────┘

Slack

Block Kit UI with action buttons:

🔒 *Approval Required*
Tool: `bash`
Command: `git push origin main`
Tier: T3 (High)
Timeout: 60 seconds

[Approve ✅]  [Deny ❌]

WhatsApp

Text-based with reply instructions:

🔒 Approval needed
Tool: bash
Command: git push origin main
Tier: T3 (High)

Reply "yes" to approve or "no" to deny (60s timeout)

If no response is received within approval_timeout_secs (default: 60), the request is automatically denied.

Broadcasting

Send a message to all active channels:

dispatcher.broadcast("System maintenance starting in 5 minutes").await?;

This is used by the heartbeat system and cron jobs to route alerts. You can also target specific channels:

[[cron.jobs]]
name = "daily-report"
schedule = "0 9 * * *"
prompt = "Generate a daily report"
channel = "slack"                  # Route output to Slack only

Message Splitting

Channels have message size limits. Ryvos automatically splits long responses:

ChannelMax LengthSplitting Strategy
Telegram4,096 charsSplit at paragraph boundaries
Discord2,000 charsSplit at paragraph boundaries
Slack40,000 charsRarely needed
WhatsApp4,096 charsSplit at paragraph boundaries

Starting Channels

Channels start automatically in daemon mode:

ryvos daemon --gateway

Or selectively:

ryvos daemon --no-channels          # No channels, just cron/heartbeat
ryvos daemon --gateway              # Channels + Web UI

Next Steps