docs: full multi-gateway setup guide for Hermes Desktop

Expand user-guide/multi-connection-desktop.md into a complete setup
walkthrough: where to find the pane (settings nav, profile-rail plug,
command palette), the exact add-connection editor fields (Name,
Gateway URL, Authentication: Session token/OAuth, SSH host), Primary /
This device pills, Test semantics, agent roster + profile-rail
switching and per-profile session/cron/messaging scoping, token
storage via Electron safeStorage with the keyring-less Linux plain-
text opt-in, and troubleshooting. All quoted labels match the desktop
i18n strings. Cross-link the rail entry point from desktop.md.
This commit is contained in:
Teknium
2026-08-16 01:45:51 -07:00
parent 34271eb069
commit ea2daa0935
2 changed files with 131 additions and 25 deletions

View File

@@ -230,7 +230,7 @@ Connection modes are configured **per profile** — a per-profile override can p
### Settings → Connections: the multi-connection registry
Alongside the per-profile connection mode above, **Settings → Connections** manages a named registry of every agent source the app knows about — the local runtime, any number of remote gateways (LAN, Tailscale, internet), Hermes Cloud instances, and SSH hosts — all persisted together in one place. The full guide, including the union agent roster, `@name-device` handles, fleet-wide updates, and the plugin SDK surface, is at [Connecting Desktop to Many Hermes Instances](./multi-connection-desktop.md).
Alongside the per-profile connection mode above, **Settings → Connections** manages a named registry of every agent source the app knows about — the local runtime, any number of remote gateways (LAN, Tailscale, internet), Hermes Cloud instances, and SSH hosts — all persisted together in one place. You can jump there from the plug button at the right end of the sidebar profile rail (**Connect another Hermes gateway…**) or via **⌘K → Connections**. The full guide, including the union agent roster, `@name-device` handles, fleet-wide updates, and the plugin SDK surface, is at [Connecting Desktop to Many Hermes Instances](./multi-connection-desktop.md).
- **Every connection needs a unique name** (a device name such as "Homelab" or "Work laptop"). When the same profile name exists on several registered sources, surfaces disambiguate it as `@profile-device` (e.g. `@research-homelab`).
- **Add / edit / remove / test** connections from the panel. The local entry is managed by the app and cannot be removed. **Test** probes the connection's own HTTP and WebSocket legs directly.

View File

@@ -6,40 +6,103 @@ sidebar_position: 5
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.
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](./multi-profile-gateways.md): that page is
about hosting several gateways on one machine; this one is about one desktop
app talking to several machines.
## Where to find it
Three doors lead to the same pane:
- **Settings → Connections** — the pane itself (**Cmd/Ctrl+,**, then
**Connections** in the settings nav).
- **The sidebar profile rail** — the plug button at the right end of the rail
(tooltip: **"Connect another Hermes gateway…"**) deep-links straight to
Settings → Connections. It is always visible, even before you have created
a second profile or a second connection.
- **The command palette** — **Cmd/Ctrl+K**, then type *Connections* (also
matches *add gateway*, *remote*, *ssh*, *instances*).
## The connection registry
**Settings → Connections** manages a named registry of agent sources. Each
entry is a *connection*:
**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 runtime this app manages on your machine | automatic |
| **Remote gateway** | A `hermes serve` backend reachable over HTTP(S) — LAN, Tailscale, VPS | 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 Nous account | portal sign-in |
| **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:
- **Every connection needs a unique device name** ("Homelab", "Work laptop").
The name shows up everywhere the instance appears — roster badges, handles,
update results.
- The **local** entry is managed by the app and cannot be removed. Removing
any other connection tears down its live backends and tunnels; the instance
itself is untouched.
update results. Uniqueness is case-insensitive, so `Homelab` and `homelab`
cannot coexist.
- The **local** entry is managed by the app (it wears a **This device** pill)
and cannot be removed. Removing any other connection tears down its live
backends and tunnels; the instance itself is untouched.
- One connection is always the **Primary** (pill on its row): it owns the
app-managed window backend — boot overlay and install/update machinery.
**Make primary** on any row retargets that; removing the primary falls back
to the local entry.
- **Test** probes the connection's own HTTP *and* WebSocket legs, so a pass
means chat will actually work — not just that the host pinged.
- Cloud entries come from the Hermes Cloud sign-in/discovery flow, not a
hand-typed URL.
- Tokens are encrypted with the OS keyring (same plain-text opt-in as
Settings → Gateway on keyring-less Linux), and never leave the Electron
main process.
(the *"Reachable"* toast) means chat will actually work — not just that the
host pinged.
- Cloud entries come from the Hermes Cloud sign-in/discovery flow
(Settings → Gateway), not a hand-typed URL — which is why the add-connection
editor only offers **Remote gateway** and **SSH**.
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."*
## Adding a connection, step by step
1. Open **Settings → Connections** (or click the plug in the profile rail).
2. Click **Add connection**.
3. Pick the kind: **Remote gateway** or **SSH**.
4. Fill the fields:
- **Name** — required, unique; the "device name" shown everywhere this
instance appears (placeholder: `Homelab`). Max 64 characters.
- *Remote gateway only:*
- **Gateway URL** — the base URL of a running `hermes serve` backend,
e.g. `http://homelab.lan:9119`. Reverse-proxy path prefixes work.
- **Authentication** — choose **Session token** or **OAuth**:
- **Session token** — paste the dashboard session token from the
remote gateway. When editing, *"Leave blank to keep the saved
token."*
- **OAuth** — sign in through the Nous Portal browser flow; no token
to paste.
- *SSH only:*
- **SSH host** — one composite field in `user@host:22` form (user and
port optional). Your SSH key is used; the app adopts a dashboard
token over the tunnel.
5. Click **Save connection** (or **Cancel**).
6. Click **Test** on the new row and wait for *"Reachable"*.
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](./desktop.md#connecting-to-a-remote-backend)
for backend-side setup — auth providers, binding to a non-loopback address,
and Tailscale guidance.
:::
### Migrating from the single-connection settings
@@ -47,12 +110,13 @@ 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.
machine keep working. If a migrated name collided, it was suffixed
(`Homelab 2`).
## Agents across sources
Every profile on every registered connection is an *agent*. The union roster
is what multi-source surfaces (and plugins like
Every [profile](./profiles.md) on every registered connection is an *agent*.
The union roster is what multi-source surfaces (and plugins like
[Bot Mode](https://github.com/NousResearch/Hermes-Bot-Mode)) render:
- When the same profile name exists on several sources, handles disambiguate
@@ -71,22 +135,57 @@ 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 and scoping
Switching agents is the same gesture as switching profiles:
- **The profile rail** at the sidebar foot switches the active profile; the
home pill returns to the default profile and the layers pill shows the
**All profiles** view. **Cmd/Ctrl+1–9** switch profiles from the keyboard.
- The sidebar's session list, cron jobs, and messaging status are **scoped to
the active profile** — and, for agents on another source, to that source's
machine. Sessions you see under `@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.
- Hovering an agent pre-warms its backend so the switch doesn't pay a cold
boot.
## Updating every instance at once
**Settings → Connections → Update all instances** dispatches `hermes update`
to every eligible connection in parallel:
**Settings → Connections → Update all instances** (shown once more than one
connection is registered) dispatches `hermes update` to every eligible
connection in parallel:
- **Local** updates through the app's own update pipeline (the same flow as
Settings → Updates).
- **Remote and SSH** connections are told to update themselves via their own
backend — the update runs on *that* machine.
- **Hermes Cloud** instances are skipped: the platform manages their
versions.
- **Hermes Cloud** instances are skipped with a *"Managed by Hermes Cloud"*
note: the platform manages their versions.
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.
## Security notes
- **Where tokens live.** Remote-gateway session tokens are encrypted at rest
with Electron's `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.
- **Keyring-less Linux.** On a Linux session without a usable keychain the
app cannot encrypt the token; saving one raises an explicit opt-in dialog
(the same consent flow as Settings → Gateway) before it will store the
token in plain text.
- **The registry file** (`connections.json` under the app's user-data
directory) holds labels, URLs, and hosts — secrets only ever appear inside
encrypted envelopes.
- The plugin SDK's `host.connections()` deliberately returns labels, kinds,
and the primary id — never token material.
## For plugin authors
The Desktop [plugin SDK](../developer-guide/desktop-plugin-sdk.md) exposes the
@@ -107,6 +206,10 @@ multi-source roster is the reference consumer.
## Troubleshooting
- **"Connection test failed"** — the backend isn't reachable at that URL from
this machine. Check that `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.
- **An agent shows but won't open** — run **Test** on its connection. The
WebSocket leg failing while HTTP passes usually means a proxy, firewall, or
gateway auth/origin guard is blocking `/api/ws`.
@@ -117,3 +220,6 @@ multi-source roster is the reference consumer.
app predates the multi-connection stack; update the desktop app itself.
- **Duplicate device names** — not possible; names are enforced unique at
save time. If a migrated name collided, it was suffixed (`Homelab 2`).
- **"Could not save the connection"** — most commonly a missing **Name**, a
name already in use, or a malformed **Gateway URL** / **SSH host**; the
error message names the exact violation.