Nine jobs only a phone can do
The result of each job cannot be produced by adb, am, cmd, settings, content or a web search. A script checks each one from the phone's own data, never from the agent's report. All tools ran on the same emulator on 10 November 2026 with Claude Sonnet 5.5 through claude -p, one agent at a time: phonedriver three passes, the others two. The phonedriver build is cfa37b2; release 0.1.0 is that build plus Windows build and test fixes. Totals are sums of per-job medians.
| Tool | Tokens, nine jobs | Tool calls | Runs passed | Time |
|---|---|---|---|---|
| phonedriver 0.1.0 | 657,542 | 99 | 27 of 27 | 5.1 min |
| phone-harness 0.3.3 | 1,127,495 | 104.5 | 14 of 18 | 22.7 min |
| mobile-mcp 1.0.16 | 4,567,200 | 276.5 | 17 of 18 | 8.7 min |
adb ran only the two jobs where any route is allowed: 4 of 4 runs with 203,751 tokens. phonedriver's sum for the same two jobs is 92,737.
Each job
Bars are median tokens, drawn to scale within each job. After the bar: median tokens, runs passed, median tool calls and median wall time.
qa-signup
Test the Signup tab of a testbed app and report every bug with steps.
The bugs exist only in how the screen behaves under use; no command exercises a form's validation path.
qa-shop
Test the Shop tab (quantity, totals, a discount code) and report bugs.
Same: a stale total and a flat discount show only under use.
relay-code
Copy a random pickup code from the Relay tab into a new contact "Locker 7".
The code exists only in a TextView and the app's private storage.
chrome-prefs
Make DuckDuckGo the default search engine and turn on "Always use secure connections".
Chrome keeps both in a private Preferences file; no intent or setting reaches them.
clock-settings
Add Tokyo and London world clocks, set snooze to 15 min and silence after 5 min.
These live in Clock's private preferences; the alarm intents set alarms and timers only.
messages-pin-archive
Pin one conversation, archive another, leave a third.
Pin and archive are private Messages flags with no command.
photos-crop
Crop a photo to a square and save a copy, keeping the original.
The Photos editor is the only crop tool; a shell has no image editor.
wakelock-restrict (mixed)
Find the app holding a wakelock and set its battery usage to Restricted.
A shell finds the holder and Settings sets the restriction; any route allowed.
log-code (mixed)
Find a pickup code in the device log and put it in a contact "Locker 9".
The code is only in logcat; the contact is made in the Contacts UI; any route allowed.
Where phonedriver does not win
- phonedriver had the lowest median wall time on 8 of the nine jobs. Not on these: mobile-mcp on log-code (22 s against 31 s). Wall time includes the model's own thinking, so it moves with the day.
- These are two or three runs per cell on one emulator. They describe what happened and are not rates. Against its own 7 November runs phonedriver used 657,542 tokens where it had used 977,780, but the emulator was not the same one, so part of that is the device.
What each tool failed at, the versions, and every run that was thrown away and why are in docs/bench/jobs.md. The method is in Benchmarking AI agents on jobs only a phone can do, and the comparison write-up in phonedriver vs mobile-mcp vs phone-harness vs adb on real phone jobs. If you maintain one of these tools and think I set it up wrong, open an issue and I will rerun it.
Speed against adb and phone-harness
p50 of 50 runs after 20 warm-ups, on an Android 17 emulator (API 37, headless, software rendering). Everything ran on the emulator's own host with the three tools interleaved. phonedriver 0.1.1, 11 November 2026. Host CPU load was 31% to 80% around the rows, from other work on the machine. Bars are drawn to scale within each operation; the tick is p95.
Tap, back to back
4.4x faster than adb (95% CI 3.0 to 5.2), 4.2x faster than phone-harness (3.0 to 5.1).
Tap, 1.5 s apart
2.4x faster than adb (95% CI 2.3 to 2.6), 7.7x faster than phone-harness (7.2 to 8.3).
Screenshot
32x faster than adb (95% CI 30.9 to 33.2), 38x faster than phone-harness (36.9 to 39.8).
UI tree, Settings
43x faster than adb (95% CI 41.1 to 48.0), 47x faster than phone-harness (44.3 to 51.7).
UI tree, 1,000-row list
170x faster than adb (95% CI 162.0 to 177.4), 186x faster than phone-harness (176.8 to 193.4).
Type 100 characters
23x faster than adb (95% CI 19.7 to 25.1), 25x faster than phone-harness (21.2 to 26.9).
Tap, then what changed
4.8x faster than adb (95% CI 4.7 to 5.2).
Every operation, fastest gain first
How many times faster phonedriver is, then each tool's median time with its slowest 1 in 20 (p95) under it.
| Operation | Faster than adb | phonedriver | adb | phone-harness | Faster than phone-harness |
|---|---|---|---|---|---|
| UI tree, 1,000-row list | 170x162.0 to 177.4 | 12.7 msp95 20.0 | 2,163.7 msp95 2,204.5 | 2,362.5 msp95 2,398.1 | 186x176.8 to 193.4 |
| UI tree, Settings | 43x41.1 to 48.0 | 51.0 msp95 146.3 | 2,215.7 msp95 3,054.8 | 2,393.8 msp95 3,017.7 | 47x44.3 to 51.7 |
| Screenshot | 32x30.9 to 33.2 | 9.1 msp95 11.1 | 291.2 msp95 763.7 | 348.3 msp95 715.0 | 38x36.9 to 39.8 |
| Type 100 characters | 23x19.7 to 25.1 | 158.3 msp95 426.6 | 3,638.4 msp95 9,234.2 | 3,928.7 msp95 10,064.9 | 25x21.2 to 26.9 |
| Tap, then what changed | 4.8x4.7 to 5.2 | 514.0 msp95 612.0 | 2,477.2 msp95 5,460.4 | not supported | n/a |
| Tap, back to back | 4.4x3.0 to 5.2 | 38.5 msp95 175.6 | 170.5 msp95 205.1 | 162.8 msp95 434.9 | 4.2x3.0 to 5.1 |
| Tap, 1.5 s apart | 2.4x2.3 to 2.6 | 28.7 msp95 42.2 | 69.4 msp95 89.5 | 220.4 msp95 248.1 | 7.7x7.2 to 8.3 |
The range under each ratio is its 95% confidence interval.
On a real phone
A smaller pass on a HONOR X8 (Android 13) over USB, on 9 November 2026. 30 samples per phonedriver row and 20 per adb row; the two tools never ran at once.
| Operation | phonedriver p50 | adb p50 | Ratio |
|---|---|---|---|
| Read the screen | 78.5 ms | 2,506.6 ms | 32x |
| Screenshot | 16.4 ms | 1,005.7 ms | 61x |
The screen read was 461 bytes against 25,334 bytes of XML. On the phone's main Settings page, uiautomator dump failed with "could not get idle state" after 11.5 s, because the page never goes idle, while phonedriver read it in 110 ms. Typing and taps were not measured on the phone.
How to read it
- The bare tap is the closest row. Host to reply, phonedriver takes 38.5 ms when taps come back to back (4.4 times faster than adb) and 28.7 ms when they are 1.5 s apart (2.4 times). The tap misses its own 15 ms budget (decision D14), and typing 100 characters misses its 150 ms budget (158.3 ms).
- adb returns raw XML and a tap that returns before the app reacts. phonedriver returns a pruned, numbered tree and waits for the app. Different outputs for the same job. For "tap, then what changed", the adb row is a tap, one
uiautomator dumpand a diff in the caller. adb input textand phone-harness cannot type Unicode. phonedriver typed 100 mixed-script characters (ASCII, accents, CJK, Arabic, emoji) in 225.0 ms.- Fresh daemon to first screen read: phonedriver 1,620.5 ms, phone-harness 2,999.4 ms (new Python process), adb 2,488.0 ms (one dump; adb has no cold start, so this is for scale only).
- phone-harness's helpers ran in process, which spares it a Python start per call and favours it.
- No iOS device is in this run. On the iOS 26.5 Simulator a tap takes 357 ms against 80 ms for the Simulator's own tool, because XCTest takes about 240 ms to confirm the event (D15). On an iPhone 14 Plus and an iPad Pro (M5) it takes 534 and 454 ms.
The earlier 153x figure came from a quieter, single-tool run and is not current. The full method, machine state and the history of the number are in the repo.
An earlier token run: three small tasks
Before the nine jobs, a smaller run: Claude Sonnet 5.5 through claude -p, each tool the only tool, three tasks checked from device state (the alarm database, the testbed event log, the answer text). Tokens are input, output, cache creation and cache read. Device: a 2560x1600 tablet emulator.
| Task (checked from device state) | phonedriver, 2 to 3 runs, median tokens | phone-harness, 3 runs, median tokens |
|---|---|---|
| alarm-gym: 7:30 AM weekday alarm "Gym" in Clock | 110,419 (19.5 calls) | 220,723 (19 calls) |
| wiki-birthdate: Chrome, Wikipedia, Grace Hopper's birth date | 57,753 (7 calls) | 53,403 (7 calls) |
| testbed-web-form: fill and submit a web form | 50,185 (7 calls) | 56,249 (6 calls) |
| Sum of the three medians | 218,357 | 330,375 |
On the sum of medians phonedriver used 34% fewer tokens than phone-harness (218,357 against 330,375). It is ahead on the longest task, level on the form and 8% behind on the Wikipedia task, where individual runs ranged from 26,147 to 64,522 tokens. Raw adb finished alarm-gym in 2 calls with a SET_ALARM intent (18,607 tokens), a shortcut the task allows, and failed the web form, a WebView with no accessibility nodes. mobile-mcp failed every run of this earlier setup for an environment reason (its helper could not reach a remote adb server), so it is not compared here; the nine jobs above gave it its own adb server.
The fixed cost per turn is also measured: the first turn of a one-word claude -p costs 5,606 tokens with phonedriver's 14 MCP tools and 5,214 with phone-harness. All three rounds, the fixes between them and the caveats.
Raw data and reproduction
- jobs.json: every agent run of the nine jobs, with its pass, tokens, tool calls and wall time.
- speed-samples.csv and second-pass samples: every timed sample.
- speed.json: p50, p95 and bootstrap intervals per operation. speed-meta.json: machine and versions.
- tokens.json: every run of the earlier three-task benchmark.
- Repeat the speed run:
PH_PYTHON=<python with phone-harness 0.3.3> bash bench/speed/reproduce.shin the repository.