You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
The warm pool is its own module: targets, a reserve, and an off switch #359
Related: #350 (closed by this feature), #76 (its warm-target half is replaced by this feature; the host activation handshake stays there).
Problem
A lease is fast only when a clean device of the right kind is already running. Today that happens by accident: a device is warm only because someone leased it and released it, and capacity happened to allow it to keep running. The first lease of the day, and the first lease of each new model, always waits for a full boot. On a fresh identity machine every lease waits for one.
The operator has no say in it. There is no way to ask Simlock to keep two iPhone 17 ready before the agents arrive, no way to keep a slot free for a model nobody predicted, and no way to turn warmth off on a machine that needs its RAM back after every lease.
The rule that decides whether a released device stays warm is also wrong in one case. At the running cap it keeps the device only when it is exactly the model the oldest waiting request would create. A released iPhone 15 that fits a waiting --class phone request is shut down and an iPhone 17 booted for it (#350).
Who it is for
The operator of a worker, on a CI runner or a shared developer Mac, who knows which devices the agents on it ask for and wants the first lease to be as fast as the tenth. Also the agent whose request is waiting while a device that would do is being shut down.
Outcome
The operator can name targets: a kind of device and a count that Simlock keeps booted, clean and unleased, ahead of demand.
The operator can keep a number of running slots free of warm devices, so a request for an unpredicted model boots at once instead of shutting a warm device down first.
The operator can turn warmth off, so no idle device ever runs.
A released device that fits a waiting request stays warm and is granted to it.
A request for a device the pool is already booting waits for that boot instead of starting another.
simlock status shows each target, how many are ready and booting, and why it is short. simlock doctor reports a target that can never be met as configured.
How it works for the user
flowchart LR
A[daemon starts] --> B[pool pass: boot targets, one at a time]
B --> C{lease request}
C -->|a warm device fits| D[granted in under a second]
C -->|one is booting for a target| E[waits, granted when the boot ends]
C -->|nothing fits| F[boots or creates its own, as today]
D --> G[pool below target: refill in background]
H[lease released] --> I[device purged]
I -->|fits a waiting request, or room under the reserve| J[kept warm]
I -->|no room, or pool off| K[shut down]
The running limit minus the reserve is full of leased or warm devices
nothing; status says short, nothing is evicted
The RAM budget has no room for a full-size boot
nothing; status says short
A request is waiting for capacity
the request goes first; the target waits
Identity is fresh
a never-leased device is created; it serves one lease and is deleted; the next is created
What could go wrong
A target holds RAM and disk on a quiet machine for as long as it is configured. The operator sees it in status and can lower the count.
A target whose device keeps failing to boot is retried with a growing delay, up to ten minutes between tries, and shows as short with the reason while it waits.
A target that does not fit the limits looks like a bug to someone who does not read status. Doctor names the limit that stops it.
A restart with several targets boots them one after another, so the pool takes a couple of minutes to fill. Requests in that window are served as today.
A request for a device the pool is booting waits up to a boot's length. It would have booted its own for the same time.
With the pool off, every lease pays a boot, including on Android. That is what off means.
Words used
warm device: a device that is running, clean, and not leased. A request it fits is granted in under a second.
target: a standing order to keep a number of warm devices of one kind, e.g. two iPhone 17 on iOS 26.0 in the default mode. A floor, not a cap, and never filled by evicting anything.
kept device: a released device left running because a request it fits is waiting or there is room under the reserve. Kept devices follow the idle timers; targeted devices do not.
reserve: running slots per platform that warm devices may not take, so a request for an unpredicted model boots at once. Default none.
mode: slim or full. A slim device has simulator daemons disabled. A full request never gets a slim device and a slim request never gets a full one.
pool pass: one run of the warm pool over the machine, after a start, a grant, a release or a cleanup. It shuts down what is over, boots what is under, one boot at a time.
short: a target with fewer ready devices than wanted at the end of a pass that could do nothing for it, with one reason, the first that applies: pool disabled, no driver for the platform, runtime not installed, unknown model, request could not be resolved, last boot failed (waiting to retry), device limit, running limit, reserve, RAM budget. A target that is still filling, or waiting behind a request, is not short.
purge: cleaning a released device. iOS erases it, which leaves it shut down. Android restores its golden snapshot, which leaves it running. Purge is not warmth: with the pool off, Android is restored and then shut down.
fresh identity: lease.identity: fresh. Every lease gets a device that never served another and is deleted after. A target then pre-creates never-leased devices.
Non-goals
Targets on a gateway, or any fleet-wide warmth. A gateway keeps routing to the worker that has a warm device.
Changing targets while the daemon runs. Config is read at start.
A target naming a class instead of a model.
Evicting a leased or kept device to make room for a target.
Any change to the capacity limits, the RAM budget, or the idle timers for devices that are not targeted.
Completion conditions
With warmPool.targets naming two iPhone 17 and no leases, simlock status shows the target with ready 2 within two boots of daemon start, and the first simlock lease for that model is granted without a booting progress line.
With a target of two and both granted, status shows ready 0 booting 1, and after the boot ready 1.
With reserveRunning.ios: 1 on a running limit of 3 and two warm iPhones, a request for an iPhone 15 is granted after one boot and no warm device is shut down for it.
A request for a model the pool is booting for a target is granted when that boot ends, and device.provisioned does not fire for it.
A targeted warm device idle for longer than idle.shutdownAfterMs is still running; a kept device of another model is shut down at that time.
With lease.identity.ios: fresh and a target of one, status shows ready 1 before any lease, a lease is granted without booting, the released device is deleted, and status returns to ready 1 after one boot.
With warmPool.enabled: false, a released iOS device is shutdown after its purge, a released Android device is shutdown after its snapshot restore, and the next lease of each boots.
A target whose count exceeds the running limit minus the reserve shows short with the limit named in status, and simlock doctor reports it; nothing is shut down or evicted for it.
A target naming a runtime that is not installed shows short with the runtime named, doctor reports it with the install command, and no download starts under any downloads.policy.
With maxConcurrentBoots at its default and three targets of one each, simlock events shows each device.provisioned after the previous device's device.ready.
A count of 0, or a target with a class key, fails config load with an error naming the key.
On a Mac with an iOS runtime and an Android system image: the slow lane (e2e/slow-warm-pool.test.ts) proves a target boots a real simulator and a real emulator ahead of a lease, and that off shuts both down after a release.
Open questions
none
Decisions
ADR 0017 — The warm pool is a module beside the lease transaction, not a step in it (docs(adr): 0017 — the warm pool is a module beside the lease transaction, not a step in it #361, Accepted — not yet implemented): where warmth lives and how it relates to the lease path. It fixes the two components (reclaim and warm pool), the module's surface and pure policy, the dependency direction, the derived budget with the reserve, what a pass does, the boot-claim wait, and the status, doctor and event surface.
The warm pool module: one budget, a keep rule that fits, and an off switch #368 The warm pool module: one budget, a keep rule that fits, and an off switch — a released device that fits a waiting request is granted to it; warmPool.enabled: false keeps nothing warm. Risk: a device shut down while a request it fits waits, or kept over the limit.
A reserve of running slots, and a request waits for a device on its way #369 A reserve of running slots, and a request waits for a device on its way — a request for an unpredicted model boots at once; a request for a device the pool is booting waits for it. Risk: a request waits on a boot that fails until its deadline.
Spec updated: split into six tasks (#373, #368, #369, #370, #371, #372), Tasks section added, Decisions names ADR 0017 as accepted, the example and the hardware line corrected after the spec check.
Merged: none
Parked: #373 — PR #375 has one blocking finding (stale docs) still open after review round 3
Waiting: #368 — on #373; #369, #370, #371, #372 — on #368 and later tasks
Merged: #375 (#373)
Parked: #368 — the spec must say which waiting requests the pool boots for (queue head only, or every waiter with idle-shutdown skipping the booted device); round 2 also left one behaviour defect and stale docs on PR #386
Waiting: #369 — on #368; #370, #371, #372 — on #369 and later tasks
Merged: #386 (#368)
Parked: #369 — Done when line 1 names which released device stays down; the code only guarantees the count (PR #394, draft)
Waiting: #370, #371, #372 — on #369
Merged: none
Parked: #369 — two blocking test findings open after review round 3 (PR #394)
Waiting: #370 — on #369; #371 — on #369, #370; #372 — on #371
Merged: #394 (#369)
Parked: #370 — the session hit its token budget during the round 4 fix. Edits are uncommitted in worktree task-370; see the handoff on #370.
Waiting: #371 — on #370; #372 — on #371
All sub-issues (#368, #369, #370, #371, #372, #373) are closed; the last, #372, passed its slow-lane run on real devices. Closing. PR #419 (the slow-lane test for #372) is still a draft and has to be merged on its own. Written by an agent.
Request: none
Related: #350 (closed by this feature), #76 (its warm-target half is replaced by this feature; the host activation handshake stays there).
Problem
A lease is fast only when a clean device of the right kind is already running. Today that happens by accident: a device is warm only because someone leased it and released it, and capacity happened to allow it to keep running. The first lease of the day, and the first lease of each new model, always waits for a full boot. On a
freshidentity machine every lease waits for one.The operator has no say in it. There is no way to ask Simlock to keep two iPhone 17 ready before the agents arrive, no way to keep a slot free for a model nobody predicted, and no way to turn warmth off on a machine that needs its RAM back after every lease.
The rule that decides whether a released device stays warm is also wrong in one case. At the running cap it keeps the device only when it is exactly the model the oldest waiting request would create. A released iPhone 15 that fits a waiting
--class phonerequest is shut down and an iPhone 17 booted for it (#350).Who it is for
The operator of a worker, on a CI runner or a shared developer Mac, who knows which devices the agents on it ask for and wants the first lease to be as fast as the tenth. Also the agent whose request is waiting while a device that would do is being shut down.
Outcome
simlock statusshows each target, how many are ready and booting, and why it is short.simlock doctorreports a target that can never be met as configured.How it works for the user
flowchart LR A[daemon starts] --> B[pool pass: boot targets, one at a time] B --> C{lease request} C -->|a warm device fits| D[granted in under a second] C -->|one is booting for a target| E[waits, granted when the boot ends] C -->|nothing fits| F[boots or creates its own, as today] D --> G[pool below target: refill in background] H[lease released] --> I[device purged] I -->|fits a waiting request, or room under the reserve| J[kept warm] I -->|no room, or pool off| K[shut down]Before, on a machine that has just started:
After, with a target of two iPhone 17:
A target the machine cannot fill:
A target for a runtime that is not installed:
Turning warmth off:
Examples
Config keys. Today every key under
warmPoolexceptquarantine.*is unknown: a warning at start, then ignored.warmPool.enabled: falsewarmPool.reserveRunning: { "ios": 1 }warmPool.reserveRunning: { "ios": 3 }on a limit of 3warmPool.targets: [{ "platform": "ios", "model": "iPhone 17", "count": 2 }]"mode": "slim"fullrequest never gets one"osVersion": "27.0", not installed"count": 0"class": "phone"warmPool.maxConcurrentBoots: 2What happens to a released device, at the running cap, with one request waiting.
--model "iPhone 17" --os 26.0--class phone--class phone --mode full--model "iPhone 17"What a target does when the pool is short.
freshWhat could go wrong
Words used
lease.identity: fresh. Every lease gets a device that never served another and is deleted after. A target then pre-creates never-leased devices.Non-goals
removing/removedidentity status of Host activation handshake and removal status for warm identities #76.Completion conditions
warmPool.targetsnaming two iPhone 17 and no leases,simlock statusshows the target withready 2within two boots of daemon start, and the firstsimlock leasefor that model is granted without abootingprogress line.ready 0 booting 1, and after the bootready 1.reserveRunning.ios: 1on a running limit of 3 and two warm iPhones, a request for an iPhone 15 is granted after one boot and no warm device is shut down for it.--class phonerequest waiting, releasing an iPhone 15 that fits grants it that device; no other device boots. This is the A released device that fits a waiting class request is shut down and another booted #350 case and closes it.device.provisioneddoes not fire for it.idle.shutdownAfterMsis still running; a kept device of another model is shut down at that time.lease.identity.ios: freshand a target of one, status showsready 1before any lease, a lease is granted without booting, the released device is deleted, and status returns toready 1after one boot.warmPool.enabled: false, a released iOS device isshutdownafter its purge, a released Android device isshutdownafter its snapshot restore, and the next lease of each boots.shortwith the limit named in status, andsimlock doctorreports it; nothing is shut down or evicted for it.shortwith the runtime named, doctor reports it with the install command, and no download starts under anydownloads.policy.maxConcurrentBootsat its default and three targets of one each,simlock eventsshows eachdevice.provisionedafter the previous device'sdevice.ready.countof 0, or a target with aclasskey, fails config load with an error naming the key.e2e/slow-warm-pool.test.ts) proves a target boots a real simulator and a real emulator ahead of a lease, and that off shuts both down after a release.Open questions
none
Decisions
Tasks
warmPool.enabled: falsekeeps nothing warm. Risk: a device shut down while a request it fits waits, or kept over the limit.Written by an agent.