# Goal: Hermes Desktop showing every customer's Slack threads

**Done when:** an operator opens Hermes Desktop, sees every Fly customer in the
profile picker, clicks one, and its Slack threads load — without relaunching
Desktop per customer.

Working today: **one customer at a time** (`--customer <name>`), verified against
Persona — Desktop rendered `SLACK 3` (Justin Custer Founder Outreach, Clover
Client-Ready Leads, Active Campaign Status) and the agent replied on gpt-5.6-sol.

Also working: **browser UI** (`--customer <name> --dashboard`) — `hermes
dashboard` serves the SPA from the image's own bundle, 200 with the customer's
sessions behind it. A real fallback when Desktop misbehaves.

Not working: **all customers in one picker** (`--all`).

---

## What is already proven — do not re-derive

Measured against the live app over CDP, not inferred:

| Layer | Status | Evidence |
|---|---|---|
| Tunnel to a customer machine | works | `/api/status` 200, `/api/ws` 101 |
| Per-profile override resolution | works | `getConnection('agent-4aae…')` → `{mode:"remote", source:"profile", hasToken:true}` |
| WebSocket for an overridden profile | works | `new WebSocket(conn.wsUrl)` → `OPEN` |
| All four profiles in the picker | works | rail lists all four `agent-…` buttons |
| Local `hermes serve` on this Mac | works | standalone `:9131` → 200 |
| Desktop primary boot in `mode: local` | **stalls** | UI sits at `CONNECTING`, Gateway `checking` |

Config rules that took a while to find:

- Tokens in `connection.json` must be `{ value: "..." }`. `sanitizeConnectionProfiles`
  (main.cjs:4685) keeps `token` only when it is an object — a bare string is
  silently dropped and Desktop reports "no session token is saved".
- A local profile directory must exist for a name to appear in local mode, and
  it must be named for the **remote** profile id (`agent-4aaeadf7…`) or the
  backend answers `Profile 'x' does not exist`.
- Clone new local profiles from a working one. An empty profile triggers a
  full-screen onboarding modal (`fixed inset-0 z-1300`) that covers the UI.
- The picker enumerates the **primary** backend's profiles. That is exactly why
  `--customer` works: global remote mode makes the customer's machine primary.
- `spawnPoolBackend` (main.cjs:5307) already short-circuits for remote profiles —
  no local child is spawned, and `waitForHermes` throws after 45s rather than
  hanging. So the per-profile path is not the stall.

## The one open question

**Why does Desktop's primary backend never become ready in `mode: local`?**

Symptoms: `CONNECTING`, Gateway `checking`, and "Waking up …" with an *empty*
profile label — which is primary boot, not a profile swap. Reproduces with a
customer profile as active and with plain `atlas`. Local `hermes serve` is
healthy standalone, so it is not the backend binary.

Next step: instrument `startHermes()` in `apps/desktop/electron/main.cjs` and
find where primary boot stalls when `mode: local`. Everything downstream of it
is already proven, so this is the last unknown. It may belong upstream as a
Hermes issue rather than something to work around here.

## Guardrails while working on this

- Restore `~/Library/Application Support/Hermes/connection.json` to the
  operator's own gateway when finished. Their normal setup is
  `{mode:"remote", remote:{url:"http://hermes-hostinger-1807396:9119", authMode:"oauth"}}`.
- Delete any `agent-*` local profiles created for a test, and leave
  `~/.hermes/active_profile` pointing at a profile that exists — a dangling
  pointer breaks every `hermes profile` command, including `profile use`.
- Run `--stop` so no `hermes serve` is left on a customer machine.
- Never start a stopped machine to test something.
