Common Features
All adapters share a common foundation:Graceful Shutdown
Coordinated shutdown via watch channel
Resilient Connections
Exponential backoff on connection failures
Secret Management
Zeroizing strings for secure token handling
Message Splitting
Automatic splitting for platform limits
Flexible Overrides
Per-channel model and prompt customization
Policy Enforcement
DM/group policy control per channel
Rate Limiting
Per-user rate limiting protection
Format Support
Markdown, Telegram HTML, Slack Mrkdwn, plain text
All 40 Channels
Core Platforms (7)
| Channel | Protocol | Environment Variables | Type |
|---|---|---|---|
| Telegram | Bot API long-polling | TELEGRAM_BOT_TOKEN | Telegram |
| Discord | Gateway WebSocket v10 | DISCORD_BOT_TOKEN | Discord |
| Slack | Socket Mode WebSocket | SLACK_BOT_TOKEN, SLACK_APP_TOKEN | Slack |
| Cloud API webhook | WA_ACCESS_TOKEN, WA_PHONE_ID, WA_VERIFY_TOKEN | WhatsApp | |
| Signal | signal-cli REST/JSON-RPC | (system service) | Signal |
| Matrix | Client-Server API /sync | MATRIX_TOKEN | Matrix |
| IMAP + SMTP | EMAIL_PASSWORD | Email |
Enterprise (8)
| Channel | Protocol | Environment Variables | Type |
|---|---|---|---|
| Microsoft Teams | Bot Framework v3 webhook + OAuth2 | TEAMS_APP_ID, TEAMS_APP_SECRET | Teams |
| Mattermost | WebSocket + REST v4 | MATTERMOST_TOKEN, MATTERMOST_URL | Mattermost |
| Google Chat | Service account webhook | GOOGLE_CHAT_SA_KEY, GOOGLE_CHAT_SPACE | Custom("google_chat") |
| Webex | Bot SDK WebSocket | WEBEX_BOT_TOKEN | Custom("webex") |
| Feishu / Lark | Open Platform webhook | FEISHU_APP_ID, FEISHU_APP_SECRET | Custom("feishu") |
| Rocket.Chat | REST polling | ROCKETCHAT_TOKEN, ROCKETCHAT_URL | Custom("rocketchat") |
| Zulip | Event queue long-polling | ZULIP_EMAIL, ZULIP_API_KEY, ZULIP_URL | Custom("zulip") |
| XMPP | XMPP protocol | XMPP_JID, XMPP_PASSWORD, XMPP_SERVER | Custom("xmpp") |
Social Media (8)
| Channel | Protocol | Environment Variables | Type |
|---|---|---|---|
| LINE | Messaging API webhook | LINE_CHANNEL_SECRET, LINE_CHANNEL_TOKEN | Custom("line") |
| Viber | Bot API webhook | VIBER_AUTH_TOKEN | Custom("viber") |
| Facebook Messenger | Platform API webhook | MESSENGER_PAGE_TOKEN, MESSENGER_VERIFY_TOKEN | Custom("messenger") |
| Mastodon | Streaming API WebSocket | MASTODON_TOKEN, MASTODON_INSTANCE | Custom("mastodon") |
| Bluesky | AT Protocol WebSocket | BLUESKY_HANDLE, BLUESKY_APP_PASSWORD | Custom("bluesky") |
| OAuth2 polling | REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, REDDIT_USERNAME, REDDIT_PASSWORD | Custom("reddit") | |
| Messaging API polling | LINKEDIN_ACCESS_TOKEN | Custom("linkedin") | |
| Twitch | IRC gateway | TWITCH_TOKEN, TWITCH_CHANNEL | Custom("twitch") |
Community Platforms (6)
| Channel | Protocol | Environment Variables | Type |
|---|---|---|---|
| IRC | Raw TCP PRIVMSG | IRC_SERVER, IRC_NICK, IRC_PASSWORD | Custom("irc") |
| Guilded | WebSocket | GUILDED_BOT_TOKEN | Custom("guilded") |
| Revolt | WebSocket | REVOLT_BOT_TOKEN | Custom("revolt") |
| Keybase | Bot API polling | KEYBASE_USERNAME, KEYBASE_PAPERKEY | Custom("keybase") |
| Discourse | REST polling | DISCOURSE_API_KEY, DISCOURSE_URL | Custom("discourse") |
| Gitter | Streaming API | GITTER_TOKEN | Custom("gitter") |
Self-Hosted (1)
| Channel | Protocol | Environment Variables | Type |
|---|---|---|---|
| Nextcloud Talk | REST polling | NEXTCLOUD_TOKEN, NEXTCLOUD_URL | Custom("nextcloud") |
Privacy-Focused (3)
| Channel | Protocol | Environment Variables | Type |
|---|---|---|---|
| Threema | Gateway API webhook | THREEMA_ID, THREEMA_SECRET | Custom("threema") |
| Nostr | NIP-01 relay WebSocket | NOSTR_PRIVATE_KEY, NOSTR_RELAY | Custom("nostr") |
| Mumble | TCP text protocol | MUMBLE_SERVER, MUMBLE_USERNAME, MUMBLE_PASSWORD | Custom("mumble") |
Workplace (4)
| Channel | Protocol | Environment Variables | Type |
|---|---|---|---|
| Pumble | Webhook | PUMBLE_WEBHOOK_URL, PUMBLE_TOKEN | Custom("pumble") |
| Flock | Webhook | FLOCK_TOKEN | Custom("flock") |
| Twist | API v3 polling | TWIST_TOKEN | Custom("twist") |
| DingTalk | Robot API webhook | DINGTALK_TOKEN, DINGTALK_SECRET | Custom("dingtalk") |
Notification Services (2)
| Channel | Protocol | Environment Variables | Type |
|---|---|---|---|
| ntfy | SSE pub/sub | NTFY_TOPIC, NTFY_SERVER | Custom("ntfy") |
| Gotify | WebSocket | GOTIFY_TOKEN, GOTIFY_URL | Custom("gotify") |
Integration (1)
| Channel | Protocol | Environment Variables | Type |
|---|---|---|---|
| Webhook | Generic HTTP with HMAC-SHA256 | WEBHOOK_URL, WEBHOOK_SECRET | Custom("webhook") |
Channel Configuration
All channel configurations live in~/.openfang/config.toml under the [channels] section.
Common Configuration Fields
bot_token_env / token_env
bot_token_env / token_env
The environment variable holding the bot/access token. OpenFang reads the token from this env var at startup. All secrets are stored as
Zeroizing<String> and wiped from memory on drop.default_agent
default_agent
The agent name (or ID) that receives messages when no specific routing applies.
allowed_users
allowed_users
Optional list of platform user IDs allowed to interact. Empty means allow all.
overrides
overrides
Optional per-channel behavior overrides. See Channel Overrides section below.
Channel Overrides
Every channel adapter supportsChannelOverrides, which let you customize behavior per channel without modifying the agent manifest.
Override Fields
| Field | Type | Default | Description |
|---|---|---|---|
model | Option<String> | Agent default | Override the LLM model for this channel |
system_prompt | Option<String> | Agent default | Override the system prompt |
dm_policy | DmPolicy | Respond | How to handle direct messages |
group_policy | GroupPolicy | MentionOnly | How to handle group/channel messages |
rate_limit_per_user | u32 | 0 (unlimited) | Max messages per minute per user |
threading | bool | false | Send replies as thread responses |
output_format | Option<OutputFormat> | Markdown | Output format for this channel |
usage_footer | Option<UsageFooterMode> | None | Append token usage to responses |
Output Formats and Policies
Output Formatter
The formatter module converts Markdown output from the LLM into platform-native formats:| Format | Target | Notes |
|---|---|---|
Markdown | Standard Markdown | Default; passed through as-is |
TelegramHtml | Telegram HTML subset | Converts **bold** to <b>, `code` to <code> |
SlackMrkdwn | Slack mrkdwn | Converts **bold** to *bold*, links to <url|text> |
PlainText | Plain text | Strips all formatting |
DM Policy
Controls how the adapter handles direct messages:| Policy | Behavior |
|---|---|
Respond | Respond to all DMs (default) |
AllowedOnly | Only respond to DMs from users in allowed_users |
Ignore | Silently drop all DMs |
Group Policy
Controls how the adapter handles messages in group chats, channels, and rooms:| Policy | Behavior |
|---|---|
All | Respond to every message in the group |
MentionOnly | Only respond when the bot is @mentioned (default) |
CommandsOnly | Only respond to /command messages |
Ignore | Silently ignore all group messages |
Policy enforcement happens in
dispatch_message() before the message reaches the agent loop. This means ignored messages consume zero LLM tokens.Setup Guides
Telegram
Create a bot
Open Telegram and message @BotFather. Send
/newbot and follow the prompts.getUpdates API with 30-second timeout. Automatically splits responses over 4096 characters.
Interactive Setup:
Discord
Create a Discord application
Go to Discord Developer Portal and create a new application.
Add a bot
Go to the Bot section, click “Add Bot”, and enable Message Content Intent under Privileged Gateway Intents.
Invite the bot
Go to OAuth2 > URL Generator, select scope
bot and permissions Send Messages + Read Message History. Use the generated URL to invite the bot.Slack
Create a Slack app
Go to Slack API, click “Create New App” > “From Scratch”.
Enable Socket Mode
Go to Settings > Socket Mode, enable it, and generate an App-Level Token with scope
connections:write.Add bot scopes
Go to OAuth & Permissions and add:
chat:write, app_mentions:read, im:history, im:read, im:write.threading = true, replies are sent to the message’s thread via thread_ts.
Set up Meta Business account
Go to Meta for Developers and create a Business App.
Matrix
How It Works: Uses Matrix Client-Server API with long-polling
/sync. Processes new messages from joined rooms.
Create app password
For Gmail, create an App Password.
Agent Routing
TheAgentRouter determines which agent receives an incoming message:
Per-channel default
Each channel config has a
default_agent field. Messages from that channel go to that agent.User-agent binding
If a user has been associated with a specific agent, messages from that user route to that agent.
Command prefix
Users can switch agents by sending
/agent coder in chat. Subsequent messages route to the “coder” agent.WebChat (Built-in)
The WebChat UI is embedded in the daemon and requires no configuration:Real-time Chat
WebSocket-based communication
Streaming Responses
Text deltas arrive as generated
Agent Selection
Switch between running agents
Token Usage Display
Live token and cost tracking
Writing Custom Adapters
To add support for a new messaging platform, implement theChannelAdapter trait.
The ChannelAdapter Trait
Implementation Steps
Implement the trait
Use
ChannelType::Custom("myplatform".to_string()) for the channel type. Wrap secrets in Zeroizing<String>. Use exponential backoff for connection failures.Key points for new adapters:
- Only the 9 most common channels have named
ChannelTypevariants. All others useCustom(String). - Wrap secrets in
Zeroizing<String>so they are wiped from memory on drop. - Accept a
watch::Receiver<bool>for coordinated shutdown with the daemon. - Use the shared
split_message(text, max_len)utility for platforms with message length limits.