Runtime behavior
- Telegram message handling runs inside the gateway process.
- Routing is deterministic: Telegram inbound replies back to Telegram (the model does not pick channels).
- Inbound messages normalize into the shared channel envelope with reply metadata, media placeholders, and persisted reply-chain context for replies the gateway has observed.
- Group sessions are isolated by group ID. Forum topics append
:topic:<threadId>. - When the bot joins an allowed group or supergroup, it posts one introduction grounded in available room metadata: the group title, description, and pinned message. The Telegram Bot API cannot read group messages from before the bot joined, so introductions never claim to use prior chat history. Introductions are enabled by default, never run in private chats, and can be disabled with
channels.telegram.joinIntro: falseor overridden per account withchannels.telegram.accounts.<accountId>.joinIntro. See group join introductions for once-per-room behavior and untrusted-content handling. - DM messages can carry
message_thread_id; OpenClaw preserves it for replies. DM topic sessions split only when TelegramgetMereportshas_topics_enabled: truefor the bot; otherwise DMs stay on the flat session. - Long polling runs in an isolated worker. Updates are saved to a durable queue and processed in order for each chat and topic.
- Multi-account startup bounds concurrent
getMeprobes so large bot fleets do not fan out every account probe at once. - Each gateway process guards long polling so only one active poller can use a bot token at a time. Persistent
getUpdates409 conflicts point to another OpenClaw gateway, script, or external poller using the same token. - The polling watchdog restarts after 120 seconds without completed
getUpdatesliveness. - Telegram Bot API has no read-receipt support (
sendReadReceiptsdoes not apply).
Upgrade note: Telegram’s default preview changed in 2026.8.1. With
channels.telegram.streaming unset, Telegram keeps one editable status draft during the turn (the agent’s current status plus its tool lines) and sends the final answer as a normal message. It previously streamed the answer text itself into the preview. No config becomes invalid and no doctor --fix is needed; to keep the previous behavior, set:channels.telegram.dm.threadReplies and channels.telegram.direct.<chatId>.threadReplies were removed. Run openclaw doctor --fix after upgrading if your config still has those keys. DM topic routing now follows Telegram getMe.has_topics_enabled (controlled by BotFather threaded mode): topics-enabled bots use thread-scoped DM sessions when Telegram sends message_thread_id; other DMs stay on the flat session.replyToMode, streaming, and textChunkLimit apply to the next
assembled turn without reconnecting Telegram, including account overrides.
Active turns keep their captured delivery settings.
Message behavior
Live stream preview (message edits)
Live stream preview (message edits)
OpenClaw streams partial replies in real time in direct chats, groups, and topics: send a preview message, then Keep tool-progress visible but hide command/exec text:For text-only replies: short previews get the final edit in place; long finals that split into multiple messages reuse the preview as the first chunk, then send only the remainder; progress-mode finals clear the status draft and use normal final delivery; if the final edit fails before completion is confirmed, OpenClaw falls back to normal final delivery and cleans up the stale preview. For complex replies (media payloads), OpenClaw always falls back to normal final delivery and cleans up the preview.Preview streaming and block streaming are mutually exclusive. An explicit non-
editMessageText repeatedly, finalizing in place.channels.telegram.streamingisoff | partial | block | progress(default:progress); setmode: "partial"to stream answer text into the preview instead of a status draft- short initial answer previews are debounced, then materialized after a bounded delay if the run is still active
progresskeeps one editable status draft, shows the stable status label when answer activity arrives before tool progress, clears it at completion, and sends the final answer as a normal message. By default the draft is quiet: status headline, commentary, plan milestones, and approval requests. Intermediate tool failures and nonzero command exits are hidden; terminal task errors still use normal error delivery.streaming.progress.toolProgress: trueadds the rolling tool log, including tool failures.streaming.preview.toolProgresscontrols whether tool/progress updates reuse the same edited preview message inpartialandblockmodes (default:truewhen preview streaming is active)streaming.preview.commandTextcontrols command/exec detail inside those lines:status(default, tool label only) orraw(explicit command text)- completed assistant preambles update the status headline by default; a new preamble keeps the previous readable status until it finishes
streaming.progress.commentary(default:false) shows those preambles as interleaved commentary rows instead of a headline; commentary remains visible beside plan steps- successful background-process polls and internal waits stay out of the progress log; failures still follow the selected tool-progress policy, and
/verboseretains diagnostic summaries - legacy
channels.telegram.streamMode, booleanstreamingvalues, and retired native draft preview keys are detected; runopenclaw doctor --fixto migrate them
partial and block previews show them by default; the progress draft shows them only with streaming.progress.toolProgress: true. Compaction status follows the same settings and appears as soon as compaction starts, including before the first model output.Keep answer-preview edits but hide tool-progress lines:progress mode can show the tool log without editing the final answer into that message. Opt in with toolProgress: true and put the command-text policy under streaming.progress:streaming.mode: "off" disables preview edits and suppresses generic tool/progress chatter instead of sending it as standalone status messages; approval prompts, media, and errors still route through normal final delivery. streaming.preview.toolProgress: false keeps only answer-preview edits.Selected quote replies are the exception. When
replyToMode is first, all, or batched and the inbound message has selected quote text, OpenClaw sends the final answer through Telegram’s native quote-reply path and skips draft previews for that turn. Current-message replies without selected quote text still stream. When reply threading is enabled, their previews carry automatic quote excerpts and retain them when finalized in place. Set replyToMode: "off" when tool-progress visibility matters more than native quote replies. To keep native quote replies and hide tool-progress lines, use streaming.progress.toolProgress: false in progress mode or streaming.preview.toolProgress: false in partial and block modes.off preview mode overrides inherited agents.defaults.blockStreamingDefault: "on"; explicit streaming.block.enabled: true overrides the preview. If a turn cannot use previews, inherited block delivery still applies.Reasoning: /reasoning stream streams reasoning into the live preview while generating, then deletes the reasoning preview after final delivery (use /reasoning on to keep it visible). The final answer is sent without reasoning text.Native commands and custom commands
Native commands and custom commands
Telegram’s command menu is registered at startup with Rules: names are normalized (strip leading Device pairing commands (
When installed:
setMyCommands. commands.native: "auto" enables native commands for Telegram.Add custom command menu entries:/, lowercase); valid pattern a-z, 0-9, _, length 1-32; custom commands cannot override native commands; conflicts/duplicates are skipped and logged.When Telegram menu limits require trimming, configured custom commands come first unless omitted per-skill entries are replaced by a leading /skill fallback.Custom commands are menu entries only — they do not auto-implement behavior. Plugin/skill commands can still work when typed even if not shown in the Telegram menu. If native commands are disabled, built-ins are removed; custom/plugin commands may still register if configured.Common setup failures:setMyCommands failedwithBOT_COMMANDS_TOO_MUCHafter a trim retry means the menu still overflows; reduce plugin/skill/custom commands or disablechannels.telegram.commands.native.deleteWebhook,deleteMyCommands, orsetMyCommandsfailing with404: Not Foundwhile direct Bot API curl commands work usually meanschannels.telegram.apiRootwas set to the full/bot<TOKEN>endpoint.apiRootmust be the Bot API root only;openclaw doctor --fixremoves an accidental trailing/bot<TOKEN>.getMe returned 401means Telegram rejected the configured bot token. UpdatebotToken,tokenFile, orTELEGRAM_BOT_TOKEN(default account) with the current BotFather token; OpenClaw stops before polling so this is not reported as a webhook cleanup failure.setMyCommands failedwith network/fetch errors usually means outbound DNS/HTTPS toapi.telegram.orgis blocked.
Device pairing commands (device-pair plugin)
When installed:/pairgenerates a setup code- paste the code in the iOS app
/pair pendinglists pending requests (including role/scopes)- approve:
/pair approve <requestId>,/pair approve(only pending request), or/pair approve latest
requestId; re-run /pair pending before approving.More detail: Pairing.Ack reactions
Ack reactions
ackReaction sends an acknowledgement emoji while OpenClaw processes an inbound message. messages.ackReactionScope decides when it is sent.Emoji resolution order:channels.telegram.accounts.<accountId>.ackReactionchannels.telegram.ackReactionmessages.ackReaction- agent identity emoji fallback (
agents.entries.*.identity.emoji, else ”👀”)
"" to disable the reaction for a channel or account.Scope (messages.ackReactionScope, default "group-mentions"; no Telegram-account or Telegram-channel override):all (DMs + groups, including ambient room events), direct (DMs only), group-all (every group message except ambient room events, no DMs), group-mentions (groups when the bot is mentioned; no DMs — default), off / none (disabled).The default scope (
group-mentions) does not fire ack reactions in DMs or ambient room events. Use direct or all for DMs; only all acknowledges ambient room events. Changes follow hot reload and apply to subsequent messages. Each assembled turn keeps its captured value.Retained group history
Retained group history
Telegram groups and forum topics use a recent automatic context window plus explicit history reads. With
requireMention: true, permitted unmentioned messages are recorded without starting agent turns. A later addressed turn receives recent context, and the agent can use message(action="read") when it needs earlier discussion.channels.telegram.historyLimitormessages.groupChat.historyLimitcaps the automatic window (default 50).0disables automatic history injection, not recording or explicit reads.- Automatic context examines a bounded recent slice before applying topic and sender permissions. A busy group can supply fewer than the configured number of messages when other topics or excluded senders dominate that slice. The agent can page farther back with explicit history reads.
- History reads stay within the authorized account, chat, and topic. They use Telegram message IDs for references and paging; omitting a topic must not expand a topic-scoped read to the whole group.
- Agent reads default to 50 messages per page, up to 100. Use
beforewith the returnedoldestMessageIdto read older messages,afterto read newer messages, ormessageIdfor an exact reference. These reads require the agent’s authenticated current group/topic; they do not fetch Telegram server history. /newand/resetreset automatic session context, not the retained conversation. Explicit history reads can retrieve permitted earlier discussion.- The existing SQLite plugin-state table owns retained group messages. Successfully persisted records survive Gateway restarts and are not evicted by message count. Direct-message cache behavior remains bounded.
- On first use, existing group-cache records move atomically into retained storage. Legacy records without history-admission provenance, including embedded reply ancestors, remain available as explicit reply context within the existing depth and visibility limits; they are not treated as a verified conversation archive.
- History contains only messages OpenClaw received and was permitted to record. It cannot recover messages evicted before this feature, messages Telegram did not deliver, or messages from before the bot joined. Media references do not guarantee that attachment bytes remain available indefinitely.
requireMention: false activation.Limits and CLI targets
Limits and CLI targets
channels.telegram.textChunkLimitdefault 4000;streaming.chunkMode="newline"prefers paragraph boundaries (blank lines) before length splitting.channels.telegram.mediaMaxMb(default 100) caps inbound and outbound media size.- When an inbound attachment cannot be downloaded and the message proceeds to the agent, its body includes a
[media unavailable: ...]notice. Oversize notices include the effective size limit; partial albums include the failed and total attachment counts. This also applies to admitted channel posts, even when their separate chat warning is suppressed. - automatic group context uses
channels.telegram.historyLimitormessages.groupChat.historyLimit(default 50);0disables the automatic window, not retained history. - reply/quote/forward supplemental context normalizes into one selected conversation context window when the gateway has observed the parent messages; the observed-message cache lives in OpenClaw SQLite plugin state, and
openclaw doctor --fiximports legacy sidecars. Telegram only includes one shallowreply_to_messageper update, so chains older than the cache are limited to that payload. - Telegram allowlists primarily gate who can trigger the agent, not a full supplemental-context redaction boundary.
- DM history:
channels.telegram.dmHistoryLimit,channels.telegram.dms["<user_id>"].historyLimit.
openclaw message poll and support forum topics:--poll-duration-seconds (5-604800; up to seven days), --poll-anonymous, --poll-public, --thread-id (or a :topic: target). --poll-option repeats 2-12 times (Telegram’s option cap).Telegram send also supports --presentation with buttons blocks for inline keyboards (when channels.telegram.capabilities.inlineButtons allows it), --pin or --delivery '{"pin":true}' to request pinned delivery when the bot can pin in that chat, and --force-document to send outbound images, GIFs, and videos as documents instead of compressed/animated/video uploads.Action gating: channels.telegram.actions.sendMessage=false disables all outbound messages including polls; channels.telegram.actions.poll=false disables poll creation while leaving regular sends enabled.