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
| Channel | Protocol | DM Policy | Approval UI | Rich Formatting |
|---|---|---|---|---|
| Telegram | Bot API (teloxide) | allowlist/open/disabled | Inline buttons | Markdown, 4096 char split |
| Discord | Gateway (serenity) | allowlist/open/disabled | Button components | Rich embeds |
| Slack | Socket Mode | allowlist/open/disabled | Block Kit UI | Block Kit, slash commands |
| Cloud API (Meta) | allowlist/open/disabled | Text-based | Plain text, 4096 char limit | |
| Web UI | HTTP + WebSocket | API key auth | Browser dialog | Full HTML, streaming |
| TUI | Terminal | Local only | Terminal prompt | ANSI colors, panels |
| CLI/REPL | stdin/stdout | Local only | Terminal prompt | Plain text, streaming |
| Webhooks | HTTP POST | API key auth | N/A | JSON |
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:
| Policy | Behavior |
|---|---|
allowlist | Only messages from users in the allowed_users list are processed. Others are ignored silently. |
open | Messages from any user are processed. Use with caution. |
disabled | DMs 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:
- Channel adapter receives the raw message
- DM policy check — is this user allowed?
- ChannelDispatcher creates or resumes a session
- AgentRuntime processes the message through the ReAct loop
- Response is streamed back to the originating channel
- 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 ❌]
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 onlyMessage Splitting
Channels have message size limits. Ryvos automatically splits long responses:
| Channel | Max Length | Splitting Strategy |
|---|---|---|
| Telegram | 4,096 chars | Split at paragraph boundaries |
| Discord | 2,000 chars | Split at paragraph boundaries |
| Slack | 40,000 chars | Rarely needed |
| 4,096 chars | Split at paragraph boundaries |
Starting Channels
Channels start automatically in daemon mode:
ryvos daemon --gatewayOr selectively:
ryvos daemon --no-channels # No channels, just cron/heartbeat
ryvos daemon --gateway # Channels + Web UINext Steps
- Telegram & Discord — Setup guides
- Slack & WhatsApp — Setup guides
- Web UI Dashboard — Web interface setup