Skip to content

fix(telegram): named DM topic delivery + private DM topic anchor contract (salvage of #27107)#32270

Merged
teknium1 merged 8 commits into
mainfrom
hermes/hermes-ee82a97b
May 25, 2026
Merged

fix(telegram): named DM topic delivery + private DM topic anchor contract (salvage of #27107)#32270
teknium1 merged 8 commits into
mainfrom
hermes/hermes-ee82a97b

Conversation

@teknium1

@teknium1 teknium1 commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Telegram DM topic delivery now creates named topics on demand, refreshes stale thread ids, and refuses to silently leak private DM topic sends into General when the requested topic can't be honored. Salvages @stepanov1975's PR #27107 onto current main with conflict resolutions for parallel changes (transient-flake retry, reply_to_mode='off' opt-in).

Changes

  • gateway/delivery.py: route DM topic deliveries by mode — named string topics go through ensure_dm_topic + createForumTopic; numeric topics on private chats require a reply anchor or fail loud.
  • gateway/platforms/telegram.py: new ensure_dm_topic(force_create=) for on-demand topic creation + stale-thread refresh; _is_private_dm_topic_send predicate gates the new fail-loud paths in send(); _persist_dm_topic_thread_id now creates missing config path + supports replace_existing=.
  • Follow-up by me: exempt reply_to_mode='off' + telegram_dm_topic_reply_fallback from the new anchor guard (preserves PR reply_to_mode: 'off'ignored by Telegram DM topic reply fallback code #23994 commit 21a15b6 user opt-in); switch one transient-flake retry test to -100123 group chat so its scenario is unambiguous against the new contract.
  • tests/conftest.py: blank API_SERVER_* env vars in the hermetic-conftest unset list.

Behavioral change worth flagging

For numeric thread_ids on private DMs (positive chat_id), sends without a reply anchor now fail with Telegram DM topic delivery requires a reply anchor instead of silently stripping the thread_id and delivering to General. This is consistent with #31444 (don't hijack new DM topics) and aligns with the report Greg flagged in support-threads. Group forum topics, explicit Direct Messages topics, and the reply_to_mode='off' opt-in path are all exempt.

tools/send_message_tool.py has its own _send_telegram retry loop (PR #27012) that retains the silent-fallback contract for cron/send_message callers; that path is not touched here.

Validation

Before After
tests/gateway/ 5915 pass on main 5915 pass with salvage
tests/cron/ + tests/tools/test_send_message_tool.py 504 pass 504 pass
E2E (real imports, 4 scenarios) n/a named topic created, numeric+no-anchor fails loud, numeric+anchor uses fallback, group thread unchanged

Closes #27107 with credit to @stepanov1975.

Infographic

telegram-dm-topic-routing

stepanov1975 and others added 8 commits May 25, 2026 14:37
Salvage follow-up. The transient thread-not-found retry test was
exercising chat_id='123' (positive, looks-like-private) which now
hits the new private-DM-topic fail-closed contract. The test's
intent is the transient-flake retry on real forum topics in groups,
so use -100123 to make the scenario unambiguous.
…quired guard

Salvage follow-up. The new private-DM-topic fail-loud contract from
PR #27107 hits 'requires a reply anchor' when reply_to_mode='off' is
configured, even though commit 21a15b6 (PR #23994) verified that
message_thread_id alone routes correctly on python-telegram-bot's
reference client when the user has explicitly opted out of quote
bubbles. Carve out the explicit opt-in path so users on reply_to_mode
'off' aren't regressed — the new guard now only applies to callers
that didn't ask for the anchor to be suppressed.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-ee82a97b vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9348 on HEAD, 9347 on base (🆕 +1)

🆕 New issues (1):

Rule Count
unresolved-import 1
First entries
tests/gateway/test_delivery.py:3: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`

✅ Fixed issues: none

Unchanged: 4946 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@teknium1 teknium1 merged commit 27df4b3 into main May 25, 2026
26 checks passed
@teknium1 teknium1 deleted the hermes/hermes-ee82a97b branch May 25, 2026 21:54
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists platform/telegram Telegram bot adapter comp/gateway Gateway runner, session dispatch, delivery labels May 25, 2026
briandevans added a commit to briandevans/hermes-agent that referenced this pull request May 30, 2026
PR NousResearch#32270 (2026-05-25) introduced a fail-loud guard in
`TelegramAdapter.send()`: a private-chat send carrying a thread_id is
rejected with "requires a reply anchor" unless metadata includes one of
`telegram_reply_to_message_id`, `direct_messages_topic_id`, or
`telegram_dm_topic_created_for_send`. The agent/cron paths populate
those keys themselves, but `WebhookAdapter._deliver_cross_platform` only
forwarded `thread_id`, so any webhook subscription targeting a private
DM topic now fails on every delivery — Hermes accepts the webhook
(`HTTP 202`, `delivered: true`), then the cross-platform send is
refused before it ever hits Telegram.

Plumb the four DM-topic routing keys straight through from
`deliver_extra` to the metadata dict. Webhook route configs are the
only place the operator can express the opt-out, so this is the
canonical place to wire them in.

Mirrors `TelegramAdapter.send()` predicate precedence:
`telegram_reply_to_message_id` (anchor) > `direct_messages_topic_id`
(true Bot-API DM topic) > `telegram_dm_topic_created_for_send`
(Hermes-created DM topic) > `telegram_dm_topic_reply_fallback`
(reply_to_mode='off' opt-in). Legacy single-key `thread_id` /
`message_thread_id` forwarding is unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/telegram Telegram bot adapter type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants