LIGH – Let coding agents use and verify the iOS apps they build LIGH, an open-source (MIT) macOS tool, lets AI coding agents run, interact with, and verify the iOS apps they build on Apple's Simulator, claiming a ~4.7× speedup over WDA/Appium on a 44-step semantic workflow (10.6s vs ~50s). The tool provides a persistent local control plane (lighd) on CoreSimulator, a host Autopilot that discovers UI paths without LLM UI tokens, and a strict verifier, targeting the local fix-run-verify loop for agents like Cursor. Make coding agents actually use the iOS apps they build. Local iOS Simulator + physical Expo/Debug · open source MIT · macOS + Xcode Try it try-it · · how-it-works How it works · physical-iphone--expo Physical / Expo · what-we-measured Evidence Your app AI coding agents can write Swift. Getting them to actually run and verify what they built on the Simulator is still painfully slow. The goal is not another iOS simulator. It is to make Apple's existing Simulator a much better execution environment for coding agents : write → build → run → interact → verify → fix Tell Cursor: "Add validation to the signup form and verify it works in the Simulator." LIGH gives the agent a local control plane: persistent lighd on CoreSimulator, accessibility JSON for observe/act, and structured pass/fail results. Requires a Mac with Xcode Simulator. Works best with accessibilityIdentifier on your views. LIGH is a local iOS execution substrate for coding agents: the model fixes Swift, and the host autonomously runs, uses and verifies the app on Simulator. - A persistent local control plane on top of CoreSimulator lighd - A host Autopilot that takes a goal plus typed data, discovers the UI path at runtime, and spends zero LLM UI tokens - A strict verifier that fail-closes and accepts a working patch - A system optimized for the real coding-agent loop: read → edit → build → run → use → verify → fix - Not a generic WebDriver replacement for every mobile automation job - Not automation of unmodified App Store apps physical path needs your Debug / Expo development build - Not a recorder or YAML-flow authoring tool - Not an LLM memory layer over app screens - Not a cloud device farm The hard part is not making a model write Swift. The hard part is making it use the app it just built quickly enough to stay inside a debugging loop . That is the wedge: - traditional mobile automation optimizes for test authoring + CI - LIGH optimizes for local fix → run → verify for coding agents vs Appium / WDA: LIGH is narrower, but much better aligned with local coding-agent execution. Appium wins on breadth, language ecosystem and general automation; LIGH wins when the job is “fix the app and prove the fix on the simulator now.” Our execution-layer benchmark is ~4.7× faster on the same semantic workflow. vs Maestro / Maestro MCP: Maestro is currently the strongest adjacent competitor for agentic mobile QA. It is excellent when the deliverable is a repeatable YAML test flow you keep in CI. LIGH is stronger when the deliverable is a working code fix and the main bottleneck is the agent's local execution loop. The host Autopilot removes UI micro-decisions from the model instead of generating a persistent scripted flow. vs XCUITest / Detox / Espresso: those are test frameworks, not agent control planes. They are great when humans write and maintain tests. LIGH is for the different job where an agent must inspect the app, change source, run it, and verify the result autonomously. LIGH should be read as: the fastest honest way to let a coding agent locally use and verify the iOS app it is actively changing not as: the best general-purpose mobile test framework That broader claim would be false. Milestone note: docs/MILESTONE HOST AUTOPILOT.md /mrmarino023/light-ios-simulator/blob/main/docs/MILESTONE HOST AUTOPILOT.md . Validation week do not change the architecture : — run /mrmarino023/light-ios-simulator/blob/main/docs/VALIDATION WEEK.md docs/VALIDATION WEEK.md ./scripts/validation-week.sh .Same 44-step semantic workflow Settings → search → assert → screenshot, ×4 cycles : LIGH lighd | WDA / Appium | | |---|---|---| | Wall time | ~10.6 s | ~50 s | | Steps | 44 / 44 | 44 / 44 | | Failures | 0 | 0 | ~ 4.7× faster than WDA/Appium on the same workflow. Evidence: docs/assets/agent-bench-latest.json /mrmarino023/light-ios-simulator/blob/main/docs/assets/agent-bench-latest.json . Reproduce: ligh agent-bench WDA baseline needs Appium listening . Product path host exercise app : OnboardingDemo. Agent fixes Swift; host runs known taps. This measures edit + host exercise , not AX-vs-vision. | Arm | Pass | Wall | LLM tokens | |---|---|---|---| LIGH AX + host exercise | yes | ~86 s | ~27k | | Vision baseline | yes | ~204 s | ~73k | | Hybrid AX→vision | no | ~334 s | ~402k | Evidence: docs/assets/killer-loop-ab-latest.json /mrmarino023/light-ios-simulator/blob/main/docs/assets/killer-loop-ab-latest.json . Reproduce: ./scripts/gate-killer-loop.sh . Historical honest A/B v1 no host exercise : XCUITestDemo login-never-navigates — same prompt, agent must type/tap via AX or vision. exercise app disabled. Both arms failed this run postcondition not met ; neither modality won. | Arm | Pass | Wall | LLM tokens | Used exercise app | |---|---|---|---|---| | LIGH AX | no | ~369 s | ~445k | no | | Vision baseline | no | ~333 s | ~306k | no | Evidence: docs/assets/killer-loop-ab-honest-latest.json /mrmarino023/light-ios-simulator/blob/main/docs/assets/killer-loop-ab-honest-latest.json . Reproduce: LIGH KILLER HONEST=1 ./scripts/gate-killer-loop-ab.sh needs OPENAI API KEY .This v1 result identified the architectural bug: the LLM was still the UI executor. It is retained as the negative baseline, not presented as a win. Honest A/B v2 — Host Autopilot. Same XCUITestDemo task, injected bug, model, acceptance target and strict harness. Neither arm receives a step list. The Autopilot arm restricts the LLM to code read/write/build/run goal ; Rust discovers and drives the UI path from live Feel IR. Vision still drives every tap through the LLM. | Arm | Pass | Wall | LLM tokens | Patches / builds | |---|---|---|---|---| LIGH Host Autopilot | yes | 41.9 s | 9,034 | 1 / 1 | | Vision baseline | yes | 152.4 s | 67,040 | 1 / 1 | That paired run is 3.64× faster wall-clock and uses 7.42× fewer LLM tokens . The UI executor itself used zero LLM tokens. Evidence: docs/assets/killer-loop-ab-v2-latest.json /mrmarino023/light-ios-simulator/blob/main/docs/assets/killer-loop-ab-v2-latest.json . Reproduce with ./scripts/gate-killer-loop-ab-v2.sh needs OPENAI API KEY . The artifact is published regardless of outcome and only passes when Autopilot both verifies and reaches the 3× threshold.The same policy also passes the no-special-cases generality gate on 6/6 apps , covering six different flow shapes: LighFixture — form: type + submit, 2 actions, 11.5 s LighOnboard — multi-screen wizard, 4 actions, 14.2 s LighModal — sheet presentation + confirmation, 2 actions, 10.0 s LighFeed — list → detail navigation, 1 action, 9.4 s XCUITestDemo — third-party OSS login with credentials, 3 actions, 11.2 s Kix — third-party catalog + auth + tabs, 3 actions, 12.7 s Kix was the hole: login worked, then Autopilot wandered catalog cards because SwiftUI tab bars walk as a childless AXGroup and XCTest ids like tab home show up in AXP as the SF Symbol plus the visible label house.fill / Home . The host now hit-tests childless tab/nav/tool bars and binds tab goal ids to tab-chrome labels only. Reproduce Kix with LIGH PILOT APPS=kix ./scripts/gate-autopilot-generality.sh . There are no per-app branches or recorded flows in Autopilot. Every run receives only an acceptance goal plus typed data; Rust discovers the path at runtime and uses zero LLM UI tokens . Evidence: docs/assets/autopilot-generality-latest.json /mrmarino023/light-ios-simulator/blob/main/docs/assets/autopilot-generality-latest.json . Reproduce with ./scripts/gate-autopilot-generality.sh .Same agent loop as Simulator. Different motors. lighd ├─ eyes → @mm-labs/ligh-expo DevDriver in-app AX over LAN └─ hands → WDA / Appium XCUITest system taps/swipes fail-closed on screen sig ACK without ΔUI = lie | Simulator | Physical owned Debug / Expo dev client | | |---|---|---| | Eyes | CoreSimulator AX | DevDriver AX dump | | Hands | IndigoHID | WDA in-app fake UITouch is lab-only | | Proof law | motor effect checks | effect: ok requires screen sig change | Proven on device Mae Expo app : tap Profile → Home with motor: physical + effect: ok , plus WDA swipe. Full runbook: docs/PHYSICAL.md /mrmarino023/light-ios-simulator/blob/main/docs/PHYSICAL.md . Wire any Expo app: ./scripts/sync-ligh-expo.sh /path/to/YourExpoApp app.json plugins: "@mm-labs/ligh-expo" then: EAS / expo run:ios development build cp scripts/wda.env.example ~/.ligh/wda.env UDID, bundle, team ./scripts/start-appium-wda.sh keep running ./target/release/lighd & ./target/release/ligh device wait ./target/release/ligh tap --json --label 'TabProfile' Package docs: packages/ligh-expo/README.md /mrmarino023/light-ios-simulator/blob/main/packages/ligh-expo/README.md . Host Autopilot ×3 evidence below is Simulator-scoped until Autopilot is re-gated on the physical WDA motor. Coding agent Cursor MCP ↓ LIGH host — Autopilot over Feel IR perceive → plan → act → verify ↓ ┌─────┴─────┐ │ │ CoreSimulator Physical HybridPhysical IndigoHID DevDriver eyes + WDA hands │ │ Your Debug .app / Expo development build | What it is | Who uses it | Role | | |---|---|---|---| Screenshot | Pixels | Vision LLMs | Fallback when AX is unusable | AX tree | Raw accessibility dump | Debug / motor | Too big and noisy for planning | Feel IR | Live interaction frame | Host + thin agent | Default world model | Feel IR is not a screenshot and not a dump of the tree. After every settle, Rust builds a small JSON frame: place → where you are fingerprint, surface, title salience → what weighs ranked CTAs / fields, top-N block → what blocks keyboard, alert, sheet delta → what just changed fp changed? events? feel → phase: settled | transition | blocked | eyes unusable suggest → optional next host act tap label/id or dismiss Example shape agent sees this from ligh perceive : { "place": { "fingerprint": "fp ab08…", "surface": "app", "title": "Welcome" }, "salience": { "rank": 1, "kind": "primary button", "label": "Get Started" }, { "rank": 2, "kind": "button", "label": "Skip" } , "block": null, "feel": { "phase": "settled", "keyboard": false, "ready": true }, "suggest": { "intent": "tap", "label": "Get Started" } } LLM slow, expensive → read/edit Swift, decide what code to fix Rust Autopilot → discover and drive the UI path, verify the goal Feel IR → the host's live interaction state ~ms update strict harness → accept/reject the patch without another LLM turn Canonical coding-agent loop on LIGH: read file → write file → build app → run goal → host accept receives an acceptance target plus typed data, never a step list. Rust discovers the path from live Feel IR and drives it with zero LLM UI tokens. run goal immediately runs the strict harness when the target appears. A passing patch ends the loop before the model can rewrite working code. host accept - The planner is app-agnostic: field kinds, CTA salience, overlays, deltas and bounded recovery. Per-app flows are forbidden. - Screenshots are debug / escalation only ligh perceive routed : AX → ready retry → vision if still eyes unusable . A persistent UX graph screens + transitions as LLM memory was measured and did not help agents navigate — they ignored it or used more tokens docs/UX GRAPH.md /mrmarino023/light-ios-simulator/blob/main/docs/UX GRAPH.md . Feel IR is the opposite design: a live frame for the computer , not a history document for the model. The graph remains useful as telemetry / compile-to-replay input llm tokens = 0 , not as agent memory.| Tool | Job | |---|---| ligh perceive | Settled world model + Feel IR | ligh attempt | Act + host verdict intent met , evidence | ligh perceive routed | AX-first; vision only on escalation | ligh cap autopilot | Goal + typed data → host-discovered path → verified result 0 UI tokens | ligh cap app job | Known multi-step job CI / fixtures | More detail: docs/QA LAYER.md /mrmarino023/light-ios-simulator/blob/main/docs/QA LAYER.md · · /mrmarino023/light-ios-simulator/blob/main/docs/ARCHITECTURE.md docs/ARCHITECTURE.md docs/STRUCTURED CONTROL.md git clone https://github.com/mrmarino023/light-ios-simulator.git cd light-ios-simulator ./scripts/ligh-init.sh or: ligh init unset CARGO TARGET DIR && cargo build --release -p ligh-cli -p ligh-daemon ./scripts/developer-trial.sh Impact bench: ligh bench holy → docs/assets/holy-shit-bench-latest.json Paste MCP config from ./scripts/print-cursor-mcp.sh into Cursor → Settings → MCP . Full guide: docs/DEVELOPER TRIAL.md