Simlock is a control plane for iOS simulators and Android emulators, built for environments where multiple coding agents run in parallel — on one machine, or across a fleet of them behind a single address.
Agents that need a simulator grab whatever simctl / avdmanager shows them.
Two agents that pick the same device start fighting over it — booting, erasing,
and installing over each other — without ever knowing the other exists.
Simlock is a CLI-first control plane (backed by a local daemon) that is the
same for both platforms and gives agents one primitive: lease a device.
An optional local stdio MCP integration exposes the focused lease/release
workflow to compatible agent clients; the CLI remains the full operator
interface. An optional, token-authenticated HTTP API lets remote agents lease
devices from a self-hosted simlock host over the network (see
HTTP-API.md). A host process that wants to allocate devices
without spawning a simlock command at all — agent-device, for
example — can instead depend on simlock/client/simlock/admin, the typed
programmatic client the CLI and MCP frontends are themselves built on (see
CLIENT.md).
simlock leasereturns a ready device — booted and health-checked — that no other agent will touch for the duration of the lease.- If no matching device is free, simlock provisions one, up to a configurable capacity limit derived from the machine's CPU and RAM.
- If the limit is reached, the CLI blocks and waits in a fair queue until a
device frees up (with
--timeoutand--no-waitescape hatches). - Devices that sit unused are cleaned up in tiers: shut down after a short idle period (reclaim RAM), deleted after a longer one (reclaim disk).
One machine's worth of devices runs out eventually, and a team with several
Macs should not have to hand each agent a specific one. So a simlock daemon
runs in one of two modes. A worker is what every
daemon is today — it owns the devices on its machine — and joining a fleet
costs it two config keys pointing at a gateway; it keeps serving its own
local agents exactly as before. A gateway owns no devices at all: workers
dial out to it over a single WebSocket uplink (so a machine behind NAT
needs no inbound port), and it fronts them with one fleet-wide queue,
dispatching each request to the worker best placed to serve it — a warm
device if one is free, otherwise the machine with the most free capacity.
Agents and the web console point at one URL and stop caring which machine
a device lives on: a gateway speaks the same contract a worker does, so
lease, renew, release, status, and even simlock simctl / simlock adb (proxied to the owning worker) are the same commands they always were.
The gateway never touches a device itself — the capacity accounting, the
registry, and every safety invariant stay on the worker.
- Advisory coordination. Simlock does not sandbox anything. It works
because agents are instructed to never call
simctl/avdmanagerdirectly and to only use devices handed to them by a lease. - TTL leases, one kind everywhere. Every lease carries a TTL and lives
until it expires, is renewed, or is released — on the socket, over HTTP,
and over MCP alike.
simlock leasestays running in the background, renewing on a timer and releasing when it exits, so killing it normally frees the device at once;--detachskips the staying alive and leaves the renewing to you. A holder that dies without releasing costs its device only until the TTL runs out. - One lease per agent (v1) — fleet-wide when a gateway issued it.
- Managed-device registry. Simlock only ever shuts down, erases, or deletes devices it created itself. Everything else on the machine is read-only to it.
- Agent-first output. CLI lease results are one JSON line on stdout; progress (e.g. provisioning ETAs) streams as JSON lines on stderr. The optional MCP server reserves stdout for MCP JSON-RPC.
See CLI.md for the command surface, CLIENT.md for the programmatic client, and HTTP-API.md for the network API.