Adom Bridge (macOS)
Public Made by Adomby adom
Adom Bridge for macOS: the menu-bar daemon that connects AI tools to your Mac.
name: adom-desktop-embedded description: Adom Bridge's EMBEDDED mode under Adom Hydrogen - what it means when Hydrogen owns Bridge, the lifecycle contract (Bridge stays up; exit when Hydrogen exits is opt-in flag-only), embedded permissions (Hydrogen's auto_approve on /embedded/enter, forwarding gated commands to Hydrogen's /permission/request, status.permissions.shell.embeddedPolicy), inspecting Hydrogen with hd_status and the hd_ verb family, and the read-only Hydrogen-managed relay entry. Use when reasoning about embedded vs standalone behavior, why Bridge survived (or should survive) a Hydrogen crash, why a gated command is waiting on Hydrogen instead of showing a Bridge dialog, or how to drive/inspect Hydrogen from a cloud caller.
Adom Bridge embedded mode (under Adom Hydrogen)
What embedded mode is
Adom Hydrogen bundles the signed Adom Bridge installer, installs it, and at runtime spawns Bridge as its child:
adom-desktop --embedded --start-hidden --relay-url ws://127.0.0.1:8765 --relay-name adom-hydrogen [--session-token <hydrogen token>]
Install locations on macOS: a Hydrogen-managed Bridge lives under
~/Library/Application Support/adom/bridge/ (Hydrogen 0.1.238+); a
standalone install is Adom Bridge.app in /Applications. Either way the
bundle id is inc.adom.desktop and the CLI binary names are unchanged
(adom-desktop / adom-desktop-cli).
While embedded:
- Bridge's window is hidden and Bridge HIDES its menu-bar item; Hydrogen is the app the human sees. With the window hidden, Bridge runs under the Accessory activation policy, so there is no Dock icon and no Cmd-Tab entry either. Bridge keeps serving everything (relay, direct API on 127.0.0.1:47200, bridges) the whole time.
- Bridge writes an
embedded.jsonmarker (withentered_via) so a restart re-embeds if Hydrogen is still alive. - Hydrogen injects a relay-back server entry tagged
added_by:"hd-spawn"(see the read-only rule below). - Sign-in is shared either way: one Adom sign-in serves both apps through a shared keychain item, so an embedded Bridge is signed in whenever Hydrogen is (and vice versa).
Entry paths: POST /embedded/enter on the direct API, or the --embedded
launch flag. Hydrogen polls GET http://127.0.0.1:47200/health after
spawning.
The lifecycle contract (AD_LIFECYCLE.md, on one page)
- Bridge stays up. It is the recovery channel for the machine: if Bridge exited whenever Hydrogen exited, one bad Hydrogen exit would leave the box unreachable to any remote agent (no log reads, no relaunch, no rollback).
- The ONLY thing that exits Bridge is an explicit shutdown:
desktop_shutdown/POST /command {"command":"shutdown"}, or the user quitting Bridge themselves. - "Embedded" is a display state, not a lifecycle. Hydrogen gone means Bridge drops the costume and reverts to a normal standalone menu-bar agent.
What happens when Hydrogen goes away:
| How Hydrogen goes away | What Bridge does |
|---|---|
Graceful: Hydrogen sends POST /embedded/release before stopping |
Revert to standalone (menu-bar item back, relay preserved), regardless of how Bridge entered embedded. |
| Ungraceful: Hydrogen's GUI crashes or is killed, no release sent | The embedded_monitor (10s probes, 3-miss debounce, ~30s) notices and reverts to standalone. Same end state. |
| A kill takes Hydrogen's whole process tree | Bridge dies WITH Hydrogen because it is Hydrogen's direct child process. This is the OS killing a child, NOT a lifecycle rule firing. Check for the tree-kill before blaming any Bridge logic. |
Reverting (revert_to_standalone) shows the menu-bar item again and keeps
the WS connection and the hd-spawn server entry; the supervisor reconnects
with backoff, so a remote caller sees status=reconnecting, never a
vanished row (Hydrogen's relay can outlive Hydrogen's GUI).
Exit-with-Hydrogen is opt-in, flag-only (v1.9.165). The only way to make
Bridge disposable is the spawner passing --exit-when-hd-exits on the
command line. Hydrogen does not pass it, so in practice Bridge always
survives. There is no config key (the old config.exit_when_hd_exits is
inert) and no Settings checkbox. The one flag-gated exit lives in
embedded_monitor::transition_to_standalone.
Forensics: both transitions write to startup.log (EXIT: / REVERT:
lines). No crash.log plus a startup.log that runs to === means it was
not a panic; an absent process with no crash.log is the signature of an
external (tree) kill.
Embedded permissions: Hydrogen owns the prompt
Hydrogen sends auto_approve in POST /embedded/enter; it is authoritative:
- absent: Bridge keeps standalone behavior (older Hydrogen; the feature is inert until Hydrogen opts in).
false(the new-contract default): Bridge does NOT auto-approve and shows no UI of its own. For each gated command from a remote relay caller, Bridge FORWARDS the request to Hydrogen'sPOST /permission/request(control port discovered fresh from Hydrogen's ports.json each call, blocking up to 180s) and applies Hydrogen's decision:allow_once,allow_1h,allow_24h,bypass_while_running, ordeny(refused witherrorCode:denied_by_user; do not auto-retry).auto_approve:falseoverrides any standing grant (the--shell-approvalflag, Settings checkbox, or timer) for relay callers, so the forward always triggers; a forwarded allow_1h/24h/bypass sets a dedicated grant window that is honored without re-forwarding.true: auto-approve (the user explicitly opted in); behaves like a standing grant.
Fail-safe: if Hydrogen is unreachable, Bridge falls back to its OWN approval
dialog (the window summons itself from the menu bar; never hard-deny, never
silent-approve). Coverage runs through one chokepoint
(consult_embedded_gate): shell_execute, run_script, and the Tier-2
class (launch_process, wiki_exec, write_file, delete_file,
process_kill). Gated verbs accept a reason arg that is shown verbatim
(attributed as "The AI says:") on Hydrogen's modal and Bridge's dialog. Only
remote relay callers are gated; local callers (CLI, Hydrogen, bridges) are
trusted by transport.
Discoverability: status.permissions.shell.embeddedPolicy reports
"standalone" | "forward-to-hd" | "auto-approve". If a gated call seems
to hang while embedded, a human is likely looking at Hydrogen's prompt; poll
rather than re-issue.
Inspecting state: hd_status's three orthogonal signals
Hydrogen is killed and respawned many times a day (upgrades, restarts), often
onto a different control port. hd_status therefore never collapses its view
into one boolean; it reports THREE independent signals:
processPresent: a process-list check (ps/pgrep on macOS), sampled with retry to ride the ~1s kill-then-respawn gap.controlApiReachable: TCP to Hydrogen's control port, discovered fresh per call (ports.json, never cached or hardcoded).embedded: Bridge's own state (itsAppState/ embedded marker).
running = processPresent || controlApiReachable. A mismatch between the
three is normal mid-churn; do not treat a single snapshot as Hydrogen being
dead (Bridge itself requires 3 consecutive misses over ~30s before leaving
embedded mode).
The hd_* verb family
Bridge exposes hd_* verbs for driving and observing Hydrogen itself
(hd_status, hd_stop / hd_restart, hd_send_files). Architectural rule:
Bridge owns host/process facts (process present, install path); anything
about Hydrogen's OWN runtime state is fetched from Hydrogen's control API
endpoints (GET /health, GET /buildinfo, GET /workspace/health) and
relayed verbatim. The Hydrogen BUILD verbs (hd_ship, hd_build_*) are part
of the Windows dev pipeline and return a clean "not supported on macOS yet"
error on this fork.
Updates while embedded follow the same split: Bridge checks, downloads,
verifies and STAGES its own update (GET /update/status shows
update_ready:true when staged); Hydrogen owns the apply timing and triggers
POST /update/apply. Embedded Bridge never auto-applies.
The Hydrogen-managed relay entry is READ-ONLY
The server entry Hydrogen injects at spawn is tagged added_by == "hd-spawn"
and is ephemeral (stripped on config persist, re-added each Hydrogen launch).
While embedded it cannot be disconnected, edited, or removed; the backend
refuses (is_hd_managed guard plus HD_MANAGED_REFUSAL) on
disconnect_server, remove_server, update_server, the upsert branch of
add_server, and the relay server_add / server_remove handlers, because
any of those would break the embedded session. The UI shows it as a "Managed
by HD" row with a locked note instead of editable fields. Do not try to work
around the refusal; the entry is torn down automatically when Bridge reverts
to standalone.
---
name: adom-desktop-embedded
description: Adom Bridge's EMBEDDED mode under Adom Hydrogen - what it means when Hydrogen owns Bridge, the lifecycle contract (Bridge stays up; exit when Hydrogen exits is opt-in flag-only), embedded permissions (Hydrogen's auto_approve on /embedded/enter, forwarding gated commands to Hydrogen's /permission/request, status.permissions.shell.embeddedPolicy), inspecting Hydrogen with hd_status and the hd_ verb family, and the read-only Hydrogen-managed relay entry. Use when reasoning about embedded vs standalone behavior, why Bridge survived (or should survive) a Hydrogen crash, why a gated command is waiting on Hydrogen instead of showing a Bridge dialog, or how to drive/inspect Hydrogen from a cloud caller.
---
# Adom Bridge embedded mode (under Adom Hydrogen)
## What embedded mode is
Adom Hydrogen bundles the signed Adom Bridge installer, installs it, and at
runtime spawns Bridge as its child:
```
adom-desktop --embedded --start-hidden --relay-url ws://127.0.0.1:8765 --relay-name adom-hydrogen [--session-token <hydrogen token>]
```
Install locations on macOS: a Hydrogen-managed Bridge lives under
`~/Library/Application Support/adom/bridge/` (Hydrogen 0.1.238+); a
standalone install is `Adom Bridge.app` in `/Applications`. Either way the
bundle id is `inc.adom.desktop` and the CLI binary names are unchanged
(`adom-desktop` / `adom-desktop-cli`).
While embedded:
- Bridge's window is hidden and Bridge HIDES its menu-bar item; Hydrogen is
the app the human sees. With the window hidden, Bridge runs under the
Accessory activation policy, so there is no Dock icon and no Cmd-Tab
entry either. Bridge keeps serving everything (relay, direct API on
127.0.0.1:47200, bridges) the whole time.
- Bridge writes an `embedded.json` marker (with `entered_via`) so a restart
re-embeds if Hydrogen is still alive.
- Hydrogen injects a relay-back server entry tagged `added_by:"hd-spawn"`
(see the read-only rule below).
- Sign-in is shared either way: one Adom sign-in serves both apps through a
shared keychain item, so an embedded Bridge is signed in whenever Hydrogen
is (and vice versa).
Entry paths: `POST /embedded/enter` on the direct API, or the `--embedded`
launch flag. Hydrogen polls `GET http://127.0.0.1:47200/health` after
spawning.
## The lifecycle contract (AD_LIFECYCLE.md, on one page)
1. **Bridge stays up.** It is the recovery channel for the machine: if
Bridge exited whenever Hydrogen exited, one bad Hydrogen exit would leave
the box unreachable to any remote agent (no log reads, no relaunch, no
rollback).
2. **The ONLY thing that exits Bridge is an explicit shutdown**:
`desktop_shutdown` / `POST /command {"command":"shutdown"}`, or the user
quitting Bridge themselves.
3. **"Embedded" is a display state, not a lifecycle.** Hydrogen gone means
Bridge drops the costume and reverts to a normal standalone menu-bar
agent.
What happens when Hydrogen goes away:
| How Hydrogen goes away | What Bridge does |
|---|---|
| Graceful: Hydrogen sends `POST /embedded/release` before stopping | Revert to standalone (menu-bar item back, relay preserved), regardless of how Bridge entered embedded. |
| Ungraceful: Hydrogen's GUI crashes or is killed, no release sent | The `embedded_monitor` (10s probes, 3-miss debounce, ~30s) notices and reverts to standalone. Same end state. |
| A kill takes Hydrogen's whole process tree | Bridge dies WITH Hydrogen because it is Hydrogen's direct child process. This is the OS killing a child, NOT a lifecycle rule firing. Check for the tree-kill before blaming any Bridge logic. |
Reverting (`revert_to_standalone`) shows the menu-bar item again and keeps
the WS connection and the hd-spawn server entry; the supervisor reconnects
with backoff, so a remote caller sees `status=reconnecting`, never a
vanished row (Hydrogen's relay can outlive Hydrogen's GUI).
**Exit-with-Hydrogen is opt-in, flag-only (v1.9.165).** The only way to make
Bridge disposable is the spawner passing `--exit-when-hd-exits` on the
command line. Hydrogen does not pass it, so in practice Bridge always
survives. There is no config key (the old `config.exit_when_hd_exits` is
inert) and no Settings checkbox. The one flag-gated exit lives in
`embedded_monitor::transition_to_standalone`.
Forensics: both transitions write to `startup.log` (`EXIT:` / `REVERT:`
lines). No `crash.log` plus a `startup.log` that runs to `===` means it was
not a panic; an absent process with no crash.log is the signature of an
external (tree) kill.
## Embedded permissions: Hydrogen owns the prompt
Hydrogen sends `auto_approve` in `POST /embedded/enter`; it is authoritative:
- **absent**: Bridge keeps standalone behavior (older Hydrogen; the feature
is inert until Hydrogen opts in).
- **`false`** (the new-contract default): Bridge does NOT auto-approve and
shows no UI of its own. For each gated command from a remote relay caller,
Bridge FORWARDS the request to Hydrogen's `POST /permission/request`
(control port discovered fresh from Hydrogen's ports.json each call,
blocking up to 180s) and applies Hydrogen's decision: `allow_once`,
`allow_1h`, `allow_24h`, `bypass_while_running`, or `deny` (refused with
`errorCode:denied_by_user`; do not auto-retry). `auto_approve:false`
overrides any standing grant (the `--shell-approval` flag, Settings
checkbox, or timer) for relay callers, so the forward always triggers; a
forwarded allow_1h/24h/bypass sets a dedicated grant window that is
honored without re-forwarding.
- **`true`**: auto-approve (the user explicitly opted in); behaves like a
standing grant.
Fail-safe: if Hydrogen is unreachable, Bridge falls back to its OWN approval
dialog (the window summons itself from the menu bar; never hard-deny, never
silent-approve). Coverage runs through one chokepoint
(`consult_embedded_gate`): `shell_execute`, `run_script`, and the Tier-2
class (`launch_process`, `wiki_exec`, `write_file`, `delete_file`,
`process_kill`). Gated verbs accept a `reason` arg that is shown verbatim
(attributed as "The AI says:") on Hydrogen's modal and Bridge's dialog. Only
remote relay callers are gated; local callers (CLI, Hydrogen, bridges) are
trusted by transport.
**Discoverability:** `status.permissions.shell.embeddedPolicy` reports
`"standalone"` | `"forward-to-hd"` | `"auto-approve"`. If a gated call seems
to hang while embedded, a human is likely looking at Hydrogen's prompt; poll
rather than re-issue.
## Inspecting state: hd_status's three orthogonal signals
Hydrogen is killed and respawned many times a day (upgrades, restarts), often
onto a different control port. `hd_status` therefore never collapses its view
into one boolean; it reports THREE independent signals:
- `processPresent`: a process-list check (ps/pgrep on macOS), sampled with
retry to ride the ~1s kill-then-respawn gap.
- `controlApiReachable`: TCP to Hydrogen's control port, discovered fresh per
call (ports.json, never cached or hardcoded).
- `embedded`: Bridge's own state (its `AppState` / embedded marker).
`running = processPresent || controlApiReachable`. A mismatch between the
three is normal mid-churn; do not treat a single snapshot as Hydrogen being
dead (Bridge itself requires 3 consecutive misses over ~30s before leaving
embedded mode).
## The hd_* verb family
Bridge exposes `hd_*` verbs for driving and observing Hydrogen itself
(`hd_status`, `hd_stop` / `hd_restart`, `hd_send_files`). Architectural rule:
Bridge owns host/process facts (process present, install path); anything
about Hydrogen's OWN runtime state is fetched from Hydrogen's control API
endpoints (`GET /health`, `GET /buildinfo`, `GET /workspace/health`) and
relayed verbatim. The Hydrogen BUILD verbs (`hd_ship`, `hd_build_*`) are part
of the Windows dev pipeline and return a clean "not supported on macOS yet"
error on this fork.
Updates while embedded follow the same split: Bridge checks, downloads,
verifies and STAGES its own update (`GET /update/status` shows
`update_ready:true` when staged); Hydrogen owns the apply timing and triggers
`POST /update/apply`. Embedded Bridge never auto-applies.
## The Hydrogen-managed relay entry is READ-ONLY
The server entry Hydrogen injects at spawn is tagged `added_by == "hd-spawn"`
and is ephemeral (stripped on config persist, re-added each Hydrogen launch).
While embedded it cannot be disconnected, edited, or removed; the backend
refuses (`is_hd_managed` guard plus `HD_MANAGED_REFUSAL`) on
`disconnect_server`, `remove_server`, `update_server`, the upsert branch of
`add_server`, and the relay `server_add` / `server_remove` handlers, because
any of those would break the embedded session. The UI shows it as a "Managed
by HD" row with a locked note instead of editable fields. Do not try to work
around the refusal; the entry is torn down automatically when Bridge reverts
to standalone.