NousResearch/hermes-agent

Telegram DM streaming transport default fix

Cluster 26157 · gateway. Data as of

2 open items. Fix merged in #28476 on 19 May 2026. Check it covers your case.

The short answer

1open issue
1open PR
5items grouped
Known?
Yes. 5 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #28476. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this unresolved. That is separate from the fix event.
Still open
1 issue and 1 PR.

Multiple fixes and reports addressing a regression where draft/native streaming was incorrectly selected as the default transport for Telegram DMs, causing visible content flicker and bubble collapse. Changes restore the edit-based streaming transport as the default.

Open issues1

  • Issue#54817Regression: Telegram draft streaming is auto-selected again and causes bubble collapse/flicker

Open PRs1

  • PR#54883fix(gateway): keep streaming transport default on edit

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #28476 fix(telegram): default streaming transport back to edit (closes #26157)

    merged to main

    files: gateway/config.py, gateway/run.py, gateway/stream_consumer.py, tests/gateway/test_config.py, tests/gateway/test_stream_consumer_draft.py, website/docs/user-guide/messaging/telegram.md

    Show the diff
    • gateway/config.py
      @@ -358,12 +358,13 @@ class StreamingConfig:
           # Transport selection:
           #   "auto"  — prefer native streaming-draft updates when the platform
           #             supports them (Telegram sendMessageDraft, Bot API 9.5+);
      -    #             fall back to edit-based when not.  Recommended.
      +    #             fall back to edit-based when not.
           #   "draft" — explicitly request native drafts; falls back to edit when
           #             the platform/chat doesn't support them.
      -    #   "edit"  — progressive editMessageText only (legacy behaviour).
      +    #   "edit"  — progressive editMessageText only (legacy/default
      +    #             behaviour).
           #   "off"   — disable streaming entirely.
      -    transport: str = "auto"
      +    transport: str = "edit"
           edit_interval: float = DEFAULT_STREAMING_EDIT_INTERVAL
           buffer_threshold: int = DEFAULT_STREAMING_BUFFER_THRESHOLD
           cursor: str = DEFAULT_STREAMING_CURSOR
      @@ -392,7 +393,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "StreamingConfig":
                   return cls()
               return cls(
                   enabled=_coerce_bool(data.get("enabled"), False),
      -            transport=data.get("transport", "auto"),
      +            transport=data.get("transport", "edit"),
                   edit_interval=_coerce_float(
                       data.get("edit_interval"), DEFAULT_STREAMING_EDIT_INTERVAL,
                   ),
    • gateway/run.py
      @@ -14809,7 +14809,7 @@ def _run_still_current() -> bool:
                               cursor=_effective_cursor,
                               buffer_only=_buffer_only,
                               fresh_final_after_seconds=_fresh_final_secs,
      -                        transport=_scfg.transport or "auto",
      +                        transport=_scfg.transport or "edit",
                               chat_type=getattr(source, "chat_type", "") or "",
                           )
                           _stream_consumer = GatewayStreamConsumer(
      @@ -15632,7 +15632,7 @@ def run_sync():
                                   cursor=_effective_cursor,
                                   buffer_only=_buffer_only,
                                   fresh_final_after_seconds=_fresh_final_secs,
      -                            transport=_scfg.transport or "auto",
      +                            transport=_scfg.transport or "edit",
                                   chat_type=getattr(source, "chat_type", "") or "",
                               )
                               _stream_consumer = GatewayStreamConsumer(
    • gateway/stream_consumer.py
      @@ -65,9 +65,9 @@ class StreamConsumerConfig:
           #             when the adapter + chat supports it; fall back to edit.
           #   "draft" — explicitly request native draft streaming; fall back to
           #             edit when unsupported.
      -    #   "edit"  — progressive editMessageText (legacy behavior).
      +    #   "edit"  — progressive editMessageText (legacy/default behavior).
           #   "off"   — handled by the gateway before the consumer is even built.
      -    transport: str = "auto"
      +    transport: str = "edit"
           # Hint for the consumer about the originating chat type (e.g. "dm",
           # "group", "supergroup", "forum").  Used to gate native draft streaming,
           # which is platform-specific (Telegram drafts are DM-only).
      @@ -846,7 +846,7 @@ def _resolve_draft_streaming(self) -> bool:
               the chat type (e.g. Telegram drafts are DM-only) and platform-version
               gates (e.g. python-telegram-bot 22.6+).
               """
      -        transport = (self.cfg.transport or "auto").lower()
      +        transport = (self.cfg.transport or "edit").lower()
               if transport == "edit":
                   return False
               # "off" is filtered upstream by the gateway; treat as edit defensively.
    • tests/gateway/test_config.py
      @@ -164,6 +164,10 @@ def test_from_dict_coerces_quoted_false_notify(self):
       
       
       class TestStreamingConfig:
      +    def test_defaults_to_edit_transport(self):
      +        restored = StreamingConfig.from_dict({"enabled": "true"})
      +        assert restored.transport == "edit"
      +
           def test_from_dict_coerces_quoted_false_enabled(self):
               restored = StreamingConfig.from_dict({"enabled": "false"})
               assert restored.enabled is False
    • tests/gateway/test_stream_consumer_draft.py
      @@ -80,6 +80,11 @@ async def _send_draft(*, chat_id, draft_id, content, metadata=None):
       class TestDraftTransportSelection:
           """Verify _resolve_draft_streaming picks the right transport."""
       
      +    def test_default_transport_stays_on_edit(self):
      +        adapter = _make_draft_capable_adapter()
      +        consumer = GatewayStreamConsumer(adapter, "12345", StreamConsumerConfig(chat_type="dm"))
      +        assert consumer._resolve_draft_streaming() is False
      +
           def test_auto_dm_with_draft_capable_adapter_picks_draft(self):
               adapter = _make_draft_capable_adapter()
               cfg = StreamConsumerConfig(transport="auto", chat_type="dm")
    • website/docs/user-guide/messaging/telegram.md
      @@ -611,7 +611,7 @@ To find a topic's `thread_id`, open the topic in Telegram Web or Desktop and loo
       
       - **Bot API 9.4 (Feb 2026):** Private Chat Topics — bots can create forum topics in 1-on-1 DM chats via `createForumTopic`. Hermes uses this for two distinct features: operator-curated [Private Chat Topics](#private-chat-topics-bot-api-94) (config-driven, fixed topic list) and user-driven [Multi-session DM mode](#multi-session-dm-mode-topic) (activated by `/topic`, unlimited user-created topics).
       - **Privacy policy:** Telegram now requires bots to have a privacy policy. Set one via BotFather with `/setprivacy_policy`, or Telegram may auto-generate a placeholder. This is particularly important if your bot is public-facing.
      -- **Bot API 9.5 (Mar 2026): Native streaming via `sendMessageDraft`.** Hermes uses Telegram's native streaming-draft API to render an animated preview of the agent's reply as tokens arrive in private chats. Drops the per-edit jitter you used to see with the legacy `editMessageText` polling path on slow models.
      +- **Bot API 9.5 (Mar 2026): Native streaming via `sendMessageDraft`.** Hermes supports Telegram's native streaming-draft API as an opt-in transport for private chats. The default remains the legacy `editMessageText` path because draft previews can visibly collapse and re-render on some Telegram clients.
       
       ### Streaming transport (`gateway.streaming.transport`)
       
      @@ -619,9 +619,9 @@ When streaming is enabled (`gateway.streaming.enabled: true`), Hermes picks one
       
       | Value | Behaviour |
       |---|---|
      -| `auto` (default) | Native draft streaming on supported chats (currently Telegram DMs); legacy edit-based path otherwise. Falls back gracefully if a draft frame fails. |
      +| `auto` | Native draft streaming on supported chats (currently Telegram DMs); legacy edit-based path otherwise. Falls back gracefully if a draft frame fails. |
       | `draft` | Force native drafts. Logs a downgrade and falls back to edit if the chat doesn't support drafts (e.g. groups/topics). |
      -| `edit` | Legacy progressive `editMessageText` polling for every chat type. |
      +| `edit` (default) | Legacy progressive `editMessageText` polling for every chat type. |
       | `off` | Disable streaming entirely (final reply only, no progressive updates). |
       
       In `~/.hermes/config.yaml`:
      @@ -630,10 +630,12 @@ In `~/.hermes/config.yaml`:
       gateway:
         streaming:
           enabled: true
      -    transport: auto    # auto | draft | edit | off
      +    transport: edit    # edit | auto | draft | off
       ```
       
      -**What you'll see in DMs with `auto` (default)** — when the agent generates a reply, Telegram shows an animated draft preview that updates token-by-token. When the reply finishes, it's delivered as a regular message and the draft preview clears naturally on the client. Drafts have no message id, so the final answer is what stays in your chat history.
      +**What you'll see in DMs with `edit` (default)** — the gateway sends a normal preview message and progressively updates it via `editMessageText`, avoiding Telegram's draft-preview collapse/rollback effect.
      +
      +**What you'll see in DMs with `auto` or `draft`** — Telegram shows an animated draft preview that updates token-by-token. When the reply finishes, it's delivered as a regular message and the draft preview clears naturally on the client. Drafts have no message id, so the final answer is what stays in your chat history.
       
       **What about groups, supergroups, forum topics?** Telegram restricts `sendMessageDraft` to private chats (DMs). The gateway transparently falls back to the edit-based path for everything else — same UX as before.
       

1 PR closed without merging

From the close comments: merged via another change 1

  • #26357 fix(telegram): default DM streaming back to edit transport · merged via another change → #28476
Closed issues · 1
  • Closed#26157ux(telegram): sendMessageDraft (native draft streaming) causes visible content flicker/rollback in DM responses
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.34: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
gateway
Merged PRs
#28476
Closed issues
1
Attempts
1 PR closed without merging
Fix link checked
19 May 2026

For your agent

The same facts, typed.