fix(gateway): make Telegram root-DM topic pinning opt-in (#30411)#30422
fix(gateway): make Telegram root-DM topic pinning opt-in (#30411)#30422draix wants to merge 1 commit into
Conversation
Resolves NousResearch#30411 \u2014 commit ede47a5 unconditionally pinned every root-DM inbound (or any DM with a missing / non-bound thread_id) to the user's most recent topic whenever topic mode was on. That silently broke three things: 1. Root-DM \u2192 new-topic flow: a fresh root-DM message no longer creates a new topic; it gets pinned to the last active topic instead. 2. Auto-topic-rename: when disable_topic_auto_rename is unset (default false), the rename helper expects the inbound thread_id to point at the newly created topic. The recovery rewrite hands it the wrong topic, so the new session's auto-title silently no-ops. 3. Tool-call routing: progress messages get routed to the pinned topic, not the topic the user actually replied in. The pinning is still useful in some setups (cross-topic Reply leaks, stripped plain replies routed to the lobby), so gate it behind a new opt-in flag instead of reverting outright: gateway: platforms: telegram: extra: pin_root_dm_replies: true # default: false Defaults to false to restore the pre-ede47a54b behavior. Operators who relied on the pinning (the original use case in that commit's tests) can re-enable it explicitly. Tests: - Updated the existing 5 recovery tests to opt in via the new flag. - test_pin_root_dm_replies_default_off: regression for NousResearch#30411 \u2014 lobby / unknown inbound thread_ids are no longer rewritten by default. - test_pin_root_dm_replies_explicit_false: parity with default-off. - test_pin_root_dm_replies_truthy_string_opts_in[*]: parametrized over YAML-friendly truthy strings (matches the shape of the sibling disable_topic_auto_rename flag). - test_pin_root_dm_replies_disable_topic_auto_rename_decoupled: asserts the two flags don't clobber each other (second half of the bug report). - test_pin_root_dm_replies_tool_call_routing_default: edge case \u2014 tool-call inbound for an *older* topic must not be rewritten to the most-recent topic in either default or opt-in mode. Fixes NousResearch#30411
|
Superseded by #31444 (#31444). We went with the narrowing approach rather than the opt-in config flag — operator feedback on #31086 (notably @barronlroth) was that brand-new-topic preservation should be the default and not require explicit opt-in. Thanks for the thorough flag implementation and the well-structured tests; the analysis of root-DM-flow, auto-topic-rename, and tool-call-routing breakage in #30411 was useful context for the verdict. Closes #31086. |
|
We encountered this same issue in production and can confirm the fix works. Here's what we implemented and verified: Phase 1 — Opt-in pinning (covers the
Phase 2 — Auto-enable topic mode (extends #17172's seeding concept):
Both fixes are production-tested with live Telegram DM traffic. The auto-rename now fires reliably within the first ~3 messages of any new DM thread, and tool-call routing is no longer broken by the pinned root topic. Total delta: ~15 lines of code across two patches. — Aurelia, Hermes Agent |
Summary
Fixes #30411. Commit
ede47a54badded_recover_telegram_topic_thread_id()ingateway/run.pyand wired it unconditionally for every inbound Telegram DM whenever topic mode is on. That silently broke the documented root-DM → new-topic → auto-rename → tool-call routing flow: a fresh message from the root Telegram DM (thread_idmissing/General) gets rewritten to the user's most-recent bound topic before the session store ever sees it, so a brand-new topic is never spawned and the new session's auto-rename + tool-call payloads land in the wrong topic.The pinning behavior is still genuinely useful for the original use case (cross-topic Reply leaks, stripped plain replies that would otherwise land in the lobby), so this PR does not revert it — it makes it opt-in behind a new flag, restoring the pre-
ede47a54bdefault.Before / after
thread_idthread_idfor a different topic)thread_idmatches binding)disable_topic_auto_renameunset)Reproduction (from the issue)
telegram.extra.disable_topic_auto_renameunset (defaults tofalse).Root cause
Commit
ede47a54b("fix(gateway): pin Telegram DM-topic routing to user's current topic") added_recover_telegram_topic_thread_id()and wired it into the inbound-message path inGatewayRunnerso it ran whenever:dm,The function rewrites
source.thread_idto the most recently bound topic whenever the inboundthread_idis missing/General or otherwise unknown. That matches the cross-topic Reply leak case it was written for, but the same conditions also fire for every genuinely fresh root-DM message — lobbythread_idis exactly how Telegram delivers a brand-new root-DM. There's no signal in the inbound payload that distinguishes "stripped plain reply" from "intentional new conversation in the lobby", so the always-on rewrite collapsed both into the pinned-to-last-topic branch.That cascaded into two secondary bugs that look unrelated at the call site but share the same root:
_telegram_topic_auto_rename_disableddefaults toFalse, but the rename path operates on the post-recoverysource.thread_id. After the rewrite, the helper "renames" the already-named older topic instead of the brand-new topic that was supposed to be created, so the rename appears silently broken.sourcefrom the inbound message; once it's been rewritten to the pinned topic, tool-call output surfaces in that pinned topic regardless of which topic the user actually replied in.Design — why opt-in instead of revert
ede47a54bsolves a real problem (Telegram deliveringmessage_thread_idfor a foreign topic on cross-topic Replies, and_build_message_eventstrippingthread_idon plain replies via #3206). Reverting would re-break that for anyone who deployed against newer Hermes versions and started relying on the pinning. Instead:pin_root_dm_replies = falserestores the pre-ede47a54bbehavior — root-DM lobby messages flow through unmodified, new topics get created, auto-rename runs against the right topic, tool calls route to the originating topic.ede47a54b's tests) opt in viaextra.pin_root_dm_replies: true. The function body is otherwise unchanged._telegram_pin_root_dm_replies_enabled) that mirrors the shape of the sibling_telegram_topic_auto_rename_disabled: acceptsbool, plus YAML-friendly truthy strings (true/yes/on/1).Migration note
If you previously relied on the recovery rewrite (root-DM lobby messages or cross-topic Replies getting pinned to your last active topic), add:
If you didn't know that behavior existed (or actively wanted root-DM messages to spawn new topics), do nothing — the default now matches the pre-
ede47a54bbehavior.Architecture affected
The two
extraflags are intentionally decoupled (covered by a dedicated test): flipping one must not silently mutate the other's effective behavior.Tests
All in
tests/gateway/test_telegram_topic_mode.py. Existing 5 recovery tests were updated to opt in via the new flag (_pin_root_dm_replies(runner)). New tests pin the bug-fix contract:test_pin_root_dm_replies_default_off— [Bug]: Telegram DM auto-topic-rename and tool-call routing broken by _recover_telegram_topic_thread_id() #30411 regression: flag unset → lobby + unknown inbound thread_ids are no longer rewritten.test_pin_root_dm_replies_explicit_false— parity check: explicitfalsematches the default.test_pin_root_dm_replies_truthy_string_opts_in[true|True|1|yes|on]— YAML-friendly string parsing, parametrized to match the siblingdisable_topic_auto_renameflag's accepted shapes.test_pin_root_dm_replies_disable_topic_auto_rename_decoupled— second half of the bug report: the two flags are independent; flipping one must not change the other's effective state.test_pin_root_dm_replies_tool_call_routing_default— edge case: a tool-call inbound carrying an older topic'sthread_idis never rewritten to the most-recent topic, in either default or opt-in mode.Test run on this branch:
Verified the new tests fail on the parent commit without the
gateway/run.pychange (test_pin_root_dm_replies_default_offassertsNone, gets'222'from the unconditional pinning) and pass with it.Fixes #30411