Files
callstack__agent-device/docs/agents/device-verification.md
Michał Pierzchała 34e8cbb7a2 docs+ux: make device ownership discoverable end to end (#2165)
* docs+ux: make device ownership discoverable end to end

Complete the #1320 agent experience so 'busy? -> inspect -> choose or
release' is discoverable from every surface an agent actually reads:

- devices now projects the blocking claim owner per row (claimedBy with
  session and workspace, observe-policy projection; provably dead owners are
  excluded because the next open replaces them automatically), so an agent
  told a device is busy can pick a free one from the same listing.
- help debugging gains a 'Device busy and ownership' section separating the
  two DEVICE_IN_USE flavors and their exact recoveries.
- AGENTS.md documents both flavors; docs/agents/device-verification.md
  retires the last ps/kill recovery guidance in favor of device status,
  daemon stop --state-dir, and device release --stale (Stage 5 of #1320).
- ADR-0010 no longer calls DEVICE_IN_USE 'the only retriable code' without
  naming the claim path's non-retriable override.
- The rendered cross-worktree claim error gains a help-conformance quiz case
  binding (sample-output-device-claim-inspects-owner).
- README points at device status / device release --stale.

Part of #1320.

* fix: key ownership projection by canonical device identity end to end

Review findings on #2165:

- blockingClaimOwnersByDevice keyed claims and inventory rows by bare
  device.id, so a live Android claim could project claimedBy onto an
  unrelated same-id Apple/Harmony/Vega row, with scan order picking the
  displayed owner. Both sides now use the canonical local device key
  (claim.deviceKey against canonicalLocalDeviceKey of the row's claim
  identity). The cross-family same-id regression was observed red against
  the bare-id keying.
- The projection is now asserted across every hop the PR promises: client
  normalization preserves well-formed claimedBy and drops malformed ones,
  and the devices CLI formatter carries it through JSON data and renders
  the text line (MCP shares the same serialization).
2026-08-31 14:32:29 +02:00

3.2 KiB

Manual Device Verification

Read this before running agent-device by hand against a simulator, emulator, or physical device.

Before the run: defeat staleness

Dev-loop staleness has three layers, and each produces a convincing false negative.

  • After changing runtime code reached through bin/agent-device.mjs or the daemon: pnpm build, then pnpm clean:daemon — the daemon does not self-reload.
  • Before any Android verification from source: pnpm build, pnpm build:android, pnpm clean:daemon. build:android refreshes and verifies both bundled Android helper artifacts for the current package version.
  • shutdown deliberately HANDS OFF a healthy simulator runner. The adopted runner keeps serving the old Swift binary until you kill its process or the source fingerprint changes, so "my change did nothing" measured against an adopted runner is a classic false negative. If Swift runner code changed, run pnpm build:xcuitest.

Prove the path under test was actually active

  • Android: capture snapshot -i --json and require androidSnapshot.backend to be android-helper with helperVersion equal to package.json's version. A stock UIAutomator fallback is not valid verification unless the fallback itself is the behavior under test.
  • For repo-owned Agent Device Tester work, examples/test-app/README.md is the source of truth for simulator, physical-device, Metro/dev-client, and app-surface steps. An already-installed com.callstack.agentdevicelab is not sufficient — the README's Metro/dev-build and snapshot -i checks must prove the expected app surface is running.
  • For Android RN/Expo/dev-client apps on any local Metro port, adb reverse tcp:<port> tcp:<port> is harmless and should be run before opening the app or URL.

Session hygiene

Every manually opened session is a resource that must be closed — including exploratory sessions and failed verification attempts.

  • Every agent-device open needs a matching close with the same --session, --platform, --udid, and --state-dir before the agent finishes.
  • Use a purpose-specific session name for experiments, and an isolated --state-dir under /private/tmp when you need cleanup isolation beyond the current worktree's default daemon.
  • Track opened sessions in working notes; close each one before the final response.
  • If close is blocked or ownership looks stuck, inspect it with agent-device device status --stale (daemonless), stop the owning daemon with agent-device daemon stop --state-dir <dir> (add --clean to remove retained runners), and release provably dead owners with agent-device device release --stale. Do not hunt PIDs with ps/kill.
  • If cleanup cannot be completed, report the remaining session name, state dir, and the device status --stale output as a blocker.

Sandboxed environments

Start the daemon outside the sandbox with escalation. The daemon binds localhost, and sandboxed runs fail before any product code executes, with listen EPERM: operation not permitted 127.0.0.1 or repeated Failed to start daemon/metadata cleanup messages. Those are not agent-device regressions — rerun with escalation. Unit tests, typecheck, lint, and build can stay sandboxed unless they need devices or listener access.