feat(gateway): per-channel profile routing via channel_routes config - #22262
feat(gateway): per-channel profile routing via channel_routes config#22262andyg5000-agent wants to merge 1 commit into
Conversation
40b08d2 to
66396d9
Compare
Route different chats, groups, or channels to different Hermes profiles so a
single gateway can serve multiple isolated personas with their own models,
credentials, memories, and identities.
New module gateway/channel_routing.py:
- resolve_channel_route() — match chat_id to config routes, load profile
config, .env, SOUL.md, and memories into a ProfileContext dataclass
- build_routed_runtime_kwargs() — extract api_key/base_url/provider for AIAgent
- build_routed_ephemeral_prompt() — combine SOUL.md + memories + platform context
Gateway integration (gateway/run.py):
- Resolve route after session creation in inbound handler
- Thread route_context through _run_agent() call chain (main + continuation)
- Override model, runtime credentials, and ephemeral prompt when route matches
- Set skip_context_files/skip_memory to prevent double-loading global context
Config example in config.yaml:
channel_routes:
group:abc123...: {profile: family}
+123****7890: personal
Includes tests (14 passing) and user-facing documentation.
66396d9 to
d8bccdf
Compare
|
If you're looking for something to manage this without patching hermes, check out https://github.com/agents-blueoi/signal-mux. It allows you to have a single signal-cli running with multiple ports for hermes profiles to subscribe to for routing groups (signal) -> profiles (hermes) |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the concrete routing proposal and tests. The use case remains distinct from current main: channel_overrides only covers model/provider/prompt (gateway/config.py:403-434), while multiplexing requires a separate adapter credential per profile (website/docs/user-guide/multi-profile-gateways.md:158-165).
Problems
- The PR creates the gateway session before route resolution at
gateway/run.py:6331-6344and never stampssource.profile. That leaves routed messages in the gateway profile’s session namespace, rather than the profile-scoped keying current main uses ingateway/session.py:1257-1322. gateway/channel_routing.py:183-193manually recognizes only OpenRouter, Anthropic, and OpenAI keys. The routed runtime then bypasses current main’s profile secret scope (gateway/run.py:1413-1444), which is the fail-closed path for profile credentials.- The gateway integration has substantially moved: the PR no longer applies cleanly to current
gateway/run.py(git apply --checkfails on its inbound-handler hunk).
Suggested changes
- Build chat-route resolution into the current multiplexing boundary before session creation, set
SessionSource.profile, and use_profile_runtime_scope()for the whole routed turn. - Add end-to-end coverage for session namespace, provider-secret isolation, memory/skills scope, and reply delivery for two channels sharing one adapter.
Automated hermes-sweeper review.
| @@ -6331,6 +6331,20 @@ async def _handle_message_with_agent(self, event, source, _quick_key: str, run_g | |||
| session_entry = self.session_store.get_or_create_session(source) | |||
There was a problem hiding this comment.
Route resolution must occur before this session is created, and the resolved profile needs to be stamped on source. Otherwise the SessionStore derives the default gateway namespace and the routed turn cannot own profile-isolated session state.
|
|
||
| # Load .env for credentials | ||
| dotenv = _load_profile_dotenv(profile_dir) | ||
| api_key = ( |
There was a problem hiding this comment.
This hand-picked API-key list bypasses Hermes’s provider and credential-scope resolution, so routed profiles using OAuth, credential pools, or another supported provider will not receive their configured credentials. Route through the profile-scoped runtime/secret resolver instead.
|
The capability this PR implements has now landed on main via PR #64835 (a salvage of #20096 by @Burgunthy, the earliest submission of this feature): Your Docs: https://hermes-agent.nousresearch.com/docs/user-guide/multi-profile-gateways Thank you for the work and for pushing on this use case — the demand from PRs like this one is what got the feature prioritized. Closing as superseded by the merged implementation. |
|
Thanks @teknium1 ! |
Problem
A single Hermes gateway instance serves all connected chats/groups/channels with one model, one set of credentials, and one identity. There is no way to route different Signal groups, Telegram chats, or Discord channels to different profiles with their own models, SOUL.md, memories, and API keys.
Users working around this run multiple gateway instances (one per profile), which is fragile and wastes resources.
Solution
Add
channel_routesconfig that maps chat IDs to Hermes profiles. When a message arrives from a routed chat, the gateway resolves the target profile and overrides:model.defaultormodel.model)Config Example
Architecture
New module
gateway/channel_routing.py:resolve_channel_route()- matches chat_id to config, loads profile into ProfileContext dataclassbuild_routed_runtime_kwargs()- extracts api_key/base_url/provider for AIAgent constructionbuild_routed_ephemeral_prompt()- combines SOUL.md + memories + platform contextGateway integration (
gateway/run.py):Testing
14 tests covering:
Documentation
New
website/docs/user-guide/features/channel-routing.mdwith setup guide, chat ID formats per platform, troubleshooting, and config examples.What This Does NOT Change