Skip to content

feat(gateway): route shared bot chats to profiles - #57873

Closed
evgyur wants to merge 1 commit into
NousResearch:mainfrom
evgyur:feat/gateway-profile-routes
Closed

feat(gateway): route shared bot chats to profiles#57873
evgyur wants to merge 1 commit into
NousResearch:mainfrom
evgyur:feat/gateway-profile-routes

Conversation

@evgyur

@evgyur evgyur commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds shared-credential chat/topic → profile routing for multiplexed gateways.

This lets one platform adapter/bot token own inbound messages while routing selected chats/topics into named Hermes profiles. It preserves the duplicate-polling/token-lock invariant because routed profiles do not start their own adapter; only the agent turn/session namespace is profile-stamped after authorization succeeds.

Why

The existing multiplex docs correctly require separate credentials for separate adapters. That is safe, but it leaves an important operator workflow uncovered:

  • one Telegram bot is already invited to several groups;
  • each group/topic needs isolated skills, memory, config, cron state, and sessions;
  • creating a new bot/token per profile is noisy or impossible;
  • duplicate polling the same token from multiple profiles must remain forbidden.

The safe model is:

one default gateway adapter owns credential + auth
→ chat/topic route selects profile
→ agent turn uses routed profile state/session namespace

What changed

  • Added _resolve_shared_credential_profile_route(...) and a compact route matcher.
  • In the gateway message pipeline, after authorization and before session-key generation, stamp SessionSource.profile when a route matches.
  • Supports mapping and list forms:
gateway:
  multiplex_profiles: true

telegram:
  profile_routes:
    "-1001111111111": research
    "-1002222222222:42": support
  • Documents the distinction between:
    • separate adapters → each needs its own credential;
    • shared-token routes → one adapter, many profile homes.
  • Adds focused regression coverage for route resolution and namespaced session keys.

Safety notes

  • Only active with gateway.multiplex_profiles: true.
  • Authorization is evaluated before route stamping.
  • Missing route targets are ignored with a warning.
  • default routes are no-ops.
  • Existing no-profile/default session keys remain byte-identical via existing Phase 0 tests.
  • Duplicate adapter/token conflict behavior is not relaxed.

Test plan

python -m pytest tests/gateway/test_multiplex_shared_profile_routes.py tests/gateway/test_multiplex_phase0.py -q -o 'addopts='
python -m compileall -q gateway/run.py tests/gateway/test_multiplex_shared_profile_routes.py
python -m ruff check gateway/run.py tests/gateway/test_multiplex_shared_profile_routes.py
git diff --check

Local result:

31 passed
All checks passed!

Closes #57871

Allow a multiplexed gateway to keep one inbound polling adapter/token on the default profile and route selected chats or topics into named profile homes via <platform>.profile_routes. This supports one public bot identity across multiple isolated profile memories, skills, configs, and session namespaces without duplicate polling.
@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery area/config Config system, migrations, profiles P3 Low — cosmetic, nice to have 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 3, 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 covering the shared-token routing use case. The capability is still absent from current main, but the current placement is unsafe.

Problems

  • gateway/run.py:8786 resolves the route only after authorization. gateway/authz_mixin.py:249-262 chooses a pairing store from source.profile; therefore routed traffic is checked against the default profile's authorization state, then executes with the target profile's configuration and credentials.
  • gateway/platforms/base.py:4660-4664 derives the adapter active-session key before it calls _handle_message. The PR only sets source.profile at gateway/run.py:8793, so that key remains unprofiled. The direct helper assertion in tests/gateway/test_multiplex_shared_profile_routes.py:76-78 does not exercise this path.

Suggested changes

  • Stamp a validated route in BasePlatformAdapter.build_source before authorization and before any adapter session-key/queue work, then route auth using that source.
  • Thread source.profile through adapter-owned session/batch key construction and add an adapter-to-handler integration test for routing, authorization, and namespacing.

Automated hermes-sweeper review.

Comment thread gateway/run.py
and not getattr(source, "profile", None)
and getattr(getattr(self, "config", None), "multiplex_profiles", False)
):
_route_profile = _resolve_shared_credential_profile_route(

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.

This lookup is after _is_user_authorized(source). In multiplex mode the authorization mixin selects the pairing store from source.profile, so this admits users under the default profile's policy and only then switches them into the target profile. Resolve and stamp the route before authorization.

Comment thread gateway/run.py
if _route_profile:
event = dataclasses.replace(
event,
source=dataclasses.replace(source, profile=_route_profile),

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.

This stamp is too late for adapter-owned state: BasePlatformAdapter.handle_message() builds its active-session key before it invokes this handler. That key is still agent:main:...; route the source at build_source() time and pass its profile through all adapter session/batch-key calls.

@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 15, 2026
@teknium1

Copy link
Copy Markdown
Contributor

This capability has now landed on main via PR #64835 (a salvage of #20096 by @Burgunthy, the earliest submission): gateway.profile_routes routes shared-bot chats to profiles, stamped on source.profile and gated on gateway.multiplex_profiles — the same architecture your PR uses, so the mapping carries over directly:

gateway:
  multiplex_profiles: true
  profile_routes:
    - platform: telegram
      chat_id: "-100111"
      profile: research
    - platform: telegram
      chat_id: "-100111"
      thread_id: "42"
      profile: support

The merged matcher additionally supports guild routing, parent-chain matching (threads inherit their channel's route), explicit specificity ordering, and profile-name validation, and routes at build_source() across all adapters.

Docs: https://hermes-agent.nousresearch.com/docs/user-guide/multi-profile-gateways

You independently arrived at the same design the merged feature uses — good instincts, and thanks for the contribution. Closing as superseded.

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/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-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.

Support shared-token chat/topic routes for multiplexed gateway profiles

3 participants