Experimental, pre-release

$ mobile-agent

Drives Android devices and emulators, iOS Simulators and iPhones for AI agents: a daemon with a live mirror of every screen, a CLI and an MCP server, built around the latency of each tap-and-observe step.

Replay a flow

A command sequence written for this task in the form an agent issues it, replayed by a script through the CLI, on an iPhone 17 Simulator. Timings as the CLI printed them.

0.00 s of 9.68 s · step 0/7
$

What it is

Experimental and pre-release: built from source, with the drivers on private Apple and hidden Android interfaces, so it is development tooling, never something to ship in an app. Status and limitations

A daemon with a live mirror

It keeps each attached screen's windows, accessibility tree and latest frame current from the platform's change events, and answers snapshots, waits and settle checks from that mirror instead of reading the device on every request.

docs/overview.md

The CLI

mobile-agent follows agent-browser's command grammar, @eN refs and JSON envelope, one process per command, so prompts and skills written for agent-browser carry over.

Agent guide

The MCP server

mobile-agent-mcp gives Claude Code, Claude Desktop, Codex or Cursor the same tools, refs and replies over the Model Context Protocol, on the same daemon.

Agent surfaces

What it drives

Android emulators and devices through an agent pushed over adb, booted iOS Simulators from the host with nothing installed, and physical iPhones through a runner built in the repository. Built from source on macOS; Linux builds the Android driver.

Supported platforms

Against the baselines

The same reference flows on the repository's fixtures, run by its harness through each tool as an agent would, one process per command. mobile-agent settles and verifies after every step.

iPhone 17 Simulator, iOS 27.0, Xcode 27.1 · 10 measured iterations after 2 warm-up · CI run of 2026-10-31

loginsign in on the fixture's login screen and wait for the welcome · p50 seconds, lower is better

login on iOS Simulator as a table
login flow on iOS Simulator, p50 per tool and fixture
FixtureToolp50 (ms)FactorFailed / flakySource
SwiftUImobile-agent 0.1.07078.3740% / 0%github.com/gesposito/mobile-agent/actions/runs/41181272250, harness-simulator artifact
SwiftUIMaestro 2.10.035899.7195.07×0% / 0%github.com/gesposito/mobile-agent/actions/runs/41181272250, harness-simulator artifact
SwiftUIagent-device 0.21.1412989.6131.84×0% / 0%github.com/gesposito/mobile-agent/actions/runs/41181272250, harness-simulator artifact

list-scrollscroll a long list to row 200 and open its detail · p50 seconds, lower is better

list-scroll on iOS Simulator as a table
list-scroll flow on iOS Simulator, p50 per tool and fixture
FixtureToolp50 (ms)FactorFailed / flakySource
SwiftUImobile-agent 0.1.0not timed: golden check failed: the content stopped moving before row-200 was on screen100% / 0%github.com/gesposito/mobile-agent/actions/runs/41181272250, harness-simulator artifact
SwiftUIMaestro 2.10.0not timed: golden check failed: timed out after 120 s100% / 0%github.com/gesposito/mobile-agent/actions/runs/41181272250, harness-simulator artifact
SwiftUIagent-device 0.21.14not timed: golden check failed: selector id="tab-list" did not match, then its scroll spent its 12-pass budget before row-200100% / 0%github.com/gesposito/mobile-agent/actions/runs/41181272250, harness-simulator artifact

big-screenopen the 500-cell screen and read it · p50 seconds, lower is better

big-screen on iOS Simulator as a table
big-screen flow on iOS Simulator, p50 per tool and fixture
FixtureToolp50 (ms)FactorFailed / flakySource
SwiftUImobile-agent 0.1.04818.8240% / 0%github.com/gesposito/mobile-agent/actions/runs/41181272250, harness-simulator artifact
SwiftUIMaestro 2.10.030038.0286.23×0% / 0%github.com/gesposito/mobile-agent/actions/runs/41181272250, harness-simulator artifact
SwiftUIagent-device 0.21.145969.8471.24×0% / 0%github.com/gesposito/mobile-agent/actions/runs/41181272250, harness-simulator artifact

mobile-agent's benchmark workflow on a GitHub Actions runner: Apple M1 (Virtual), 3 cores, macOS 27.0.1, Xcode 27.1, the iPhone 17 Simulator on iOS 27.0; commit ffe48e1. 10 measured iterations after 2 warm-up, 1 retry; wall clock around each tool's process, nearest-rank p50. These are shared CI runners, no noise study yet, and the host's load average was 519 on its 3 cores when the run started. Not timed: the harness times a tool only once it passes the flow's golden check. github.com/gesposito/mobile-agent/actions/runs/41181272250, harness-simulator artifact, report.md

Per-step gates

Each gate is judged at p95 over the run's measured iterations, and “under” is strict. Values are p95; where a platform has no gate the value is reported beside “no gate”.

Per-step gates per platform: limit, p95 and verdict
GateiOS SimulatoriPhone 17, iOS 26.5, commit ec567d8Android emulatorAPI 36 arm64, controller on, -gpu host, commit 60e528aPhysical AndroidNexus 5X, Android 8.1, commit 9769aa0Physical iPhoneiPhone SE (3rd gen), iOS 26.6.2, commit 9652e5e
CLI process start
5.88 ms
under 20 ms · pass
README.md
11.6 ms
under 20 ms · pass
README.md
6.33 ms
under 20 ms · pass
README.md
SwiftUI phase 6.96 ms
Expo phase 7.56 ms
under 20 ms · pass
README.md
Cold attach
SwiftUI 947 ms
Expo 838 ms
under 2 s · pass
README.md
Expo 894 ms
Compose 1.21 s
under 1.5 s · pass
README.md
Expo 13.9 s
Compose 3.13 s
under 1.5 s · fail
app_process alone takes 1.1 to 1.4 s to start on this phone, and two of Expo's 20 opens did not settle within 10 s.
README.md
SwiftUI 4.05 s
Expo 2.30 s
under 5 s · pass
README.md
Warm snapshot
SwiftUI 7.88 ms
Expo 7.65 ms
under 150 ms (SwiftUI), 250 ms (Expo) · pass
README.md
Expo 12.8 ms
Compose 19.4 ms
under 100 ms · pass
README.md
Expo 7.00 ms
Compose 8.69 ms
under 100 ms · pass
README.md
SwiftUI 8.64 ms
Expo 9.38 ms
under 1 s · pass
README.md
Tap dispatch
SwiftUI 1.56 ms
Expo 0.178 ms
under 20 ms · pass
README.md
Expo 2.81 ms
Compose 3.32 ms
no emulator gate · no gate
README.md
Expo 16.4 ms
Compose 11.4 ms
under 50 ms · pass
README.md
SwiftUI 55.1 ms
Expo 54.6 ms
under 150 ms · pass
README.md
Tap to frame change
SwiftUI 75.3 ms
Expo 52.7 ms
no gate · no gate
reports/10.2-simulator/report.md
Expo 52.5 ms
Compose 55.1 ms
under 67 ms, host GPU · pass
The run's own report judged these against the 50 ms gate of the time and failed them; task 10.13 restated the gate for host rendering.
README.md
no frame stream
no device gate · not measured
README.md
SwiftUI 364 ms
Expo 180 ms
no gate while frames are polled screenshots · reported
README.md

Where a command's time goes

Spike S15 profiled the daemon on the Simulator and the emulator. This code's CPU is under 3% of every command that reads or acts. The rest of a click or a fill is waiting: on the device, on the accessibility bridge's tree reads, for frames, and in the quiet windows the replies promise.

This code's CPU share of a command's time, spike S15
TargetCommandMean wallWorker CPUShare
Simulatorclick #tab-big, back to back645.3 ms6.9 ms
1.1%
Simulatorfill alice<n>493.0 ms3.6 ms
0.7%
Simulatorclick text=General257.4 ms1.4 ms
0.5%
Simulatorwait --settle in Settings1555.6 ms4.3 ms
0.3%
Emulatorclick #tab-big, selected214.9 ms2.4 ms
1.1%
Emulatoropen com.android.settings, cold1756.4 ms5.6 ms
0.3%

Mean ms per request at the daemon's worker thread, from the waiting-thread profile; the share is the worker's CPU over the request's wall time. The bars run to 3%. docs/spikes/s15-profiles.md

This flow, step by step

Open mobileagent.dev in Safari, on an iPhone 17 Simulator, iOS 26.5, with mobile-agent 0.1.0 at ffe48e1. Wall time is each CLI process from start to exit, as the recording script measured it; 9.68 s from the first command's start to the last one's end. Source: src/content/flows/safari.json in this site's repository.

Per-step wall time of the flow shown above
StepCommandWallReply (first line)
1open com.apple.mobilesafari2.25 s✓ Opened com.apple.mobilesafari on udid redacted
2click '#TabBarItemTitle'0.55 s✓ Done (hit text "Search or enter website" (no ref), dispatch 0.0 ms, first frame change 457.2 ms, first tree delta 541.4 ms)
3type mobileagent.dev1.03 s✓ Done (read back "mobileagent.dev", typed)
4press Enter0.01 s✓ Done
5wait --settle1.44 s✓ Settled after 1436.71 ms: the tree changed last
6scroll down 24004.20 s✓ Done (4 strokes, at rest 2747.8 ms after the last)
7wait --settle0.20 s✓ Settled after 194.06 ms: the tree changed last