feat(gateway): /profile <name> switches active profile in messaging platforms - #66519
feat(gateway): /profile <name> switches active profile in messaging platforms#66519mehmetkr-31 wants to merge 1 commit into
Conversation
…latforms
The /profile slash command in messaging platforms (Telegram, Discord, Slack)
previously only displayed the active profile name and home directory. It
now accepts an optional profile name to switch to.
- /profile → show active profile (unchanged)
- /profile <name> → switch active profile, close current session
(to preserve prompt-cache integrity), and prompt
user to start fresh with /new
This enables single-bot multi-profile workflows (e.g., /profile rental
→ /profile default) without running separate gateway instances.
Fixes NousResearch#65936
Related: OPEN #61205 and #24914 cover broader source-aware/multiplexed profile routing. This gateway-only approach changes the process active profile and closes the current session, so it needs a maintainer choice on multi-chat behavior rather than a duplicate closure. |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused gateway proposal. The underlying per-chat routing request remains valid, but this implementation needs architectural rework against current main.
Problems
gateway/slash_commands.py:369only callsset_active_profile(), which writes the sticky selection (hermes_cli/profiles.py:1807-1829); it does not alter the gateway runtimeHERMES_HOME. Non-multiplexed turns pass straight through atgateway/run.py:17631-17639, so the next/newstill runs in the original profile.- Current main already resolves multiplexed turns from
source.profile(gateway/run.py:17641-17649,17700-17742) and has source-aware bare/profilebehavior atgateway/slash_commands.py:332-377. This diff replaces that handler without binding the source to the target profile. - The close path at PR lines 379-382 omits
/new's run-generation invalidation and running-agent release (gateway/slash_commands.py:111-117), leaving stale in-flight work unguarded.
Suggested changes
- Design this as a per-source binding with the open multiplex-routing work (#61205 / #24914), while preserving bare
/profilereporting. - Reuse the complete session-boundary lifecycle and add a real two-profile gateway test; the current test mocks
set_active_profileto a no-op attests/gateway/test_session_boundary_security_state.py:343.
Automated hermes-sweeper review.
| return f"✅ Already on profile `{target_profile}`." | ||
|
|
||
| try: | ||
| set_active_profile(target_profile) |
There was a problem hiding this comment.
set_active_profile() only writes the sticky default selection; it does not change this running gateway's HERMES_HOME. Since get_active_profile_name() derives from HERMES_HOME and non-multiplexed _run_agent is unscoped, the next /new still runs the original profile. This needs a source/profile runtime binding, not just the sticky-default write.
| source = event.source | ||
| session_key = self._session_key_for_source(source) | ||
| current_entry = await self.async_session_store.get_or_create_session(source) | ||
| await self.async_session_store.end_session(session_key, reason="profile_switch") |
There was a problem hiding this comment.
This boundary needs the same run-generation invalidation and running-agent release used by current /new (gateway/slash_commands.py:111-117); otherwise an old in-flight turn can still complete after the profile/session boundary.
| original_exists = profiles_mod.profile_exists | ||
| original_set = profiles_mod.set_active_profile | ||
| profiles_mod.profile_exists = lambda name: name == "rental" | ||
| profiles_mod.set_active_profile = lambda name: None |
There was a problem hiding this comment.
This no-op mock proves only the response path. Add a real temporary-profile test that verifies the post-switch gateway turn resolves the target profile's runtime home/config; the current implementation's central failure is that the process runtime scope never changes.
|
Closing this one myself rather than leaving it open. The sweeper's read was right and I don't think it's salvageable as written: the diff calls Fixing those properly means designing this as a per-source binding alongside the open multiplex-routing work (#61205 / #24914), not patching this handler — and it would need a real two-profile gateway test rather than the one here, which mocks That is a different change from this one, so a fresh PR against that design is the honest path. The underlying request — per-chat profile routing — stays valid and is tracked by those issues. For the record the branch had also drifted ~3900 commits behind main and gone |
Problem
The
/profileslash command in messaging platforms (Telegram, Discord, Slack) currently only displays the active profile name and home directory. It does not switch profiles.This contradicts user expectations — the command accepts an optional argument but ignores it on the gateway path. By contrast,
/model <name>does switch the model mid-session./profile <name>should behave analogously.Use Case
A user operates two businesses from a single Hermes instance via Telegram:
Each profile has its own SOUL.md persona, cron jobs, skills, and memory. Currently, to switch between them in Telegram requires running two separate gateway instances with two Telegram bots — heavy infrastructure for a simple profile switch.
A single-bot
/profile rental→/profile defaultworkflow is far more ergonomic.Solution
/profile <name>now switches the active profile:/profile/profile <name>/newWhy close the session?
A mid-conversation profile switch would invalidate the cached system prompt and tool schemas, breaking the per-conversation prompt-cache contract (AGENTS.md: "Prompt caching is sacred"). Closing the session and prompting
/newpreserves cache integrity while keeping the UX simple.Tests
Added 4 new tests in
tests/gateway/test_session_boundary_security_state.py:/profilewithout args shows current profile/profile <name>switches profile and closes session/profile <same-name>is a no-op/profile <nonexistent>returns error without switchingAll 9 tests pass.
Fixes #65936