No More Restart: Hermes Hot-Serves Profiles You Create While the Gateway Is Running


It is 2 a.m. and you have just finished setting up a third bot: hermes profile create, paste the bot token, then a quick hello in Telegram — and nothing answers. The desktop still shows that profile’s gateway as stopped. You look it up, and the answer is restart the gateway. But the other two profiles are mid-way through long jobs, and a restart means interrupting both of them.

That trade-off is gone. PR #109239, merged to main on 2026-09-12, teaches a running multiplex gateway to notice profiles that appear underneath it and serve them immediately — no restart, no collateral damage to the profiles already working.

What a multiplex gateway actually is

By default one profile means one independent config plus one bot, and keeping that bot online means running a gateway process just for it: hermes -p name gateway run. More profiles, more processes — more memory, more logs, more ports to babysit.

A multiplex gateway does it differently: a single gateway process hosts the bot adapters for several profiles at once. You turn it on with gateway.multiplex_profiles: true in your config, and after that you do not start a process per profile. Everything shares one gateway, while each profile keeps its own isolated runtime — its own memory, its own SQLite database, its own agent cache.

The cost of that design lands on discovery. The multiplexer used to enumerate the profiles/ directory exactly once, at startup: _start_secondary_profile_adapters built adapters from the list as it existed in that moment, _record_served_profiles wrote down who was being served, and cron reused the same profile_homes snapshot. A profile created after that point was not on the list — no adapter, no polling, its bot could never connect, and the desktop reported that profile’s gateway as stopped. Delete a profile instead, and it stayed in served_profiles forever.

In a one-bot-one-process world that is a non-issue: a new bot gets a new process. Under multiplexing, though, profiles/ is a directory that changes, and the gateway had looked at it exactly once. Creating a profile should mean it works, without asking you to restart the whole gateway — and interrupt everyone else’s long job — because of an adapter that was never built.

Three cases, three paths

PR #109239 (merged 2026-09-12, +641 / −43 across 27 files) turns the multiplexer from “enumerate once at startup” into “reconcile continuously.” A running gateway keeps comparing the profiles/ directory against the set it is already serving, and acts on the difference. Three situations cover almost everything.

Situation How it starts What the gateway does
New profile hermes profile create, the dashboard’s POST /api/profiles, Desktop’s New profile Builds the adapter and serves it right away; it also pings the gateway control socket via a new rescan-profiles verb
Credentials added after creation config.yaml or .env of a served profile changes The signature change triggers a rescan; the adapter is built under that profile’s own runtime scope. Live platforms are skipped, duplicate tokens hit the same guard
Profile deleted Removed from the profiles/ directory Tears the adapter down, cancels routing, releases pairing / busy / agent-cache / SQLite / memory handles

Created: served immediately. All three entry points work. The hermes profile create command, the dashboard’s POST /api/profiles, and Desktop’s New profile flow each ping the gateway control socket after creating a profile, using the new rescan-profiles verb to make the gateway look again now rather than on the next pass.

Credentials later. Creating a profile first and pasting the bot token second is the normal order, and those credentials count as configuration that appeared after startup. That case is caught by mtime changes on the served profile’s config.yaml or .env: the change triggers a rescan, and the adapter is built under that profile’s own runtime scope. Platforms that are already live are skipped, and a duplicate token runs into the exact same duplicate-credential guard that startup has always used — the second one is parked with a warning, never given its own poller, because two pollers on one bot token fight over the same messages.

Deleted: torn down and de-routed. Reconnects are cancelled, pairing / busy / agent-cache / SQLite / memory handles are released, the profile’s /p/name/ paths disappear, and served_profiles is updated to match. No half-dead adapter is left holding memory or squatting on a bot token.

The 30-second fallback rescan

Pinging the control socket is the fast path, and fast paths fail: the gateway may be starting up, the control socket may not be listening yet, or there may simply be no answer. So the change also ships a supervised watcher that re-scans profiles/ every 30 seconds (the source constant is _PROFILE_RESCAN_INTERVAL_SECS = 30.0). Immediate serving is best-effort; correctness is guaranteed by the periodic pass underneath it.

The control socket client is rescan_gateway_profiles(home, timeout=8.0), which sends rescan-profiles and normally returns a payload with served_profiles, added, removed and friends. The interesting part is the deliberate downgrade: if no gateway answers, or the gateway that answers is an older version, the call returns None. The caller does not error out — it falls back to the periodic rescan, which in the CLI means keeping the familiar restart reminder. In the other direction it is just as forgiving: an older Hermes install will not fall over because a newer client asked it a question it does not understand.

Rescans are cheap because signature detection does not walk the directory contents: it looks at exactly two files, config.yaml and .env, and compares their (mtime_ns, size). Those are the source-level constants _PROFILE_SIGNATURE_FILES = ("config.yaml", ".env"). That is also why pasting credentials triggers a rebuild at all — you save the file, the mtime moves, the signature changes. Anything else changing nearby, such as a new log file or a stray cache, will not cause a false trigger.

The implementation lives in a new module, gateway/run_profile_reconcile.py, attached to the gateway runtime as GatewayProfileReconcileMixin — closer to a reconciler that keeps one eye on the profiles/ directory than to yet another process-management layer.

cron, MCP discovery and log routing come along for the ride

A new profile is not just one more bot. cron timers were switched to a live enumerator here, so a newly created profile’s scheduled jobs actually fire instead of being skipped for not appearing in the startup snapshot. New profiles also get MCP discovery and log routing. Together those details are what makes “it just works once created” honest — otherwise your new bot would answer messages while its own scheduled jobs sat there doing nothing.

If you want each profile running cron with its own bot, the cron-per-profile walkthrough has the full example.

Nothing else gets interrupted

The question worth asking about hot serving is whether it quietly restarts everyone else while it is at it. It does not. Throughout the reconcile pass, other served profiles are entirely unaffected: same pid, same poll counts, and conversations in flight are not interrupted. Reconciliation only touches the difference — add what is new, tear down what is gone, leave everything already correct exactly as it is.

That is the real distinction from restarting the gateway. A restart tears down the process and every adapter with it and builds them all again. Reconciliation changes only the part that needs changing. For a multiplex gateway, that is what the word hot ought to mean.

What you will see in the UI

The most visible signal is a banner that stops appearing. The platform configuration endpoint (a PUT to /api/messaging/platforms/ with the platform id) now returns a hot_served field. When it is true, both Desktop (in all six languages) and the dashboard skip the restart-required banner.

hermes profile create also confirms hot serving directly. You only keep the restart reminder when the multiplexer genuinely did not pick the profile up — for example, when no gateway is running at all, or when the control socket did not answer.

How to use it

  1. Make sure multiplexing is on: gateway.multiplex_profiles: true.
  2. Create the profile: hermes profile create nightshift.
  3. Put the bot token in its .env or config.yaml. Either order is fine; if you paste it afterwards, the signature change builds the adapter for you.
  4. Check with hermes gateway status and confirm the profile shows up among the served ones.
  5. If it does not show up: give the fallback rescan up to 30 seconds. If it still does not, hermes gateway restart remains the escape hatch. An older gateway does not know the rescan-profiles verb, and in that case restarting is the only path — so first confirm your Hermes is new enough to contain this change.

The short version

This change adds no new command. It changes how the multiplex gateway reacts to change: enumerate once at startup becomes reconcile while running. New profiles are served immediately, late credentials rebuild the adapter, deleted profiles are torn down and de-routed, a 30-second rescan covers the fast path failing, and profiles already being served are never touched. If you want to check it yourself, tests/gateway/test_multiplex_hot_serve.py has three cases that map to the three paths: a created profile gets served, added credentials build an adapter, and a deleted one has its routing cancelled.

For the wider picture of running several profiles side by side, see running multiple profiles on one machine; for controlling which profiles may serve bots at all, the gateway profile allowlist goes deeper.

One version note, since it matters: this landed on main on 2026-09-12 and belongs to the work that came after v0.21.2, released 2026-09-11. It has not shipped in a tagged release yet, so if you want it today, you want main.