{"slug": "ligh-let-coding-agents-use-and-verify-the-ios-apps-they-build", "title": "LIGH – Let coding agents use and verify the iOS apps they build", "summary": "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.", "body_md": "**Make coding agents actually use the iOS apps they build.**\n\nLocal iOS Simulator + physical Expo/Debug · open source (MIT) · macOS + Xcode\n\n[ Try it](#try-it) ·\n\n[·](#how-it-works)\n\n**How it works**[·](#physical-iphone--expo)\n\n**Physical / Expo**[·](#what-we-measured)\n\n**Evidence**\n\n**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.**\n\nThe goal is not another iOS simulator. It is to make Apple's existing Simulator a **much better execution environment for coding agents**:\n\n```\nwrite → build → run → interact → verify → fix\n```\n\nTell Cursor:\n\n\"Add validation to the signup form and verify it works in the Simulator.\"\n\nLIGH gives the agent a local control plane: persistent `lighd`\n\non CoreSimulator, accessibility JSON for observe/act, and structured pass/fail results.\n\n**Requires** a Mac with Xcode Simulator. **Works best** with `accessibilityIdentifier`\n\non your views.\n\n**LIGH is a local iOS execution substrate for coding agents:** the model fixes\nSwift, and the host autonomously runs, uses and verifies the app on Simulator.\n\n- A\n**persistent local control plane** on top of CoreSimulator (`lighd`\n\n) - A\n**host Autopilot** that takes a goal plus typed data, discovers the UI path at runtime, and spends**zero LLM UI tokens** - A\n**strict verifier** that fail-closes and accepts a working patch - A system optimized for the real coding-agent loop:\n\n```\nread → edit → build → run → use → verify → fix\n```\n\n- Not a generic WebDriver replacement for every mobile automation job\n- Not automation of unmodified App Store apps (physical path needs\n**your** Debug / Expo development build) - Not a recorder or YAML-flow authoring tool\n- Not an LLM memory layer over app screens\n- Not a cloud device farm\n\nThe hard part is not making a model write Swift. The hard part is making it\n**use the app it just built quickly enough to stay inside a debugging loop**.\n\nThat is the wedge:\n\n- traditional mobile automation optimizes for\n**test authoring + CI** - LIGH optimizes for\n**local fix → run → verify for coding agents**\n\n**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.\n\nLIGH should be read as:\n\nthe fastest honest way to let a coding agent locally use and verify the iOS app it is actively changing\n\nnot as:\n\nthe best general-purpose mobile test framework\n\nThat broader claim would be false.\n\nMilestone note: [ docs/MILESTONE_HOST_AUTOPILOT.md](/mrmarino023/light-ios-simulator/blob/main/docs/MILESTONE_HOST_AUTOPILOT.md).\nValidation week (do not change the architecture):\n\n[— run](/mrmarino023/light-ios-simulator/blob/main/docs/VALIDATION_WEEK.md)\n\n`docs/VALIDATION_WEEK.md`\n\n`./scripts/validation-week.sh`\n\n.Same 44-step semantic workflow (Settings → search → assert → screenshot, ×4 cycles):\n\nLIGH (`lighd` ) |\nWDA / Appium | |\n|---|---|---|\n| Wall time | ~10.6 s |\n~50 s |\n| Steps | 44 / 44 | 44 / 44 |\n| Failures | 0 | 0 |\n\n~**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).\n\nReproduce: `ligh agent-bench`\n\n(WDA baseline needs Appium listening).\n\n**Product path** (host `exercise_app`\n\n): OnboardingDemo. Agent fixes Swift; host runs known taps. This measures *edit + host exercise*, not AX-vs-vision.\n\n| Arm | Pass | Wall | LLM tokens |\n|---|---|---|---|\nLIGH (AX + host exercise) |\nyes | ~86 s |\n~27k |\n| Vision baseline | yes | ~204 s | ~73k |\n| Hybrid (AX→vision) | no | ~334 s | ~402k |\n\nEvidence: [ docs/assets/killer-loop-ab-latest.json](/mrmarino023/light-ios-simulator/blob/main/docs/assets/killer-loop-ab-latest.json). Reproduce:\n\n`./scripts/gate-killer-loop.sh`\n\n.**Historical honest A/B v1** (no host exercise): XCUITestDemo\n`login-never-navigates`\n\n— same prompt, agent must type/tap via AX **or** vision.\n`exercise_app`\n\ndisabled. Both arms **failed** this run (postcondition not met);\nneither modality won.\n\n| Arm | Pass | Wall | LLM tokens | Used exercise_app |\n|---|---|---|---|---|\n| LIGH (AX) | no | ~369 s | ~445k | no |\n| Vision baseline | no | ~333 s | ~306k | no |\n\nEvidence: [ docs/assets/killer-loop-ab-honest-latest.json](/mrmarino023/light-ios-simulator/blob/main/docs/assets/killer-loop-ab-honest-latest.json). Reproduce:\n\n`LIGH_KILLER_HONEST=1 ./scripts/gate-killer-loop-ab.sh`\n\n(needs `OPENAI_API_KEY`\n\n).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.\n\n**Honest A/B v2 — Host Autopilot.** Same XCUITestDemo task, injected bug,\nmodel, acceptance target and strict harness. Neither arm receives a step list.\nThe Autopilot arm restricts the LLM to code (`read/write/build/run_goal`\n\n);\nRust discovers and drives the UI path from live Feel IR. Vision still drives\nevery tap through the LLM.\n\n| Arm | Pass | Wall | LLM tokens | Patches / builds |\n|---|---|---|---|---|\nLIGH Host Autopilot |\nyes | 41.9 s |\n9,034 |\n1 / 1 |\n| Vision baseline | yes | 152.4 s | 67,040 | 1 / 1 |\n\nThat paired run is **3.64× faster wall-clock** and uses **7.42× fewer LLM\ntokens**. The UI executor itself used zero LLM tokens. Evidence:\n[ docs/assets/killer-loop-ab-v2-latest.json](/mrmarino023/light-ios-simulator/blob/main/docs/assets/killer-loop-ab-v2-latest.json).\nReproduce with\n\n`./scripts/gate-killer-loop-ab-v2.sh`\n\n(needs\n`OPENAI_API_KEY`\n\n). The artifact is published regardless of outcome and only\npasses when Autopilot both verifies and reaches the 3× threshold.The same policy also passes the no-special-cases generality gate on **6/6\napps**, covering six different flow shapes:\n\n**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\n\nKix was the hole: login worked, then Autopilot wandered catalog cards because\nSwiftUI tab bars walk as a childless `AXGroup`\n\nand XCTest ids like `tab_home`\n\nshow up in AXP as the SF Symbol plus the visible label (`house.fill`\n\n/ `Home`\n\n).\nThe host now hit-tests childless tab/nav/tool bars and binds `tab_*`\n\ngoal ids\nto tab-chrome labels only. Reproduce Kix with\n`LIGH_PILOT_APPS=kix ./scripts/gate-autopilot-generality.sh`\n\n.\n\nThere are no per-app branches or recorded flows in Autopilot. Every run receives\nonly an acceptance goal plus typed data; Rust discovers the path at runtime and\nuses **zero LLM UI tokens**. Evidence:\n[ docs/assets/autopilot-generality-latest.json](/mrmarino023/light-ios-simulator/blob/main/docs/assets/autopilot-generality-latest.json).\nReproduce with\n\n`./scripts/gate-autopilot-generality.sh`\n\n.Same agent loop as Simulator. Different motors.\n\n```\nlighd\n ├─ eyes  → @mm-labs/ligh-expo DevDriver (in-app AX over LAN)\n └─ hands → WDA / Appium XCUITest (system taps/swipes)\n              fail-closed on screen_sig (ACK without ΔUI = lie)\n```\n\n| Simulator | Physical (owned Debug / Expo dev client) | |\n|---|---|---|\n| Eyes | CoreSimulator AX | DevDriver AX dump |\n| Hands | IndigoHID | WDA (in-app fake UITouch is lab-only) |\n| Proof law | motor effect checks | `effect: ok` requires `screen_sig` change |\n\n**Proven on device (Mae Expo app):** tap Profile → Home with\n`motor: physical`\n\n+ `effect: ok`\n\n, plus WDA swipe. Full runbook:\n[ docs/PHYSICAL.md](/mrmarino023/light-ios-simulator/blob/main/docs/PHYSICAL.md).\n\nWire any Expo app:\n\n```\n./scripts/sync-ligh-expo.sh /path/to/YourExpoApp\n# app.json plugins: [\"@mm-labs/ligh-expo\"]\n# then: EAS / expo run:ios development build\ncp scripts/wda.env.example ~/.ligh/wda.env   # UDID, bundle, team\n./scripts/start-appium-wda.sh                # keep running\n./target/release/lighd &\n./target/release/ligh device wait\n./target/release/ligh tap --json --label 'TabProfile'\n```\n\nPackage docs: [ packages/ligh-expo/README.md](/mrmarino023/light-ios-simulator/blob/main/packages/ligh-expo/README.md).\n\nHost Autopilot ×3 evidence below is **Simulator-scoped** until Autopilot is\nre-gated on the physical WDA motor.\n\n```\nCoding agent (Cursor MCP)\n        ↓\nLIGH host — Autopilot over Feel IR (perceive → plan → act → verify)\n        ↓\n  ┌─────┴─────┐\n  │           │\nCoreSimulator  Physical HybridPhysical\n(IndigoHID)    (DevDriver eyes + WDA hands)\n  │           │\nYour Debug .app / Expo development build\n```\n\n| What it is | Who uses it | Role | |\n|---|---|---|---|\nScreenshot |\nPixels | Vision LLMs | Fallback when AX is unusable |\nAX tree |\nRaw accessibility dump | Debug / motor | Too big and noisy for planning |\nFeel IR |\nLive interaction frame | Host + thin agent | Default world model |\n\n**Feel IR** is not a screenshot and not a dump of the tree. After every settle, Rust builds a small JSON frame:\n\n```\nplace     → where you are (fingerprint, surface, title)\nsalience  → what weighs (ranked CTAs / fields, top-N)\nblock     → what blocks (keyboard, alert, sheet)\ndelta     → what just changed (fp changed? events?)\nfeel      → phase: settled | transition | blocked | eyes_unusable\nsuggest   → optional next host act (tap label/id or dismiss)\n```\n\nExample shape (agent sees this from `ligh_perceive`\n\n):\n\n```\n{\n  \"place\": { \"fingerprint\": \"fp_ab08…\", \"surface\": \"app\", \"title\": \"Welcome\" },\n  \"salience\": [\n    { \"rank\": 1, \"kind\": \"primary_button\", \"label\": \"Get Started\" },\n    { \"rank\": 2, \"kind\": \"button\", \"label\": \"Skip\" }\n  ],\n  \"block\": null,\n  \"feel\": { \"phase\": \"settled\", \"keyboard\": false, \"ready\": true },\n  \"suggest\": { \"intent\": \"tap\", \"label\": \"Get Started\" }\n}\nLLM (slow, expensive)     →  read/edit Swift, decide what code to fix\nRust Autopilot            →  discover and drive the UI path, verify the goal\nFeel IR                   →  the host's live interaction state (~ms update)\nstrict harness            →  accept/reject the patch without another LLM turn\n```\n\nCanonical coding-agent loop on LIGH:\n\n```\nread_file → write_file → build_app → run_goal → host_accept\n```\n\nreceives 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`\n\nimmediately runs the strict harness when the target appears. A passing patch ends the loop before the model can rewrite working code.`host_accept`\n\n- The planner is app-agnostic: field kinds, CTA salience, overlays, deltas and bounded recovery. Per-app flows are forbidden.\n- Screenshots are\n**debug / escalation only**(`ligh_perceive_routed`\n\n: AX → ready retry → vision if still`eyes_unusable`\n\n).\n\nA 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\n\n**live frame for the computer**, not a history document for the model. The graph remains useful as telemetry / compile-to-replay input (\n\n`llm_tokens = 0`\n\n), not as agent memory.| Tool | Job |\n|---|---|\n`ligh_perceive` |\nSettled world model + Feel IR |\n`ligh_attempt` |\nAct + host verdict (`intent_met` , evidence) |\n`ligh_perceive_routed` |\nAX-first; vision only on escalation |\n`ligh_cap_autopilot` |\nGoal + typed data → host-discovered path → verified result (0 UI tokens) |\n`ligh_cap_app_job` |\nKnown multi-step job (CI / fixtures) |\n\nMore detail: [ docs/QA_LAYER.md](/mrmarino023/light-ios-simulator/blob/main/docs/QA_LAYER.md) ·\n\n[·](/mrmarino023/light-ios-simulator/blob/main/docs/ARCHITECTURE.md)\n\n`docs/ARCHITECTURE.md`\n\n`docs/STRUCTURED_CONTROL.md`\n\n```\ngit clone https://github.com/mrmarino023/light-ios-simulator.git\ncd light-ios-simulator\n./scripts/ligh-init.sh\n# or: ligh init\nunset CARGO_TARGET_DIR && cargo build --release -p ligh-cli -p ligh-daemon\n./scripts/developer-trial.sh\n```\n\nImpact bench: `ligh bench holy`\n\n→ `docs/assets/holy-shit-bench-latest.json`\n\nPaste MCP config from `./scripts/print-cursor-mcp.sh`\n\ninto **Cursor → Settings → MCP**.\n\nFull guide: `docs/DEVELOPER_TRIAL.md`", "url": "https://wpnews.pro/news/ligh-let-coding-agents-use-and-verify-the-ios-apps-they-build", "canonical_source": "https://github.com/mrmarino023/light-ios-simulator", "published_at": "2026-08-25 22:41:25+00:00", "updated_at": "2026-08-25 23:14:32.192465+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-tools", "artificial-intelligence"], "entities": ["LIGH", "Apple", "CoreSimulator", "Appium", "WDA", "Maestro", "XCUITest", "Detox"], "alternates": {"html": "https://wpnews.pro/news/ligh-let-coding-agents-use-and-verify-the-ios-apps-they-build", "markdown": "https://wpnews.pro/news/ligh-let-coding-agents-use-and-verify-the-ios-apps-they-build.md", "text": "https://wpnews.pro/news/ligh-let-coding-agents-use-and-verify-the-ios-apps-they-build.txt", "jsonld": "https://wpnews.pro/news/ligh-let-coding-agents-use-and-verify-the-ios-apps-they-build.jsonld"}}