Follow-ups on the salvaged #20096 profile-routing feature:
- _profile_name_for_source now returns None unless gateway.multiplex_profiles
is on. Routing stamps source.profile, which namespaces session/batch keys,
but the profile-scoped agent run only activates under multiplexing — without
the gate, configured routes with multiplexing off split batch/session keys
into agent:<profile> while the agent still ran from agent:main.
- Widen the profile-aware _text_batch_key fix from Discord to every adapter
that builds batch keys via build_session_key (telegram, whatsapp, matrix,
feishu, wecom, weixin) — routing is platform-generic, so the batch-key
namespace fix must be too.
- Downgrade the no-route-matched log from INFO to DEBUG (fired on every
unrouted inbound message).
- GatewayConfig.to_dict(): serialize profile_routes as plain dicts
(ProfileRoute dataclasses are not JSON-safe).
- Docs: correct the 'independent of multiplexing' claim in
docs/profile-routing.md (routing requires multiplexing), fix the
platform-only specificity row (0, not 1), and document profile_routes in
website/docs/user-guide/multi-profile-gateways.md.
- Tests: pin the multiplex gate (routes ignored when off, active when on,
build_source end-to-end stays in agent:main when off).
Extend the 'Running Many Gateways at Once' user-guide page with a
'one gateway for all profiles (multiplexing)' section, kept to a single page:
- How to opt in (gateway.multiplex_profiles on the default profile) and when to
prefer it vs one-process-per-profile.
- Every contract change a user sees when the flag is on:
1. secondary-profile 'gateway start' is a hard error (--force escape hatch),
2. HTTP-inbound reached via /p/<profile>/ prefix; secondary profiles must NOT
enable a port-binding platform (webhook/api_server/msgraph_webhook/feishu/
wecom_callback/bluebubbles/sms) — config error at startup,
3. per-credential platforms still need their own token per profile,
4. session keys namespaced agent:<profile>: (default stays agent:main:),
5. single PID/lock + aggregated hermes status, per-profile runtime_status.json.
- What does NOT change: per-profile .env credential isolation (stricter, incl.
MCP/Kanban subprocess env), Kanban, profile-scoped skills/memory/SOUL, routing.
All inert when the flag is off.
Covers running multiple Hermes profiles as managed services on one host:
- A shell-loop wrapper pattern for start/stop/restart/status across every
profile (the per-profile CLI commands stay unchanged).
- Per-platform service file locations (LaunchAgent on macOS, systemd user
unit on Linux), plus the rules around clashes.
- Log paths per profile and how to tail every gateway at once.
- Config file layout per profile and the restart-after-edit workflow.
- Keeping the host awake: caffeinate flags on macOS,
systemd-inhibit + loginctl enable-linger on Linux.
- Token-conflict auditing across .env files.
- Troubleshooting for the common "Could not find service in domain for
user gui: 501" message and stale PIDs after a crash.
Tested locally with five profiles on macOS launchd.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>