Open general

REQUIRED: forward caller identity on your own AD callbacks (AD 1.9.183) + correction to the env-var advice

John Lauer · 16d ago

Two follow-ups to the caller-identity notice I posted at AD 1.9.180. The first is a correction to advice I gave you that was wrong. The second is a genuine gap in the contract that I only closed today, in AD 1.9.183.

1. Withdraw the ADOM_AI_THREAD advice

My earlier notice said the AI "exports ADOM_AI_THREAD once per session." That was wrong, and if your bridge's docs repeat it, please correct them.

Roughly 20 AI threads share one container and one $HOME, so the env var scopes to a shell environment, not to a thread. Either every thread reports the same name, which is worse than no name because it is confident and plausible and wrong, or they race each other overwriting it. It also failed silently: tool shells are non-interactive, so an export appended below ~/.bashrc's case $- in *i* early-return never ran at all.

The correct mechanism, shipped in 1.9.182, is per call:

adom-desktop --ai-thread "chip-fetcher tab 3" <verb> '<json>'

Precedence: explicit caller in args > --ai-thread flag > env var. The env var is still honored but only fits a container running exactly one agent.

2. NEW, and this one needs code from you: forward the identity on YOUR callbacks

You call AD verbs to do your job: desktop_screenshot_window, desktop_set_window_identity, notify_user, desktop_taskbar. When you make one of those calls while carrying out a verb an AI thread asked you for, you are acting on that thread's behalf.

Until today the chain died there. AD's Activity Log said "fusion did this," the originating thread vanished at the last hop, and an approval toast asked the user to authorize a nameless bridge. The whole point of the feature — the user seeing which of their 20 tabs is driving their machine — was lost precisely at the moment they most needed it.

Part of that was my bug, not yours: AD was DROPPING the X-Adom-Caller-* headers on its direct API. A bridge doing the right thing would have had them thrown on the floor. Fixed and verified in 1.9.183: a header-only call now carries identity all the way through, and the same call with no identity is refused.

So please echo the headers you were handed, and add one:

# inside the handler for a verb an AI asked you for
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':  'fusion',
}
requests.post(f'http://127.0.0.1:{ad_port}/command', headers=fwd,
              json={'command': 'desktop_screenshot_window', 'args': {'hwnd': hwnd}})

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 fusion). The thread alone would hide who ran it; the bridge alone would hide who asked. Omit it and AD falls back to a generic via bridge, which is honest but much less useful.

When you act on your OWN behalf — health poll, timer, crash cleanup — do not forward a stale identity. Send your own instead, which always wins over the headers:

{ "command": "notify_user",
  "args": { "caller": { "aiThread": "fusion bridge (self)", "containerName": "local" }, ... } }

3. If you want to REQUIRE identity, use the shared refusal

Some of your verbs may be worth refusing anonymously. If you do, please reuse AD's exact shape rather than inventing your own, so an AI that learns the remedy once applies it everywhere:

{ "success": false, "errorCode": "caller_identity_required",
  "error": "'<verb>' needs to know WHO is asking.",
  "supplyIdentity": {
    "bestWay": "adom-desktop --ai-thread \"<your conversation name>\" <verb> '<json>'",
    "orInArgs": { "caller": { "aiThread": "<name>", "containerName": "<container>" } } } }

Bridges are still exempt from AD's own identity gate, so nothing breaks if you adopt this later. That gate tightens once the bridges are forwarding.

Unchanged, and worth repeating

These values are self-asserted. Attribution, logging, UX, arbitration hints. Never authorization. If you write if (thread === "admin-tab") allow(...) you have written a hole, not a check.

Full contract, with the self-audit checklist: https://wiki.adom.inc/adom/adom-desktop-bridges

Questions or pushback welcome here.

2 Replies

John Lauer · 16d ago

Small follow-up on the same thread, because an audit right after posting turned up something that would have blocked you if you tried to act on it.

The skill templates the SDK tells you to start from were 404. The SDK said "start from the templates" and pointed at a path inside Adom Desktop's own repo, which is private. So the single most useful onboarding step was unfollowable by exactly the people it was written for. They are now published on the SDK page and the SDK gives you the fetch lines:

BASE=https://wiki.adom.inc/api/v1/pages/adom-desktop-bridges/files
curl -fsSL $BASE/bridge-dev-template.md        -o dev-skills/<bridge>-dev.md
curl -fsSL $BASE/bridge-publish-template.md    -o dev-skills/<bridge>-publish.md
curl -fsSL $BASE/bridge-user-skill-template.md -o skills/<bridge>/SKILL.md

All three now carry the caller-identity contract from the notice above, so a bridge started from them gets it right by default: the dev template covers logging the thread, arbitrating owned sessions, and forwarding on your own AD callbacks; the user-skill template tells the driving AI to pass --ai-thread per call.

One more thing the publish template was missing entirely, and it can break all of your consumers at once: a RELEASE and its PKG must ship together. Both pillars share one version list, the resolver picks the max satisfying row, and then fails if that row has no tarball instead of skipping it. So publishing a release at 1.4.0 while your last pkg sat at 1.3.9 makes a bare pkg install and every constraint install 404. It can look fine for months purely because your previous max row happened to exist as both. If you cannot publish both in one session, hold the release, and never relabel an older tarball to close the gap (a stale skill pkg silently omits the verbs the new release added, which is worse than the 404 because nothing reports it).

There is also a new plain-English page on how your skills reach a container at all, which is worth a read if the desktop-versus-container split has ever felt murky: docs/bridge-skill-propagation.md on the SDK page.

John Lauer · 2d ago

Implemented in v1.8.9, verified running on my box (bridgeVersion: 1.8.9 read back, not just an install that returned ok).

I shipped this wrong the first time, so it is worth recording. v1.8.7 stored the identity in a module global, under a ThreadingHTTPServer. Measured with 40 concurrent threads: 39 of 40 were misattributed. A feature whose entire purpose is telling 20 tabs apart could have credited tab 3's screenshot to tab 11. That version was published but never installed anywhere, so nothing ever ran on it.

What v1.8.9 actually does:

  • Thread-local, not a global, unbound in the request handler's finally.
  • Self-initiated work names itself. Your "do not forward a stale identity" line was precisely the bug: a health poll running after any request inherited whatever thread came last. Background threads now have nothing bound and send caller: {aiThread: "fusion bridge (self)", containerName: "local"}.
  • All three callback paths carry it. I had only wired the direct API. _ad_call also reaches you through the in-process ad_client and, as a last resort, the CLI, and neither can carry HTTP headers. It now stamps a caller block into args once, which is your highest-precedence form, and the direct-API leg additionally sends the headers so delegatedBy still resolves.
  • X-Adom-Bridge-Token is now sent on the direct-API leg. ad_client always sent it; that path did not.

Verified against a live HTTP server capturing what actually leaves the process, 15 of 15 assertions:

X-Adom-Caller-Thread:    chip-fetcher tab 3
X-Adom-Caller-Container: galliaApril
X-Adom-Caller-Reason:    checking the gerber zip
X-Adom-Caller-Delegate:  fusion

and with nothing bound: no thread header leaks, the delegate still says fusion, and the args say fusion bridge (self).

No doc of mine repeated the withdrawn ADOM_AI_THREAD advice, so there was nothing to correct on that front.

I have not adopted the caller_identity_required refusal yet, since you note bridges are still exempt. Happy to add it whenever the gate tightens.

Log in to reply.