Skip to content

feat(telegram): /profile picker with cross-restart persistence - #72957

Open
antunes-hq wants to merge 1 commit into
NousResearch:mainfrom
antunes-hq:feat/profile-picker-telegram
Open

feat(telegram): /profile picker with cross-restart persistence#72957
antunes-hq wants to merge 1 commit into
NousResearch:mainfrom
antunes-hq:feat/profile-picker-telegram

Conversation

@antunes-hq

Copy link
Copy Markdown

Summary

Adds a /profile slash command to the Telegram adapter that lets users pick an active Hermes profile and persists the choice in session.metadata["profile"] so it survives restarts.

Changes

  • gateway/run.py — profile callback from Telegram inline keyboard routed to session.metadata["profile"]
  • gateway/slash_commands.py — registers /profile command, renders inline keyboard with available profiles
  • plugins/platforms/telegram/adapter.py — wires profile-picker lifecycle into Telegram message flow
  • 2 test files — coverage for the new flow

Closes: #profile-picker

Adds an interactive inline-keyboard picker to the /profile slash command,
letting users switch the multiplexed gateway between Hermes profiles
without restarting. The choice is persisted in session metadata and
rehydrated on the next inbound event, so it survives gateway restarts.

- Telegram adapter: send_profile_picker + _handle_profile_picker_callback
  (one-tap = one profile, current profile marked, authorization gate)
- slash_commands: /profile lists profiles via list_profiles(), invokes
  picker when available, falls back to text list otherwise
- gateway/run: _rehydrate_profile_from_session looks up the saved
  profile in session_store metadata before the multiplex resolves
- session-store callback writes metadata['profile'] on tap

Requires gateway.multiplex_profiles: true. 27 new tests, no regressions
in the model_picker suite. Existing test_profile_resolution + thread
fallback suites remain green (the 3 thread_fallback failures are
pre-existing test pollution unrelated to this change).
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins platform/telegram Telegram bot adapter area/profiles Multi-profile isolation, HERMES_HOME scoping needs-decision Awaiting maintainer decision before any implementation sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 27, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #69441: both add interactive multiplex /profile switching, but #69441 uses gateway-wide persistent per-source overrides while this PR persists a session-metadata choice and rehydrates it from session entries. Please choose the persistence/routing contract rather than merging both independently.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the profile-picker work. The feature is not already present on current main, but this implementation needs a contract-level rework.

Problems

  • gateway/run.py:20133 calls origin.get(...); real SessionStore entries retain origin=source (gateway/session.py:2389-2402), so this raises on SessionSource and the surrounding resolver silently falls back without rehydrating.
  • gateway/run.py:20132-20150 selects the first matching session metadata value. Multiple profile-namespaced sessions can match one chat after repeated switches, so this cannot reliably represent the latest selection.
  • Main already has the generic picker seam in gateway/slash_commands.py:3290-3326 and Telegram's send_choice_picker in plugins/platforms/telegram/adapter.py:5611-5730; the new Telegram-only picker duplicates it.

Suggested changes

  • First align the persistent source-routing contract with #69441, as the maintainer comment requests.
  • Use the generic picker and test persistence through real SessionStore/SessionSource objects, including repeated switches and thread scope.

Automated hermes-sweeper review.

Comment thread gateway/run.py
)

for entry in entries.values():
origin = getattr(entry, "origin", None) or {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SessionStore stores SessionEntry.origin as a SessionSource, not a dict (gateway/session.py:2389-2402 on current main). This .get() raises for every real entry, then the caller catches it and silently disables cross-restart rehydration. Use the typed source attributes or a dedicated persistent override record.

Comment thread gateway/run.py
meta = getattr(entry, "metadata", None) or {}
picked = meta.get("profile")
if isinstance(picked, str) and picked.strip():
return picked.strip()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning the first matching metadata record is not a stable source-level override: profile multiplexing creates separate session keys per selected profile, so repeated selections can leave several matching entries and an older entry may win by insertion order. Persist one authoritative value keyed by the full source scope instead.

logger.warning("[%s] send_model_picker failed: %s", self.name, _redact_telegram_error_text(e))
return SendResult(success=False, error=_redact_telegram_error_text(e))

async def send_profile_picker(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main already exposes the generic send_choice_picker adapter contract and gateway _try_send_choice_picker helper. Reuse that capability rather than adding a Telegram-only parallel picker and callback namespace, so the profile command can share behavior with other interactive platforms.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have platform/telegram Telegram bot adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants