Channels
Moltis connects to messaging platforms through channels. Each channel type has a distinct inbound mode, determining how it receives messages, and a set of capabilities that control what features are available.
Supported Channels
| Channel | Inbound Mode | Public URL Required | Key Capabilities |
|---|---|---|---|
| Telegram | Polling | No | Streaming, voice ingest, reactions, OTP, location |
| Discord | Gateway (WebSocket) | No | Streaming, interactive messages, threads, voice ingest, reactions |
| Matrix | Gateway (sync loop) | No | Streaming, voice ingest, interactive polls, threads, reactions, OTP, location, encrypted chats, device verification, ownership bootstrap |
| Microsoft Teams | Webhook | Yes | Streaming, interactive messages, threads, reactions |
| Gateway (WebSocket) | No | Streaming, voice ingest, OTP, pairing, location | |
| Slack | Socket Mode | No | Streaming, interactive messages, threads, reactions |
| Nostr | Gateway (relay subscription) | No | OTP, encrypted DMs (NIP-04) |
| Signal | Gateway (signal-cli SSE) | No | OTP, DMs, groups, outbound text |
Inbound Modes
Polling
The bot periodically fetches new messages from the platform API. No public URL or open port is needed. Used by Telegram.
Gateway / WebSocket
The bot opens a persistent outbound WebSocket connection to the platform and receives events in real time, or uses a persistent sync loop over outbound HTTP. No public URL needed. Used by Discord, Matrix, and WhatsApp.
Socket Mode
Similar to a gateway connection, but uses the platform’s Socket Mode protocol. No public URL needed. Used by Slack.
Webhook
The platform sends HTTP POST requests to a publicly reachable endpoint on your server. You must configure the messaging endpoint URL in the platform’s settings. Used by Microsoft Teams.
None (Send-Only)
For channels that only send outbound messages and do not receive inbound traffic. No channels currently use this mode, but it is available for future integrations (e.g. email, SMS).
Capabilities Reference
| Capability | Description |
|---|---|
supports_outbound | Can send messages to users |
supports_streaming | Can stream partial responses (typing/editing) |
supports_interactive | Can send interactive components (buttons, menus) |
supports_threads | Can reply in threads |
supports_voice_ingest | Can receive and transcribe voice messages |
supports_pairing | Requires device pairing (QR code) |
supports_otp | Supports OTP-based sender approval |
supports_reactions | Can add/remove emoji reactions |
supports_location | Can receive and process location data |
Setup
Channels can be configured in two places:
- In
moltis.tomlunder[channels], for file-managed setups - In the web UI under Settings -> Channels, which stores channel accounts in the internal
channelstable insidedata_dir()/moltis.db
The web UI does not write channel settings back into moltis.toml. It includes an advanced JSON config editor so channel-specific settings remain reachable even when a dedicated form field has not been added yet.
The channel picker itself is controlled by [channels].offered in
moltis.toml. If you edit that list by hand, reload the page so the web UI
re-reads the current picker options.
Channel configs stored through the web UI currently live as JSON records in the
internal channels table in data_dir()/moltis.db. They are not currently
wrapped by the Moltis vault, so treat local access to that database as access
to the configured channel credentials.
Some channel integrations also have platform-specific limits. For Matrix, encrypted chats require password auth. Access-token auth is only suitable for plain Matrix traffic because Moltis cannot import an existing device’s private E2EE keys from an access token alone. See Matrix for the full setup, ownership, verification, and troubleshooting flow.
moltis.toml and the web UI are both loaded at startup. If the same (channel_type, account_id) exists in both, the moltis.toml entry wins.
Manual file configuration looks like this:
[channels.telegram.my_bot]
token = "123456:ABC-DEF..."
dm_policy = "allowlist"
allowlist = ["alice", "bob"]
[channels.msteams.my_teams_bot]
app_id = "..."
app_password = "..."
[channels.discord.my_discord_bot]
token = "..."
[channels.slack.my_slack_bot]
bot_token = "xoxb-..."
app_token = "xapp-..."
[channels.matrix.my_matrix_bot]
homeserver = "https://matrix.example.com"
access_token = "syt_..."
user_id = "@bot:example.com"
[channels.whatsapp.my_wa]
dm_policy = "open"
[channels.signal.my_signal]
account = "+15551234567"
http_url = "http://127.0.0.1:8080"
For detailed configuration, see the per-channel pages: Telegram, Microsoft Teams, Discord, Slack, Matrix, WhatsApp, Nostr, Signal.
You can also use the web UI’s Channels tab for guided setup with each platform. Web-added channels do not get written back into moltis.toml.
For Matrix specifically, the web UI now supports the full normal setup flow:
- password auth is the default because it unlocks encrypted chats
- dedicated bot accounts default to
moltis_ownedso Moltis can bootstrap cross-signing and recovery - older Matrix accounts that need one external approval expose that approval flow in the channel card instead of failing silently
Proactive Outbound Messaging
Agents are not limited to replying in the current chat. Moltis supports three main outbound patterns:
send_messagetool for direct proactive messages to any configured channel account/chatupdate_channel_settingstool for safe in-chat edits to channel access rules, allowlists, and model routing- Cron job delivery for background jobs that should post their final output to a channel
- Heartbeat delivery for periodic heartbeat acknowledgements sent to a chosen chat
Example send_message tool call:
{
"account_id": "my-telegram-bot",
"to": "123456789",
"text": "Deployment finished successfully."
}
account_id is the configured channel account name, either from moltis.toml or from a channel account stored through the web UI, and to is the destination chat, peer, or room identifier for that platform.
Example update_channel_settings tool call:
{
"account_id": "my-telegram-bot",
"settings": {
"dm_policy": "allowlist",
"allowlist_add": ["alice"],
"model": "openai/gpt-5"
}
}
update_channel_settings intentionally supports a narrow patch surface. It is
for non-secret channel settings only, not raw moltis.toml editing, token
rotation, or arbitrary config mutation.
Access Control
All channels share the same access control model with three settings:
DM Policy
Controls who can send direct messages to the bot.
| Value | Behavior |
|---|---|
"allowlist" | Only users listed in allowlist can DM (default for all channels except WhatsApp) |
"open" | Anyone can DM the bot |
"disabled" | DMs are silently ignored |
Group Policy
Controls who can interact with the bot in group chats / channels / guilds.
| Value | Behavior |
|---|---|
"open" | Bot responds in all groups (default) |
"allowlist" | Only groups on the allowlist are allowed |
"disabled" | Group messages are silently ignored |
The group allowlist field name varies by channel: group_allowlist (Telegram,
WhatsApp, MS Teams), guild_allowlist (Discord), channel_allowlist (Slack),
room_allowlist (Matrix).
Mention Mode
Controls when the bot responds in groups (does not apply to DMs).
| Value | Behavior |
|---|---|
"mention" | Bot only responds when @mentioned (default) |
"always" | Bot responds to every message |
"none" | Bot never responds in groups (DM-only) |
Allowlist Matching
All allowlist fields across all channels share the same matching behavior:
- Values are strings — even for numeric IDs, use
"123456789"not123456789 - Case-insensitive —
"Alice"matches"alice" - Glob wildcards —
"admin_*","*@example.com","user_*_vip" - Multiple identifiers — both the user’s numeric ID and username are checked (where applicable)
Operators (Privileged Senders)
Passing the access gate lets someone talk to the bot. It does not let them run commands on your machine. That is a separate list:
[channels.telegram.my-bot]
allowlist = ["owner-id"] # who may DM the bot
operators = ["owner-id"] # who may use privileged access in proven DMs
```admonish warning title=“Upgrading: privileged commands are off until you set operators”
Earlier versions treated the DM allowlist as the privileged list, so anyone
allowed to DM the bot could run /sh, /approve, and /update. That fallback
is gone: an empty operators list means nobody, including you.
After upgrading, add your own exact platform sender ID to operators for each
account, or those commands will refuse everyone. If you do not know your ID,
run /sh — the refusal message tells you what yours is on that channel.
```admonish warning title="Upgrading: shared chats start from an empty history once"
An untrusted turn only sees history from runs that were themselves untrusted, and
messages written before this change carry no such marker. The first turn after
upgrading in a group chat, guild, or channel-bound session therefore starts with
no prior context. This happens once per session; everything written from then on
accumulates normally.
A command requires an operator DM when the worst case reaches beyond the current chat. Everything scoped to the conversation you are already in stays open to any sender who clears the access gate:
| Commands | |
|---|---|
| Anyone who may chat | /help, /new, /clear, /compact, /title, /fork, /stop, /model, /mode, /fast |
| Operators in proven direct chats only | /sh, /update, /approve, /deny, /approvals, /sandbox, /attach, /sessions, /context, /insights, /peek, /btw, /rollback, /agent, /steer, /queue |
The public set can only disrupt the room’s own conversation — something any
member can already do by talking. The operator set runs host commands (/sh,
/update), acts on the owner’s behalf (/approve and /deny resolve the
owner’s pending exec requests, which is code execution by proxy), weakens
isolation (/sandbox can turn the sandbox off), or reads state outside the
current chat (/attach, /sessions, /context, /insights, /peek, /btw).
New commands default to operator direct-chat only, so adding one is safe until it is deliberately reviewed.
Guest, shared-room, and unknown-topology channel turns receive no tools. The gateway applies both the registry’s public audience ceiling and a deny-all name policy, so account, group, and per-sender tool policies cannot restore even a tool reviewed for other untrusted origins.
Every normal turn in a shared room is untrusted, including turns sent by an
operator, because the shared history contains messages from other people.
/sh, shell command mode, and every privileged command are denied there. In an
operator’s conversation that the adapter can prove is direct, normal agent
turns may use the full configured tool set. Unknown chat kinds fail closed as
shared.
Untrusted turns also omit owner-private prompt context: user profile, project context, long-term memory, skills, and automatic memory extraction.
Channel-bound sessions
A session is bound to a chat when the chat created it, or when an operator ran
/attach to move an existing session into that chat. Binding is what makes the
chat’s replies land in the right place — and it also means the session’s history
contains messages the bot did not author.
Every request into a bound session that does not come from the channel gateway
is therefore treated as untrusted, whoever made it: web UI turns, cron jobs,
webhooks, and the sessions_send tool all run with no tools and no private
context. The downgrade is logged (session is bound to a channel; running this turn without tools or private context), because otherwise an affected cron job
simply answers as though it had no tools.
Granting operator
Two places, and both state what it means:
- When approving a sender. Approving asks for a role: Guest (may chat) or Operator (may run commands on this machine). While an account has no operators at all, Operator is pre-selected — on a fresh install the person holding the OTP code read it off this very UI, so they are almost certainly the owner. Once one operator exists, Guest is the default.
- Settings → Channels → Edit → Operators, to add or remove IDs directly.
Approving as operator records the sender’s exact platform ID, not their username, because operator matching is exact.
Resolution is fail-closed:
operators | allowlist | Who is an operator |
|---|---|---|
| non-empty | anything | only exact sender IDs in operators |
| empty | anything | nobody — privileged commands are disabled |
A sender with no identifier (for example an unattributed button callback) is never an operator.
operators contains exact, case-sensitive platform sender IDs. Globs,
usernames, and partial IDs do not grant privilege. For WhatsApp use the full
JID; for Matrix use the full Matrix user ID.
Untrusted tool restrictions stack with the per-channel tool policy
(channels.<type>.<account>.tools.groups.<chat_type>). Those policies can
further restrict an operator DM, but cannot enable tools for a guest, shared
room, or unknown chat. Grant eligibility for privileged access by adding the
sender to operators; the sender must still use a proven direct chat.
OTP Self-Approval
Channels that support OTP (Telegram, Microsoft Teams, Slack, Discord, Matrix, WhatsApp) allow non-allowlisted users to self-approve by entering a 6-digit code. The code appears in the web UI under Channels > Senders. See each channel’s page for details.
| Field | Default | Description |
|---|---|---|
otp_self_approval | true | Enable OTP challenges for non-allowlisted DM users |
otp_cooldown_secs | 300 | Lockout duration after 3 failed attempts |