The headline feature. Telegram’s Bot API 10.1 introduced sendRichMessage —
bots can now send raw Markdown instead of the notoriously painful MarkdownV2 format.
Tables, task lists, collapsible <details> blocks, and math expressions render natively.
| Aspect | Rich path | Legacy path |
|---|---|---|
| API method | sendRichMessage | sendMessage with MarkdownV2 |
| Content limit | 32,768 chars | 4,096 chars |
| Markdown | Raw agent output (tables, math, details) | Escaped MarkdownV2 (fragile) |
| Streaming | sendRichMessageDraft (ephemeral previews) | Edit-in-place via editMessageText |
The rich path is opportunistic. The adapter tries rich first and degrades gracefully:
do_api_request?sendRichMessage — if Telegram returns a permanent error (BadRequest, unsupported endpoint), fall back to legacy MarkdownV2._rich_send_disabled = True for the adapter’s lifetime. No repeated failed attempts.This conservative design avoids the worst failure mode: sending the same message twice via different paths.
Telegram Desktop 6.9.1 crashes on rich messages containing math inside collapsible <details> blocks
(tdesktop#30808).
Hermes detects this pattern via regex and skips the rich path entirely for affected content.
Agent responses stream in real-time via two mechanisms:
sendRichMessageDraft emits ephemeral preview frames. Each frame overwrites the previous. The final sendRichMessage replaces the draft.editMessageText updates a single message bubble in place as chunks arrive. Subject to the 4,096-char MarkdownV2 limit.The streaming_overflow_limit() method raises the split threshold to 32K when rich is available, so a reply that fits one rich message isn’t fragmented at the legacy 4K limit.
Status callbacks (“Compressing context…”, “Calling tool…”) route through send_or_update_status().
A {(chat_id, status_key) → message_id} cache ensures the same bubble is edited instead of spawning new ones.
If the edit fails (user deleted the message, too old), the cache entry drops and the next emit posts fresh.
| Mode | Behavior |
|---|---|
important (default) | Only final responses, approval prompts, and command confirmations ring. Tool progress and streaming chunks use disable_notification=true. |
all | Every outgoing message fires a push notification. Legacy behavior. |
This is a smart UX pattern — long agent turns with tool progress bubbles would otherwise spam the user’s phone.
When the agent calls the clarify tool, Telegram renders inline keyboard buttons:
❓ Which framework should I use?
[1. Next.js][2. Remix][3. Astro]
[✏️ Other (type answer)]
Exec approval for dangerous commands works the same way — reply “yes”/“no” inline.
Configurable timeout (agent.clarify_timeout, default 600s) auto-unblocks if the user doesn’t respond.
When a user message triggers an agent turn, the adapter pins that message for the duration (with disable_notification=true)
and unpins it when the response finishes. A lightweight visual indicator that the bot is working, not ignoring you.
| Lines of code | 6,685 (largest platform adapter) |
| Base class | 4,932 lines (base.py) |
| Network layer | 259 lines (telegram_network.py) |
| Library | python-telegram-bot 22.x |
| Rich messages added | June 12, 2026 (with 5 follow-up fixes in 2 days) |
sendRichMessageDraft is Telegram-specific, but the concept (disposable preview frames that the final message replaces) generalizes to any platform with message editing.