Agent Device v0.20 gives coding agents a more dependable path from exploring an app to running the resulting workflow again. The release redesigns gesture execution, expands Maestro YAML compatibility, and turns .ad recordings into reusable scripts that agents can diagnose and repair when an interface changes.
One gesture system across every surface
Gestures now pass through one normalization and planning model across the CLI, Node.js client, MCP, replay, Android, and Apple platforms.
Pan, fling, swipe, pinch, rotate, transform, and two-finger pan use explicit, viewport-aware pointer trajectories. Agent Device validates every planned point before injection, preserves authored timing, and reports an actionable error when the requested motion cannot fit inside the active app.
Android gestures now run through the persistent automation helper, which owns snapshot capture, viewport resolution, and touch injection. iOS simulator multitouch consumes the same canonical plans through native XCTest synthesis.
The result is consistent gesture intent across live commands, .ad replay, and Maestro flows.
Maestro compatibility through a typed engine
Maestro YAML now runs through a source-preserving typed interpreter built directly into Agent Device. Commands retain their source paths and line numbers while the engine handles hooks, includes, environment values, conditions, repeat and retry blocks, observations, and gestures.
v0.20 also expands the supported Maestro subset:
- Numeric options accept
${VAR}lookup interpolation. optionalcomposes withscrollUntilVisibleandextendedWaitUntil.- Visibility assertions accept
childOfancestor scoping. scrollUntilVisibletargets the correct scroll container.- Maestro swipe behavior follows the redesigned gesture system on Android and iOS.
Compatibility is checked against generated, version-pinned Maestro fixtures. Unsupported syntax still fails explicitly, so agents do not mistake partial execution for a passing flow.
Reusable and fixable .ad scripts
Agents can turn a live exploration into a deterministic .ad script, replay it later, and repair it when the app drifts.
A replay divergence now includes a bounded screen report, ranked selector suggestions, and a plan-bound resume point. The agent can inspect the current state, perform the correct action, continue from the safe step, and publish the repaired script atomically. Earlier successful steps do not need to run again.
v0.20 also adds active-session publication:
agent-device open com.example.app --save-script=checkout.ad
# Navigate through the app
agent-device wait 'role="heading" label="Checkout"'
agent-device session save-scriptsession save-script publishes the recording without closing the app. The resulting script can recreate the journey from a cold start, stop at a verified destination, and return an active session for the agent’s next task.
Recorded input values are written literally to .ad files. Use non-secret fixture credentials until parameterized recording is available.
Migration notes
v0.20 removes several deprecated gesture forms:
- Use
orientationfor device rotation. The oldrotatealias has been removed. - Use
gesture rotatefor a two-finger rotation. - Use
gesture panwhen authored duration matters. Timedswipeandflinginputs have been removed. - Capture a fresh snapshot or use selectors between ref-based mutations. Session ref frames now expire after mutation to prevent stale
@refactions.
Validation: The claims above are grounded in the v0.20 tag and accepted architecture records. I treated this as release-highlight copy rather than a complete changelog and kept the three requested themes central.
What's Changed
- refactor: remove redundant facade exports by @thymikee in #1204
- feat: parse and preserve .ad target-v1 evidence (ADR 0012 migration step 3) by @thymikee in #1196
- fix: honor session metro hints and expo dev-client bundle urls by @thymikee in #1199
- fix: scope Android settle diffs to app content and collapse IME chrome by @thymikee in #1200
- feat: add Android test IME helper for deterministic text entry (#1198) by @thymikee in #1201
- feat(replay): ADR 0012 migration step 2 — structured replay divergence report by @thymikee in #1197
- refactor: keep command and daemon-route owner-file claims tooling-only (#1178) by @devin-ai-integration[bot] in #1192
- feat: disclose selector resolution in interaction responses by @thymikee in #1193
- refactor: consolidate architecture ownership and client results by @devin-ai-integration[bot] in #1210
- Project navigation contracts and add a network digest by @devin-ai-integration[bot] in #1208
- fix(macos): allow XCTest teardown before runner kill by @lott-ai in #1206
- fix: unbreak main — repoint relocated request-progress/cancel imports by @thymikee in #1215
- feat(replay): ADR 0012 migration steps 5+6 — resume + --update retirement by @thymikee in #1211
- chore: close out architecture experiments by @devin-ai-integration[bot] in #1213
- fix: preserve Maestro coordinate swipes on Android by @thymikee in #1207
- feat(replay): ADR 0012 migration step 4 — target-binding verification enforcement by @thymikee in #1209
- fix(mcp): advertise public Apple platform leaves in boot/shutdown/prepare output schemas by @devin-ai-integration[bot] in #1224
- fix: wrap replay selector-miss as REPLAY_DIVERGENCE (repair-loop regression) by @thymikee in #1223
- docs: ADR-0012 Decision 6 (agent-supervised re-record repair) by @thymikee in #1226
- feat(replay): ADR 0012 decision 6 — agent-supervised re-record repair by @thymikee in #1228
- docs: remove ready-for-human guidance by @thymikee in #1237
- fix: align layering claims, remove app-log ineffective dynamic import, refresh architecture records (#1222) by @devin-ai-integration[bot] in #1227
- feat: unify gesture planning and multi-touch execution by @thymikee in #1212
- fix: write Android debug_http_host to the RN default-preferences file by @thymikee in #1242
- fix: support physical iOS close with Xcode 26 by @thymikee in #1236
- docs: ADR-0012 Decision 6 — repair-transaction lifecycle (R7 + commit semantics) by @thymikee in #1234
- fix(record): validate finalized iOS device MP4 before reporting record stop success by @mvanhorn in #1238
- fix(daemon): isolate disconnect cancellation and resource teardown by @devin-ai-integration[bot] in #1225
- fix: reject stale iOS refs after navigation by @thymikee in #1241
- docs: define session ref-frame lifetime by @thymikee in #1247
- fix: remove Android ADB swipe fallbacks by @thymikee in #1243
- fix: exclude keyboard/IME chrome from replay divergence screen.refs by @thymikee in #1233
- fix: bound the iOS system-modal snapshot probe to the capture deadline (#1244) by @thymikee in #1248
- fix(replay): repair-transaction lifecycle — keep-alive, no-partial-emit, close-as-lifecycle, atomic publish by @thymikee in #1235
- fix(replay): resume.from now agrees with repairHint's record-and-heal continuation by @thymikee in #1260
- fix: Android status/nav-bar systemui chrome leaks into non-raw captures (#1251) by @thymikee in #1256
- refactor: rename
rotatecommand toorientation(rotate kept as deprecated alias) by @thymikee in #1252 - fix(ios): support remote-hosted alerts on physical devices by @knchst in #1232
- fix(replay): extend resume.from record-and-heal shape to caution/manual by @thymikee in #1267
- feat(daemon): ADR 0014 session ref-frame lifetime — full implementation by @thymikee in #1257
- fix: unify divergence screen capture with snapshot's full-window scope by @thymikee in #1265
- feat: --force/--overwrite for --save-script + arm-time EEXIST preflight by @thymikee in #1266
- fix(apple): avoid blocked interactions on physical iOS by @billsbooth in #1263
- fix: refresh Android snapshots after in-place ComposeView content swaps (#1254) by @thymikee in #1259
- refactor(daemon): remove the superseded coarse snapshotRefsStale marker (ADR 0014 step 8) by @thymikee in #1268
- refactor: replace Maestro compat with typed direct engine by @thymikee in #1217
- refactor: colocate native platform sources under android/, apple/, linux/ by @thymikee in #1273
- fix: demote non-unique ids from writer identity/selector chain (#1269) by @thymikee in #1272
- chore: baseline-free production-exports cleanup (#1276) by @devin-ai-integration[bot] in #1282
- chore: remove deprecated rotate CLI command alias (#1277) by @devin-ai-integration[bot] in #1283
- chore: remove unreachable read-only find handling from daemon find handler by @thymikee in #1290
- fix: warn agents that armed-repair diagnostic reads are recorded too (#1271 stage 1) by @thymikee in #1287
- feat(android): one persistent automation helper owning snapshot + viewport + canonical injection by @thymikee in #1281
- fix(android): bound the scroll-hint dumpsys probe and skip it in wait polling (#1270) by @thymikee in #1288
- fix(android): actionable error when the snapshot helper is unavailable (#1284) by @thymikee in #1285
- fix(replay): retarget identity-empty press containers to their labeled descendant (#1280) by @thymikee in #1286
- feat: add direct Limrun provider runtime by @thymikee in #1278
- test: stop pinning settle capture counts against a wall-clock loop by @thymikee in #1307
- docs: clarify keyboard dismiss fallbacks by @thymikee in #1302
- fix(cli): forward --no-record from every recordable command reader by @thymikee in #1305
- test: replace the hand-typed Maestro fixture with a generated conformance oracle by @thymikee in #1289
- fix(replay): default-exclude observation-only reads from repair heals, add --record opt-in (#1271 stage 2) by @thymikee in #1303
- test: stabilize iOS snapshot timeout regression by @thymikee in #1309
- fix(android): return meaningful occluding system surfaces instead of failing the snapshot by @thymikee in #1301
- fix(cli): deliver --no-record to the daemon (supersedes #1305) by @thymikee in #1311
- fix: sleep past the settle quiet deadline instead of onto it (#1306) by @thymikee in #1308
- fix(android): drop the dumpsys scroll-hint probe instead of capping it (#1270) by @thymikee in #1314
- fix(replay): publish a full-cover system overlay's targets in divergence screen.refs by @thymikee in #1318
- fix(mcp): advertise --no-record on every recordable command's tool schema by @devin-ai-integration[bot] in #1313
- fix(remote): preserve tenant scope for proxy artifact downloads by @thymikee in #1317
- feat!: remove deprecated gesture duration and rotate velocity inputs (#1218, #1216) by @devin-ai-integration[bot] in #1315
- chore: gitignore .env files by @thymikee in #1322
- test: give the tap-retry differential a fixture control that forces the retry by @thymikee in #1327
- fix(record): finalize active recording on session/daemon teardown by @adamTrz in #1325
- feat: add daemon stop lifecycle by @thymikee in #1323
- ci(test-app): key the fixture build cache on the Expo fingerprint by @thymikee in #1321
- Maestro compat: support childOf on assertVisible/assertNotVisible (#1294) by @devin-ai-integration[bot] in #1334
- docs: improve README onboarding by @thymikee in #1333
- feat: add advisory device claims by @thymikee in #1329
- fix(maestro): consume deferred stability before waitForAnimationToEnd (#1326) by @devin-ai-integration[bot] in #1337
- feat(maestro): support optional on scrollUntilVisible and extendedWaitUntil (#1291) by @devin-ai-integration[bot] in #1339
- fix(mcp): restore result and config parity by @thymikee in #1343
- feat(maestro): numeric option fields accept ${VAR} lookup interpolation (#1293) by @devin-ai-integration[bot] in #1342
- refactor(mcp): extract reference-pin state into tool-ref-pins module (#1344) by @devin-ai-integration[bot] in #1345
- docs: propose active-session script publication by @thymikee in #1347
- docs: simplify README language by @thymikee in #1352
- docs: retire Maestro compatibility tracker by @thymikee in #1350
- fix(tvos): skip SpringBoard system-modal probe on tvOS (#1351) by @thymikee in #1353
- fix: target Maestro scrollUntilVisible container by @thymikee in #1338
- chore: add FreeRange range-analysis check by @thymikee in #1354
- fix: align Android Maestro gesture dispatch by @thymikee in #1356
- feat: publish scripts from active sessions by @thymikee in #1357
New Contributors
- @lott-ai made their first contribution in #1206
- @mvanhorn made their first contribution in #1238
- @billsbooth made their first contribution in #1263
- @adamTrz made their first contribution in #1325
Full Changelog: v0.19.3...v0.20.0