Skip to content

fix(gateway): slash config writes, executor hops, personality and status follow the routed profile (#87939 #75684 #89161, salvage #87976 #78440 #69118) - #101246

Merged
teknium1 merged 6 commits into
mainfrom
salvage/mux-slash-scope
Sep 2, 2026
Merged

teknium1 merged 6 commits into
mainfrom
salvage/mux-slash-scope

Conversation

@teknium1

@teknium1 teknium1 commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Under gateway.multiplex_profiles, slash dispatch already runs inside _profile_runtime_scope (9ab748a), but four residual paths still resolved the launch profile: handlers building write paths from the module constant _hermes_home, bare loop.run_in_executor(None, …) hops that drop the contextvar, a boot-time _ephemeral_system_prompt snapshot handed to every routed turn, and PID-only liveness that reported served named profiles as stopped.

Changes

  • _save_gateway_config_key (/reasoning, /fast, show/hide), /memory, /skills, /verbose, /footer write via _gateway_config_home() (reads already did).
  • /insights, /debug, /goal draft (fix(gateway): run /insights, /debug and /goal draft inside the routed profile #78440) + siblings /review, /reload-skills use _run_in_executor_with_context.
  • _get_system_prompt_for_channel resolves display.personality/agent.system_prompt per turn from the scoped profile's config; /personality only persists (routed profile), no process-global state.
  • named_profile_served_by_running_multiplexer(profile_name=None) reused by gateway status/list, profile list/show, dashboard profiles payload.

Validation

Surface Before (origin/main) After
_save_gateway_config_key / /memory approval on in routed scope wrote default config.yaml; routed untouched routed updated; default byte-identical
/insights, /reload-skills worker get_hermes_home() launch home routed profile home
routed turn prompt DEFAULT-PERSONA; /personality from routed chat changed default's turn too BETA-PERSONA; after /personality pirate: routed ARR, default DEFAULT-PERSONA
gateway list / gateway status / list_profiles for served profile ✗ not running / False ✓ served by the default multiplexer / True
Sabotage-verified: each new test fails with its fix reverted. Targeted suites: 500+ tests green.

Credits

@StanleyStetson (#87976, commit author) · @kingrubic (#75729) · @Drexuxux (#78440, cherry-picked) · @worlldz (#89177) · @Mushisushi28 (#69118) · reporters @kh-mitya #87939, @jimzord12 #75684, @pipidaxian #89161

Fixes #87939
Fixes #75684
Fixes #89161

Infographic

mux-slash-scope

@github-actions

github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

ran on 9011fe3 — chore: retrigger CI (zero-job dispatch failure, auto-heal)

⚠️ Warnings

OSV vulnerability scan · View job

13 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


debug info

CI timings

CI timings · View report · View job

Wall time 5m17s vs 5m46s (-8.4%). 6 job(s) slower, 7 faster, 1 unchanged.

  • OS-specific tests / macOS-only tests: +61.0s
  • OSV scan / Scan lockfiles / osv-scan: -45.0s
  • OS-specific tests / Windows-only tests: +37.0s
  • Check no case-colliding filenames / check-case-collisions: -33.0s
  • OSV scan / Emit review status: +29.0s

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles area/profiles Multi-profile isolation, HERMES_HOME scoping 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 labels Sep 2, 2026
@teknium1
teknium1 force-pushed the salvage/mux-slash-scope branch 4 times, most recently from 07460ad to 1e8d2fb Compare September 2, 2026 13:09
Drexuxux and others added 5 commits September 2, 2026 06:19
… profile

The multiplexed inbound handler wraps every message in _profile_runtime_scope,
which installs the routed profile's HERMES_HOME override and its secret scope
as contextvars. A bare loop.run_in_executor(None, fn) starts the worker with an
EMPTY context, so neither reaches the blocking work.

GatewaySlashCommandsMixin already knows this -- /compress goes through
_run_in_executor_with_context and the call site says why. Three siblings in the
same file still used the bare hop:

  /insights   SessionDB() with no explicit path resolves get_hermes_home() at
              call time (_default_db_path), so the worker opened the DEFAULT
              profile's state.db. Under multiplexing the command reported
              another profile's conversations, session counts and sources to
              this profile's user.

  /debug      collects that home's logs/config and uploads them to a public
              paste, so it published the default profile's diagnostics from
              another profile's chat.

  /goal draft calls the auxiliary LLM, whose provider/credential resolution
              reads the profile secret scope -- unscoped it falls back to
              process-global os.environ, which under multiplexing may hold a
              different profile's keys.

Route all three through _run_in_executor_with_context.

/reload-skills is deliberately left alone: tools.skills_tool binds SKILLS_DIR
at import time, so it does not follow the contextvar either way. Fixing that
needs the module-global retarget web_server._profile_scope performs under a
lock, which is a different change from context propagation.

Single-profile gateways never enter the scope, so their behaviour is unchanged.
…the scoped helper

Sibling sites of the bare loop.run_in_executor(None, …) class fixed for
/insights, /debug and /goal draft: the worker started with an empty
context, so get_hermes_home()-relative reads (skills.external_dirs,
disabled skills, the reviewer subagent's home and secret scope) resolved
the launch home instead of the routed profile under multiplex.
…file

Slash dispatch already runs inside _profile_runtime_scope under multiplex,
but _save_gateway_config_key (/reasoning --global, /fast, show/hide),
/memory approval, /skills approval, /verbose and /footer built their write
path from the module constant gateway.run._hermes_home — the launch home —
so a routed profile's toggles landed in the default profile's config.yaml
while the reads (via _gateway_config_home()) saw the routed one.

Resolve the write path through _gateway_config_home() at all five sites so
reads and writes agree. Single-profile gateways never install the override
and keep resolving the launch home.

Fixes #87939
Fixes #75684

Co-authored-by: Bao <nnqbao@gmail.com>
…the scoped profile

GatewayRunner.__init__ snapshotted _ephemeral_system_prompt once from the
launch profile's config and _get_system_prompt_for_channel returned that
string for every source, so under multiplex a routed profile's
display.personality / agent.system_prompt never injected (#89161), and
/personality from any chat rewrote the one process-global attribute for
everyone.

Drop the snapshot: _get_system_prompt_for_channel now calls
_load_ephemeral_system_prompt() (env var, then
resolve_ephemeral_system_prompt_from_config(_load_gateway_runtime_config()))
on each call. Its caller run_sync already runs inside
_profile_runtime_scope, so the routed profile's config.yaml is what gets
read; single-profile hot-edits of the personality also take effect on the
next turn instead of requiring a restart. /personality only persists via
persist_personality() (get_hermes_home()/config.yaml = the routed profile)
and no longer touches in-memory state.

Fixes #89161

Co-authored-by: worlldz <101180447+worlldz@users.noreply.github.com>
…xer serves it

`hermes gateway status`, `hermes gateway list`, `hermes profile list/show`
and the dashboard profiles payload keyed liveness off the profile's own
gateway.pid / gateway_state.json, so a satellite profile served by the
default multiplexer (gateway.multiplex_profiles) showed "not running"
even though the multiplexer is its live inbound process.

Reuse the single lookup the start guard and cron liveness already share —
named_profile_served_by_running_multiplexer() — with an optional
profile_name so list surfaces can ask about any profile, and OR it into
gateway_running for named profiles. Default profile and unserved named
profiles are unchanged.

Salvage of #69118 rebased onto the shared helper (which post-dates it).

Co-authored-by: Isaac Dobson <isaac@dobsonheadlights.com>
Co-authored-by: Mushisushi28 <133449918+Mushisushi28@users.noreply.github.com>
@teknium1
teknium1 force-pushed the salvage/mux-slash-scope branch from 1e8d2fb to 39d5492 Compare September 2, 2026 13:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles area/profiles Multi-profile isolation, HERMES_HOME scoping comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists 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 type/bug Something isn't working

Projects

None yet

4 participants