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 ) 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

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

// --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-names, 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.