Skip to content

feat(gateway): interactive /profile picker (parity with /model) - #69441

Open
rarf wants to merge 7 commits into
NousResearch:mainfrom
rarf:feat/gateway-profile-picker
Open

feat(gateway): interactive /profile picker (parity with /model)#69441
rarf wants to merge 7 commits into
NousResearch:mainfrom
rarf:feat/gateway-profile-picker

Conversation

@rarf

@rarf rarf commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds support for interactive profile switching in the gateway via the new /profile slash command (with picker support, parity with /model).

This allows changing which profile serves a specific chat or thread at runtime, without restarting the gateway.

Motivation

Previously, to route a chat or thread to a different profile, users had to manually edit profile_overrides.json or restart everything. There was no first-class, user-friendly way to do live profile changes from within the chat.

What the PR does

  • New /profile command with subcommands:

    • /profile → shows the current profile serving this source (chat/thread)
    • /profile set <name> → pins the current chat/thread to the given profile (live)
    • /profile set (no name) → opens an interactive picker (same experience as /model)
    • /profile clear → removes the override for this chat/thread
    • /profile list → lists available profiles
  • The picker is available on platforms that support rich interactions (Telegram, Discord, Matrix) and falls back gracefully on others.

  • Thread-level overrides are supported (thread-specific overrides take precedence over chat-level).

  • When gateway.multiplex_profiles is disabled (the default), plain /profile now shows a helpful message explaining the requirement instead of only showing the current profile.

Requirements

Live profile switching requires gateway.multiplex_profiles: true in the gateway configuration.

When this setting is off, the command still works for viewing the current profile and listing profiles, but switching is blocked with clear guidance on how to enable it.

Usage example

/profile set casa

From that point on, messages in that chat will be handled by the casa profile (no restart needed).

Additional improvements in this PR

  • Better UX and discoverability when multiplex_profiles is not enabled.
  • Clear error messages guiding users to the correct configuration.
  • Consistent behavior with other picker commands.

Testing

  • Tested on Telegram (primary platform used during development).
  • Built using the free HY3 model available on the Nous Portal.

Notes

Only @Teknium needs to review and merge.

@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery comp/cli CLI entry point, hermes_cli/, setup wizard area/profiles Multi-profile isolation, HERMES_HOME scoping P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 22, 2026

@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 building the live-routing UX; current main still only reports the serving profile, so the capability remains useful.

Problems

  • gateway/platforms/base.py:5771 resolves persisted overrides without the multiplex_profiles gate. Current main's route resolver deliberately returns None while multiplexing is off (gateway/run.py:20748-20767) to avoid profile-stamped session/batch state without a profile-scoped runtime. A saved pin must be ignored when multiplexing is disabled.
  • The mutations beginning at PR gateway/slash_commands.py:386 have no admin check. A configured user_allowed_commands list can permit non-admin users to run /profile (gateway/slash_access.py:79-88), whereas existing persistent security mutations recheck admin authority (gateway/slash_commands.py:3655-3661). Retargeting a chat changes its profile-scoped config, skills, memory, and secrets.
  • gateway/profile_overrides.py:49-54 uses an unlocked read-modify-write cycle and one shared .json.tmp path. Picker callbacks write from worker threads, so concurrent selections can lose an update or race during rename.

Suggested changes

  • Gate override lookup on multiplexing and cover the disabled-after-pin case.
  • Require admin authority for set/clear and picker selections; retain read-only status/list behavior.
  • Serialize persistent updates with unique temporary files and test concurrent pins.

Automated hermes-sweeper review.

Comment thread gateway/platforms/base.py Outdated
Comment thread gateway/slash_commands.py
Comment thread gateway/profile_overrides.py Outdated
@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-contained Sweeper blast radius: contained — one narrow path / opt-in / few users labels Jul 30, 2026
@rarf
rarf force-pushed the feat/gateway-profile-picker branch from ef475a4 to 1194876 Compare July 30, 2026 23:01
@rarf

rarf commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Reconciled with the current main in 1194876.

  • Preserved the shared /profile command executor introduced on main.
  • Reapplied the live picker, multiplex gate, admin checks, and process-safe override persistence on top of that architecture.
  • Updated command metadata and slash-command documentation.

Local verification: 55 targeted gateway tests passed; ruff, py_compile, uv lock --check, and git diff --check passed. The PR is now reported as mergeable; GitHub checks are running.

rarf added a commit to rarf/hermes-agent that referenced this pull request Jul 30, 2026
Preserve the shared profile command executor while adding live picker overrides, authorization checks, multiplex gating, and serialized persistence.
@rarf

rarf commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

🔄 Updated the PR branch with two enhancements:

  1. Auto-reset session on profile switch — (and picker selection) now immediately resets the session so the new profile takes effect for the very next message — no manual needed.

  2. Active model in response — the success message now includes the profile's configured model:

Commit: on

This makes live profile switching truly live + shows exactly which model will handle the next message.

@rarf

rarf commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

🔄 Updated the PR branch with two enhancements:

  1. Auto-reset session on profile switch/profile set <name> (and picker selection) now immediately resets the session so the new profile takes effect for the very next message — no manual /new needed.

  2. Active model in response — the success message now includes the profile's configured model:

✅ This chat now serves profile **casa** (model: **nous/tencent/hy3:free**) (live — session reset, next message runs under it.)
To revert: `/profile clear`

Commit: 656617053 on rarf:feat/gateway-profile-picker

This makes live profile switching truly live + shows exactly which model will handle the next message.

@rarf

rarf commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all three points from the sweeper review in 1194876:\n\n- base.py:5771 — build_source now applies a persisted override only when the injected runner config has multiplex_profiles enabled (ignored when disabled-after-pin).\n- slash_commands.py:407 — set/clear now recheck policy_for_source(...).is_admin(...), and the picker callback rechecks the same authority at selection time.\n- profile_overrides.py:52 — read-modify-write is now guarded by a process-safe lock and atomic_json_write (unique temp files + atomic replace).\n\nRequesting another pass when you have a moment. Thanks!

rarf and others added 7 commits August 14, 2026 23:13
Preserve the shared profile command executor while adding live picker overrides, authorization checks, multiplex gating, and serialized persistence.
Mirrors the /model cost-confirm cancel: selecting Cancel in the picker
leaves the current profile pin untouched (no-op), giving users an
explicit escape from the switch without persisting anything.
…er/home)

The /profile command referenced gateway.profile.picker_title, .header and
.home but those keys were never in the locale catalogs, so the picker title
and status card rendered the raw key path. Add human-readable strings to
en.yaml and pt.yaml (the two languages in use).
…NameError)

The cancel-option edit nested the choices.append(cancel) and the
_on_profile_selected function definition INSIDE the for info in infos loop,
leaving the function body (which references 'value') dangling at loop level.
Calling /profile hit 'name value is not defined'. Re-indent so the cancel
choice and the full callback are outside the loop.
…tribution

- Add gateway.profile.picker_title to the 15 locales missing it (was only
  in en/pt), restoring test_catalog_*_match_english parity. Uses the same
  {current} placeholder as English.
- Update test_no_args_opens_picker_and_set to expect the explicit Cancel
  escape hatch (value 'cancel') appended after the profile choices.
- Map hermes@local (Hermes Local) -> rarf via contributors/emails so the
  check-attribution gate passes.
…t /new)

When a user runs  or picks a profile from the
interactive picker, immediately reset the session so the new profile
takes effect for the very next message — no manual  required.

Applies to both the direct subcommand path and the picker callback.
… in response

- Adds immediate session reset when switching profiles via /profile set or picker
- Adds get_profile_model_info() to read each profile's configured model
- Response now includes: '✅ This chat now serves profile **casa** (model: **nous/tencent/hy3:free**) (live — session reset, next message runs under it.)'
- Works for both direct /profile set <name> and interactive picker selection
@rarf
rarf force-pushed the feat/gateway-profile-picker branch from 6566170 to f8baf6d Compare August 14, 2026 22:14
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/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades 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