app
DEPRECATED: Bridge SDK (moved into Adom Bridge)
Public Made by Adomby adom
Bridge SDK guide: bridge.json schema, kind:python/node/exe, hello-python + hello-rust reference templates, packaging + lifecycle commands.
← Commit history
Caller identity: bridges MUST forward the headers on their own AD callbacks (1.9.183); withdraw the env-var advice
3 files changed
+210−15
README.md+70−5@@ -191,19 +191,81 @@ The recommended posture: `notify_user` derives its toast attribution line from the caller automatically, so a toast your bridge sends **on an agent's behalf already names the thread** — you do not have to build that string. Pass an explicit `attribution` string only when you want to override it. +### ⚠ REQUIRED — when YOU call an AD verb, forward the identity++This is the half most bridges will miss, and it is the half that makes the whole feature work or fail.++Your bridge calls AD verbs to do its job: `desktop_screenshot_window`, `desktop_set_window_identity`, `notify_user`, `desktop_taskbar`. When you make that call **while carrying out a verb an AI thread asked you for, you are acting on that thread's behalf** — so echo the identity you were handed straight back onto your own request. Otherwise the chain dies at your doorstep: the user's Activity Log says *"pup did this"*, the originating thread vanishes, and the approval toast asks them to authorize a nameless bridge.++**Echo all four headers, verbatim:**++```javascript+// node — inside the handler for a verb an AI asked you for+const fwd = {+ 'X-Adom-Caller-Thread': req.headers['x-adom-caller-thread'] || '',+ 'X-Adom-Caller-Container': req.headers['x-adom-caller-container'] || '',+ 'X-Adom-Caller-Reason': req.headers['x-adom-caller-reason'] || '',+ 'X-Adom-Caller-Delegate': 'pup', // ← YOUR bridge name+};+await fetch(`http://127.0.0.1:${adPort}/command`, {+ method: 'POST',+ headers: { 'Content-Type': 'application/json', ...fwd },+ body: JSON.stringify({ command: 'desktop_screenshot_window', args: { hwnd } }),+});+```++```python+fwd = {+ 'X-Adom-Caller-Thread': self.headers.get('X-Adom-Caller-Thread', ''),+ 'X-Adom-Caller-Container': self.headers.get('X-Adom-Caller-Container', ''),+ 'X-Adom-Caller-Reason': self.headers.get('X-Adom-Caller-Reason', ''),+ 'X-Adom-Caller-Delegate': 'kicad', # YOUR bridge name+}+```++`X-Adom-Caller-Delegate` is the one you **add** rather than forward. It names *you*, and it is what lets AD show the user both facts at once: **`chip-fetcher tab 3 (via pup)`**. Attributing the call to the thread alone would hide who ran it; attributing it to the bridge alone would hide who asked. Omit the header and AD falls back to the generic `via bridge`, which is honest but much less useful.++**When you are acting on your OWN behalf — a health poll, a timer, a crash cleanup — do NOT forward a stale identity.** Send your own instead:++```jsonc+{ "command": "notify_user",+ "args": { "caller": { "aiThread": "pup bridge (self)", "containerName": "local" }, … } }+```++An explicit `caller` block in the args always beats the headers, so this is unambiguous.++**Two things AD does for you here:** explicit args win over forwarded headers (so the override above just works), and AD carries the delegate forward onto any onward hop, so a chain two bridges deep still shows the original delegate.+ ### Where it comes from (the caller side) Useful to understand, though none of it is your responsibility: ```bash-# The AI exports this ONCE per session, in its container:-export ADOM_AI_THREAD="chip-fetcher tab 3"-export ADOM_CONTAINER_NAME="galliaApril" # optional+# Per call — the CORRECT mechanism:+adom-desktop --ai-thread "chip-fetcher tab 3" <verb> '<json>' ``` -The `adom-desktop` CLI then attaches a `caller` block to **every** verb automatically. A relay call with **no `aiThread` is REFUSED by AD** with `errorCode: caller_identity_required` before it ever reaches a bridge — the refusal names exactly what is missing and how to set it permanently, so callers fix it once and move on.+The `adom-desktop` CLI attaches a `caller` block to **every** verb automatically. A relay call with **no `aiThread` is REFUSED by AD** with `errorCode: caller_identity_required` before it ever reaches a bridge.++> **Correction, 2026-07-25 (AD 1.9.182):** an earlier version of this page told callers to `export ADOM_AI_THREAD` once per session. **That advice was wrong and has been withdrawn.** Roughly 20 AI threads share one container and one `$HOME`, so the env var scopes to the shell environment, not to the thread — every thread then reports the *same* name, which is worse than no name at all because it is confident and plausible. It also failed silently: tool shells are non-interactive, so an export appended below `~/.bashrc`'s `case $- in *i*` early-return never ran. The env var is still honored, but only fits a container running exactly one agent (CI, a single-purpose job). **If your bridge's own docs repeat the export advice, please correct them.**++**So by the time a request reaches you, identity is normally present.** The exception is **loopback callers** — HD, another bridge, or the local CLI — which are trusted by transport and not yet *required* to identify (that gate tightens once bridges adopt the forwarding contract above). Code for the absent case (`|| 'unidentified'`), never assume the header exists.++### If your bridge wants to REQUIRE identity — use the shared contract++Some verbs are worth refusing anonymously. If you do, **use AD's exact refusal shape** so the AI sees one consistent remedy across the whole ecosystem instead of a different fix per bridge:++```jsonc+{ "success": false, "errorCode": "caller_identity_required",+ "error": "'<verb>' needs to know WHO is asking — the user runs many AI threads and needs to see which one drove this.",+ "supplyIdentity": {+ "bestWay": "adom-desktop --ai-thread \"<your conversation name>\" <verb> '<json>'",+ "orInArgs": { "caller": { "aiThread": "<name>", "containerName": "<container>" } }+ },+ "_hint": "This is attribution, not a permission check — the name is self-reported and nothing is gated on it." }+``` -**So by the time a request reaches you, identity is normally present.** The exception is **loopback callers** — HD, another bridge, or the local CLI — which are trusted by transport and are not required to identify. Code for the absent case (`|| 'unidentified'`), never assume the header exists.+Reusing `caller_identity_required` verbatim is the point: an AI that learned the remedy once applies it everywhere. ### Self-audit @@ -212,6 +274,9 @@ The `adom-desktop` CLI then attaches a `caller` block to **every** verb automati - [ ] Do your **session-using** verbs warn (or, if destructive, refuse) when a different thread arrives — **naming the current owner**? - [ ] Does any **window you drive** show the owning thread to the user (title / tooltip / jump-list)? - [ ] Do your **status / list verbs** report `ownerThread` so an agent can discover it is not alone?+- [ ] **When you call an AD verb on a thread's behalf, do you FORWARD all three caller headers plus your own `X-Adom-Caller-Delegate`?**+- [ ] When you call an AD verb on your **own** behalf (poll, timer, cleanup), do you send your own `caller` block rather than a stale forwarded one?+- [ ] If your docs told callers to `export ADOM_AI_THREAD`, have you corrected them to the per-call `--ai-thread` flag? - [ ] Have you confirmed you use these headers **nowhere** as an authorization check? ---
SKILL.md+70−5@@ -191,19 +191,81 @@ The recommended posture: `notify_user` derives its toast attribution line from the caller automatically, so a toast your bridge sends **on an agent's behalf already names the thread** — you do not have to build that string. Pass an explicit `attribution` string only when you want to override it. +### ⚠ REQUIRED — when YOU call an AD verb, forward the identity++This is the half most bridges will miss, and it is the half that makes the whole feature work or fail.++Your bridge calls AD verbs to do its job: `desktop_screenshot_window`, `desktop_set_window_identity`, `notify_user`, `desktop_taskbar`. When you make that call **while carrying out a verb an AI thread asked you for, you are acting on that thread's behalf** — so echo the identity you were handed straight back onto your own request. Otherwise the chain dies at your doorstep: the user's Activity Log says *"pup did this"*, the originating thread vanishes, and the approval toast asks them to authorize a nameless bridge.++**Echo all four headers, verbatim:**++```javascript+// node — inside the handler for a verb an AI asked you for+const fwd = {+ 'X-Adom-Caller-Thread': req.headers['x-adom-caller-thread'] || '',+ 'X-Adom-Caller-Container': req.headers['x-adom-caller-container'] || '',+ 'X-Adom-Caller-Reason': req.headers['x-adom-caller-reason'] || '',+ 'X-Adom-Caller-Delegate': 'pup', // ← YOUR bridge name+};+await fetch(`http://127.0.0.1:${adPort}/command`, {+ method: 'POST',+ headers: { 'Content-Type': 'application/json', ...fwd },+ body: JSON.stringify({ command: 'desktop_screenshot_window', args: { hwnd } }),+});+```++```python+fwd = {+ 'X-Adom-Caller-Thread': self.headers.get('X-Adom-Caller-Thread', ''),+ 'X-Adom-Caller-Container': self.headers.get('X-Adom-Caller-Container', ''),+ 'X-Adom-Caller-Reason': self.headers.get('X-Adom-Caller-Reason', ''),+ 'X-Adom-Caller-Delegate': 'kicad', # YOUR bridge name+}+```++`X-Adom-Caller-Delegate` is the one you **add** rather than forward. It names *you*, and it is what lets AD show the user both facts at once: **`chip-fetcher tab 3 (via pup)`**. Attributing the call to the thread alone would hide who ran it; attributing it to the bridge alone would hide who asked. Omit the header and AD falls back to the generic `via bridge`, which is honest but much less useful.++**When you are acting on your OWN behalf — a health poll, a timer, a crash cleanup — do NOT forward a stale identity.** Send your own instead:++```jsonc+{ "command": "notify_user",+ "args": { "caller": { "aiThread": "pup bridge (self)", "containerName": "local" }, … } }+```++An explicit `caller` block in the args always beats the headers, so this is unambiguous.++**Two things AD does for you here:** explicit args win over forwarded headers (so the override above just works), and AD carries the delegate forward onto any onward hop, so a chain two bridges deep still shows the original delegate.+ ### Where it comes from (the caller side) Useful to understand, though none of it is your responsibility: ```bash-# The AI exports this ONCE per session, in its container:-export ADOM_AI_THREAD="chip-fetcher tab 3"-export ADOM_CONTAINER_NAME="galliaApril" # optional+# Per call — the CORRECT mechanism:+adom-desktop --ai-thread "chip-fetcher tab 3" <verb> '<json>' ``` -The `adom-desktop` CLI then attaches a `caller` block to **every** verb automatically. A relay call with **no `aiThread` is REFUSED by AD** with `errorCode: caller_identity_required` before it ever reaches a bridge — the refusal names exactly what is missing and how to set it permanently, so callers fix it once and move on.+The `adom-desktop` CLI attaches a `caller` block to **every** verb automatically. A relay call with **no `aiThread` is REFUSED by AD** with `errorCode: caller_identity_required` before it ever reaches a bridge.++> **Correction, 2026-07-25 (AD 1.9.182):** an earlier version of this page told callers to `export ADOM_AI_THREAD` once per session. **That advice was wrong and has been withdrawn.** Roughly 20 AI threads share one container and one `$HOME`, so the env var scopes to the shell environment, not to the thread — every thread then reports the *same* name, which is worse than no name at all because it is confident and plausible. It also failed silently: tool shells are non-interactive, so an export appended below `~/.bashrc`'s `case $- in *i*` early-return never ran. The env var is still honored, but only fits a container running exactly one agent (CI, a single-purpose job). **If your bridge's own docs repeat the export advice, please correct them.**++**So by the time a request reaches you, identity is normally present.** The exception is **loopback callers** — HD, another bridge, or the local CLI — which are trusted by transport and not yet *required* to identify (that gate tightens once bridges adopt the forwarding contract above). Code for the absent case (`|| 'unidentified'`), never assume the header exists.++### If your bridge wants to REQUIRE identity — use the shared contract++Some verbs are worth refusing anonymously. If you do, **use AD's exact refusal shape** so the AI sees one consistent remedy across the whole ecosystem instead of a different fix per bridge:++```jsonc+{ "success": false, "errorCode": "caller_identity_required",+ "error": "'<verb>' needs to know WHO is asking — the user runs many AI threads and needs to see which one drove this.",+ "supplyIdentity": {+ "bestWay": "adom-desktop --ai-thread \"<your conversation name>\" <verb> '<json>'",+ "orInArgs": { "caller": { "aiThread": "<name>", "containerName": "<container>" } }+ },+ "_hint": "This is attribution, not a permission check — the name is self-reported and nothing is gated on it." }+``` -**So by the time a request reaches you, identity is normally present.** The exception is **loopback callers** — HD, another bridge, or the local CLI — which are trusted by transport and are not required to identify. Code for the absent case (`|| 'unidentified'`), never assume the header exists.+Reusing `caller_identity_required` verbatim is the point: an AI that learned the remedy once applies it everywhere. ### Self-audit @@ -212,6 +274,9 @@ The `adom-desktop` CLI then attaches a `caller` block to **every** verb automati - [ ] Do your **session-using** verbs warn (or, if destructive, refuse) when a different thread arrives — **naming the current owner**? - [ ] Does any **window you drive** show the owning thread to the user (title / tooltip / jump-list)? - [ ] Do your **status / list verbs** report `ownerThread` so an agent can discover it is not alone?+- [ ] **When you call an AD verb on a thread's behalf, do you FORWARD all three caller headers plus your own `X-Adom-Caller-Delegate`?**+- [ ] When you call an AD verb on your **own** behalf (poll, timer, cleanup), do you send your own `caller` block rather than a stale forwarded one?+- [ ] If your docs told callers to `export ADOM_AI_THREAD`, have you corrected them to the per-call `--ai-thread` flag? - [ ] Have you confirmed you use these headers **nowhere** as an authorization check? ---
bridges-SDK.md+70−5@@ -191,19 +191,81 @@ The recommended posture: `notify_user` derives its toast attribution line from the caller automatically, so a toast your bridge sends **on an agent's behalf already names the thread** — you do not have to build that string. Pass an explicit `attribution` string only when you want to override it. +### ⚠ REQUIRED — when YOU call an AD verb, forward the identity++This is the half most bridges will miss, and it is the half that makes the whole feature work or fail.++Your bridge calls AD verbs to do its job: `desktop_screenshot_window`, `desktop_set_window_identity`, `notify_user`, `desktop_taskbar`. When you make that call **while carrying out a verb an AI thread asked you for, you are acting on that thread's behalf** — so echo the identity you were handed straight back onto your own request. Otherwise the chain dies at your doorstep: the user's Activity Log says *"pup did this"*, the originating thread vanishes, and the approval toast asks them to authorize a nameless bridge.++**Echo all four headers, verbatim:**++```javascript+// node — inside the handler for a verb an AI asked you for+const fwd = {+ 'X-Adom-Caller-Thread': req.headers['x-adom-caller-thread'] || '',+ 'X-Adom-Caller-Container': req.headers['x-adom-caller-container'] || '',+ 'X-Adom-Caller-Reason': req.headers['x-adom-caller-reason'] || '',+ 'X-Adom-Caller-Delegate': 'pup', // ← YOUR bridge name+};+await fetch(`http://127.0.0.1:${adPort}/command`, {+ method: 'POST',+ headers: { 'Content-Type': 'application/json', ...fwd },+ body: JSON.stringify({ command: 'desktop_screenshot_window', args: { hwnd } }),+});+```++```python+fwd = {+ 'X-Adom-Caller-Thread': self.headers.get('X-Adom-Caller-Thread', ''),+ 'X-Adom-Caller-Container': self.headers.get('X-Adom-Caller-Container', ''),+ 'X-Adom-Caller-Reason': self.headers.get('X-Adom-Caller-Reason', ''),+ 'X-Adom-Caller-Delegate': 'kicad', # YOUR bridge name+}+```++`X-Adom-Caller-Delegate` is the one you **add** rather than forward. It names *you*, and it is what lets AD show the user both facts at once: **`chip-fetcher tab 3 (via pup)`**. Attributing the call to the thread alone would hide who ran it; attributing it to the bridge alone would hide who asked. Omit the header and AD falls back to the generic `via bridge`, which is honest but much less useful.++**When you are acting on your OWN behalf — a health poll, a timer, a crash cleanup — do NOT forward a stale identity.** Send your own instead:++```jsonc+{ "command": "notify_user",+ "args": { "caller": { "aiThread": "pup bridge (self)", "containerName": "local" }, … } }+```++An explicit `caller` block in the args always beats the headers, so this is unambiguous.++**Two things AD does for you here:** explicit args win over forwarded headers (so the override above just works), and AD carries the delegate forward onto any onward hop, so a chain two bridges deep still shows the original delegate.+ ### Where it comes from (the caller side) Useful to understand, though none of it is your responsibility: ```bash-# The AI exports this ONCE per session, in its container:-export ADOM_AI_THREAD="chip-fetcher tab 3"-export ADOM_CONTAINER_NAME="galliaApril" # optional+# Per call — the CORRECT mechanism:+adom-desktop --ai-thread "chip-fetcher tab 3" <verb> '<json>' ``` -The `adom-desktop` CLI then attaches a `caller` block to **every** verb automatically. A relay call with **no `aiThread` is REFUSED by AD** with `errorCode: caller_identity_required` before it ever reaches a bridge — the refusal names exactly what is missing and how to set it permanently, so callers fix it once and move on.+The `adom-desktop` CLI attaches a `caller` block to **every** verb automatically. A relay call with **no `aiThread` is REFUSED by AD** with `errorCode: caller_identity_required` before it ever reaches a bridge.++> **Correction, 2026-07-25 (AD 1.9.182):** an earlier version of this page told callers to `export ADOM_AI_THREAD` once per session. **That advice was wrong and has been withdrawn.** Roughly 20 AI threads share one container and one `$HOME`, so the env var scopes to the shell environment, not to the thread — every thread then reports the *same* name, which is worse than no name at all because it is confident and plausible. It also failed silently: tool shells are non-interactive, so an export appended below `~/.bashrc`'s `case $- in *i*` early-return never ran. The env var is still honored, but only fits a container running exactly one agent (CI, a single-purpose job). **If your bridge's own docs repeat the export advice, please correct them.**++**So by the time a request reaches you, identity is normally present.** The exception is **loopback callers** — HD, another bridge, or the local CLI — which are trusted by transport and not yet *required* to identify (that gate tightens once bridges adopt the forwarding contract above). Code for the absent case (`|| 'unidentified'`), never assume the header exists.++### If your bridge wants to REQUIRE identity — use the shared contract++Some verbs are worth refusing anonymously. If you do, **use AD's exact refusal shape** so the AI sees one consistent remedy across the whole ecosystem instead of a different fix per bridge:++```jsonc+{ "success": false, "errorCode": "caller_identity_required",+ "error": "'<verb>' needs to know WHO is asking — the user runs many AI threads and needs to see which one drove this.",+ "supplyIdentity": {+ "bestWay": "adom-desktop --ai-thread \"<your conversation name>\" <verb> '<json>'",+ "orInArgs": { "caller": { "aiThread": "<name>", "containerName": "<container>" } }+ },+ "_hint": "This is attribution, not a permission check — the name is self-reported and nothing is gated on it." }+``` -**So by the time a request reaches you, identity is normally present.** The exception is **loopback callers** — HD, another bridge, or the local CLI — which are trusted by transport and are not required to identify. Code for the absent case (`|| 'unidentified'`), never assume the header exists.+Reusing `caller_identity_required` verbatim is the point: an AI that learned the remedy once applies it everywhere. ### Self-audit @@ -212,6 +274,9 @@ The `adom-desktop` CLI then attaches a `caller` block to **every** verb automati - [ ] Do your **session-using** verbs warn (or, if destructive, refuse) when a different thread arrives — **naming the current owner**? - [ ] Does any **window you drive** show the owning thread to the user (title / tooltip / jump-list)? - [ ] Do your **status / list verbs** report `ownerThread` so an agent can discover it is not alone?+- [ ] **When you call an AD verb on a thread's behalf, do you FORWARD all three caller headers plus your own `X-Adom-Caller-Delegate`?**+- [ ] When you call an AD verb on your **own** behalf (poll, timer, cleanup), do you send your own `caller` block rather than a stale forwarded one?+- [ ] If your docs told callers to `export ADOM_AI_THREAD`, have you corrected them to the per-call `--ai-thread` flag? - [ ] Have you confirmed you use these headers **nowhere** as an authorization check? ---