Skip to main content

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

ChannelProtocolThreadsStreamingReactionsGroupsRich Media
TelegramBot API / MTProto
DiscordGateway WS
SlackSocket Mode
WhatsAppCloud API
SignalSignal-CLI
iMessageAppleScript bridge
WebChatHTTP + WS
MatrixClient-Server API
LineMessaging API
Google ChatREST + Pub/Sub
MS TeamsBot Framework
NostrNIP-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"
tip

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
warning

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);
info

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:

MetricTypeDescription
clawdesk_channel_messages_received_totalCounterTotal inbound messages
clawdesk_channel_messages_sent_totalCounterTotal outbound messages
clawdesk_channel_errors_totalCounterTotal errors by type
clawdesk_channel_latency_secondsHistogramMessage processing latency
clawdesk_channel_active_connectionsGaugeCurrent active connections
clawdesk_channel_reconnects_totalCounterTotal reconnection attempts

Troubleshooting

Common Issues

ProblemSolution
Channel stuck in "Starting"Check token/credentials with clawdesk doctor
Messages not arrivingVerify allowlist/filter settings, check clawdesk channels logs <ID>
Rate limitingReduce message frequency or upgrade API tier
Webhook failuresEnsure your URL is publicly accessible and TLS-enabled
Reconnection loopCheck network connectivity and increase max_delay_ms

Debug Mode

Enable per-channel debug logging:

CLAWDESK_LOG="clawdesk_channel::telegram=debug" clawdesk gateway