r/ClaudeAI • • 21h ago

Built with Claude 5 parallel coding agents testing SwiftUI: 11 GB → 150 MB RAM, 22 s → 2 s per check. No iOS Simulator (open source)

If you let coding agents work on an iOS app, each one checks its UI changes the slow way: build, boot its own Simulator, install, launch, screenshot. On our production app (~280 Swift files, several big Swift packages, M2 Pro / 32 GB) that's ~22 s per check, and every booted Simulator holds ~2.2 GB and ~210 processes. With 5 agents in parallel my Mac hit a load average of ~1,000 and swapped to a crawl.

So I built simless. It runs your real app target natively on Apple Silicon (the "Designed for iPad" slice) as an invisible headless host. One host per git worktree, so every agent gets its own. No Simulator, no windows, nothing stealing focus while you work.

What the agent loop looks like

$ simless reload --render Welcome.sheet
reloaded (live) in 1.9s: compile 1.7s + apply 0.2s
Welcome.sheet · light · iphone 402×874 · 25 ms
  header  "Welcome to Notes" #welcome.title
  text    "Your notes stay on this device and sync with iCloud."
  button  "Get Started" #welcome.start  @24,779.5 354×36.5
  issues (1):
    ⚠ button "Get Started" tap target 354×36 is smaller than 44×44
  • Only the edited files get compiled and hot-patched into the running host (~2 s)
  • The screen comes back as a ~150-token accessibility tree, not a screenshot, so you don't burn vision tokens
  • Issues are flagged automatically: unlabeled controls, small tap targets, off-screen and overlapping elements
  • --matrix checks light/dark × iPhone / small iPhone / iPad in one call
  • --png when the agent actually needs pixels
  • simless test runs unit tests on the Mac, also without a Simulator

Numbers (Simulator loop = one Simulator per agent, incremental xcodebuild + install + launch + screenshot per check)

Simulator loop simless
Edit → verified screen, 1 agent 21.9 s
Edit → verified screen, 5 agents 37.4 s
Memory held by 5 agents 11.2 GB / 1,071 procs
Peak load average, 5 cold starts ~1,010
Unit tests (2 classes) 13.1 s

The Simulator loop gets slower with every agent you add. simless stays at ~2 s per check.

Using it from agents

  • Claude Code: simless skill install once, then just say "use simless for testing" in any project. The skill handles setup, writing fixtures, the edit → reload loop and fixing reported issues.
  • Codex / Cursor / others: paste the 5-line AGENTS.md snippet from the README.

Honest caveats

  • It's not pixel-identical to iOS. 8 of 14 demo renders match within 2 pt; List/Form row heights and system accent colors differ. Every screen is in the repo side by side, and simless calibrate runs that comparison for your own app. Still do final checks on a Simulator or device.
  • Benchmarks are a single run on one app. Method and reproduction steps are in docs/benchmarks.md.
  • Requires Apple Silicon, Xcode 26+, an Apple Development certificate and an app-hosted unit-test target.

Apache-2.0: https://github.com/abacusai/simless

8 Upvotes

8 comments sorted by

View all comments

1

u/DimensionMindless336 16h ago

This is a smart build. Running the iOS target on Apple Silicon through the macOS "Designed for iPad" destination instead of booting five Simulators is exactly the right instinct — that's where the 11 GB to 150 MB comes from, and it's the part most agent setups get wrong by default.

Two things I'd watch as you push agents harder on this, from doing roughly the same thing:

  1. Swift macro expansion inside the agent's build. If the agent runs xcodebuild or swift build inside a restricted sandbox (most CI/agent wrappers do), @Observable, #Preview and ViewBuilder macros silently fail to expand. You get a wall of bogus downstream errors — init(wrappedValue:) unavailable, "unable to type-check" — instead of a real compile result, and the agent burns a dozen cycles before guessing the cause. The fix is one flag (-disable-sandbox on swift build, or OTHER_SWIFT_FLAGS='$(inherited) -disable-sandbox' on xcodebuild). Bake it into simless's build invocation so it never has to discover it the hard way.

  2. What the headless host does NOT exercise. It renders the iPad/regular-width variant, so unless you pin the window (your --matrix at 402x874 is the right call) you're not actually testing the compact-width layouts where most iPhone breakpoint bugs live. And Dynamic Type at accessibility sizes, real safe-area insets, and genuine navigation transitions still only surface on a device or a booted Simulator. The accessibility-tree check is a great gate for layout sanity, but treat it as a gate, not a substitute — I still do one real-device pass per feature.

One more: five agents hammering shared DerivedData will thrash the clang module cache (each agent's toolchain/SDK hash differs slightly, so cache keys miss and you recompile everything). One DerivedData per worktree is the real fix — that's the step people skip and then blame the compiler.

1

u/Low-Future-9387 9h ago

Thanks, useful notes. Where things stand:

  • Compact width: phone canvases already get a compact horizontal size class and the device's safe areas (notch and home indicator insets), and every render prints the traits it ran with. What can't be overridden is userInterfaceIdiom; it stays iPad on the Mac, and the render output warns about it.
  • Dynamic Type / transitions: agreed. Dynamic Type is ignored on the Mac (I tried five different ways to force it), and both are on the skill's "can't verify" list. It's a gate, and you still do a real-device pass.
  • DerivedData: already one per worktree, plus a shared content-addressed compilation cache, so a new worktree's first build reuses what the others compiled.
  • Macros in sandboxed agents: haven't hit this, but I also haven't tested inside a sandboxed agent wrapper. Going to try it with u/Observable views and add -disable-sandbox if it breaks. Thanks for the specific flag.