website/docs/user-guide/multi-connection-desktop.md
Register every Hermes backend you own — the local runtime, remote gateways on your LAN or VPS, SSH hosts, and Hermes Cloud instances — in one desktop app, and use the agents on all of them side by side. Connections are persistent: each registered gateway dials its own backends and WebSockets on demand, and background agents keep streaming while you look at another gateway.
This is the desktop-side complement to Running Many Gateways at Once: that page is about hosting several gateways on one machine; this one is about one desktop app talking to several machines.
Everything lives on the unified Settings → Gateways page (older builds had separate Gateway and Connections pages; legacy Connections deep links redirect there). Three doors lead to it:
The Registered gateways section of Settings → Gateways manages a named list of Hermes gateways. Its intro says it plainly: "Manage this device and every Hermes gateway it can reach through remote, SSH, or Cloud connections." Each entry is a connection:
| Kind | What it is | Auth |
|---|---|---|
| Local | "The Hermes runtime managed by this app." | automatic |
| Remote gateway | "A Hermes gateway reachable over HTTP(S) — LAN, Tailscale, or the internet." | session token or OAuth |
| SSH | "A Hermes install reached over SSH." The app opens the tunnel and starts the dashboard for you | SSH key + adopted token |
| Hermes Cloud | "A hosted instance discovered through your Hermes Cloud account." | portal sign-in |
Rules worth knowing:
Homelab and homelab
cannot coexist.user@host:port plus
remote profile.Switch gateways from the Sessions sidebar. Profiles, chats, messaging, and cron stay scoped to that gateway; the app-managed window backend is still chosen by the connection-mode controls above. Primary is the registry fallback and does not switch the current workspace.
Homelab). Max 64 characters.hermes serve backend,
e.g. http://homelab.lan:9119. Reverse-proxy path prefixes work.user@host:22 form (user and
port optional). Your SSH key is used; the app adopts a dashboard
token over the tunnel.Edit any non-local entry later with the pencil button, or remove it with the trash button — removal asks for confirmation and reminds you that "The instance itself is not touched — you can add it again any time."
:::info The remote backend is a running hermes serve process
Nothing here works unless the backend is actually up and reachable on the
other machine. The desktop app attaches to it; it does not start it for you
(except for SSH connections, where the app starts the dashboard over the
tunnel on demand). See
Connecting to a remote backend
for backend-side setup — auth providers, binding to a non-loopback address,
and Tailscale guidance.
:::
The first launch of a registry-capable build imports your existing settings
automatically: the global connection mode and any legacy per-profile
overrides from Settings → Gateway become named registry entries (deduplicated
by URL/host). (Newer builds no longer offer per-profile overrides in the
Gateways settings page — gateway connections are machine-level, and profiles
are discovered from the gateways you connect.)
The legacy settings file is left untouched, so older builds on the same
machine keep working. If a migrated name collided, it was suffixed
(Homelab 2).
Every profile on every registered connection is an agent. The union roster is what multi-gateway surfaces (and the built-in Bot Mode roster) render:
@name-device — research on your Homelab renders as
@research-homelab, while a profile unique across all gateways keeps its
bare name.Each (connection, profile) pair gets its own backend and socket, pooled
with the same idle-reaping as local per-profile backends — background agents
keep streaming while you look at another gateway.
The sidebar foot follows one hierarchy: gateway → profile → sessions. Gateways are machines or hosted backends; profiles are isolated Hermes agents that live on one gateway.
(gateway, profile). Switching from a Telegram
gateway to a Signal gateway cannot leave the previous gateway's channel groups
or sessions in the sidebar.(profile, device) agent from the
union roster, and picking one reads and writes that machine's skills,
toolsets, and MCP servers without switching the Sessions workspace. Hub
installs, env keys, and MCP setup all land on the selected agent's backend.
The MCP tab's hot-reload into a live session button appears only for agents
on the gateway the window is connected to; edits on other machines apply on
their next session.Add, test, rename, or remove gateways in Settings → Gateways. The plug button beside the profile actions is a shortcut to that single management home, not a second add flow.
Sessions intentionally show one active gateway at a time: this keeps files,
tools, channels, cron, and session history in one understandable execution
context. Bot Mode serves a different job and may present the union roster,
grouped by gateway, so a user can open one agent on a NAS and another on a VPS
from one surface. Opening a bot still activates its exact (gateway, profile)
route.
Direct bot mentions and delegation remain gateway-local by default. Crossing a backend boundary changes filesystem, credentials, tools, and trust context, so cross-gateway execution should be an explicit bridge rather than an accidental side effect of sharing one Desktop window.
Settings → Gateways → Update all instances (shown once more than one
connection is registered) dispatches hermes update to every eligible
connection in parallel:
Each instance reports independently, so one unreachable box never wedges the batch. Backends that manage updates externally (Docker, Nix) refuse politely with their own message, per row.
You rarely need the Settings button, though: once more than one update target exists, the app's regular update affordances (Update now on the About panel, ⌘K Update Hermes, the update-ready toast) run the same fan-out automatically — active backend first, then every other eligible gateway, then the desktop app itself last. See Updating in the desktop guide.
safeStorage (the OS keychain — Keychain on macOS, DPAPI
on Windows, the session keyring backend on Linux) and stay in the Electron
main process; the renderer and plugins never see token bytes. OAuth tokens
for native sign-in are stored the same way, keyed by gateway base URL, and
refreshed automatically before expiry.connections.json under the app's user-data
directory) holds labels, URLs, and hosts — secrets only ever appear inside
encrypted envelopes.host.connections() deliberately returns labels, kinds,
and the primary id — never token material.The Desktop plugin SDK exposes the multi-gateway surface directly:
host.connections() — the registered connection list (labels, kinds,
primary; never token bytes).host.agents() — the union roster: one row per (gateway, profile) with
the precomputed @name-device handle.host.ensureAgent(connectionId, profile) — activate an agent's gateway so
subsequent host.request calls hit its backend.host.warmAgent(connectionId, profile) — fire-and-forget socket pre-warm
(hover-intent).All four are feature-detected: on an older Desktop build they're absent and a
plugin should fall back to the single-gateway profiles.list flow. Bot Mode's
multi-gateway roster is the reference consumer.
hermes serve is running on the remote host, the
port is open, and (for token auth) the token is current. Re-run Test
after fixing./api/ws.Homelab 2).