Parallelise your agent workflows and e2e tests.

warden leases every simulator, emulator and port on your Mac to one agent, worktree or run at a time, then fans your e2e suite across as many simulators as you give it. No two runs ever drive the same device.

$ npm i -g @delacour/warden && warden install

npm release pending. Build from source until then.

warden batch salient-e2e37 Salient e2e flows · 5 iPhone 17 simulators · 3m25s (13m55s on one) · 37/37 passed · shown at 5× speed

Your whole suite, split across every simulator.

Salient's 37 e2e flows take 13m55s on one simulator and 3m25s on five. A batch leases the simulators, starts your serve step once, and hands the next flow to whichever simulator frees up first. Declare it once in warden.config.json; every agent and human runs the same command.

  • Lease

    N simulators and every port the run needs, all-or-nothing, released on exit.

    --count
  • Serve

    Metro, an API, a seeded database: started once, and jobs wait until it is ready.

    --serve
  • Queue

    One worker per simulator pulls the next flow, so a slow flow never holds up the rest.

    {job}
  • Retry

    A failed flow reruns on the same simulator, with its own log.

    --retry
  • Record

    Every simulator on video, the live grid as a cast, and a timeline of every job.

    --record
// warden.config.json
"batches": {
  "salient-e2e": {
    "project": "salient",
    "platform": "ios",
    "count": 5,
    "profile": "iphone-17",
    "app": true,
    "retry": 1,
    "serve": "bun scripts/e2e/run-ios.ts --session",
    "serveReady": "file:e2e-artifacts/batch/session.json",
    "jobsFrom": { "command": "bun scripts/e2e/select-flows.ts --list" },
    "cmd": ["bun", "scripts/e2e/run-ios.ts", "--attach", "{job}", "--device", "{udid}"]
  }
}
$ warden batch salient-e2e

New simulators in sixteen seconds, not four minutes.

Five simulators in parallel means five devices to boot. A new simulator's first boot spends most of its time on a one-time data migration; warden does that once, keeps the result as a golden image, and clones every new pool device from it.

  1. Fresh create + first boot

    simctl create, then the one-time data migration

    146–239 s

  2. Clone a settled golden + boot

    clone 3.7 s + boot 11.9 s, what claim does for a new sim

    15.6 s

  3. 3 clones booted in parallel

    three new sims at once

    ~20 s

  4. Reuse an existing pool device

    second boot; warden always reuses first

    ~6.5 s

measured timespread across runs (146 s best, 239 s worst)Measured on the salient e2e suite. Clones are APFS copy-on-write, about 30 MB each; if cloning fails warden falls back to simctl create.

Parallel, never shared.

Every claim runs in one sqlite transaction on this machine, so two agents asking at the same instant are served in turn, never both.

  • Claim

    Reuse a free booted warden sim, else clone one, up to --max per profile.

    claim
  • Stay alive

    A lease lives while its pid lives or its heartbeat is under 30 min old.

    heartbeat
  • Release

    Only sims warden created, or you booted, are shut down on release.

    release
  • Reclaim

    Dead sessions' leases go stale and their sims are shut down. Never deleted.

    gc
  • Touch a device it didn't create

    Your own Simulator.app or another tool's sim is read-only unless you pass --adopt.

  • Hand one device to two owners

    warden check exits 2 when another owner holds a device.

  • Give out a port that's in use

    Ports are bind-probed and leased from a range, like devices.

  • Boot a golden image

    Goldens are never allocated, booted by gc, or counted in the pool.

Your agents claim before they tap.

warden install adds two hooks to Claude Code and Codex. An agent's first simulator tool call claims a device for its session; a call on someone else's device is blocked.

  • PreToolUse

    Auto-claims an unleased device, heartbeats your own, blocks another owner's with their repo and worktree.

  • SessionEnd

    Shuts down the sims the session booted and releases its leases. /clear keeps them running.

  • warden run

    Claims devices and ports, exports them to your script, releases on exit.

$ warden run ios --count 2 --port 8091:20 -- bun run e2e

WARDEN_UDIDS=8F3A2C1E-…,41D09B7A-…
WARDEN_PORT_0=8093
# heartbeat every 30 s · SIGINT forwarded · released on exit

Codex skips new hooks until you trust them: run /hooks once after installing.

Install

Install the CLI, wire the agent hooks, check the setup. Every agent-config change shows a diff and asks first. Until the npm release lands, build from a checkout.

  1. 1Install warden

    $ npm i -g @delacour/warden
  2. 2Hook up your agents

    $ warden install
  3. 3Check it

    $ warden doctor
Read the install guide