Skip to content

feat: per-user profile routing via config.yaml (#33548) - #33892

Open
ousiaresearch wants to merge 1 commit into
NousResearch:mainfrom
ousiaresearch:feat/profile-routing
Open

feat: per-user profile routing via config.yaml (#33548)#33892
ousiaresearch wants to merge 1 commit into
NousResearch:mainfrom
ousiaresearch:feat/profile-routing

Conversation

@ousiaresearch

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds per-user profile routing via config.yaml. Maps user identity strings to Hermes profiles — the gateway switches profiles for individual users.

Fixes #33548

Type of Change

  • ✨ New feature

Changes Made

  • gateway/platforms/base.py: Added target_profile field to MessageEvent
  • gateway/run.py: Added _load_profile_routing, _resolve_profile_for_user, routing block in _handle_message

How to Test

  1. Add profile_routing: {"<user_id>": "<profile>"} to config.yaml
  2. Verify messages from that user route to target profile

Checklist

  • I have tested these changes locally

Adds profile_routing config section mapping user identity strings
(Telegram UID, Discord snowflake) to Hermes profile names. When
matched, the gateway switches to the target profile for message
processing and restores afterward. Includes MessageEvent.target_profile
field and _load_profile_routing/_resolve_profile_for_user helpers.
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery area/config Config system, migrations, profiles labels May 28, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Competes with #33558 — both implement per-user profile routing for #33548. This PR uses config.yaml profile_routing map; #33558 uses env var. Also bundled in #33884. Related to #22262, #20096 (per-channel routing).

@ousiaresearch

Copy link
Copy Markdown
Contributor Author

Design notes — config.yaml vs env var approach

This PR implements per-user profile routing through a profile_routing map in config.yaml:

```yaml
profile_routing:
"716505545": palantir # Telegram UID → palantir profile
"123456789": arien # Discord ID → arien profile
```

Why config.yaml over env var (#33558):

  1. Declarative & auditable: The mapping lives in version-controlled config, not in shell environment. You can `git diff` changes to routing.
  2. No shell injection risk: Env var approaches require parsing a string format (e.g., `uid=profile,uid=profile`) which needs careful escaping. A YAML dict is unambiguous.
  3. Per-process isolation: Env vars leak to child processes. Config.yaml is loaded by the gateway only.
  4. Easier to manage at scale: Adding a new user is a single YAML line vs modifying an increasingly long env var string.
  5. Survives restarts cleanly: No need to export variables in `.bashrc` or systemd units — config.yaml is always loaded.

Both approaches solve #33548. The env var approach (#33558) is simpler for single-user setups; the config.yaml approach is better for multi-user routing (which is the actual use case — different humans hitting the same gateway instance).

@samuelperezh

Copy link
Copy Markdown

Agreed @ousiaresearch - @alt-glitch are we good to merge here?

@lexiismadd

Copy link
Copy Markdown

Would love to see this implemented!

@alt-glitch alt-glitch added the sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages label Jun 26, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Competing-PR cluster for #33548 (per-user profile routing). This PR uses a profile_routing map in config.yaml; #33558 implements the same feature via an env-var map; #36872 takes a per-profile-worker approach. Cross-linking as related_to so a maintainer can pick the surface — not marking any as duplicate since the mechanisms differ.

@agent00bob

Copy link
Copy Markdown

👍 for the config.yaml approach over env vars — the version-controlled audit trail and clean multi-user scaling matter for real deployments.

Our use case: single BlueBubbles (iMessage) instance on a Mac Mini serving three household users, each with their own Hermes profile (separate SOUL, AGENTS, memory banks, model config). Phone number → profile routing via profile_routing in config.yaml is exactly what we need — one gateway, one BlueBubbles server, three fully isolated agent identities.

Would love to see this (or #33558) merged. Either approach unblocks multi-user iMessage without custom middleware.

@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 pursuing the config.yaml route; the current gateway has the profile-scoping primitives needed for this feature, but this patch is not yet connected to them.

Problems

  • gateway/run.py:6917 in this PR stores event.target_profile, while current execution resolves the profile solely from source.profile in gateway/run.py:16977-16987. There is no current gateway consumer of target_profile, so a matched map does not switch the turn.
  • The mapping runs after the authorization/pairing return path. Current authorization and adapter lookup use SessionSource.profile (gateway/authz_mixin.py:39-70), so the target profile cannot govern intake or reply delivery.
  • Main rejects duplicate same-platform credentials in multiplex mode (gateway/run.py:8586-8600); the single-ingress adapter must remain the reply transport while the turn runs in the mapped profile's scope.

Suggested changes

  • Resolve and apply the mapped profile at the SessionSource.profile ingress seam before authorization and session creation, then exercise the existing _profile_runtime_scope path.
  • Add integration tests covering mapped/unmapped users, profile-local session/config isolation, authorization, and replies through the ingress adapter.

Automated hermes-sweeper review.

Comment thread gateway/run.py
_routed_profile,
self._active_profile_name(),
)
event.target_profile = _routed_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.

target_profile is not consumed by the gateway execution path. Current main scopes the turn from source.profile (gateway/run.py:16977-16987) and resolves the response adapter from that same field (gateway/authz_mixin.py:61-70), so this assignment does not route the message. Apply the resolved profile at the source/profile ingress seam before authorization and session-key creation.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state 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-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 13, 2026
@alt-glitch alt-glitch removed the sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data label Jul 13, 2026
@teknium1 teknium1 added the area/profiles Multi-profile isolation, HERMES_HOME scoping label Jul 19, 2026
@alt-glitch alt-glitch added the needs-decision Awaiting maintainer decision before any implementation label Jul 19, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #65571. Current main now has the richer gateway.profile_routing route model (platform/chat/guild/thread matching); this older user-ID mapping branch needs a rebase and an explicit scope decision.

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 needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit 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-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.

Feature: Per-user profile routing for messaging platforms (user->profile binding)

7 participants