The map. Each module's manual (src/<module>/AGENTS.md) says how it works;
each flow doc under flows/ follows one path across modules; the
ADRs say why.
A worker is one daemon owning the simulators and emulators (and enrolled
phones) on one machine. A gateway owns no device; workers dial out to it
and it fronts them as one fleet. mode in the config picks which a daemon is.
agent ─spawns─> simlock CLI ──┐
MCP client ─> stdio MCP ──────┼─ simlock/client ── unix socket ──┐
│ ▼
remote agent ── bearer token ─> HTTP frontend ── in process ──> Dispatcher ── handlers
console (browser) ────────────┘ │
worker: leasing + core ── drivers ── simctl / adb / devicectl
gateway: fleet coordinator ── worker views
▲
worker daemon ── uplink (WebSocket to /v1/uplink, worker dials) ──┘
- One contract, one
Dispatcherper daemon, every transport. HTTP calls the dispatcher in process, never through the socket. - A gateway is a second set of handlers for the same contract. Every frontend works against either mode unchanged.
- State lives on the worker (
state.db, its registry tables written only by core'sRegistry; the warm pool's learned kinds in its own tables). A gateway keeps only its tokens, its instance id and its drained-worker set on disk (state.db); everything else is rebuilt from the workers that reconnect.
| Module | Does | Manual |
|---|---|---|
| contract | Every operation, schema, role, error code, protocol range | contract |
| modules | Optional-module contract and the host that loads each turned-on module | modules |
| module-registry | The list of optional modules, read by the composition root and the CLI | module-registry |
| daemon | Composition root, socket server, Dispatcher, sessions, uplink dialler |
daemon |
| leasing | Every lease rule: admit, queue, plan, grant, renew, end | leasing |
| core | Registry, device lifecycle, reclaim, quarantine, cleanup, components, doctor | core |
| core/warm-pool | What stays booted, and warm targets | warm-pool |
| core/capacity | Limits, RAM budget, reservations | capacity |
| core/usage | Usage figures from the history rows, read on a thread | usage |
| drivers | The Driver port's two implementations |
drivers · android · android screen · ios |
| gateway | Fleet queue, routing, forwarding to workers, relay | gateway · routing |
| alerts | Gateway only: chosen fleet events POSTed to webhook destinations | alerts |
| link | Byte channels over the uplink | link |
| agent-device | Optional edge: the two agent-device daemons (simulators, phones) beside a worker, its HTTP front | agent-device |
| stream | Optional edge: a running device's live screen over HTTP, relayed by a gateway | stream |
| auth | Credential → identity: tokens, admin secret, hello, uplink join token, HTTP strategies |
auth |
| http | HTTP frontend: routes, SSE | http |
| cli | Operator command surface | cli |
| mcp | stdio MCP server for one agent session | mcp |
| simlock-client, client, admin | The typed programmatic client (simlock/client, simlock/admin) |
simlock-client · client · admin |
| updater | The detached process that installs a Simlock version with npm, restarts the daemon and rolls back | updater |
| lease-policy | Client-side renew timer | lease-policy |
| bus | Post-commit business events and their in-memory ring | bus |
| state | state.db: opens it, refuses an old file, damage and a newer version, migrates each owner's tables |
state |
| history | The durable event history: history.db, prune, set-aside, projections (usage: request, minute and device fact rows) |
history |
| ports | Every external API behind an interface, with a fake | ports |
| host-load | CPU, memory and disk load samples | host-load |
| instructions | The static rules text for agents: simlock instructions, the MCP server instructions and resource; simlock setup, which installs the router skill |
instructions |
| agent-identity | Default requester id for CLI and MCP | agent-identity |
| daemon-protocol | Socket framing and the protocol version | daemon-protocol |
| test-support | Shared test helpers | test-support |
ui/ |
Web console, served by the daemon: data, model, state, design system and screens in layers | ui · console.mdx |
Arrows point from the optional to the required (architecture rule 15).
pnpm lint (.dependency-cruiser.mjs, ADR 0027) enforces each line:
| Rule | Means |
|---|---|
past-index |
Outside a component, import only its index file (tests may also import its testing file) |
core-not-leasing |
Core declares a port; the composition root connects leasing to it |
core-not-drivers |
Core and leasing reach a platform only through the Driver interface |
no-fs-or-process |
Everything under src/ but ports/ uses ports, never fs or child_process |
definition-is-light |
A module's definition file imports its own module only for types or by dynamic import |
auth-imports-little |
src/auth imports only ports and contract |
modules-imports-little, modules-imports-bus-types-only |
src/modules imports only ports, contract and core, and the bus for types |
not-modules |
Core, leasing, drivers, gateway and contract import nothing from modules or module-registry |
not-agent-device |
Core, leasing and drivers import nothing from agent-device |
host-load-imports-ports-only, not-host-load |
host-load stands alone |
alerts-imports-bus-ports-core-only, not-alerts |
alerts imports the bus, ports and core only; only src/daemon/main.ts imports it |
not-stream, stream-not-edges |
Nothing required imports stream; stream imports no frontend, gateway or driver |
gateway-not-edges, gateway-only-dispatch |
Gateway imports no driver or frontend, and from daemon only src/daemon/dispatch.ts |
only-main-knows-gateway |
In daemon, only src/daemon/main.ts imports the gateway |
link-is-portable, link-imports-port-types-only |
The link uses no Node module or package, and only the types of ports |
contract-imports-nothing |
The contract imports nothing outside itself |
A directory under src/ with an index file is a component. Two components
never import each other, directly or through a third.
| Flow | Doc |
|---|---|
| A lease, from request to reclaim | leasing |
| A lease through a gateway | gateway |
| Which device a request gets | device-selection |
| Startup and stop | startup |
device.exec, local and through a gateway |
device-exec |
| agent-device through simlock | agent-device |
| A screen stream, local and through a gateway | stream |