# NousResearch/hermes-agent · Telegram DM streaming transport default fix

## Open now

- 1 issue, 1 PR open on GitHub.
- #54817: Regression: Telegram draft streaming is auto-selected again and causes bubble collapse/flicker
- PR #54883: fix(gateway): keep streaming transport default on edit

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.

## TL;DR

- 5 member(s): 1 open issue(s), 1 closed issue(s), 1 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #28476. Verify it addresses your case.
- **Recommended:** read PR #28476 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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 member 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.

## Open related issues

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

## Closed / duplicate issues

- #26157: ux(telegram): sendMessageDraft (native draft streaming) causes visible content flicker/rollback in DM responses

## Prior & competing PRs

- **#28476: fix(telegram): default streaming transport back to edit (closes #26157)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - 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`
  - fix hunks:
  - `gateway/config.py`

    ```diff
    @@ -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
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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(
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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).
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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`)
     
    ```

    ```diff
    @@ -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`:
    ```

    ```diff
    @@ -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.
     
    ```
- **#26357: fix(telegram): default DM streaming back to edit transport**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#28476`
- **#54883: fix(gateway): keep streaming transport default on edit**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `gateway/config.py`, `hermes_cli/config.py`, `tests/gateway/test_config.py`, `tests/gateway/test_per_platform_streaming_defaults.py`, `website/docs/user-guide/messaging/telegram.md`, `website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/messaging/telegram.md`

## Fix status

- Merged to main in PR #28476 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-19`.
- Subsystem: `gateway`
