open source · MIT · iOS + Android

Every agent gets
its own simulator.

warden leases iOS simulators, Android emulators and real phones to your coding agents, drives the app on them and keeps what they checked as tests. Devices, ports and builds, leased across every worktree on your Mac. One CLI, free and open source.

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

Installs the CLI and the skill for every agent it finds.

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

Works with

  • Claude Code+hook
  • Codex+hook
  • Cursor
  • GitHub Copilot
  • Gemini CLI
  • OpenCode
  • Windsurf
  • Zed

Every agent the skills CLI supports gets the warden skill. Claude Code and Codex also get a SessionEnd hook that releases what the session leased.

Claim a device. Drive it. Keep the proof.

Leasing, device control and e2e tests in one tool, on one driver, so what an agent checked by hand passes the same way as a spec.

  1. 01

    Install

    One global CLI, then warden install adds the skill to every agent it finds. There is no daemon to start.

    $ npm i -g @delacour/warden
  2. 02

    Claim

    Ask for a platform. You get a booted simulator no other agent, worktree or shell on the Mac will be handed.

    $ warden claim ios --json
  3. 03

    Drive and keep

    Read the screen, tap and type with warden ui, then keep the same checks as a spec that warden e2e runs on many devices.

    $ warden ui snapshot -i

One Mac. Every branch at once.

Each agent works in its own worktree. warden hands each one a simulator, a Metro port and the build for its native fingerprint, and never hands the same one to two owners.

  • Claude Code, leasedfeature/checkoutiPhone 17:80813f9a1c · cache
  • Codex, leasedfix/login-crashiPhone 17:80823f9a1c · cache
  • Cursor, leasedfeature/dark-modePixel 9 emulator:8083b72e04 · local
  • you, leasedmainiPhone 17 Pro:80843f9a1c · installed
  • Gemini CLI, queuedfeature/searchnext boot slot
Illustrative. Three worktrees share a native fingerprint, so one cached build serves all three; the fifth claim waits for a boot slot.

Devices that look after themselves, for agents that don't wait.

Every agent, worktree, CI job and shell on the Mac claims from one sqlite registry. Each claim is a transaction, so two agents asking at the same instant are served in turn, never both.

--ttl 30m

One lease, one device

A leased device belongs to one holder until it releases it or the TTL runs out. Wrapper commands heartbeat every 30 s and release when they exit.

Read more →
warden capacity

Devices on demand

Nothing free that fits? warden boots or creates one, up to what the Mac's RAM and CPU can hold, and shuts down idle ones first when it needs room.

Read more →
--wait 30m

A fair queue

Past capacity, claims wait their turn instead of failing, by --priority and age. --no-wait fails at once; warden queue shows where you are.

Read more →
warden golden

New simulators in seconds

New iOS simulators clone from a settled golden image: 3.7 s clone plus 11.9 s boot, instead of 146–239 s for a fresh one.

Read more →
warden gc

Idle devices shut down

A warden device nobody has leased for 20 min is shut down to free RAM. warden sims prune deletes long-unused ones to free disk.

Read more →
warden release --bad

Bad devices set aside

A device that fails its health check at claim time is quarantined, and the claim tries another. Release one as bad yourself and it is set aside for 24 h.

Read more →
warden port claim

Ports and builds too

Metro ports are leased like devices. App builds are cached by native fingerprint and shared by every worktree, so a native change builds once.

Read more →
--physical

Real phones too

Claim a plugged-in Android phone or iPhone and drive it with warden ui or a spec. warden never boots or shuts one down, and only uninstalls what it installed.

Read more →

Everything it needs on that device.

Once it holds a simulator, your agent runs the app, drives it, reads what went wrong and keeps what it checked as a test. Every step stays on its own device.

Run it

warden dev in every worktree

  • Claims a device and leases a Metro port per worktree
  • Installs the cached build: installed, cache, EAS, then local
  • Never attaches to another checkout's Metro
  • Expo or bare React Native, with or without a config
Dev client per worktree →
terminal
# worktree 1: feature/checkout$ warden dev ios --json# worktree 2: fix/login-crash, at the same time$ warden dev ios --json# each gets its own simulator and Metro port,# and the cached build for its native fingerprint

Drive it

Device control for agents

  • Reads the screen as @e refs: role, name, testID, state
  • Presses, fills, scrolls, swipes, waits and checks
  • Every call checks the lease and heartbeats it
  • Another agent's device is never a valid target
Drive a device →
terminal
# your agent, on its own simulator$ warden claim ios --json$ warden ui open com.example.app$ warden ui snapshot -i@e1 [textbox] "Add a todo" id=todo-input value="" enabled hittable@e2 [button] "Add" id=todo-add enabled hittable$ warden ui fill @e1 "Write e2e specs"$ warden ui press @e2 --settle$ warden ui wait text="Write e2e specs"

Debug it

Logs, network and profiles

  • Metro's bundle failures, with file, line and code frame
  • The app's console, exceptions and network, symbolicated
  • CPU, memory, frames, startup and React profiles with warden perf
  • The same debug functions as MCP tools with warden mcp
Debug the running app →
terminal
# what broke, from the running app$ warden dev logs --kind bundle_failed,error$ warden ui exceptions$ warden ui network --status 4xx# why it is slow$ warden perf record --all --duration 15s --udid $UDID# or all of it over MCP$ claude mcp add warden -- warden mcp

Keep it

What it verified becomes a test

  • warden ui record writes the session as a spec and runs it once
  • The testIDs warden ui found become the spec's locators
  • warden ui and specs share one driver, so a check that passed by hand passes in the spec
  • The warden skill tells the agent when a check belongs in a spec
Write your first test →
e2e/add-todo.e2e.ts
import { expect, test } from "@delacour/warden-e2e"; test("add a todo", async ({ screen }) => {  await screen.getByTestId("todo-input").fill("Write e2e specs");  await screen.getByTestId("todo-add").tap();  await expect(screen.getByText("Write e2e specs")).toBeVisible();  await expect(screen.getByTestId("remaining")).toHaveText("2 left");});

Run the suite

The suite on every device at once

  • warden e2e leases N simulators and installs the build
  • One bun test per device, the specs shared between them
  • warden affected runs only the specs a diff reaches
  • Failures leave a screenshot, the screen tree and a trace
How e2e works →
1 simulator13m55s
5 simulators3m25s
Salient's 37 offline e2e specs on iPhone 17 simulators, job time after Metro is ready. Oct 2026.

Change the world around the app.

Take it offline, break one host, move the GPS, jump the clock or fail Face ID, from a spec or a terminal, on a leased simulator. The app runs unmodified, and warden undoes every condition when the lease ends.

warden ui biometrics fail and match answer the real system prompt. Face ID and Touch ID on simulators, fingerprint on emulators, enrolled with warden claim --biometrics.Read more →

It only touches what it leased.

warden is advisory, not a sandbox. What it enforces is its own reach.

--adopt

Your simulator stays yours

A device warden didn't create or lease is read-only: never handed to an agent, shut down, erased or installed on unless you pass --adopt.

Read more →
warden check

Every call checks the lease

warden ui refuses a device leased to someone else, and heartbeats yours on every call. warden check exits 2 when a device is someone else's.

Read more →
127.0.0.1

Nothing listening

No server to run and no open port: leases are rows in one sqlite file, and warden's helpers talk over unix sockets. The network proxy binds loopback, only while it shapes traffic.

Read more →
SessionEnd

Cleans up after itself

Wrappers release when they exit. The Claude Code and Codex hook releases what a session leased and shuts down what it booted; device conditions are undone on release.

Read more →

Productive from the first prompt.

Paste this into Claude Code, Codex, Cursor or any agent in your app's directory. The warden skill tells it the rest.

prompt

Install warden (npm i -g @delacour/warden && warden install) and set this Expo app up with warden init. Run the app from this worktree with warden dev, then drive it with warden ui and check the home screen renders. Save what you checked as an e2e spec under e2e/, run it with warden e2e, and release the simulator when you're done.

What agents build with it.

The jobs agents get asked to do on a mobile app, and the warden command behind each.

warden dev

Parallel worktrees

Five agents, five worktrees, five simulators and five Metro ports, side by side on one Mac.

Read more →
warden ui wait

Verify a UI change

The agent changes a screen, opens it on its own simulator and checks it before it says done.

Read more →
warden ui record

Reproduce a bug

Walk the steps from the issue on a clean device and keep the repro as a spec that fails until the fix lands.

Read more →
warden ui network

Test the unhappy paths

Offline, a failing host, an expired trial, a failed Face ID: the states nobody tests by hand, as specs.

Read more →
warden affected

Gate every pull request

Only the specs a diff reaches run, in CI or a pre-push hook, across leased simulators.

Read more →
warden perf diff

Find what got slow

Record CPU, memory, frames and startup before and after a change, and diff the two.

Read more →

Questions.

Short answers. The docs have the long ones.

What is warden?

One open-source CLI that leases iOS simulators, Android emulators and phones to coding agents, drives the app on them, and runs e2e specs across many of them at once. Devices, Metro ports and app builds are leased per worktree, so agents in parallel never collide.

Does warden sandbox my agents?

No. It is advisory: the skill tells agents to use only devices a lease hands them, and raw simctl or adb are not intercepted. What it enforces is its own reach: warden ui refuses a device leased to someone else, and warden only shuts down, erases or deletes devices it created.

What happens when an agent dies holding a device?

Every lease has a TTL (30 min, 5 min for a bare agent warden claim) and lives only while it is renewed. warden run, dev, batch and e2e heartbeat every 30 s and release when they exit, so a killed wrapper frees its device within about 90 s; one that vanishes costs the device until the TTL runs out.

What if every device is busy?

warden boots or creates another, up to a capacity it derives from the Mac's RAM and CPU. After that the claim waits in a queue ordered by priority and age, up to --wait (30 min by default); --no-wait fails at once.

Does it work on Android and real phones?

Yes. Android emulators are leased like simulators, and warden ui and specs drive them. Plugged-in Android phones and iPhones are opt-in with --physical; device conditions and biometrics are simulator and emulator only. iOS needs macOS with Xcode.

Which agents does it support?

Any agent the skills CLI installs into gets the warden skill: Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, OpenCode, Windsurf, Zed and more. Claude Code and Codex also get the SessionEnd cleanup hook.

Is there an MCP server?

Yes, for debugging. warden mcp serves a warden dev session's Metro output, console, exceptions, network, eval, logpoints and profiles over stdio. Claiming devices and running specs stay on the CLI.

What does my app need?

Builds, the dev client and Metro target Expo and React Native. warden ui and specs drive any installed app by bundle id, finding elements by testID, label, text or role.

How much does it cost?

Nothing. warden is MIT-licensed and runs on your own Mac or CI runner. No account, no cloud.

Give every agent a device of its own.

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