Channels
ClawDesk connects to messaging platforms through channels. Each channel is a bidirectional bridge between a messaging service and the agent pipeline. ClawDesk supports 12 channels out of the box and provides a trait-based architecture for adding custom ones.
Supported Channels
| Channel | Protocol | Threads | Streaming | Reactions | Groups | Rich Media |
|---|---|---|---|---|---|---|
| Telegram | Bot API / MTProto | ✅ | ✅ | ✅ | ✅ | ✅ |
| Discord | Gateway WS | ✅ | ✅ | ✅ | ✅ | ✅ |
| Slack | Socket Mode | ✅ | ✅ | ✅ | ✅ | ✅ |
| Cloud API | ❌ | ❌ | ✅ | ✅ | ✅ | |
| Signal | Signal-CLI | ❌ | ❌ | ✅ | ✅ | ✅ |
| iMessage | AppleScript bridge | ❌ | ❌ | ✅ | ✅ | ✅ |
| WebChat | HTTP + WS | ✅ | ✅ | ❌ | ❌ | ✅ |
| Matrix | Client-Server API | ✅ | ❌ | ✅ | ✅ | ✅ |
| Line | Messaging API | ❌ | ❌ | ❌ | ✅ | ✅ |
| Google Chat | REST + Pub/Sub | ✅ | ❌ | ❌ | ✅ | ✅ |
| MS Teams | Bot Framework | ✅ | ✅ | ✅ | ✅ | ✅ |
| Nostr | NIP-04 / NIP-44 | ❌ | ❌ | ✅ | ❌ | ❌ |
Channel Trait Architecture
Channels implement a layered trait hierarchy that allows ClawDesk to negotiate capabilities at compile time and runtime.
Layer 0: Core Channel Trait
Every channel must implement the base trait:
#[async_trait]
pub trait Channel: Send + Sync + 'static {
/// Unique identifier for this channel instance
fn id(&self) -> &ChannelId;
/// Metadata: name, channel type, capabilities
fn meta(&self) -> &ChannelMeta;
/// Start receiving messages
async fn start(&mut self) -> Result<(), ChannelError>;
/// Send an outbound message
async fn send(&self, message: OutboundMessage) -> Result<MessageId, ChannelError>;
/// Gracefully shut down the channel
async fn stop(&mut self) -> Result<(), ChannelError>;
}
Layer 1: Opt-in Capabilities
Channels declare additional capabilities by implementing opt-in traits:
#[async_trait]
pub trait Threaded: Channel {
async fn reply_in_thread(
&self,
thread_id: &ThreadId,
message: OutboundMessage,
) -> Result<MessageId, ChannelError>;
async fn get_thread(&self, thread_id: &ThreadId) -> Result<Vec<Message>, ChannelError>;
}
#[async_trait]
pub trait Streaming: Channel {
async fn send_stream(
&self,
stream: MessageStream,
) -> Result<MessageId, ChannelError>;
async fn edit_message(
&self,
message_id: &MessageId,
new_content: &str,
) -> Result<(), ChannelError>;
}
#[async_trait]
pub trait Reactions: Channel {
async fn add_reaction(
&self,
message_id: &MessageId,
emoji: &str,
) -> Result<(), ChannelError>;
async fn remove_reaction(
&self,
message_id: &MessageId,
emoji: &str,
) -> Result<(), ChannelError>;
}
Layer 2: Composed RichChannel
The RichChannel trait is automatically implemented for any channel that implements all three Layer 1 traits:
pub trait RichChannel: Channel + Threaded + Streaming + Reactions {}
// Blanket implementation
impl<T> RichChannel for T
where
T: Channel + Threaded + Streaming + Reactions,
{}
Configuration
Channel configuration lives in your config.toml under the [channels] section.
Telegram
[[channels]]
type = "telegram"
id = "tg_main"
name = "My Telegram Bot"
[channels.telegram]
bot_token = "${CLAWDESK_TELEGRAM_TOKEN}" # env var interpolation
# Or set directly (not recommended for production):
# bot_token = "123456:ABC-DEF..."
allowed_chat_ids = [12345678, -100987654321] # optional allowlist
webhook_url = "https://bot.example.com/webhook/telegram" # optional, uses polling if unset
parse_mode = "MarkdownV2" # "HTML" | "MarkdownV2" | "Markdown"
Telegram supports both polling and webhook modes. Use webhooks in production for lower latency and reduced API calls:
clawdesk channels add telegram \
--token "$TELEGRAM_TOKEN" \
--webhook-url "https://bot.example.com/webhook/telegram"
Discord
[[channels]]
type = "discord"
id = "dc_server"
name = "My Discord Bot"
[channels.discord]
bot_token = "${CLAWDESK_DISCORD_TOKEN}"
application_id = "123456789012345678"
guild_ids = [123456789012345678] # optional, empty = all guilds
intents = ["GUILD_MESSAGES", "DIRECT_MESSAGES", "MESSAGE_CONTENT"]
command_prefix = "!" # optional slash-command prefix
Slack
[[channels]]
type = "slack"
id = "sl_work"
name = "Work Slack Bot"
[channels.slack]
app_token = "${CLAWDESK_SLACK_APP_TOKEN}" # xapp-...
bot_token = "${CLAWDESK_SLACK_BOT_TOKEN}" # xoxb-...
signing_secret = "${CLAWDESK_SLACK_SIGNING_SECRET}"
channel_ids = ["C01234ABCDE"] # optional allowlist
socket_mode = true # recommended
WhatsApp
[[channels]]
type = "whatsapp"
id = "wa_biz"
name = "WhatsApp Business"
[channels.whatsapp]
phone_number_id = "123456789"
access_token = "${CLAWDESK_WHATSAPP_TOKEN}"
verify_token = "my-verify-token"
webhook_url = "https://bot.example.com/webhook/whatsapp"
Signal
[[channels]]
type = "signal"
id = "sig_main"
name = "Signal Bot"
[channels.signal]
phone_number = "+15551234567"
signal_cli_path = "/usr/local/bin/signal-cli"
data_dir = "/var/lib/signal-cli"
trust_mode = "always" # "always" | "tofu" | "never"
iMessage
[[channels]]
type = "imessage"
id = "im_main"
name = "iMessage"
[channels.imessage]
# macOS only — uses AppleScript bridge
poll_interval_secs = 2
handle_filter = ["+15551234567", "user@icloud.com"] # optional allowlist
iMessage integration requires macOS and Accessibility permissions for the ClawDesk process. Full Disk Access may also be required to read the Messages database.
WebChat
[[channels]]
type = "webchat"
id = "wc_public"
name = "Website Chat"
[channels.webchat]
port = 8080
path = "/chat"
allowed_origins = ["https://example.com", "https://app.example.com"]
max_connections = 100
auth_mode = "none" # "none" | "token" | "oauth"
rate_limit_per_ip = 30 # messages per minute
Matrix
[[channels]]
type = "matrix"
id = "mx_home"
name = "Matrix Bot"
[channels.matrix]
homeserver_url = "https://matrix.org"
user_id = "@clawdesk:matrix.org"
access_token = "${CLAWDESK_MATRIX_TOKEN}"
room_ids = ["!abc123:matrix.org"] # optional
auto_join = true
Line
[[channels]]
type = "line"
id = "line_main"
name = "LINE Bot"
[channels.line]
channel_secret = "${CLAWDESK_LINE_SECRET}"
channel_access_token = "${CLAWDESK_LINE_TOKEN}"
webhook_url = "https://bot.example.com/webhook/line"
Google Chat
[[channels]]
type = "google_chat"
id = "gc_workspace"
name = "Google Chat Bot"
[channels.google_chat]
service_account_key = "/etc/clawdesk/google-sa.json"
project_id = "my-project-123"
subscription_id = "clawdesk-gchat-sub"
Microsoft Teams
[[channels]]
type = "teams"
id = "teams_org"
name = "Teams Bot"
[channels.teams]
app_id = "${CLAWDESK_TEAMS_APP_ID}"
app_password = "${CLAWDESK_TEAMS_APP_PASSWORD}"
tenant_id = "${CLAWDESK_TEAMS_TENANT_ID}"
Nostr
[[channels]]
type = "nostr"
id = "nostr_main"
name = "Nostr Bot"
[channels.nostr]
private_key = "${CLAWDESK_NOSTR_NSEC}" # nsec or hex
relays = [
"wss://relay.damus.io",
"wss://nos.lol",
"wss://relay.nostr.band",
]
nip = 44 # 4 = NIP-04 (legacy), 44 = NIP-44 (recommended)
Multi-Channel Routing
When multiple channels are active, ClawDesk routes messages through a unified pipeline. You can configure channel-specific overrides:
[[channels]]
type = "telegram"
id = "tg_support"
[channels.overrides]
provider = "anthropic"
model = "claude-sonnet-4-20250514"
system_prompt = "You are a support agent. Be concise and helpful."
max_tokens = 1024
tools = ["knowledge_base", "ticket_create"]
[[channels]]
type = "discord"
id = "dc_community"
[channels.overrides]
provider = "openai"
model = "gpt-4o"
system_prompt = "You are a friendly community helper."
max_tokens = 2048
tools = ["web_search", "code_execution"]
Channel Lifecycle
Reconnection Policy
All channels implement automatic reconnection with exponential backoff:
[channels.reconnect]
enabled = true
initial_delay_ms = 1000
max_delay_ms = 60000
backoff_factor = 2.0
max_retries = 10 # 0 = infinite
jitter = true
Custom Channel Development
To create a custom channel, implement the Channel trait:
use clawdesk_channel::{
Channel, ChannelId, ChannelMeta, ChannelError,
OutboundMessage, MessageId,
};
pub struct MyCustomChannel {
id: ChannelId,
meta: ChannelMeta,
// ... your fields
}
#[async_trait]
impl Channel for MyCustomChannel {
fn id(&self) -> &ChannelId {
&self.id
}
fn meta(&self) -> &ChannelMeta {
&self.meta
}
async fn start(&mut self) -> Result<(), ChannelError> {
// Connect to your messaging service
Ok(())
}
async fn send(&self, message: OutboundMessage) -> Result<MessageId, ChannelError> {
// Send the message through your service
Ok(MessageId::new("msg_123"))
}
async fn stop(&mut self) -> Result<(), ChannelError> {
// Clean up connections
Ok(())
}
}
Register your channel in the configuration:
use clawdesk_channel::ChannelRegistry;
let mut registry = ChannelRegistry::new();
registry.register("my_custom", MyCustomChannel::builder);
Custom channels can be distributed as plugins. See the Plugins guide for packaging and distribution.
Monitoring Channels
CLI Monitoring
# Real-time log stream
clawdesk channels logs tg_main --follow
# Status overview
clawdesk channels status tg_main
Metrics
Each channel exposes Prometheus-compatible metrics:
| Metric | Type | Description |
|---|---|---|
clawdesk_channel_messages_received_total | Counter | Total inbound messages |
clawdesk_channel_messages_sent_total | Counter | Total outbound messages |
clawdesk_channel_errors_total | Counter | Total errors by type |
clawdesk_channel_latency_seconds | Histogram | Message processing latency |
clawdesk_channel_active_connections | Gauge | Current active connections |
clawdesk_channel_reconnects_total | Counter | Total reconnection attempts |
Troubleshooting
Common Issues
| Problem | Solution |
|---|---|
| Channel stuck in "Starting" | Check token/credentials with clawdesk doctor |
| Messages not arriving | Verify allowlist/filter settings, check clawdesk channels logs <ID> |
| Rate limiting | Reduce message frequency or upgrade API tier |
| Webhook failures | Ensure your URL is publicly accessible and TLS-enabled |
| Reconnection loop | Check network connectivity and increase max_delay_ms |
Debug Mode
Enable per-channel debug logging:
CLAWDESK_LOG="clawdesk_channel::telegram=debug" clawdesk gateway