Migrating Gestures
agent-device 0.20.0 removed the timed forms of swipe, gesture fling, and gesture swipe, and
the velocity argument of gesture rotate. Nothing is silently reinterpreted: every removed form
now fails with an INVALID_ARGS error that names its replacement.
This page is the migration for all four public surfaces — CLI, Node.js, MCP, and saved .ad
recordings — plus the policy that governs the next such removal.
What changed
Gesture vocabulary now separates a throw from a deliberate drag:
swipeandgesture flingare quick, fixed-duration directional throws. They do not take a duration, because a caller-supplied duration made them something else.gesture panis the deliberate timed translation. It keepsdurationMs.gesture rotatederives its pacing from the requesteddegrees.
durationMs remains on gesture pan and gesture transform.
CLI
gesture pan takes an origin plus a delta, where swipe took two absolute points, so
dx = x2 - x1 and dy = y2 - y1:
Drop the duration instead when the timing was incidental and a throw is what you wanted. The resulting gesture is a 100ms fling, which travels further on a scrollable list than a 300ms drag over the same distance:
The error message computes this for you:
Node.js
interactions.swipe, interactions.fling, and interactions.swipeGesture no longer accept
durationMs; interactions.rotateGesture no longer accepts velocity. Passing them is a type
error at compile time and an INVALID_ARGS rejection at runtime — the rejection happens
client-side, before the request reaches the daemon, so a plain JavaScript caller or a stale
compiled build gets the error rather than a silently retimed gesture.
interactions.pan and interactions.transformGesture keep durationMs.
MCP
The swipe, gesture (fling, swipe kinds), and gesture rotate tool schemas no longer
advertise durationMs or velocity, so an agent reading the schema will not produce the removed
form. An agent that sends one anyway — from a cached schema or a memorized example — gets an
INVALID_ARGS rejection that names the removed key and the command to use instead, for example
gesture fling does not accept durationMs; use gesture pan for timed movement. This is the
structured-input equivalent of the CLI error; it names the replacement command but, unlike the CLI
and .ad messages, does not echo a fully-substituted rewrite, because the structured request
carries no positional string to rewrite.
No MCP server configuration changes.
Saved .ad recordings
.ad scripts keep their positional syntax: it is the same vocabulary as the CLI, and it is not
scheduled for removal (see Positional .ad syntax below). Only the removed
arguments have to go.
A script that still carries one fails when it is parsed, before the replay executes any device action, and the error names the line:
Apply the same rewrites as the CLI table above. Flags are unaffected: --count, --pause-ms, and
--pattern stay on swipe, and --pointer-count stays on gesture pan.
A duration held in a variable (swipe 197 650 197 300 ${DURATION}) is reported the same way — the
preflight counts arguments, so it does not need the value.
The authoritative check is the parser itself. Every retired form is rejected when the script is
parsed, before the replay runs any device action, so running the suite (agent-device test <glob>,
or agent-device replay <file>.ad) finds every stale line by construction and names it — a missed
grep can never let one reach execution. The patterns below are a bulk pre-flight to locate them
without a device. They follow the .ad tokenizer: tokens are separated by any whitespace (space or
tab), and the numeric slots accept a bare or double-quoted number or ${VAR} — so
swipe\t…\t"300" is flagged like swipe … 300. They still stop short of the tokenizer's rarer
encodings (a quoted command word, backslash escapes inside a quoted token), which is why the parser,
not the grep, is the gate.
Re-recording also produces a migrated script: the recorder writes the canonical form, so a fresh
open --save-script → interact → close run is a valid alternative to editing by hand. Recording
evidence is only captured from action zero, so arm at open; a bare close --save-script on a
session that was not armed at open is rejected.
Maestro flows
Maestro swipe with a duration is not affected. agent-device runs Maestro YAML through its
own compatibility engine, which normalizes a timed Maestro swipe to the canonical gesture pan
input and preserves Maestro's fast-swipe-then-hold execution profile. Maestro flows need no
migration.
replay export writes an explicit duration: 100 — the canonical fling duration — so an exported
flow runs at the speed the .ad script ran, rather than picking up Maestro's own 400ms default.
Deprecation policy
This is the process a public gesture input follows on its way out, and the bar the next removal has to clear:
- Announce. The input is documented as deprecated and recorded in
CHANGELOG.mdunderUnreleased, together with the replacement. - Warn for one minor release. The input keeps working and normalizes to the replacement, with a
deprecationsentry in the response so an agent sees the migration while the call still succeeds. - Publish the migration. A section on this page covers CLI, Node.js, MCP, and
.ad, with a concrete before/after per surface. - Prove the repository is clean. A repository-wide search for the removed shape returns no hits
in fixtures, examples, skills, docs, or tests.
agent-devicehas no usage telemetry, so this search — plus the migration guide's publication — is the evidence, and it is the reason the removal cannot be inferred from "nobody complained". - Remove. The input is rejected with an
INVALID_ARGSerror that states the replacement, and the normalization branch and its compatibility tests are deleted in the same change. For an input that can appear in a saved recording, the rejection must fire at parse time and name the line, so a stale script never half-executes.
The 0.20.0 removal completed all five steps.
Positional .ad syntax
Positional gesture parsing in .ad is not a compatibility shim and is not scheduled for
removal.
.ad is a line-based script format whose syntax is the CLI's syntax. Its gesture codec —
gesturePayloadFromPositionals / gesturePayloadToPositionals — is what defines the file format,
not a bridge to an older one. CLI, Node.js, and MCP already send structured input directly; the
codec's only remaining jobs are parsing CLI argv and reading and writing .ad lines, and both are
the current public syntax. Replacing it with a structured payload would make recordings
unreadable and ungreppable for no behavioral gain.
See ADR 0013 for the gesture normalization and planning model this rests on.
