Repository navigation
Proxy lease by device spec: --os and --image-tag, honoured or refused by the backend #3864
Description
Purpose
open through connect proxy already allocates by spec: without --udid/--serial it sends platform, target, device, and the backend picks the device (#3487). Simlock, as the backend, can also pick by OS version and Android image tag. This adds those two keys to the proxy lease, and makes a backend that cannot honour them refuse instead of silently ignoring them.
Simlock side: callstackincubator/simlock#814.
Flow
sequenceDiagram
participant CLI as agent-device CLI
participant B as backend (simlock front)
participant D as agent-device daemon
CLI->>B: lease.allocate {leaseId, platform, target, device?, osVersion?, imageTag?}
alt backend consumes the spec
B->>B: pick device by spec (validate os grammar, class, tag)
B->>D: lease.allocate {leaseId, udid|serial, ttlMs} (spec keys stripped)
D-->>B: lease {deviceKey}
B-->>CLI: lease {deviceKey, device: {model, osVersion, imageTag?}}
else plain daemon receives a spec key
D-->>CLI: UNSUPPORTED_OPERATION reason device-spec-unsupported, keys: [...]
else backend older than this feature
B-->>CLI: lease {deviceKey} without device facts
CLI->>B: lease.release
CLI-->>CLI: fail: device-spec-unsupported
end
Wire
New keys on agent_device.lease.allocate, one exported list beside LEASE_ALLOCATE_PROVIDER_FLAG_KEYS in packages/contracts/src/lease-scope.ts, also on LeaseAllocateOptions:
| Flag | Key | Type | Validated by |
|---|---|---|---|
--os <version|range> |
osVersion |
string, opaque | backend (simlock: exact or range, Android = API level) |
--image-tag <tag> |
imageTag |
string | backend (Android only) |
Echo on the returned lease (data.lease.device), written by the backend that honoured the request:
| Field | Type | Meaning |
|---|---|---|
lease.device.model |
string | model granted |
lease.device.osVersion |
string | exact OS granted |
lease.device.imageTag |
string? | Android image tag granted |
deviceKey stays the identity. lease.device is the proof the spec was understood.
Rules
- Flags on
open,install,reinstall,uninstall;connect proxystores them as defaults likeplatformanddevice. --targetstays the class selector (mobile,tv,desktop); the backend maps it. No--device-class.- A daemon that receives either key on an allocate refuses:
UNSUPPORTED_OPERATION,details.reason: 'device-spec-unsupported',details.keys. Local daemon and plainagent-device proxyincluded. No partial honouring. - A lease returned without
lease.deviceafter a request that named a spec key: the CLI releases it and fails with the same reason. - A live lease serves a later
openonly when its spec matches the request's; otherwiseCONNECTION_DEVICE_CONFLICT. - Keys are agent-visible in MCP and the SDK schemas (not
operatorField). - No
--allow-download,--mode, lease-wait: downloads and mode are backend config. - Decision recorded as an amendment to ADR 0007, no new ADR.
Example
agent-device connect proxy --daemon-base-url http://worker:7700/v1/agent-device
agent-device open MyApp --platform ios --device "iPhone 17 Pro" --os ">=26"
agent-device open MyApp --platform android --os 34 --image-tag google_apis_playstore
agent-device open MyApp --platform ios --os ">=26" # local daemon
# Error: UNSUPPORTED_OPERATION device-spec-unsupported (keys: osVersion)open --json prints lease.device.
Completion conditions
-
open --os/--image-tagthrough a proxy sends the keys on allocate;devices.listis not called first. - A fake backend that echoes
lease.device→opensucceeds and reports the granted model and OS. - A fake backend that returns no
lease.device→ the lease is released,openfails withdevice-spec-unsupported. - Local daemon and
agent-device proxyrefuse the keys with the typed reason; the lease registry records nothing. - Second
openwith a different--oson a live lease →CONNECTION_DEVICE_CONFLICT. - MCP
opentool schema andLeaseAllocateOptionscarry the two keys;remote-proxy.mdandhelp remotedocument them. - ADR 0007 amended.
Postponed
--physical was in the first cut and is dropped (Decision comment below): neither phone reaches the daemon simlock runs today (iOS: the daemon-wide simulator set hides physical devices in agent-device's inventory; Android: phones sit on the default adb server, the daemon on simlock's). Simlock will spec a phone path on its side first.
Sequencing
- This issue (agent-device release).
- Simlock front maps the keys and echoes
lease.device(agent-device leases by spec: --os and --image-tag reach Simlock callstackincubator/simlock#814).
Activity
[-]Proxy lease by device spec: --os, --image-tag, --physical, honoured or refused by the backend[/-][+]Proxy lease by device spec: --os and --image-tag, honoured or refused by the backend[/+]V3RON commentedon Oct 21, 2026
Decision: scope:
--physicalis dropped from this issue. Neither phone reaches the daemon simlock supervises today (iOS: the daemon-wide simulator set hides physical devices in the Apple inventory; Android: phones live on the default adb server while the daemon is on simlock's). Simlock specs a phone path on its side first. The feature is--os→osVersionand--image-tag→imageTag; the echo islease.device: {model, osVersion, imageTag?}.V3RON commentedon Oct 21, 2026
Current state: PR #3865 (branch
feat/proxy-device-spec) implements the two-key scope (--os→osVersion,--image-tag→imageTag, echolease.device: {model, osVersion, imageTag?});--physicalis not in it.pnpm check:affected --runpassed at 4f94d93 (exactCommit). Three Opus review rounds done; open gaps are listed under Known gaps in the PR body. CI pending.feat(agent-device): --os and --image-tag reach lease.request (#840)