Channel Configuration

Configuration for all 45 channel adapters, per-channel behavior overrides, and sidecar channel integration.


[channels]

All 45 channel adapters are configured under [channels.<name>]. Each channel is Option<T> -- omitting the section disables the adapter entirely. Including the section header (even empty) enables it with default values.

Universal channel fields: Every channel adapter supports the following common fields in addition to its own specific fields:

FieldTypeDefaultDescription
default_agentstring or nullnullAgent name to route messages to by default.
account_idstring or nullnullUnique identifier for this bot instance. Used for multi-bot routing via [[bindings]] match rules.
overridesobject(defaults)Per-channel behavior overrides. See Channel Overrides.

Webhook security

Channels that receive inbound traffic over HTTP enforce a uniform contract:

OutcomeStatus
Required header missing or malformed (X-Hub-Signature, X-Line-Signature, X-Viber-Content-Signature, Authorization: HMAC …, DingTalk timestamp/sign)400 Bad Request
Header present but signature mismatched, replayed, or expired401 Unauthorized

HMAC verification covers the raw wire bytes (not bytes round-tripped through serde_json::Value), so the signing key set in the platform portal must match the env var configured below. Comparison runs in constant time.

Channels with a per-secret env var (Messenger app_secret_env, Teams security_token_env) fall back to "skip verification + log a startup warning" when the env var is unset — this preserves backwards compatibility but should never be left unset in production.

Outbound webhook URLs (callback_url on [channels.webhook]) are run through an SSRF guard at adapter construction: any private (10/8, 172.16/12, 192.168/16), CGN (100.64/10), loopback (127/8, ::1), link-local (169.254/16, fe80::/10), unique-local (fc00::/7), multicast, or cloud-metadata (169.254.169.254, metadata.google.internal, metadata.azure.com) target — including IPv6-bracket forms like [::], [::ffff:127.0.0.1], NAT64 [64:ff9b::7f00:1], and trailing-dot FQDNs — is rejected with a startup error.

Upgrading from earlier versions

If you are upgrading from a release before this contract was introduced:

  1. Operating Messenger? Copy your App Secret from the Facebook app dashboard and export it as MESSENGER_APP_SECRET (or whatever name you set app_secret_env to). Without it, inbound webhooks still work but are unauthenticated — a warning is logged once at startup.
  2. Operating Teams? Copy the outgoing-webhook security token (base64) from the Teams portal and export it as TEAMS_SECURITY_TOKEN (or whatever you set security_token_env to). Same fallback semantics — warning once, no verification.
  3. Operating LINE / Viber / DingTalk? No new env var to set, but probes/health-checks that hit the webhook path without the platform's signature header now return 400/401. Real platform traffic always carries a signature, so genuine inbound is unaffected; only direct probes break.
  4. Using [channels.webhook] with callback_url pointing at a private address? The most common case is a local dev setup with callback_url = "http://127.0.0.1/...". The adapter now refuses to start with that. Switch to a public tunnel (e.g. ngrok, cloudflared) or omit callback_url entirely if you don't need outbound delivery.

If you operate none of the channels above, no action is required — your existing config keeps working unchanged.

[channels.telegram]

[channels.telegram]
bot_token_env = "TELEGRAM_BOT_TOKEN"
allowed_users = []
# default_agent = "assistant"
poll_interval_secs = 1
# api_url = "https://api.telegram.org"  # override for local Bot API server
# account_id = "my-telegram-bot"
FieldTypeDefaultDescription
bot_token_envstring"TELEGRAM_BOT_TOKEN"Env var holding the Telegram Bot API token.
allowed_userslist of i64[]Telegram user IDs allowed to interact. Empty = allow all.
default_agentstring or nullnullAgent name to route messages to.
poll_interval_secsu641Long-polling interval in seconds.
account_idstring or nullnullUnique bot instance identifier for multi-bot routing.
api_urlstring or nullnullOverride the Telegram Bot API base URL. Useful for local Bot API server instances. Defaults to https://api.telegram.org.
overridesobject(defaults)Per-channel behavior overrides. See Channel Overrides.

[channels.discord]

[channels.discord]
bot_token_env = "DISCORD_BOT_TOKEN"
allowed_guilds = []
# default_agent = "assistant"
intents = 37376
ignore_bots = true
# account_id = "my-discord-bot"
FieldTypeDefaultDescription
bot_token_envstring"DISCORD_BOT_TOKEN"Env var holding the Discord bot token.
allowed_guildslist of u64[]Guild (server) IDs allowed. Empty = allow all.
default_agentstring or nullnullAgent name to route messages to.
allowed_userslist of strings[]Discord user IDs allowed to interact. Empty = allow all.
intentsu6437376Gateway intents bitmask. Default = GUILD_MESSAGES | DIRECT_MESSAGES | MESSAGE_CONTENT (37376).
ignore_botsbooltrueWhen true, messages from other bots are ignored.
account_idstring or nullnullUnique bot instance identifier for multi-bot routing.
overridesobject(defaults)Per-channel behavior overrides. See Channel Overrides.

[channels.slack]

[channels.slack]
app_token_env = "SLACK_APP_TOKEN"
bot_token_env = "SLACK_BOT_TOKEN"
allowed_channels = []
# account_id = "my-slack-bot"
FieldTypeDefaultDescription
app_token_envstring"SLACK_APP_TOKEN"Env var holding the Slack app-level token (xapp-) for Socket Mode.
bot_token_envstring"SLACK_BOT_TOKEN"Env var holding the Slack bot token (xoxb-) for REST API.
allowed_channelslist of strings[]Channel IDs allowed. Empty = allow all.
default_agentstring or nullnullAgent name to route messages to.
account_idstring or nullnullUnique bot instance identifier for multi-bot routing.
overridesobject(defaults)Per-channel behavior overrides. See Channel Overrides.

[channels.whatsapp]

[channels.whatsapp]
access_token_env = "WHATSAPP_ACCESS_TOKEN"
verify_token_env = "WHATSAPP_VERIFY_TOKEN"
phone_number_id = ""
# webhook_port = 8443  # Deprecated: webhooks now share the API port
allowed_users = []
# gateway_url_env = "WHATSAPP_WEB_GATEWAY_URL"  # for QR/Web mode
# account_id = "my-whatsapp-bot"
# owner_numbers = []   # optional admin phone numbers
FieldTypeDefaultDescription
access_token_envstring"WHATSAPP_ACCESS_TOKEN"Env var holding the WhatsApp Cloud API access token.
verify_token_envstring"WHATSAPP_VERIFY_TOKEN"Env var holding the webhook verification token.
phone_number_idstring""WhatsApp Business phone number ID.
webhook_portu168443Deprecated. Previously used for standalone webhook server; now ignored. All webhooks share the API port.
gateway_url_envstring"WHATSAPP_WEB_GATEWAY_URL"Env var holding the WhatsApp Web gateway URL. When set, outgoing messages are routed through the QR/Web gateway instead of the Cloud API.
allowed_userslist of strings[]Phone numbers allowed. Empty = allow all.
default_agentstring or nullnullAgent name to route messages to.
account_idstring or nullnullUnique bot instance identifier for multi-bot routing.
owner_numberslist of strings[]Phone numbers with administrative privileges for this bot instance.
overridesobject(defaults)Per-channel behavior overrides. See Channel Overrides.

[channels.signal]

[channels.signal]
api_url = "http://localhost:8080"
phone_number = ""
allowed_users = []
FieldTypeDefaultDescription
api_urlstring"http://localhost:8080"URL of the signal-cli REST API.
phone_numberstring""Registered phone number for the bot.
allowed_userslist of strings[]Allowed phone numbers. Empty = allow all.
default_agentstring or nullnullAgent name to route messages to.

[channels.matrix]

[channels.matrix]
homeserver_url = "https://matrix.org"
user_id = "@librefang:matrix.org"
access_token_env = "MATRIX_ACCESS_TOKEN"
allowed_rooms = []
FieldTypeDefaultDescription
homeserver_urlstring"https://matrix.org"Matrix homeserver URL.
user_idstring""Bot user ID (e.g., "@librefang:matrix.org").
access_token_envstring"MATRIX_ACCESS_TOKEN"Env var holding the Matrix access token.
allowed_roomslist of strings[]Room IDs to listen in. Empty = all joined rooms.
default_agentstring or nullnullAgent name to route messages to.

[channels.email]

[channels.email]
imap_host = "imap.gmail.com"
imap_port = 993
smtp_host = "smtp.gmail.com"
smtp_port = 587
username = "bot@example.com"
password_env = "EMAIL_PASSWORD"
poll_interval_secs = 30
folders = ["INBOX"]
allowed_senders = []
FieldTypeDefaultDescription
imap_hoststring""IMAP server hostname.
imap_portu16993IMAP server port (993 for TLS).
smtp_hoststring""SMTP server hostname.
smtp_portu16587SMTP server port (587 for STARTTLS).
usernamestring""Email address for both IMAP and SMTP.
password_envstring"EMAIL_PASSWORD"Env var holding the email password or app password.
poll_interval_secsu6430IMAP polling interval in seconds.
folderslist of strings["INBOX"]IMAP folders to monitor.
allowed_senderslist of strings[]Only process emails from these senders. Empty = all.
default_agentstring or nullnullAgent name to route messages to.

[channels.teams]

[channels.teams]
app_id = ""
app_password_env = "TEAMS_APP_PASSWORD"
security_token_env = "TEAMS_SECURITY_TOKEN"
# webhook_port = 3978  # Deprecated: webhooks now share the API port
allowed_tenants = []
FieldTypeDefaultDescription
app_idstring""Azure Bot App ID.
app_password_envstring"TEAMS_APP_PASSWORD"Env var holding the Azure Bot Framework app password.
security_token_envstring"TEAMS_SECURITY_TOKEN"Env var holding the outgoing webhook security token (base64-encoded, copied from the Teams portal). Used for HMAC-SHA256 verification of every inbound Authorization: HMAC <…> header. If unset or non-base64, signature verification is skipped and a warning is logged once at startup — production deployments should always set this.
webhook_portu163978Deprecated. Previously used for standalone webhook server; now ignored. All webhooks share the API port.
allowed_tenantslist of strings[]Azure AD tenant IDs allowed. Empty = allow all.
default_agentstring or nullnullAgent name to route messages to.

[channels.mattermost]

[channels.mattermost]
server_url = "https://mattermost.example.com"
token_env = "MATTERMOST_TOKEN"
allowed_channels = []
FieldTypeDefaultDescription
server_urlstring""Mattermost server URL.
token_envstring"MATTERMOST_TOKEN"Env var holding the Mattermost bot token.
allowed_channelslist of strings[]Channel IDs to listen in. Empty = all.
default_agentstring or nullnullAgent name to route messages to.

[channels.irc]

[channels.irc]
server = "irc.libera.chat"
port = 6667
nick = "librefang"
# password_env = "IRC_PASSWORD"
channels = ["#librefang"]
use_tls = false
FieldTypeDefaultDescription
serverstring"irc.libera.chat"IRC server hostname.
portu166667IRC server port.
nickstring"librefang"Bot nickname.
password_envstring or nullnullEnv var holding the server password (optional).
channelslist of strings[]IRC channels to join (e.g., ["#librefang", "#general"]).
use_tlsboolfalseUse TLS for the connection.
default_agentstring or nullnullAgent name to route messages to.

[channels.google_chat]

[channels.google_chat]
service_account_env = "GOOGLE_CHAT_SERVICE_ACCOUNT"
space_ids = []
# webhook_port = 8444  # Deprecated: webhooks now share the API port
FieldTypeDefaultDescription
service_account_envstring"GOOGLE_CHAT_SERVICE_ACCOUNT"Env var holding the service account JSON key.
space_idslist of strings[]Google Chat space IDs to listen in.
webhook_portu168444Deprecated. Previously used for standalone webhook server; now ignored. All webhooks share the API port.
default_agentstring or nullnullAgent name to route messages to.

[channels.twitch]

[channels.twitch]
oauth_token_env = "TWITCH_OAUTH_TOKEN"
channels = ["mychannel"]
nick = "librefang"
FieldTypeDefaultDescription
oauth_token_envstring"TWITCH_OAUTH_TOKEN"Env var holding the Twitch OAuth token.
channelslist of strings[]Twitch channels to join (without # prefix).
nickstring"librefang"Bot nickname in Twitch chat.
default_agentstring or nullnullAgent name to route messages to.

[channels.rocketchat]

[channels.rocketchat]
server_url = "https://rocketchat.example.com"
token_env = "ROCKETCHAT_TOKEN"
user_id = ""
allowed_channels = []
FieldTypeDefaultDescription
server_urlstring""Rocket.Chat server URL.
token_envstring"ROCKETCHAT_TOKEN"Env var holding the Rocket.Chat auth token.
user_idstring""Bot user ID.
allowed_channelslist of strings[]Channel IDs to listen in. Empty = all.
default_agentstring or nullnullAgent name to route messages to.

[channels.zulip]

[channels.zulip]
server_url = "https://zulip.example.com"
bot_email = "bot@zulip.example.com"
api_key_env = "ZULIP_API_KEY"
streams = []
FieldTypeDefaultDescription
server_urlstring""Zulip server URL.
bot_emailstring""Bot email address registered in Zulip.
api_key_envstring"ZULIP_API_KEY"Env var holding the Zulip API key.
streamslist of strings[]Stream names to listen in. Empty = all.
default_agentstring or nullnullAgent name to route messages to.

[channels.xmpp]

[channels.xmpp]
jid = "bot@jabber.org"
password_env = "XMPP_PASSWORD"
server = ""
port = 5222
rooms = []
FieldTypeDefaultDescription
jidstring""XMPP JID (e.g., "bot@jabber.org").
password_envstring"XMPP_PASSWORD"Env var holding the XMPP password.
serverstring""XMPP server hostname. Defaults to the JID domain if empty.
portu165222XMPP server port.
roomslist of strings[]MUC (multi-user chat) rooms to join.
default_agentstring or nullnullAgent name to route messages to.

[channels.line]

[channels.line]
channel_secret_env = "LINE_CHANNEL_SECRET"
access_token_env = "LINE_CHANNEL_ACCESS_TOKEN"
# webhook_port = 8450  # Deprecated: webhooks now share the API port
FieldTypeDefaultDescription
channel_secret_envstring"LINE_CHANNEL_SECRET"Env var holding the LINE channel secret. Required — every inbound webhook must carry a matching X-Line-Signature; missing → 400, mismatched → 401.
access_token_envstring"LINE_CHANNEL_ACCESS_TOKEN"Env var holding the LINE channel access token.
webhook_portu168450Deprecated. Previously used for standalone webhook server; now ignored. All webhooks share the API port.
default_agentstring or nullnullAgent name to route messages to.

[channels.viber]

[channels.viber]
auth_token_env = "VIBER_AUTH_TOKEN"
webhook_url = ""
# webhook_port = 8451  # Deprecated: webhooks now share the API port
FieldTypeDefaultDescription
auth_token_envstring"VIBER_AUTH_TOKEN"Env var holding the Viber Bot auth token. Required — also used as the HMAC-SHA256 key to verify every inbound X-Viber-Content-Signature; missing → 400, mismatched → 401.
webhook_urlstring""Public URL for the Viber webhook endpoint.
webhook_portu168451Deprecated. Previously used for standalone webhook server; now ignored. All webhooks share the API port.
default_agentstring or nullnullAgent name to route messages to.

[channels.messenger]

[channels.messenger]
page_token_env = "MESSENGER_PAGE_TOKEN"
verify_token_env = "MESSENGER_VERIFY_TOKEN"
app_secret_env = "MESSENGER_APP_SECRET"
# webhook_port = 8452  # Deprecated: webhooks now share the API port
FieldTypeDefaultDescription
page_token_envstring"MESSENGER_PAGE_TOKEN"Env var holding the Facebook page access token.
verify_token_envstring"MESSENGER_VERIFY_TOKEN"Env var holding the webhook verify token.
app_secret_envstring"MESSENGER_APP_SECRET"Env var holding the Facebook App Secret. Used for HMAC-SHA1 verification of every inbound X-Hub-Signature header. If unset, signature verification is skipped and a warning is logged once at startup — production deployments should always set this.
webhook_portu168452Deprecated. Previously used for standalone webhook server; now ignored. All webhooks share the API port.
default_agentstring or nullnullAgent name to route messages to.

[channels.reddit]

[channels.reddit]
client_id = ""
client_secret_env = "REDDIT_CLIENT_SECRET"
username = ""
password_env = "REDDIT_PASSWORD"
subreddits = []
FieldTypeDefaultDescription
client_idstring""Reddit app client ID.
client_secret_envstring"REDDIT_CLIENT_SECRET"Env var holding the Reddit client secret.
usernamestring""Reddit bot username.
password_envstring"REDDIT_PASSWORD"Env var holding the Reddit bot password.
subredditslist of strings[]Subreddit names to monitor.
default_agentstring or nullnullAgent name to route messages to.

[channels.mastodon]

[channels.mastodon]
instance_url = "https://mastodon.social"
access_token_env = "MASTODON_ACCESS_TOKEN"
FieldTypeDefaultDescription
instance_urlstring""Mastodon instance URL (e.g., "https://mastodon.social").
access_token_envstring"MASTODON_ACCESS_TOKEN"Env var holding the Mastodon access token.
default_agentstring or nullnullAgent name to route messages to.

[channels.bluesky]

[channels.bluesky]
identifier = "mybot.bsky.social"
app_password_env = "BLUESKY_APP_PASSWORD"
service_url = "https://bsky.social"
FieldTypeDefaultDescription
identifierstring""Bluesky handle or DID.
app_password_envstring"BLUESKY_APP_PASSWORD"Env var holding the Bluesky app password.
service_urlstring"https://bsky.social"PDS (Personal Data Server) URL.
default_agentstring or nullnullAgent name to route messages to.

[channels.feishu]

[channels.feishu]
app_id = ""
app_secret_env = "FEISHU_APP_SECRET"
# webhook_port = 8453  # Deprecated: webhooks now share the API port
FieldTypeDefaultDescription
app_idstring""Feishu/Lark app ID.
app_secret_envstring"FEISHU_APP_SECRET"Env var holding the Feishu app secret.
webhook_portu168453Deprecated. Previously used for standalone webhook server; now ignored. All webhooks share the API port.
default_agentstring or nullnullAgent name to route messages to.

[channels.revolt]

[channels.revolt]
bot_token_env = "REVOLT_BOT_TOKEN"
api_url = "https://api.revolt.chat"
FieldTypeDefaultDescription
bot_token_envstring"REVOLT_BOT_TOKEN"Env var holding the Revolt bot token.
api_urlstring"https://api.revolt.chat"Revolt API base URL.
default_agentstring or nullnullAgent name to route messages to.

[channels.nextcloud]

[channels.nextcloud]
server_url = "https://nextcloud.example.com"
token_env = "NEXTCLOUD_TOKEN"
allowed_rooms = []
FieldTypeDefaultDescription
server_urlstring""Nextcloud server URL.
token_envstring"NEXTCLOUD_TOKEN"Env var holding the Nextcloud Talk auth token.
allowed_roomslist of strings[]Room tokens to listen in. Empty = all.
default_agentstring or nullnullAgent name to route messages to.

[channels.guilded]

[channels.guilded]
bot_token_env = "GUILDED_BOT_TOKEN"
server_ids = []
FieldTypeDefaultDescription
bot_token_envstring"GUILDED_BOT_TOKEN"Env var holding the Guilded bot token.
server_idslist of strings[]Server IDs to listen in. Empty = all.
default_agentstring or nullnullAgent name to route messages to.

[channels.keybase]

[channels.keybase]
username = ""
paperkey_env = "KEYBASE_PAPERKEY"
allowed_teams = []
FieldTypeDefaultDescription
usernamestring""Keybase username.
paperkey_envstring"KEYBASE_PAPERKEY"Env var holding the Keybase paper key.
allowed_teamslist of strings[]Team names to listen in. Empty = all DMs.
default_agentstring or nullnullAgent name to route messages to.

[channels.threema]

[channels.threema]
threema_id = ""
secret_env = "THREEMA_SECRET"
# webhook_port = 8454  # Deprecated: webhooks now share the API port
FieldTypeDefaultDescription
threema_idstring""Threema Gateway ID.
secret_envstring"THREEMA_SECRET"Env var holding the Threema API secret.
webhook_portu168454Deprecated. Previously used for standalone webhook server; now ignored. All webhooks share the API port.
default_agentstring or nullnullAgent name to route messages to.

[channels.nostr]

[channels.nostr]
private_key_env = "NOSTR_PRIVATE_KEY"
relays = ["wss://relay.damus.io"]
FieldTypeDefaultDescription
private_key_envstring"NOSTR_PRIVATE_KEY"Env var holding the Nostr private key (nsec or hex format).
relayslist of strings["wss://relay.damus.io"]Nostr relay WebSocket URLs to connect to.
default_agentstring or nullnullAgent name to route messages to.

[channels.webex]

[channels.webex]
bot_token_env = "WEBEX_BOT_TOKEN"
allowed_rooms = []
FieldTypeDefaultDescription
bot_token_envstring"WEBEX_BOT_TOKEN"Env var holding the Webex bot token.
allowed_roomslist of strings[]Room IDs to listen in. Empty = all.
default_agentstring or nullnullAgent name to route messages to.

[channels.pumble]

[channels.pumble]
bot_token_env = "PUMBLE_BOT_TOKEN"
# webhook_port = 8455  # Deprecated: webhooks now share the API port
FieldTypeDefaultDescription
bot_token_envstring"PUMBLE_BOT_TOKEN"Env var holding the Pumble bot token.
webhook_portu168455Deprecated. Previously used for standalone webhook server; now ignored. All webhooks share the API port.
default_agentstring or nullnullAgent name to route messages to.

[channels.flock]

[channels.flock]
bot_token_env = "FLOCK_BOT_TOKEN"
# webhook_port = 8456  # Deprecated: webhooks now share the API port
FieldTypeDefaultDescription
bot_token_envstring"FLOCK_BOT_TOKEN"Env var holding the Flock bot token.
webhook_portu168456Deprecated. Previously used for standalone webhook server; now ignored. All webhooks share the API port.
default_agentstring or nullnullAgent name to route messages to.

[channels.twist]

[channels.twist]
token_env = "TWIST_TOKEN"
workspace_id = ""
allowed_channels = []
FieldTypeDefaultDescription
token_envstring"TWIST_TOKEN"Env var holding the Twist API token.
workspace_idstring""Twist workspace ID.
allowed_channelslist of strings[]Channel IDs to listen in. Empty = all.
default_agentstring or nullnullAgent name to route messages to.

[channels.mumble]

[channels.mumble]
host = "mumble.example.com"
port = 64738
username = "librefang"
password_env = "MUMBLE_PASSWORD"
channel = ""
FieldTypeDefaultDescription
hoststring""Mumble server hostname.
portu1664738Mumble server port.
usernamestring"librefang"Bot username in Mumble.
password_envstring"MUMBLE_PASSWORD"Env var holding the Mumble server password.
channelstring""Mumble channel to join.
default_agentstring or nullnullAgent name to route messages to.

[channels.dingtalk]

Supports two modes: stream (default, recommended) and webhook (legacy).

# Stream mode (recommended — no public IP needed)
[channels.dingtalk]
receive_mode = "stream"
app_key_env = "DINGTALK_APP_KEY"
app_secret_env = "DINGTALK_APP_SECRET"

# Webhook mode (legacy)
[channels.dingtalk]
receive_mode = "webhook"
access_token_env = "DINGTALK_ACCESS_TOKEN"
secret_env = "DINGTALK_SECRET"
# webhook_port = 8457  # Deprecated: webhooks now share the API port
FieldTypeDefaultDescription
receive_modestring"stream"Connection mode: "stream" or "webhook".
app_key_envstring"DINGTALK_APP_KEY"Env var holding the DingTalk App Key (stream mode).
app_secret_envstring"DINGTALK_APP_SECRET"Env var holding the DingTalk App Secret (stream mode).
access_token_envstring"DINGTALK_ACCESS_TOKEN"Env var holding the DingTalk webhook access token (webhook mode).
secret_envstring"DINGTALK_SECRET"Env var holding the DingTalk signing secret (webhook mode). Required — webhook mode rejects requests without a numeric timestamp header (400), with a stale or mismatched signature (401).
webhook_portu168457Deprecated. Previously used for standalone webhook server; now ignored. All webhooks share the API port.
robot_codestring or nullnullRobot code for stream mode replies (defaults to app_key).
default_agentstring or nullnullAgent name to route messages to.

[channels.discourse]

[channels.discourse]
base_url = "https://forum.example.com"
api_key_env = "DISCOURSE_API_KEY"
api_username = "system"
categories = []
FieldTypeDefaultDescription
base_urlstring""Discourse forum base URL.
api_key_envstring"DISCOURSE_API_KEY"Env var holding the Discourse API key.
api_usernamestring"system"Discourse API username.
categorieslist of strings[]Category slugs to monitor.
default_agentstring or nullnullAgent name to route messages to.

[channels.gitter]

[channels.gitter]
token_env = "GITTER_TOKEN"
room_id = ""
FieldTypeDefaultDescription
token_envstring"GITTER_TOKEN"Env var holding the Gitter auth token.
room_idstring""Gitter room ID to listen in.
default_agentstring or nullnullAgent name to route messages to.

[channels.ntfy]

[channels.ntfy]
server_url = "https://ntfy.sh"
topic = "my-agent-topic"
token_env = "NTFY_TOKEN"
FieldTypeDefaultDescription
server_urlstring"https://ntfy.sh"ntfy server URL. Can be self-hosted.
topicstring""Topic to subscribe/publish to.
token_envstring"NTFY_TOKEN"Env var holding the auth token. Optional for public topics.
default_agentstring or nullnullAgent name to route messages to.

[channels.gotify]

[channels.gotify]
server_url = "https://gotify.example.com"
app_token_env = "GOTIFY_APP_TOKEN"
client_token_env = "GOTIFY_CLIENT_TOKEN"
FieldTypeDefaultDescription
server_urlstring""Gotify server URL.
app_token_envstring"GOTIFY_APP_TOKEN"Env var holding the Gotify app token (for sending messages).
client_token_envstring"GOTIFY_CLIENT_TOKEN"Env var holding the Gotify client token (for receiving messages via WebSocket).
default_agentstring or nullnullAgent name to route messages to.

[channels.webhook]

[channels.webhook]
secret_env = "WEBHOOK_SECRET"
listen_port = 8460
# callback_url = "https://example.com/webhook"
FieldTypeDefaultDescription
secret_envstring"WEBHOOK_SECRET"Env var holding the HMAC signing secret for verifying incoming webhooks.
listen_portu168460Port to listen for incoming webhook requests.
callback_urlstring or nullnullURL to POST outgoing messages to. SSRF-guarded: the daemon refuses to start the adapter if this resolves to a private, loopback, link-local, multicast, or cloud-metadata range (e.g. 127.0.0.1, 10.0.0.1, 169.254.169.254, [::1], [::ffff:127.0.0.1]). Public hostnames only.
default_agentstring or nullnullAgent name to route messages to.

[channels.voice]

WebSocket-based voice channel with STT (Speech-to-Text) and TTS (Text-to-Speech) support.

[channels.voice]
api_key_env = "OPENAI_API_KEY"
stt_url = "https://api.openai.com"
tts_url = "https://api.openai.com"
tts_voice = "alloy"
# buffer_threshold = 32768
# default_agent = "assistant"
FieldTypeDefaultDescription
api_key_envstring"OPENAI_API_KEY"Env var holding the API key for STT/TTS services.
stt_urlstring"https://api.openai.com"Base URL for the STT API (OpenAI Whisper-compatible).
tts_urlstring"https://api.openai.com"Base URL for the TTS API.
tts_voicestring"alloy"TTS voice name. Options: alloy, echo, fable, onyx, nova, shimmer.
buffer_thresholdusize32768Audio buffer size in bytes before triggering STT.
default_agentstring or nullnullAgent name to route voice messages to.

[channels.linkedin]

[channels.linkedin]
access_token_env = "LINKEDIN_ACCESS_TOKEN"
organization_id = ""
FieldTypeDefaultDescription
access_token_envstring"LINKEDIN_ACCESS_TOKEN"Env var holding the LinkedIn OAuth2 access token.
organization_idstring""LinkedIn organization ID for messaging.
default_agentstring or nullnullAgent name to route messages to.

[channels.qq]

QQ Bot channel adapter using the official QQ Bot Open Platform API.

[channels.qq]
app_id = "your-app-id"
app_secret_env = "QQ_BOT_APP_SECRET"
allowed_users = []
# default_agent = "assistant"
# account_id = "my-qq-bot"
FieldTypeDefaultDescription
app_idstring""QQ Bot application ID registered on the QQ Open Platform.
app_secret_envstring"QQ_BOT_APP_SECRET"Env var holding the QQ Bot application secret. The actual secret is never stored in config.
allowed_userslist of strings[]QQ user IDs allowed to interact. Empty = allow all.
default_agentstring or nullnullAgent name to route messages to.
account_idstring or nullnullUnique bot instance identifier for multi-bot routing.
overridesobject(defaults)Per-channel behavior overrides. See Channel Overrides.

[channels.wecom]

WeCom (formerly WeChat Work / Enterprise WeChat) intelligent bot adapter. Connects via WebSocket to wss://openws.work.weixin.qq.com.

[channels.wecom]
bot_id = "YOUR_BOT_ID"
secret_env = "WECOM_BOT_SECRET"
# account_id = "my-wecom-bot"
# default_agent = "assistant"
FieldTypeDefaultDescription
bot_idstring""Bot ID from the WeCom admin console (intelligent bot).
secret_envstring"WECOM_BOT_SECRET"Env var holding the bot secret.
account_idstring or nullnullUnique bot instance identifier for multi-bot routing.
default_agentstring or nullnullAgent name to route messages to.
overridesobject(defaults)Per-channel behavior overrides. See Channel Overrides.

[channels.wechat]

WeChat personal account channel adapter using Tencent's iLink Bot API. Connects via QR code scan — no API keys or developer accounts required.

[channels.wechat]
# bot_token_env = "WECHAT_BOT_TOKEN"  # optional: persisted token from previous session
allowed_users = []
# default_agent = "assistant"
# account_id = "my-wechat-bot"
FieldTypeDefaultDescription
bot_token_envstring"WECHAT_BOT_TOKEN"Env var holding a persisted bot token from a previous QR login session. When set, skips QR login on restart.
allowed_userslist of strings[]WeChat user IDs allowed to interact (format: hash@im.wechat). Empty = allow all.
default_agentstring or nullnullAgent name to route messages to.
account_idstring or nullnullUnique bot instance identifier for multi-bot routing.
overridesobject(defaults)Per-channel behavior overrides. See Channel Overrides.

Channel Overrides

Every channel adapter supports an [channels.<name>.overrides] sub-table that customizes agent behavior per-channel.

[channels.telegram.overrides]
model = "claude-haiku-4-5-20251001"
system_prompt = "You are a concise Telegram assistant."
dm_policy = "respond"
group_policy = "mention_only"
rate_limit_per_minute = 0
rate_limit_per_user = 10
threading = true
output_format = "telegram_html"
usage_footer = "tokens"
typing_mode = "instant"
disable_commands = false
allowed_commands = []
blocked_commands = []
FieldTypeDefaultDescription
modelstring or nullnullModel override for this channel. Uses the agent's default model when null.
system_promptstring or nullnullSystem prompt override for this channel.
dm_policystring"respond"How the bot handles direct messages. See below.
group_policystring"mention_only"How the bot handles group messages. See below.
rate_limit_per_minuteu320Global rate limit for this channel (messages per minute). 0 = unlimited.
rate_limit_per_useru320Maximum messages per user per minute. 0 = unlimited.
threadingboolfalseEnable thread replies (where supported by the platform).
output_formatstring or nullnullOverride output formatting. See below.
usage_footerstring or nullnullOverride usage footer mode for this channel. Values: off, tokens, cost, full.
typing_modestring or nullnullTyping indicator behavior. See below. Defaults to instant.
disable_commandsboolfalseDisable all built-in slash commands. See Command Policy.
allowed_commandslist of strings[]Whitelist of command names (no leading /). When non-empty, only these commands work.
blocked_commandslist of strings[]Blacklist of command names (no leading /). Applied when allowed_commands is empty.

dm_policy values:

ValueDescription
respondRespond to all direct messages (default).
allowed_onlyOnly respond to DMs from users in the allowed list.
ignoreIgnore all direct messages.

group_policy values:

ValueDescription
allRespond to all messages in group chats.
mention_onlyOnly respond when the bot is @mentioned (default).
commands_onlyOnly respond to slash commands.
ignoreIgnore all group messages.

output_format values:

ValueDescription
markdownStandard Markdown (default).
telegram_htmlTelegram HTML subset (<b>, <i>, <code>, etc.).
slack_mrkdwnSlack mrkdwn format (*bold*, _italic_, `code`).
plain_textNo formatting markup.

typing_mode values:

ValueDescription
instantSend typing indicator immediately on message receipt (default).
messageSend typing indicator only when the first text delta arrives from the LLM.
thinkingSend typing indicator only during LLM reasoning/thinking phase.
neverNever send typing indicators.

Command Policy

Every channel ships with a set of built-in slash commands (/agent, /new, /reboot, /model, /usage, etc.) that let users switch agents, fork into a fresh session, inspect usage, and trigger workflows. On public-facing bots (customer support, community assistants), exposing these to arbitrary users is a security hole — anyone can type /agent admin to switch to an internal agent, /new to fork off into a new session (the prior conversation stays resumable on the channel), or /model to change the model.

The three command-policy fields gate the built-in commands per channel. Precedence: disable_commands > allowed_commands (whitelist) > blocked_commands (blacklist).

Blocked commands are forwarded to the agent as plain text — they are not rejected with an error. So a public bot with disable_commands = true will respond conversationally when a user types /agent admin, and the user never learns that any command system exists.

Recipe: locked-down public bot (no commands at all)

[channels.telegram.overrides]
disable_commands = true

Recipe: public bot that keeps platform UX commands

Telegram's UX expects /start to display a welcome message. Keep only the safe ones:

[channels.telegram.overrides]
allowed_commands = ["start", "help"]

Recipe: admin bot that just blocks the dangerous ones

For an operator-facing bot (with an allowed_users gate), you may still want to block commands that change costly state:

[channels.telegram.overrides]
blocked_commands = ["agent", "new", "reboot", "model", "stop"]

Command names are the bare tokens used internally — either "agent" or "/agent" in TOML works, the leading slash is stripped on match. The full list of built-in commands is in /help.


[[sidecar_channels]]

Sidecar channel adapters allow external processes (written in any language) to act as channel adapters. Communication uses newline-delimited JSON over stdin/stdout.

[[sidecar_channels]]
name = "my-custom-channel"
command = "python3"
args = ["adapters/my_adapter.py"]
channel_type = "custom_platform"
[sidecar_channels.env]
MY_API_TOKEN = "secret"
FieldTypeDefaultDescription
namestringrequiredDisplay name for this adapter.
commandstringrequiredExecutable to run (e.g., "python3", "/usr/local/bin/my-adapter").
argslist of strings[]Arguments to pass to the command.
envmap of string to string{}Extra environment variables to pass to the subprocess.
channel_typestring or nullnullChannel type identifier. Defaults to Custom(<name>) if null.