Skip to content

Repository files navigation

A control plane so parallel coding agents stop fighting over the same simulator

Simlock is a CLI-first control plane for iOS simulators and Android emulators, built for environments where multiple coding agents run on one machine at the same time. Agents don't touch simctl or avdmanager directly — they ask Simlock for a device and get one back, booted and health-checked, that no other agent will touch until they're done with it.

Why you'd want this

Two agents, one simulator, zero coordination. Left to themselves, agents that need a device grab whatever simctl / avdmanager happens to show them. Two agents that pick the same one start booting, erasing, and installing over each other — without either ever knowing the other exists. Simlock gives them a single primitive instead: lease a device.

You don't provision devices by hand. If nothing matching is free, Simlock provisions one itself, up to a capacity limit it derives from the machine's CPU and RAM. Once that limit is reached, further requests block and wait in a fair queue rather than failing outright, with --timeout and --no-wait escape hatches for callers that want different behavior.

Idle devices don't sit there burning RAM and disk. A device nobody's using is shut down after a short idle period to reclaim RAM, then deleted after a longer one to reclaim disk — automatically, in tiers.

A crashed simulator doesn't just quietly cost you a device. If a leased device's process dies outside simlock, Simlock notices, reboots it under the same lease, and tells the holder — it can't restore whatever was running inside the device when it died, but the lease and its device don't just vanish.

It's advisory, not a sandbox. Simlock doesn't wrap or intercept simctl / avdmanager — it works because agents are instructed to only use devices handed to them by a lease. What it does enforce is its own blast radius: Simlock only ever shuts down, erases, or deletes devices it created itself. Everything else on the machine is read-only to it.

Built for agents first, humans second. Lease results are one JSON line on stdout; progress (queueing, provisioning ETAs, boot) streams as JSON lines on stderr. Status and other operator-facing commands default to a human-readable view and take --json when a script wants the structured form instead.

What it looks like

simlock lease --platform ios --device "iPhone 16" --detach
{
  "lease": "lse_9f2c",
  "platform": "ios",
  "device": "iPhone 16",
  "os": "18.4",
  "udid": "ABCD-...",
  "state": "leased"
}

That's the whole interaction: ask for a platform and a device model, get back an identified, ready-to-use device. Release it explicitly, or let its TTL expire.

Now say a second agent asks for the same iPhone 16 a moment later. It's already leased to the first agent — no problem, Simlock just provisions another one. Progress streams as JSON lines on stderr while it happens, and the lease result lands on stdout the moment the new device is ready:

{"event":"provisioning","eta_seconds":5}
{"event":"booting","eta_seconds":30}
{
  "lease": "lse_a731",
  "platform": "ios",
  "device": "iPhone 16",
  "os": "18.4",
  "udid": "EFGH-...",
  "state": "leased"
}

No manual bookkeeping, no "device busy" error to handle — just a second device, a few seconds later. That keeps up until the machine's capacity limit is reached (derived from its CPU and RAM, or set explicitly in docs/CONFIGURATION.md); after that, further requests wait in a fair queue instead of failing, with --timeout and --no-wait for callers that want different behavior.

Agents that speak the Model Context Protocol can skip the CLI entirely and get the same lease/release workflow as tools, through a local stdio server:

{
  "mcpServers": {
    "simlock": {
      "command": "simlock",
      "args": ["mcp"],
      "env": { "SIMLOCK_AGENT_ID": "agent-1" }
    }
  }
}

Getting started

pnpm install
pnpm build
simlock lease --platform ios --device "iPhone 16" --detach
simlock status --json

The daemon starts on demand — there's no separate setup step. Use simlock doctor to reconcile managed state with reality, and simlock nuke --yes --delete-devices only for an emergency reset of Simlock-managed devices.

See docs/CLI.md for the full command reference and docs/CLI.md#mcp-integration-optional or the README section below for wiring up an MCP client.

MCP integration (optional)

The CLI remains Simlock's primary, full operator interface. MCP is a narrower, agent-focused integration: it intentionally exposes neither status, configuration, events, lease renewal, nor destructive or other operator commands. Start it with simlock mcp — it reserves stdout for MCP JSON-RPC, so lease results never mix with protocol framing.

SIMLOCK_AGENT_ID sets the server's stable requester identity. Simlock allows at most one active lease per identity, so give each agent session a distinct, stable id — run one MCP server process per agent session, each with its own id.

The server exposes exactly four tools: list_devices (read-only catalog of what can be leased), lease_simulator, release_simulator, and lease_status (cheap, safe to poll after a context compaction to check whether a device is still held). Full tool contracts, progress reporting, and lease-loss notifications are documented in docs/CLI.md.

Documentation

Made with ❤️ at Callstack

simlock is an open source project and will always remain free to use. If you think it's cool, please star it 🌟. Callstack is a group of React and React Native geeks, contact us at hello@callstack.com if you need any help with these or just want to say hi!

Like the project? ⛸️ Join the team who does amazing stuff for clients and drives React Native Open Source! 🔥

About

A control plane so parallel coding agents stop fighting over the same simulator.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages