Batching
Use batch to run several commands in one daemon request. It saves round trips when you already know
the next sequence of actions.
The whole batch has a 60-second wall-clock budget within a 90-second client timeout. At the
budget, the running step receives the same cancellation as a canceled request. When that step
settles, a failed batch returns COMMAND_FAILED with details.reason: daemon_request_timeout.
The daemon and other sessions stay available if it responds to the client. A close step may
already have ended its session. A failed batch keeps
details.executed, details.partialResults, and its dispatch status, so you can continue from
the failing step. A batch that completes every step returns success even if it settles shortly
after the deadline. Cancellation does not prove that native device work has stopped. If a step
ignores cancellation beyond the remaining 30 seconds, the client may report a transport timeout.
For a daemon that does not respond, the client's existing liveness check can reset it. Split
longer sequences into separate requests.
Some resource operations cannot run as bounded steps. The failed step returns
details.reason: bounded_composite_resource_unsupported, details.shape,
details.dispatched: no, and details.standaloneCommandName for a known public command. The name
is not a complete command. Affected commands include perf, audio, record, trace, and some
logs operations. A close that would dispose an owned resource or release a provider lease, and
acquisition that would recover another session's durable resources, can also be refused. Opening
another app in the same session keeps its owned resource handles. An Android test IME alone is
restored by ordinary close, including a bounded step. Restoration may settle after the remaining
batch time. If it fails, the session retires and the device claim stays for recovery; inspect the
device and claim before acting. Run a refused step separately with its original arguments and options, the same --session value, and
the same private CLI context. Inspect details.partialResults and
details.dispatchedSteps before retrying the batch: earlier steps may already have changed the app.
The batch still authenticates and renews its own lease while it runs.
Run a batch from the CLI
From a file:
Inline, for small payloads:
Step payload format
Pass batch a JSON array of steps:
Notes:
inputis required and takes the same fields as the matching MCP or Node client command.- A step can only have
command,input, andruntimekeys; any other key is rejected. - Steps written as
positionals/flagsare rejected. Rewrite each step with structured input, for example{"command":"open","input":{"app":"settings","platform":"ios"}}. batchandreplaysteps can't be nested inside a batch.--on-error stopis the only error mode: the batch stops at the first failing step.- A batch runs at most 100 steps by default. Pass
--max-steps <n>to raise or lower the limit, up to 1000. - A step without
platformuses the batch's--platform. Session lock defaults don't override it. - Steps use the same session binding and session lock mode as the
batchcommand itself.
Response shape
Success:
Without --json, batch prints the overall completion line followed by a short summary of each step.
Failure:
Tips for agents
- Keep each batch to one screen flow.
- After steps that change the UI (
open,click,fill,swipe), add awaitoris existsstep before reads that matter. - Treat earlier refs and snapshots as stale after the UI changes.
- Prefer
--steps-fileover inline JSON. - Keep batches to about 5-20 steps.
- When a batch fails, inspect
details.step,details.partialResults, anddetails.dispatchedStepsbefore retrying.
Example recipes
Open app -> open thread -> type -> send
Open app -> open action menu -> choose option -> verify
Avoid acting on a stale accessibility tree
The accessibility tree can lag behind fast UI changes. Add explicit waits, and split long workflows into separate batches by phase:
- navigate
- verify/extract
- cleanup
