flow-execute
Run saved UI automation flows for iOS and Android apps, executing steps like taps, gestures, and assertions on devices, and returning a structured report.
Instructions
Run a saved flow from the .argent/flows/ directory, or an explicit boundary-managed flow_path.
Steps run in order: launch starts an app from scratch (terminate + relaunch) and waits until it is
ready; tool calls dispatch through the registry; tap/long-press/type resolve a selector to an
element and act on it (tap: { on, times: 2 } double-taps; long-press: { on, duration } presses and
holds; tap/long-press alternatively take a raw normalized point — bare { x, y } or on: { x, y };
any selector may scope its matches geometrically, the CSS combinators read off frames: within: <selector>
(descendant — inside that container's frame), after: <selector> (CSS ~ — following it in reading
order), next: <selector> (CSS + — the nearest such follower, which unlike CSS reaches past a
non-matching neighbour rather than failing), plus any: true (CSS * — legal only WITH a scope and
never beside text/id/role). Scopes nest to disambiguate — within: { id: card, within: { id: list } }
reads "inside card inside list", each container's frame inside the next);
scroll-to scrolls (momentum-free) until a target is visible; pinch zooms
(pinch: { on?, scale } — scale > 1 in, < 1 out; screen center when on is omitted); rotate is the
two-finger rotation gesture (rotate: { on?, by } — degrees, + clockwise, within ±3000°; screen center
when on is omitted; distinct from the rotate tool, which changes device orientation); await waits
for a UI condition, and additionally takes the one condition that has no selector: idle: true waits
until the screen has content and stops moving in BOTH the UI tree and the rendered pixels (it never
fails a run — a screen that never settles passes carrying a warning, which is what makes it safe to
persist; the one outcome that does stop the run is an error for a tree source that could not be read
at all — a broken window rather than a verdict about the app, which leaves the run not-ok and skips
every later step; it says nothing about WHICH screen settled — a dropped tap leaves the source screen
perfectly idle — so pair it with the element check that names the destination); wait pauses for a fixed number of milliseconds; assert checks one now; snapshot
diffs a screenshot — or, with cropOn: <selector>, one element's cropped region — against a stored
baseline (a missing baseline fails the step — set updateBaselines to adopt the current screen; a
cropped element whose size drifted fails on dimensions); echo annotates; run executes another flow
inline — a YAML path resolved against the directory of the flow file that references it (co-located
runs only).
A when: block (condition + steps:, no else) runs its steps only if the condition holds —
checked once with the short assert grace — for one-sided divergences like interstitials and coach
marks; a skipped block reports distinctly and failures inside an entered block are real failures.
A flow that begins with a launch step is a self-contained e2e flow; one that doesn't runs against the
device's current state. Device id is injected by the runner (flows store none) — pass device or
platform to pick one, else the single booted device is used. On Chromium a launch step's value is an
Electron app path ({ chromium: | { path, args } }) the runner boots (on the tool-server host) rather
than an installed app id it relaunches. With no explicit device, a run whose leading launch is
unambiguously chromium (platform: chromium, or a lone { chromium: … } target) boots that app and
starts there — following a leading run:, so a fragment that composes a chromium e2e flow boots too;
otherwise the first launch attaches to an already-running instance and never kills it. Every later
launch — a nested e2e flow's own, or a mid-flow relaunch — boots a fresh instance the run moves onto;
an instance the run already owns for that same app is killed first (its exit awaited) so the
replacement can't lose the race against its single-instance lock. Instances the runner still owns at
run end are torn down then. A launch declaring no id for the run's platform is an error, not a cue to
switch platforms. Every step hard-stops the flow on failure; later steps are reported as skipped.
Returns a structured report ({ flow, device, executionPrerequisite, ok, aborted?, passed, failed,
skipped, errored, steps }) — device is the device the run STARTED on; when launches moved it onto
runner-booted instances, each names its instance in that step's reason and marks the move — run moved off <id>, or retired <id> (same app relaunched) when the instance it left was the one killed —
a relaunch that retired an older owned instance names both.
If a fragment has an execution prerequisite and prerequisiteAcknowledged is not set to true, the tool returns a notice with the prerequisite instead of running.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | Name of a saved flow to run from `.argent/flows` (e.g. "settings-explore"). Omit when flow_path is set. | |
| device | No | Device id to run against (iOS UDID, Android/Vega serial, Chromium id) — the id list-devices reports. Auto-detected when omitted, but only when exactly one booted device matches (optionally narrowed by `platform`); with several booted the run fails and lists them, so pass this explicitly whenever more than one device is up. | |
| platform | No | Restrict auto-detection to this platform when several devices are booted. `chromium` does more than filter: with no `device` it SELECTS the self-boot branch for an e2e flow - the runner boots an Electron instance from the `launch` step's chromium value and tears it down after the run (a single-key `launch: { chromium: … }` map selects it on its own, without this parameter). When it selects that branch it never falls back to device auto-detection (a fragment, or an e2e launch map with no `chromium` key, still does), and the launch value must be a real Electron app path on the tool-server host: a bare-string `launch:` - what the recorder writes - holds an installed-app bundle id, so passing `chromium` for one fails the whole run with `Electron boot: path does not exist`. Edit the launch to `{ chromium: <app path> }` first. | |
| flow_file | No | Path to the flow .yaml as readable by the tool-server. Internal — the argent client derives it from project_root and name automatically; leave unset. | |
| flow_path | No | Absolute path to a co-located flow .yaml on the client and tool server's shared filesystem. This must be supplied through the file-input boundary. For remote execution, pass name + project_root instead. | |
| project_root | Yes | Absolute path to the calling agent's project root — the cwd it is working in. With name, the saved flow is read from `.argent/flows/<name>.yaml` under this root; with flow_path, the flow, its run: siblings, and baselines all resolve beside the YAML instead, so pass the agent's cwd. | |
| updateBaselines | No | Write/refresh screenshot baselines for `snapshot` steps instead of diffing against them. | |
| prerequisiteAcknowledged | No | Set to true to confirm the execution prerequisite has been met. Required (LLM path) when a fragment defines an executionPrerequisite. |