ITADN
software-mansion/app-control-bench
software-mansion/app-control-bench · 文件
文件最后提交记录最后更新时间
README.md

AppControlBench

How well can an LLM agent actually operate a real iOS app - and how much of that ability comes from the model versus the tool you hand it?

This repo is the benchmark we built to answer that. It runs coding agents against real, unmodified iOS apps in the Simulator, gives them a plain-language task ("open the Feeds tab", "in the Book Club room, reply to one of the existing messages with the text 'sounds good'"), and grades the final screenshot. Every run is isolated, every screenshot and transcript is published, and the whole matrix is reproducible from this repo.

Results, run explorer and per-run screenshots: appcontrolbench.swmansion.com

What the numbers say

720 runs: 4 model configurations x 3 tool conditions x 60 tasks, one attempt each.

ModelToolCompletionCost / runTime / run
haiku-4.5 (high)argent97.5%$0.21975s
gpt-5.4-mini (high)argent95.0%$0.13476s
haiku-4.5 (low)argent94.2%$0.19566s
gpt-5.4-mini (high)agent-device85.0%$0.096121s
haiku-4.5 (low)agent-device84.2%$0.131142s
haiku-4.5 (high)agent-device83.3%$0.153133s
gpt-5.4-mini (low)argent82.5%$0.13990s
gpt-5.4-mini (low)agent-device82.5%$0.05980s
gpt-5.4-mini (high)no tool45.8%$0.618668s
gpt-5.4-mini (low)no tool15.8%$0.101128s
haiku-4.5 (low)no tool15.0%$0.584558s
haiku-4.5 (high)no tool11.7%$0.620583s

Completion scores a success as 1, a partial as 0.5 and a failure as 0.

The headline is that the tool dominates the model. Averaged over every model, agents scored 92.3% with argent, 83.8% with agent-device and 22.1% with no device-control tool at all. Turning reasoning effort up doesn't rescue a bad surface: haiku-4.5 on high effort with no tool finishes 11.7% of tasks, taking 583 seconds and $0.62 a run to do it, while the same model on low effort reaches 84.2% with agent-device and 94.2% with argent - for a third of the cost or less, and in 66 seconds a run in argent's case. The cheapest cell in the whole table (gpt-5.4-mini low + agent-device, $0.059) beats the most expensive one (gpt-5.4-mini high + no tool, $0.618) by 37 points of completion at a tenth of the cost.

The no-tool baseline is not a strawman. Those agents get a POSIX shell, the full Xcode command-line toolchain and xcrun simctl - screenshots, launch, terminate, openurl, ui, the lot - described neutrally, with no suggestion of how to use it. What they don't get is a way to make it pay off. Three of the four no-tool cells grind: a median of 67-71 shell calls per task, up to 197 in one run, and 27 of 60 runs hitting the 15-minute wall in the gpt-5.4-mini high cell alone. The fourth fails the opposite way - gpt-5.4-mini on low effort gives up early, a median of 19 calls in 94 seconds, and lands at 15.8%. Grinding and quitting both end in the same place.

Disclosure: argent is built by Software Mansion, who also built this benchmark. That is exactly why every run's screenshot, transcript, prompt, verdict and timing ships in this repo - so you can check the result rather than take our word for it. callstack/agent-device is driven through its own shipped skills the way its authors intend.

What a task looks like

Tasks live in benchmarks/tasks/tasks.json. Each one pairs the prompt the agent is given with an exact prose description of the screen that counts as solved:

{
  "id": "bsky-01",
  "app": "bluesky",
  "kind": "nav",
  "prompt": "Open the \"Feeds\" tab (next to Following) at the top of the Bluesky app.",
  "solved_screen": "The Feeds screen is open with the header \"Feeds\" centered at top (back arrow left, gear icon right). Below are the sections \"My Feeds\" ... Bottom tab bar shows Home, Search, Chat, Notifications, Profile.",
  "nav_category": "tab-switch"
}

The current set is 60 tasks over two apps - 30 in Bluesky 1.122.0 and 30 in Element iOS 1.11.40 - split across navigation (39), interaction (13) and composing/posting (8), and tagged by category so the report can break results down by what the task actually demanded: tab switches, drawer menus, search, opening an item, settings navigation, reactions, form entry, compose.

Prompts are tool-agnostic. The harness prepends a thin preamble naming the app, the device and the tool's interaction surface, then gets out of the way and lets each tool's own skills describe how to drive a phone - which is what a real agent would have.

How a run works

Every single run gets a clean world:

  • A fresh device. Each run clones a golden simulator, uses the clone, and deletes it. No run ever inherits another run's device state.
  • A fresh process tree. All bench processes are killed and verified dead at both ends of every run, and a host-wide lock means exactly one bench stream exists at a time. A second invocation fails loudly instead of quietly interleaving.
  • Fresh server state. Per-app reset hooks roll back the backend the app talks to, so a post made in one run can't change the screen another run sees.
  • Pinned everything. Tool versions (argent 0.15.0, agent-device 0.17.6) and app versions are pinned in benchmarks/configs/, checked against what's installed, and stamped into each run's metadata. The runner warns on drift rather than silently producing results that don't match the version they claim.

Scoring is a separate, resumable pass. A vision model (GPT-5.4 at temperature 0) sees the final screenshot, the task the agent was given, the description of the solved screen and the list of actions taken, and returns success / partial / fail. The prompt is explicit that the task text is the authority: an agent is never marked down for skipping something it was never asked to do. Same judge, same prompt, every cell.

Running it yourself

You'll need macOS with Xcode and the Simulator, Python 3.12+, Node, opencode with API keys in its auth store, and whichever tools you want to benchmark on PATH. The target apps have to be installed and seeded first - benchmarks/tasks/setup/ holds the per-app scripts, and Bluesky additionally needs its Metro dev server running, since it's an Expo dev client. Then build the golden simulator once (benchmarks/tasks/setup/golden/make_golden.sh) and:

cd benchmarks
./run_all.sh

That preflights the surface before spending anything on API calls, self-heals the ledger, runs every pending cell, judges the screenshots and prints final coverage. It is resumable - re-running picks up exactly what's missing. Narrow it with environment variables:

ONLY=gpt_high,haiku_low ./run_all.sh   # restrict models
TOOLS=argent ./run_all.sh              # restrict tools
APPS=bluesky ./run_all.sh              # restrict apps
SKIP_JUDGE=1 ./run_all.sh              # run the matrix without scoring

For a single run, or to inspect the plan, use the runner directly:

python3 runner/bench.py --list
python3 runner/bench.py --cell gpt_high:argent --task bsky-01
python3 runner/doctor.py                 # health, coverage and ledger check

Machine-specific paths resolve through bench_env.py (env override, then auto-detect, then a documented fallback), so there are no constants to edit before your first run. On a shared machine, call bench.py directly rather than run_all.sh - the latter assumes a dedicated host and uses a full process-kill scope.

Repo layout

benchmarks/
  run_all.sh           one command: preflight -> run -> judge -> coverage
  runner/              the harness: matrix runner, isolation, judge, doctor, report export
  configs/             per-tool opencode configs, pinned tool and app versions
  tasks/               tasks.json plus per-app seeding and the golden-simulator builder
data/                  every run: final.png, transcript.jsonl, meta.json, score.json
website/               the report site (Preact + Vite, prerendered)
public/                built site, exported report JSON and per-run webp screenshots

The report site is built from data/ - npm run build in website/ exports the JSON, re-encodes each run's screenshot to webp (needs Pillow) and prerenders both the report and the run explorer. Pushes deploy to Vercel through .github/workflows/deploy-vercel.yml.

Caveats worth knowing

  • One attempt per cell. 720 runs is one shot at each (model, tool, task). Run-to-run variance is real and this matrix does not measure it, so treat small gaps between neighbouring rows as noise and read the large ones.
  • Two apps. Bluesky and Element are real, complex, unmodified apps, but they are two apps. The task file is built to grow, and results should be re-read as it does.
  • Simulator, not hardware. Everything runs on the iOS Simulator.
  • One judge. A single vision model grades every run. It is consistent across cells, which is what fairness needs here, but it is not infallible - the run explorer publishes every screenshot and verdict so you can disagree with a specific call.
  • A moving target. Models and tools both ship fast. Every number here is tied to the pinned versions above, which is why they're recorded per run rather than in a footnote.

Built by Software Mansion.