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 source dials its own backends and WebSockets on demand, and background agents keep streaming while you look at another source.
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.
Three doors lead to the same pane:
Settings → Connections manages a named registry of agent sources. The pane's intro says it plainly: "Register every place your agents live — this device, remote gateways on your network, and Hermes Cloud instances. All of them are stored here." 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.As the pane's own caption notes: "Chats and the agent roster follow the source you pick; the app-managed window backend is still chosen in Settings → Gateway."
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 per-profile overrides from
Settings → Gateway become named registry entries (deduplicated by URL/host).
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-source surfaces (and plugins like Bot Mode) render:
@name-device — research on your Homelab renders as
@research-homelab, while a profile unique across all sources 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 source.
Switching agents is the same gesture as switching profiles:
@research-homelab live on the Homelab;
its cron jobs run there; its messaging channels are the ones its gateway
hosts. The All profiles view merges every profile's sessions into one
list, with per-profile tags.Settings → Connections → 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.
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-source surface directly:
host.connections() — the registered connection list (labels, kinds,
primary; never token bytes).host.agents() — the union roster: one row per (source, 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-source profiles.list flow. Bot Mode's
multi-source 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).