# Telegram Moltis can connect to Telegram as a bot, letting you chat with your agent from any Telegram conversation. The integration uses Telegram's [Bot API](https://core.telegram.org/bots/api) with long polling — no public URL or webhook endpoint is required. ## How It Works ``` ┌──────────────────────────────────────────────────────┐ │ Telegram Bot API │ │ (api.telegram.org/bot...) │ └──────────────────┬───────────────────────────────────┘ │ long polling (getUpdates) ▼ ┌──────────────────────────────────────────────────────┐ │ moltis-telegram crate │ │ ┌────────────┐ ┌────────────┐ ┌────────────────┐ │ │ │ Handler │ │ Outbound │ │ Plugin │ │ │ │ (inbound) │ │ (replies) │ │ (lifecycle) │ │ │ └────────────┘ └────────────┘ └────────────────┘ │ └──────────────────┬───────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ Moltis Gateway │ │ (chat dispatch, tools, memory) │ └──────────────────────────────────────────────────────┘ ``` The bot connects **outward** to Telegram's servers via polling. No port forwarding, public domain, or TLS certificate is needed. This makes it easy to run on a home machine or behind a NAT. ## Prerequisites Before configuring Moltis, create a Telegram bot: 1. Open Telegram and message [@BotFather](https://t.me/BotFather) 2. Send `/newbot` and follow the prompts to choose a name and username 3. Copy the bot token (e.g. `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`) ```admonish warning The bot token is a secret — treat it like a password. Never commit it to version control. Moltis stores it with `secrecy::Secret` and redacts it from logs, but your `moltis.toml` file is plain text on disk. Consider using [Vault](vault.md) for encryption at rest. ``` ## Configuration Add a `[channels.telegram.]` section to your `moltis.toml`: ```toml [channels.telegram.my-bot] token = "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" ``` Make sure `"telegram"` is included in `channels.offered` so the Web UI shows the Telegram option: ```toml [channels] offered = ["telegram"] ``` ### Configuration Fields | Field | Required | Default | Description | |-------|----------|---------|-------------| | `token` | **yes** | — | Bot token from @BotFather | | `dm_policy` | no | `"allowlist"` | Who can DM the bot: `"open"`, `"allowlist"`, or `"disabled"` | | `group_policy` | no | `"open"` | Who can talk to the bot in groups: `"open"`, `"allowlist"`, or `"disabled"` | | `mention_mode` | no | `"mention"` | When the bot responds in groups: `"always"`, `"mention"` (only when @mentioned), or `"none"` | | `allowlist` | no | `[]` | User IDs or usernames allowed to DM the bot (when `dm_policy = "allowlist"`) | | `group_allowlist` | no | `[]` | Group/chat IDs allowed to interact with the bot | | `untrusted_audience` | no | `"public"` | Tool audience ceiling for turns outside an operator direct chat: `"public"` or `"trusted"` | | `untrusted_tools` | no | `"deny_all"` | Tool name policy for those turns: `"deny_all"`, or `"policy"` to let configured policy layers decide | | `model` | no | — | Override the default model for this channel | | `model_provider` | no | — | Provider for the overridden model | | `agent_id` | no | — | Default agent ID for this bot's sessions | | `reply_to_message` | no | `false` | Send bot responses as Telegram replies to the user's message | | `otp_self_approval` | no | `true` | Enable OTP self-approval for non-allowlisted DM users | | `otp_cooldown_secs` | no | `300` | Cooldown in seconds after 3 failed OTP attempts | | `stream_mode` | no | `"edit_in_place"` | Streaming mode: `"edit_in_place"` or `"off"` | | `edit_throttle_ms` | no | `2000` | Minimum milliseconds between streaming edit updates | | `stream_notify_on_complete` | no | `false` | Send the final answer as a notifying message instead of silently reusing the progress message | | `stream_min_initial_chars` | no | `30` | Minimum characters before sending the first temporary progress message | | `stream_progress_max_chars` | no | `3500` | Maximum recent progress characters kept visible in the temporary streamed message | ```admonish important title="Allowlist values are strings" All allowlist entries must be **strings**, even for numeric Telegram user IDs. Write `allowlist = ["123456789"]`, not `allowlist = [123456789]`. Both numeric user IDs and usernames are supported — the bot checks both when evaluating access. ``` ### Full Example ```toml [channels] offered = ["telegram"] [channels.telegram.my-bot] token = "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" dm_policy = "allowlist" group_policy = "open" mention_mode = "mention" allowlist = ["123456789", "alice_username"] group_allowlist = ["-1001234567890"] reply_to_message = true model = "claude-sonnet-4-20250514" model_provider = "anthropic" agent_id = "research" otp_self_approval = true stream_mode = "edit_in_place" edit_throttle_ms = 2000 ``` ### Tools in Shared Chats By default, Telegram group chats, guest DMs (messages from non-operators), and unknown chat kinds receive no tools. As with Slack, MCP tools require **both** account settings below: ```toml [channels.telegram.my-bot] untrusted_audience = "trusted" untrusted_tools = "policy" ``` `untrusted_audience = "trusted"` makes MCP, WASM, and other trusted-audience tools eligible. `untrusted_tools = "policy"` removes the blanket name denial and lets the [tool policy layers](tool-policy.md) decide which tools are available. Neither setting alone enables MCP tools, and a policy allow list alone cannot bypass the default ceiling. ```admonish warning title="Account-wide trust opt-in" These settings apply to every turn outside an operator direct chat for this bot account, including guest DMs, not just the group you intend to enable. Without restrictive policies, those turns can reach every tool allowed by the remaining policy layers. Set `group_policy = "allowlist"` with a narrow `group_allowlist`, restrict DM access with `dm_policy` and `allowlist` (or disable DMs), and review OTP approvals before lifting the ceiling. Mention mode is not an access-control boundary. ``` For TOML-managed accounts, restrict tools for each enabled chat type with `tools.groups.` policies, including `private` for guest DMs. For UI-managed accounts, database-backed `tools.groups` policies are not currently part of runtime policy resolution: configure a restrictive global or provider policy before lifting the ceiling. Denials in any policy layer still win. These settings do not grant `/sh`, privileged commands, or owner-private prompt context in shared chats. Those remain restricted to operators in proven direct chats. ### Per-User and Per-Channel Model and Agent Overrides You can override the model or agent for specific users or group chats: ```toml [channels.telegram.my-bot] token = "..." model = "claude-sonnet-4-20250514" model_provider = "anthropic" [channels.telegram.my-bot.channel_overrides."-1001234567890"] model = "gpt-4o" model_provider = "openai" agent_id = "triage" [channels.telegram.my-bot.user_overrides."123456789"] model = "claude-opus-4-20250514" model_provider = "anthropic" agent_id = "research" ``` User overrides take priority over channel overrides, which take priority over the account default, for both model selection and agent selection. ## Access Control Telegram uses the same gating system as Discord and other channels. ### DM Policy Controls who can send direct messages to the bot. | Value | Behavior | |-------|----------| | `"allowlist"` | Only users listed in `allowlist` can DM (default) | | `"open"` | Anyone who can find the bot can DM it | | `"disabled"` | DMs are silently ignored | ```admonish warning title="Default denies unlisted users" The default `dm_policy` is `"allowlist"`. With an empty `allowlist`, **all DMs are blocked**. You must either add entries to `allowlist` or set `dm_policy = "open"`. ``` ### Group Policy Controls who can interact with the bot in group chats. | Value | Behavior | |-------|----------| | `"open"` | Bot responds in any group (default) | | `"allowlist"` | Only groups listed in `group_allowlist` are allowed | | `"disabled"` | Group messages are silently ignored | ### 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 in allowed groups | | `"none"` | Bot never responds in groups (useful for DM-only bots) | ### Allowlist Matching Allowlist entries support: - **Exact match** (case-insensitive): `"alice"`, `"123456789"` - **Glob wildcards**: `"admin_*"`, `"*_bot"`, `"user_*_vip"` Both the user's **numeric Telegram ID** and their **username** are checked against the allowlist. For example, if a user has ID `123456789` and username `alice`, either `"123456789"` or `"alice"` in the allowlist grants access. ### OTP Self-Approval When `dm_policy = "allowlist"` and `otp_self_approval = true` (the default), unknown users who DM the bot receive a verification challenge: 1. User sends a DM to the bot 2. Bot responds with a challenge prompt (the 6-digit code is **not** shown to the user) 3. The code appears in the Moltis web UI under **Channels > Senders** 4. The bot owner shares the code with the user out-of-band 5. User replies with the 6-digit code 6. On success, the user is automatically added to the allowlist After 3 failed attempts, the user is locked out for `otp_cooldown_secs` seconds (default: 300). Codes expire after 5 minutes. Set `otp_self_approval = false` if you want to manually approve every user from the web UI. ## Streaming By default (`stream_mode = "edit_in_place"`), Telegram shows temporary progress while a turn is running. The bot sends a silent progress message after `stream_min_initial_chars` characters (default: 30), edits that progress message at most once every `edit_throttle_ms` milliseconds (default: 2000), and keeps only the latest `stream_progress_max_chars` progress characters visible. When the final answer is ready, the progress message is cleaned up or reused and the final answer is delivered separately from intermediate progress. With `stream_notify_on_complete = false` (default), Moltis may silently reuse the existing progress message for the final answer. With `stream_notify_on_complete = true`, Moltis deletes the progress message where possible and sends the final answer as a new notifying message. ## Session Commands Telegram supports the standard channel session commands: | Command | Description | |---------|-------------| | `/new` | Start a fresh session for the current chat | | `/sessions` | List or switch among sessions already bound to the current chat | | `/attach` | List existing non-cron sessions and rebind one to the current chat | | `/agent` | List or switch chat agents | | `/mode` | List or switch temporary session modes | | `/model` | List or switch models | | `/approvals` | List pending exec approvals for the current session | | `/approve N` | Approve the numbered exec request from `/approvals` | | `/deny N` | Deny the numbered exec request from `/approvals` | `/sessions` is intentionally scoped to the current chat. If you want to bring a different existing session into the chat, use `/attach` instead. Reattaching a session moves that session's channel binding to the current chat, it is not a copy. Set `stream_mode = "off"` to disable streaming and send the full response as a single message. When `stream_notify_on_complete = true`, the bot sends a short non-silent message after streaming finishes. This can trigger a Telegram push notification on the user's device (since silent edits don't always trigger notifications). ## Finding Your Telegram User ID To find your numeric Telegram user ID (for the `allowlist`): 1. Message [@userinfobot](https://t.me/userinfobot) on Telegram 2. It replies with your user ID, first name, and username 3. Use the numeric ID as a string in your config: `allowlist = ["123456789"]` Alternatively, use your Telegram username (without the `@`): `allowlist = ["your_username"]`. ## Web UI Setup You can also configure Telegram through the web interface: 1. Open **Settings > Channels** 2. Click **Connect Telegram** 3. Enter an account ID (any alias) and your bot token 4. Adjust DM policy, mention mode, and allowlist as needed 5. Click **Connect** The same form is available during onboarding when Telegram is in `channels.offered`. In **Settings > Channels**, use the account's **Advanced Config** JSON editor for settings without dedicated form fields. To opt into trusted-audience tools and policy-based access, set: ```json { "untrusted_audience": "trusted", "untrusted_tools": "policy" } ``` Read [Tools in Shared Chats](#tools-in-shared-chats) and configure restrictive access and tool policies first. Channel settings added or edited in the web UI are stored in `data_dir()/moltis.db`, not written back to `moltis.toml`. Use the TOML examples only for manually managed accounts. ## Troubleshooting ### Bot doesn't respond - Verify the bot token is correct (ask @BotFather for `/token` if unsure) - Check `dm_policy` — if set to `"allowlist"`, make sure your user ID or username is listed in `allowlist` - An empty `allowlist` with `dm_policy = "allowlist"` blocks **all** DMs - Check `group_policy` — if `"disabled"`, group messages are ignored - Look at logs: `RUST_LOG=moltis_telegram=debug moltis` ### "allowed_users" doesn't work The field name is `allowlist`, not `allowed_users`. If you're migrating from OpenClaw, note that the field was renamed. Values must also be **strings** (e.g. `["123456789"]`), not integers. ### Bot responds to everyone - Check that `dm_policy = "allowlist"` is set (it's the default) - Verify you have entries in `allowlist` — an empty allowlist with `dm_policy = "allowlist"` blocks everyone, but `dm_policy = "open"` allows everyone ### Bot doesn't respond in groups - Check `mention_mode` — if set to `"mention"`, you must @mention the bot - Check `group_policy` — if `"disabled"`, group messages are ignored - Check `group_allowlist` — if non-empty, the group must be listed