Use case
Several coding agents run on one Mac, each building and testing its own macOS app (each with its own bundle id) through the native backend (AGENT_DEVICE_MACOS_APP_BACKEND=native, ADR 0031). Each agent has its own daemon (separate AGENT_DEVICE_STATE_DIR). Today only one of them can run: every macOS session takes the single device claim local:apple:macos:host-macos-local, so the others get DEVICE_IN_USE even though a native app session only touches its own app through accessibility.
Related: #3254 (no activation, no real pointer), #3229 and #3236 (the macos-app lease, which takes no claim), #3213 (native backend follow-ups). I found nothing open for local concurrent macOS sessions.
Proposed behavior
Native-backend sessions on the app surface whose bundle id is known before launch claim <host key>:app:<bundleId> instead of the host key:
- Two sessions on different bundle ids coexist; two on the same bundle id conflict (
DEVICE_IN_USE).
- A whole-Mac claim (XCTest backend,
desktop/menubar/frontmost-app surfaces, deep-link-only opens) and any foreign per-app claim exclude each other. Lock order is host key, then app key.
- The per-app record is a schema-v2 claim with an optional
app: { bundleId }, so sweep, device release --stale, takeover and idle expiry reuse the existing paths.
- Native
open uses open -g, so opening an app no longer activates it.
screenshot takes a short host-wide capture lock (see below).
macos-app leases are unchanged.
Evidence (macOS 27.0, native backend, two fixture AppKit apps, two daemons)
- Both sessions ran open, snapshot, click, fill, type, screenshot concurrently. The frontmost app was unchanged after every step, and both apps logged
active=false at launch.
- Per-app claim files were created for each bundle id. A third session on the same app, and a whole-Mac
desktop session, both got DEVICE_IN_USE (DEVICE_CLAIM_LIVE_OWNER).
- Without per-app claims, the second daemon is refused with
DEVICE_IN_USE.
- Two concurrent ScreenCaptureKit captures failed (
screenshot failed) or hung to the 30 s helper timeout in four of four attempts; a single capture takes about 0.5 s. Hence the capture lock. Other actions run in parallel.
Open questions for maintainers
- Is per-app scope acceptable at the claim layer, or should it live elsewhere?
- Mixed versions on one claims dir: an older daemon reads a per-app record as inconsistent and does not scan per-app files when taking the host key, so versions do not exclude each other (same boundary as ADR 0030).
devices shows claimedBy only for the host key; device status lists per-app claims.
I have a branch with the implementation and ADR 0034 and will open it as a draft PR.
Use case
Several coding agents run on one Mac, each building and testing its own macOS app (each with its own bundle id) through the native backend (
AGENT_DEVICE_MACOS_APP_BACKEND=native, ADR 0031). Each agent has its own daemon (separateAGENT_DEVICE_STATE_DIR). Today only one of them can run: every macOS session takes the single device claimlocal:apple:macos:host-macos-local, so the others getDEVICE_IN_USEeven though a native app session only touches its own app through accessibility.Related: #3254 (no activation, no real pointer), #3229 and #3236 (the
macos-applease, which takes no claim), #3213 (native backend follow-ups). I found nothing open for local concurrent macOS sessions.Proposed behavior
Native-backend sessions on the
appsurface whose bundle id is known before launch claim<host key>:app:<bundleId>instead of the host key:DEVICE_IN_USE).desktop/menubar/frontmost-appsurfaces, deep-link-only opens) and any foreign per-app claim exclude each other. Lock order is host key, then app key.app: { bundleId }, so sweep,device release --stale, takeover and idle expiry reuse the existing paths.openusesopen -g, so opening an app no longer activates it.screenshottakes a short host-wide capture lock (see below).macos-appleases are unchanged.Evidence (macOS 27.0, native backend, two fixture AppKit apps, two daemons)
active=falseat launch.desktopsession, both gotDEVICE_IN_USE(DEVICE_CLAIM_LIVE_OWNER).DEVICE_IN_USE.screenshot failed) or hung to the 30 s helper timeout in four of four attempts; a single capture takes about 0.5 s. Hence the capture lock. Other actions run in parallel.Open questions for maintainers
devicesshowsclaimedByonly for the host key;device statuslists per-app claims.I have a branch with the implementation and ADR 0034 and will open it as a draft PR.