NousResearch/hermes-agent

Telegram notification mode to suppress intermediate pushes

Cluster 22771. Data as of

Nothing open on GitHub. Fix merged in #22793 on 9 May 2026. Check it covers your case.

The short answer

0open issues
0open PRs
3items grouped
Known?
Yes. 3 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #22793. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #22793. That is separate from the fix event.
Still open
Nothing open on GitHub.

These members all concern adding a notification mode to the Telegram gateway integration that suppresses intermediate push notifications. The work appears across a feature request and at least two related implementation attempts.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

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

  • #22793 feat(gateway): add Telegram notification mode (salvage #22772)

    merged to main

    files: gateway/platforms/base.py, gateway/platforms/telegram.py, gateway/run.py, scripts/release.py, tests/gateway/test_base_topic_sessions.py

    Show the diff
    • gateway/platforms/base.py
      @@ -2950,6 +2950,18 @@ async def _stop_typing_task() -> None:
                       if text_content:
                           logger.info("[%s] Sending response (%d chars) to %s", self.name, len(text_content), event.source.chat_id)
                           _reply_anchor = _reply_anchor_for_event(event)
      +                    # Mark final response messages for notification delivery.
      +                    # Platform adapters that support per-message notification
      +                    # control (e.g. Telegram's disable_notification) use this
      +                    # flag to override silent-mode and ensure the final
      +                    # response triggers a push notification.
      +                    # Clone to avoid mutating the metadata shared with the
      +                    # typing-indicator task (which must remain unmarked).
      +                    if _thread_metadata is not None:
      +                        _thread_metadata = dict(_thread_metadata)
      +                        _thread_metadata["notify"] = True
      +                    else:
      +                        _thread_metadata = {"notify": True}
                           result = await self._send_with_retry(
                               chat_id=event.source.chat_id,
                               content=text_content,
    • gateway/platforms/telegram.py
      @@ -319,6 +319,30 @@ def __init__(self, config: PlatformConfig):
               # Slash-confirm button state: confirm_id → session_key (for /reload-mcp
               # and any other slash-confirm prompts; see GatewayRunner._request_slash_confirm).
               self._slash_confirm_state: Dict[str, str] = {}
      +        # Notification mode for message sends.
      +        # "important" — only final responses, approvals, and slash confirmations
      +        #               trigger notifications; tool progress, streaming, status
      +        #               messages are delivered silently via disable_notification.
      +        #               This is the default — Telegram users found per-tool-call
      +        #               push notifications too noisy.
      +        # "all"       — every message triggers a push notification (legacy
      +        #               behavior; opt-in via display.platforms.telegram.notifications).
      +        self._notifications_mode: str = "important"
      +
      +    def _notification_kwargs(
      +        self, metadata: Optional[Dict[str, Any]]
      +    ) -> Dict[str, Any]:
      +        """Return disable_notification kwargs when the adapter is in silent mode.
      +
      +        In "important" mode, all message sends are silently delivered
      +        (disable_notification=True) unless the caller explicitly requests a
      +        notification by setting ``metadata["notify"] = True``.
      +        """
      +        if getattr(self, "_notifications_mode", "important") != "important":
      +            return {}
      +        if (metadata or {}).get("notify"):
      +            return {}
      +        return {"disable_notification": True}
       
           def _is_callback_user_authorized(
               self,
      @@ -1414,6 +1438,7 @@ async def send(
                                       reply_to_message_id=reply_to_id,
                                       **thread_kwargs,
                                       **self._link_preview_kwargs(),
      +                                **self._notification_kwargs(metadata),
                                   )
                               except Exception as md_error:
                                   # Markdown parsing failed, try plain text
      @@ -1427,6 +1452,7 @@ async def send(
                                           reply_to_message_id=reply_to_id,
                                           **thread_kwargs,
                                           **self._link_preview_kwargs(),
      +                                    **self._notification_kwargs(metadata),
                                       )
                                   else:
                                       raise
      @@ -2374,6 +2400,7 @@ async def send_voice(
                                   "caption": caption[:1024] if caption else None,
                                   "reply_to_message_id": reply_to_id,
                                   **voice_thread_kwargs,
      +                            **self._notification_kwargs(metadata),
                               },
                               metadata,
                               reply_to_id,
      @@ -2398,6 +2425,7 @@ async def send_voice(
                                   "caption": caption[:1024] if caption else None,
                                   "reply_to_message_id": reply_to_id,
                                   **audio_thread_kwargs,
      +                            **self._notification_kwargs(metadata),
                               },
                               metadata,
                               reply_to_id,
      @@ -2534,6 +2562,7 @@ def _reset_opened_files() -> None:
                               "media": media,
                               "reply_to_message_id": reply_to_id,
                               **thread_kwargs,
      +                        **self._notification_kwargs(metadata),
                           },
                           metadata,
                           reply_to_id,
      @@ -2591,6 +2620,7 @@ async def send_image_file(
                               "caption": caption[:1024] if caption else None,
                               "reply_to_message_id": reply_to_id,
                               **thread_kwargs,
      +                        **self._notification_kwargs(metadata),
                           },
                           metadata,
                           reply_to_id,
      @@ -2686,6 +2716,7 @@ async def send_document(
                               "caption": caption[:1024] if caption else None,
                               "reply_to_message_id": reply_to_id,
                               **thread_kwargs,
      +                        **self._notification_kwargs(metadata),
                           },
                           metadata,
                           reply_to_id,
      @@ -2731,6 +2762,7 @@ async def send_video(
                               "caption": caption[:1024] if caption else None,
                               "reply_to_message_id": reply_to_id,
                               **thread_kwargs,
      +                        **self._notification_kwargs(metadata),
                           },
                           metadata,
                           reply_to_id,
      @@ -2781,6 +2813,7 @@ async def send_image(
                           "caption": caption[:1024] if caption else None,
                           "reply_to_message_id": reply_to_id,
                           **photo_thread_kwargs,
      +                    **self._notification_kwargs(metadata),
                       },
                       metadata,
                       reply_to_id,
      @@ -2816,6 +2849,7 @@ async def send_image(
                               "caption": caption[:1024] if caption else None,
                               "reply_to_message_id": reply_to_id,
                               **upload_thread_kwargs,
      +                        **self._notification_kwargs(metadata),
                           },
                           metadata,
                           reply_to_id,
      @@ -2861,6 +2895,7 @@ async def send_animation(
                           "caption": caption[:1024] if caption else None,
                           "reply_to_message_id": reply_to_id,
                           **animation_thread_kwargs,
      +                    **self._notification_kwargs(metadata),
                       },
                       metadata,
                       reply_to_id,
    • gateway/run.py
      @@ -4649,7 +4649,29 @@ def _create_adapter(
                   if not check_telegram_requirements():
                       logger.warning("Telegram: python-telegram-bot not installed")
                       return None
      -            return TelegramAdapter(config)
      +            adapter = TelegramAdapter(config)
      +            # Apply Telegram notification mode from config.  Controls whether
      +            # intermediate messages (tool progress, streaming, status) trigger
      +            # push notifications.  Supports ENV override for quick testing.
      +            _notify_mode = os.getenv("HERMES_TELEGRAM_NOTIFICATIONS", "")
      +            if not _notify_mode:
      +                try:
      +                    _gw_cfg = _load_gateway_config()
      +                    _raw = cfg_get(_gw_cfg, "display", "platforms", "telegram", "notifications")
      +                    if _raw not in (None, ""):
      +                        _notify_mode = str(_raw).strip().lower()
      +                except Exception:
      +                    pass
      +            _notify_mode = _notify_mode or "important"
      +            if _notify_mode not in ("all", "important"):
      +                logger.warning(
      +                    "Unknown telegram notifications mode '%s', "
      +                    "defaulting to 'important' (valid: all, important)",
      +                    _notify_mode,
      +                )
      +                _notify_mode = "important"
      +            adapter._notifications_mode = _notify_mode
      +            return adapter
               
               elif platform == Platform.DISCORD:
                   from gateway.platforms.discord import DiscordAdapter, check_discord_requirements
    • scripts/release.py
      @@ -65,6 +65,7 @@
           "[redacted:email]": "subtract0",
           "[redacted:email]": "qWaitCrypto",
           "[redacted:email]": "Qwinty",
      +    "[redacted:email]": "CalmProton",
           "[redacted:email]": "wesleysimplicio",
           "[redacted:email]": "SiliconID",
           "[redacted:email]": "xieNniu",
    • tests/gateway/test_base_topic_sessions.py
      @@ -130,8 +130,8 @@ async def hold_typing(_chat_id, interval=2.0, metadata=None):
                   {
                       "chat_id": "-1001",
                       "content": "ack",
      -                "reply_to": "1",
      -                "metadata": {"thread_id": "17585"},
      +                "reply_to": None,
      +                "metadata": {"thread_id": "17585", "notify": True},
                   }
               ]
               assert typing_calls == [

1 PR closed without merging

From the close comments: merged via another change 1

  • #22772 feat(gateway): add Telegram notification mode to suppress intermediate push notifications · merged via another change → #22793
Closed issues · 1
  • Closed#22771Telegram: add notification mode to suppress intermediate push notifications
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.67: 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

Merged PRs
#22793
Closed issues
1
Attempts
1 PR closed without merging
Fix link checked
9 May 2026

For your agent

The same facts, typed.