hermes - ✅(Solved) Fix [Feature]: one gateway serves multiple agents — switch via `/profile <name>` or `@<name>` [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#24913Fetched 2026-05-14 03:50:37
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
0
Author
Participants
Timeline (top)
labeled ×3cross-referenced ×1

Fix Action

Fixed

PR fix notes

PR #24914: feat: One gateway for multiple agent profiles.

Description (problem / solution / changelog)

What does this PR do?

Lets a single gateway process serve multiple Hermes profiles as switchable agents — instead of needing one gateway per profile bound to a separate platform credential.

User-facing surface

InputEffect
/profileShow the chat's active profile + host info
/profile lsList all available profiles
/profile coderBind this chat to the coder profile (persisted)
/profile defaultReset to default
@coder fix bugRoute only this turn to coder; binding intact
(every reply)Prefixed with [<agent>], toggleable via gateway.show_agent_name

Isolation guarantees

  • Memory / skills / SOUL.md: hard-isolated per profile (existing ~/.hermes/profiles/<name>/ layout)
  • Session / transcript: each (chat, agent) pair has its own session_id — switching back to a previously-used agent resumes its own conversation, no leakage from other agents
  • Gateway runtime state (sessions DB, host platform tokens): stays in the host profile

Mechanism

  • HERMES_HOME becomes a ContextVar; get_hermes_home() consults it first → per-turn agent swap with no env-var rebind
  • _run_agent wraps the executor in agent_home_scope(<agent.home>) so AIAgent construction + run-conversation see the right paths
  • build_session_key takes agent_name(chat, agent) pairs get distinct session_keys → distinct transcripts; chat bindings persist to sessions/chat_bindings.json
  • default agent keeps the legacy agent:main:... prefix → zero migration for existing state.db / sessions.json

Details see #24913

Related Issue

<!-- Link the issue this PR addresses. If no issue exists, consider creating one first. -->

Fixes #24913

Type of Change

<!-- Check the one that applies. -->
  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

<!-- List the specific changes. Include file paths for code changes. -->

How to Test

<!-- Steps to verify this change works. For bugs: reproduction steps + proof that the fix works. -->
  1. Added comprehensive unit tests
  2. Test locally.

Checklist

<!-- Complete these before requesting review. -->

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: <!-- e.g. Ubuntu 24.04, macOS 15.2, Windows 11 -->

Documentation & Housekeeping

<!-- Check all that apply. It's OK to check "N/A" if a category doesn't apply to your change. -->
  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

For New Skills

<!-- Only fill this out if you're adding a skill. Delete this section otherwise. -->
  • This skill is broadly useful to most users (if bundled) — see Contributing Guide
  • SKILL.md follows the standard format (frontmatter, trigger conditions, steps, pitfalls)
  • No external dependencies that aren't already available (prefer stdlib, curl, existing Hermes tools)
  • I've tested the skill end-to-end: hermes --toolsets skills -q "Use the X skill to do Y"

Screenshots / Logs

<!-- If applicable, add screenshots or log output showing the fix/feature in action. -->

Changed files

  • AGENTS.md (modified, +91/-0)
  • cli.py (modified, +64/-6)
  • gateway/__init__.py (modified, +4/-0)
  • gateway/agent_context.py (added, +78/-0)
  • gateway/agent_mention.py (added, +94/-0)
  • gateway/agent_registry.py (added, +274/-0)
  • gateway/agent_response.py (added, +105/-0)
  • gateway/run.py (modified, +248/-15)
  • gateway/session.py (modified, +214/-9)
  • hermes_cli/commands.py (modified, +5/-1)
  • hermes_constants.py (modified, +25/-1)
  • tests/e2e/test_multi_agent.py (added, +251/-0)
  • tests/gateway/test_agent_context.py (added, +167/-0)
  • tests/gateway/test_agent_mention.py (added, +124/-0)
  • tests/gateway/test_agent_registry.py (added, +220/-0)
  • tests/gateway/test_agent_response.py (added, +110/-0)
  • tests/gateway/test_multi_agent_isolation.py (added, +200/-0)
  • tests/gateway/test_profile_command.py (added, +177/-0)
  • tests/gateway/test_resolve_turn_agent.py (added, +137/-0)
  • tests/gateway/test_session_active_agent.py (added, +169/-0)
RAW_BUFFERClick to expand / collapse

Problem or Use Case

Hermes profiles give you fully isolated agent identities — each with its own SOUL, memory, skills, and config. But today, the only way to expose them as distinct "agents" to a user is to run one gateway process per profile, each bound to its own platform credential.

This works on platforms where multiple credentials are cheap and idiomatic:

  • Telegram: one bot token per profile → users get @CoderHermesBot, @WriterHermesBot, etc.
  • Discord: one bot user per profile → users see different bot accounts in a server.

But for other channels, a user who wants both a coding assistant and a writing assistant has to choose one — or run two completely separate gateways and remember which port/URL is which.

And for some channel, like WeChat, it can only bind to one single instance.

Proposed Solution

Make the gateway profile-agnostic and surface profile switching through chat-level slash commands. No new platform credentials needed.

User-facing surface

CommandEffect
/profileShow the chat's active profile + host info
/profile lsList all available profiles (one per ~/.hermes/profiles/<name>/)
/profile coderBind this chat to the coder profile (persisted; survives restart)
/profile defaultReset to the default profile
@coder fix this bugRoute just this turn to coder; chat binding intact

Replies are prefixed with [<agent>] so the user can tell who answered. Toggle via gateway.show_agent_name: true/false in config.yaml.

Isolation guarantees

  • Memory / skills / soul: hard-isolated per profile (each profile's ~/.hermes/profiles/<name>/ directory).
  • Session / transcript: each (chat, agent) pair owns an independent session_id/profile coder after talking to default starts (or resumes) coder's own conversation. Switching back to default restores its prior transcript. Matches "two independent Telegram bots" semantics rather than "one bot wearing different hats."
  • Gateway runtime state (sessions DB, gateway config, host platform tokens): stays in the host profile — the gateway is the orchestrator, the agents are what it routes between.

Mechanism (one paragraph)

HERMES_HOME becomes a ContextVar consulted by hermes_constants.get_hermes_home() before the env var, so each turn runs inside agent_home_scope(<agent.home>) and every profile-aware path (memory, skills, SOUL.md) resolves to the right profile without rebinding the process env var. build_session_key takes an agent_name parameter so (chat, agent) pairs naturally get distinct session_keys → distinct session_ids → distinct transcripts. Chat-level bindings persist to sessions/chat_bindings.json.

Backward compatibility

  • Default agent keeps the legacy agent:main:... session_key prefix → existing state.db and sessions.json work with zero migration.
  • Single-profile users see no behavior change: get_chat_agent defaults to default, response prefix is on by default but can be disabled, no new commands required.
  • build_session_key(source) without agent_name still works (defaults to default).

Alternatives Considered

No response

Feature Type

Gateway / messaging improvement

Scope

Large (new module or significant refactor)

Contribution

  • I'd like to implement this myself and submit a PR

Debug Report (optional)

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix [Feature]: one gateway serves multiple agents — switch via `/profile <name>` or `@<name>` [1 pull requests, 1 participants]