---
name: adom-desktop-multi-ad
description: Address and fan out to multiple Adom Bridges on one relay. Use when more than one machine runs Adom Bridge on the same relay and you need to target a specific one (--target <name>) or broadcast to all (--target all), e.g. a Mac laptop plus a second desk machine. Covers client naming, --target, the targets verb, stable-identity eviction, and ambiguous-target handling.
---

# Multi-machine addressing: name + fan out to many Adom Bridges on one relay

> **Status: SHIPPED (v1.8.133+).** One relay holds many named Adom Bridge clients at once;
> each is addressable with `--target <name>`, and `--target all` fans a verb out to every
> connected machine with aggregated results. Client machines can be Macs (the auth payload
> reports `os: macOS`).
>
> **Trigger words:** multiple Adom Bridges, multiple desktops, two laptops, last one wins,
> relay fight, commands go to wrong machine, target a specific machine, --target,
> ADOM_TARGET, broadcast to all, fan out, name each machine, client name, machine name,
> relay multi-client.

---

## Context (why this exists)

Adom Bridge gets installed on multiple machines, all pointed at **one** cloud relay
(a container's `adom-desktop serve`). Before v1.8.133 they fought: when a client
authenticated, the relay evicted any existing client with the **same `appName`**, and
every client authed with the same appName, so the last machine to connect evicted the
others. The cloud AI's commands then landed on whichever box connected last, silently.

The shipped model: every Bridge stays connected and is **addressable by name**, with the
ability to **fan one command out to all** connected machines.

**Product decisions:**
1. **Friendly name, default = hostname.** Each machine has a configurable name
   (`--client-name` launch flag, the Settings field, or the OS hostname by default).
2. **`--target all` fan-out**: one command to every machine, results aggregated per
   machine.
3. **No target + multiple machines connected = ERROR** listing the connected names.
   The relay never guesses which box you meant.

## How it works

- **Auth payload:** each Bridge sends `clientName` (resolved friendly name), `version`,
  `hostname`, and `os` (`macOS` on the Mac build) in its relay auth handshake. The relay
  routes and de-duplicates on this.
- **Stable-identity eviction, never appName:** on auth, the relay evicts only a stale
  connection whose identity matches the new one. Identity = `clientName` when set, else
  `hostname`. Eviction fires on a `clientName` match always, and on a `hostname` match
  only when neither side has a `clientName`. So distinct named machines always coexist,
  and a genuine same-machine reconnect still kills its zombie connection.
- **CLI targeting:** `--target <name>` is stripped from raw argv before clap parses
  (it works before or after the verb). Precedence: flag, then `ADOM_TARGET` env. When a
  target is set the CLI always traverses the relay (the direct-API short-circuit is
  local-GUI-only). `"all"` broadcasts.
- **Broadcast** uses a dedicated awaiting/aggregating relay action: each target gets a
  clone of the message with a fresh message id, per-target timeouts and failures are
  isolated to that machine's entry, and the batch always returns.

## Using it

```bash
adom-desktop targets                       # list connected machines
# {count, targets:[{name, hostname, version, os, connectedAt}]}

adom-desktop --target mymac desktop_screenshot_screen
adom-desktop --target all ping             # note: use a relay-path verb for routing tests
adom-desktop --target all server_list
```

- Exactly one machine connected: no `--target` needed.
- `--target` matches `clientName` then `hostname`, case-insensitively.
- Caveat: `ping` has its own direct-API path and does NOT honor `--target`; smoke-test
  routing with a relay-path verb (`server_list`, any `desktop_*`).

### Result / error shapes

```jsonc
// --target all
{ "broadcast": true, "targets": ["mymac","deskmac"],
  "results": { "mymac": {/*verb payload*/},
               "deskmac": {"ok":false,"error":"Request timed out","timedOut":true} },
  "summary": { "total":2, "ok":1, "failed":1 } }          // duplicate names get a "#<idtail>" suffix

// no target, >1 machine connected (exit 1)
{ "error":"ambiguous_target",
  "_hint":"Multiple Adom Bridges connected. Re-run with --target <name> (or --target all).",
  "connected":[{"name":"mymac","hostname":"MYMAC","version":"1.9.x"}, ...] }

// --target names a machine that isn't connected (exit 1)
{ "error":"target_not_found", "requested":"deskmac",
  "_hint":"No connected Adom Bridge matches 'deskmac'. Connected: mymac, desk-mac-01.",
  "connected":[ /* same shape */ ] }
```

## Naming a machine

- **Settings field** in the Bridge window: "This machine's name (for cloud addressing)",
  default hostname. Writes `client_name` in `~/.adom/config.json` and reconnects.
- **`--client-name <name>` launch flag**: how a parent process (Adom Hydrogen) names a
  spawned Bridge. Distinct from `--relay-name` (the relay's display name in Bridge's
  server list, not this machine's self-name).

## Edge cases & back-compat

| Case | Behavior |
|---|---|
| Old client (no clientName) | Relay falls back to `hostname` for identity + addressing. |
| No `--target`, 1 machine | Routes to it, unchanged. |
| No `--target`, >1 machine | `ambiguous_target` (self-documenting); pass a target. |
| No target, local direct API up | Direct short-circuit preserved (fast local hop). |
| `--target <self>` | Skips direct, goes via relay: explicit and correct. |
| Same hostname, no clientName | They collide; set distinct `--client-name`s, then they coexist. `targets` shows the collision. |
| Reconnect (zombie) | Same name/hostname evicts the stale connection (intended). |
| Broadcast partial failure/timeout | Per-target error object; batch still returns with `summary.failed`. |
| `--target all`, 0 machines | `{broadcast:true,targets:[],results:{},summary:{total:0,...}}`, exit 0. |

## Verification recipe

Two Bridges against one relay:
1. Connect machine A (`--client-name mymac`) + machine B (`--client-name deskmac`);
   `adom-desktop targets` shows 2 rows with names + versions.
2. A no-target relay verb returns `ambiguous_target` + both names, exit 1.
3. `--target mymac` reaches A only; `--target deskmac` reaches B only.
4. `--target all` returns `{broadcast:true, results:{mymac, deskmac}}`.
5. `--target nope` returns `target_not_found` + the connected list, exit 1.
6. Kill and reconnect B: `targets` still shows exactly 2 (the reconnect evicted its own
   stale entry).
7. Single-machine regression: disconnect B; no-target verbs work and the local direct
   path is unchanged.
