This document defines the implementation and release gates for the opt-in,
experimental warm-cache mode. Ordinary start and stop remain the default
ephemeral contract and continue to create a new MicroVM and one GitHub JIT
runner for each target job.
Warm-cache mode reuses a Lambda MicroVM's disk and memory state for trusted jobs within the platform's eight-hour maximum lifetime. It does not reuse a GitHub runner registration. Every target job receives a new JIT configuration, runner ID, runner name, and unique label.
The intended product description is:
Reuse a suspended Lambda MicroVM between trusted jobs for up to eight hours. Docker layers, package caches, and toolchains survive, while every job uses a fresh GitHub JIT runner registration.
- Preserve Docker images, Docker build cache, package-manager caches, and downloaded toolchains across trusted jobs.
- Suspend the MicroVM between jobs so idle periods do not incur compute charges.
- Retain the current one-job GitHub JIT scheduling model.
- Keep the existing ephemeral workflow unchanged and safe by default.
- Deliver the first working version without DynamoDB or another state service.
- Add cross-workflow discovery and concurrency control only after local suspend/resume behavior is proven.
- Continue supporting
overlay2, copy-on-writefuse-overlayfs, and the final productionvfsfallback. - Keep setup script-driven and avoid Terraform.
- Durable caches beyond the lifetime of one MicroVM.
- Sharing a warm MicroVM between repositories or trust domains.
- Supporting public fork pull requests.
- Making a reused machine equivalent to a clean ephemeral machine.
- Building an internal webhook dispatcher, unbounded queue, or autoscaling control plane in the first two phases.
- Preserving a persistent GitHub runner registration between jobs.
GitHub recommends ephemeral runners for autoscaling and warns that a persistent runner can receive work while an orchestrator believes that it is unavailable. Warm-cache mode therefore starts a new JIT runner process for each job. The process exits and is deregistered after that one job, while the supervisor and the cached MicroVM remain available for suspension.
See GitHub's self-hosted runner reference.
A workflow has root-equivalent access through Docker and can modify any cache that a later job consumes. A fresh JIT registration prevents accidental job assignment to an old runner identity, but it does not clean the filesystem or memory. Warm reuse is supported only when every job sharing a server name is equally trusted.
Once DynamoDB is introduced, every lease mutation uses a conditional write and a monotonically increasing generation. An expired workflow must not be able to stop or suspend a MicroVM after another workflow has acquired it.
Explicit stop is the normal path. Workflow always() cleanup, on-access
reconciliation, idle suspension, and Lambda's maximum duration are independent
backstops. Warm-cache operations reconcile only the item they touch; there is no
scheduled garbage collector or table scan. DynamoDB TTL eventually removes
expired metadata and is never treated as a MicroVM termination mechanism.
Record the real platform lifetime instead of adjusting the launch timestamp:
platformExpiresAt = observedLaunchTime + maximumDurationSeconds
reuseDeadline = platformExpiresAt - reuseSafetyMarginSeconds
The default safety margin is 30 minutes. A MicroVM at or beyond its reuse deadline is never assigned another job. If it is encountered while still running or suspended, the Action terminates it idempotently instead of suspending or resuming it. Nothing executes automatically at the reuse deadline: it is a decision boundary for the next Action call. An untouched MicroVM remains subject to Lambda's platform expiry.
Prefer the launch timestamp returned by AWS. If the service response does not
provide one, record the local time immediately before RunMicrovm, which
expires the cache conservatively earlier rather than later. The safety margin is
configurable because it cannot guarantee completion of a job that runs longer
than the remaining lifetime.
The public lifecycle remains the existing two modes and uses one server
input/output:
startwithoutserver: preserve today's ephemeral behavior.startwith a human-readableserver: lease an available member of that warm pool, or create a member when the request's capacity permits it.startwith an opaquelmvm1_...server value: resume the exact explicit no-DynamoDB MicroVM used by the Phase 2 proof.stopwith legacymicrovm-id: terminate an ephemeral MicroVM.stopwith the opaqueserverreturned by warmstart: conditionally release that exact lease and suspend it. At or beyond its reuse deadline, terminate it instead.
Every DynamoDB-backed warm operation performs targeted reconciliation for its
pool. There are no additional lifecycle modes and no gc mode.
Proposed inputs:
| Input | Phase | Purpose |
|---|---|---|
server |
2-4 | Human pool name on start, or opaque exact lease returned by warm start |
server-capacity |
4 | Optional ceiling for this request creating another pool member |
state-table |
3-4 | DynamoDB table used for pool discovery and leases |
lease-timeout-seconds |
3-4 | Abandoned-lease deadline; defaults to max-lifetime-seconds |
max-lifetime-seconds |
2-4 | Platform lifetime; defaults to 7,200 and cannot exceed 28,800 seconds |
reuse-safety-margin-seconds |
2-4 | Required lifetime remaining before another job; defaults to 1,800 seconds |
microvm-id |
legacy | Direct termination compatibility for the existing ephemeral lifecycle |
Proposed outputs:
labelrunner-namerunner-idimage-versionserverwarm-hitwarm-expires-atreuse-deadline
The opaque server value identifies one exact resource and lease generation. It
intentionally contains no AWS credentials, GitHub tokens, endpoint auth tokens,
or JIT configuration data, and must remain usable as a GitHub job output. A
stale warm handle fails its conditional release without making an AWS lifecycle
call; possession of a handle does not bypass AWS authorization.
server-capacity is deliberately not durable pool configuration. It controls
only whether the current start request may add another member when every
existing member is busy:
- If any healthy member is available, lease it regardless of the supplied capacity or current pool size.
- If none is available and
server-capacityis omitted, reserve and create a new member without an Action-level pool bound. - If none is available and the active member count is below the supplied capacity, reserve and create one member.
- Otherwise fail with a clear pool-at-capacity error.
Capacity never shrinks or terminates existing members. If one workflow supplies
2 and another supplies 3, the latter can grow the pool to three while the
former can grow it only while fewer than two members exist. Omitting capacity
allows growth to observed concurrency. Users are responsible for keeping
workflow values consistent when they want a stable bound.
Warm mode requires a control path for delivering a new JIT configuration after a
MicroVM resumes. Lambda's /resume lifecycle hook has no caller-provided
payload, so it cannot carry the next job's configuration.
The runner image will expose a dedicated control server on a separate port from the Lambda lifecycle-hook server. The Action will:
- create or resume the selected MicroVM;
- create a new repository JIT runner with a unique label;
- request a short-lived, port-scoped MicroVM authentication token;
- send the masked JIT payload to the control endpoint;
- wait for the exact GitHub runner ID and name to become online.
All Lambda MicroVM endpoint requests require a service-issued JWE token. The Action token will allow only the control port and will expire as soon as practical. The lifecycle-hook port must not be exposed through the ingress connector. See the AWS MicroVM networking guide.
The control protocol should be small and versioned:
POST /v1/runner/start
{
"requestId": "collision-resistant idempotency value",
"microvmId": "expected MicroVM ID",
"jit": "encoded GitHub JIT configuration"
}
The endpoint accepts a request only while the supervisor is idle. Repeating an
accepted requestId returns its existing outcome without starting a second
runner. Request bodies, authentication tokens, and JIT values are never logged.
The warm supervisor extends the current single-use state machine:
SNAPSHOTTED
|
v
IDLE <---- runner exits after one job
| ^
| start fresh JIT |
v |
STARTING_DOCKER -> STARTING_RUNNER -> RUNNING
|
+---- failure ----> FAILED
IDLE -> SUSPENDING -> SUSPENDED -> RESUMING -> IDLE
any non-terminal state -> TERMINATING
For an ephemeral launch, the existing behavior remains: runner exit triggers
self-termination. For a warm launch, runner exit clears the process and secret
references, records an idle result, and leaves suspension or termination to
stop, idle policy, or the platform duration limit.
The /suspend hook must stop Docker and containerd cleanly, flush logs and
filesystem writes, and reject suspension while a runner is busy. Stopping the
daemons must preserve /var/lib/docker. The /resume hook restarts Docker,
accepting overlay2, fuse-overlayfs, or the final production vfs fallback,
validates networking, and leaves the supervisor idle until a new JIT payload
arrives.
Prove the image and supervisor state machine locally before introducing AWS lifecycle and GitHub scheduling variables.
- Version the run-hook envelope so the image distinguishes
ephemeralfromwarmlaunches. - Add the dedicated control server and reject malformed, oversized, duplicate-conflicting, wrong-MicroVM, and non-idle requests.
- Make a warm runner exit return to
IDLE; preserve ephemeral self-termination. - Stop Docker and containerd, call
sync, and preserve/var/lib/dockerbefore suspension. Restart and validate them after resume. - Prove
overlay2,fuse-overlayfs, and the always-available productionvfsfallback. - Run Node 24 and Redis service-equivalent containers after a local suspend/resume hook cycle.
Prove the core claim with the fewest cloud-side moving parts: two different
GitHub JIT jobs use one MicroVM and observe the same Docker cache across a real
AWS suspend/resume boundary. The opaque server output carries the exact
MicroVM between control jobs. There is no discovery, lease table, or
cross-workflow pool.
- Extend the AWS client with
SuspendMicrovm,ResumeMicrovm,CreateMicrovmAuthToken, and the MicroVM endpoint returned byRunMicrovmandGetMicrovm. - Extend the existing
startandstopparsing with the optional unifiedserverinput/output. Inputs without it preserve today's behavior. - Add bounded, full-jitter retries and state polling for suspend and resume.
- Add the minimum IAM permissions for suspend, resume, and short-lived MicroVM endpoint tokens. Do not add DynamoDB permissions yet.
- Add an experimental workflow that passes the opaque
serveroutput through two start/target/stop cycles. - Validate
max-lifetime-seconds, default it to 7,200, reject values over 28,800, and derive expiry and reuse deadline from AWS's observed start time. - Document that cancellation can leave the experimental VM suspended or running until the platform lifetime backstop fires.
prepare-first:
runs-on: ubuntu-latest
outputs:
label: ${{ steps.runner.outputs.label }}
server: ${{ steps.runner.outputs.server }}
steps:
- id: runner
uses: neebs12/lambda-microvm-github-runner@ref
with:
mode: start
server: phase2-proof
# The remaining image, role, token, and Region inputs are unchanged.
first-job:
needs: prepare-first
runs-on: ${{ needs.prepare-first.outputs.label }}
steps:
- run: docker pull public.ecr.aws/docker/library/node:24
- run: docker build -t warm-cache-proof .
stop-first:
if: always()
needs: [prepare-first, first-job]
runs-on: ubuntu-latest
steps:
- uses: neebs12/lambda-microvm-github-runner@ref
with:
mode: stop
server: ${{ needs.prepare-first.outputs.server }}
prepare-second:
needs: [prepare-first, stop-first]
runs-on: ubuntu-latest
outputs:
label: ${{ steps.runner.outputs.label }}
server: ${{ steps.runner.outputs.server }}
steps:
- id: runner
uses: neebs12/lambda-microvm-github-runner@ref
with:
mode: start
server: ${{ needs.prepare-first.outputs.server }}
second-job:
needs: prepare-second
runs-on: ${{ needs.prepare-second.outputs.label }}
steps:
- run: docker image inspect warm-cache-proofThe complete test workflow must include a final terminating cleanup after the
proof. The test harness may use legacy microvm-id for that terminal cleanup;
normal warm workflow cleanup uses the opaque server value.
- The two target jobs have different runner IDs, names, labels, and JIT configurations.
- Both target jobs report the same MicroVM ID.
- AWS reports
SUSPENDEDbetween the target jobs. - The second job finds the first job's Docker image without pulling or rebuilding the unchanged layers.
- Node 24 job containers and Redis service containers work after resume.
- The proof passes with
overlay2, forcedfuse-overlayfs, and forced finalvfsfallback. - The existing ephemeral E2E matrix remains unchanged and green.
- No JIT value, endpoint authentication token, PAT, or AWS secret appears in Action, supervisor, runner, or CloudWatch logs.
Allow independent workflow runs to request a repository-scoped server name and lease a healthy suspended MicroVM without knowing its ID. This phase initially proves one-member discovery, fencing, expiry, and on-access recovery; it fails clearly when that member is busy.
The Quickstart script creates one on-demand DynamoDB table using the operator's
existing local AWS credentials. The stored Quickstart IAM user receives only
data-plane access to that exact table. It must not receive CreateTable,
UpdateTable, or IAM administration permissions.
Suggested keys:
PK = REPOSITORY#<repository-id>#SERVER#<sha256(effective-server-key)>
SK = CONTROL
SK = MEMBER#<member-id>
The effective server key includes the server name plus Region, architecture,
image ID and version, execution role, and security-sensitive network
configuration. The CONTROL item contains only the transactional member count
and revision needed to reserve creation safely; it does not store an
authoritative capacity. Each MEMBER item describes one MicroVM and lease.
Suggested attributes:
| Attribute | Purpose |
|---|---|
memberId |
Stable pool member identity |
microvmId |
AWS resource backing the member |
imageId and imageVersion |
Prevent reuse after an image change |
region |
Prevent cross-Region lookup mistakes |
state |
CREATING, READY, LEASED, SUSPENDING, DESTROYING, or DEAD |
leaseId |
Opaque current owner token |
leaseGeneration |
Monotonic fencing value |
leaseOwner |
Repository run ID, attempt, and control job identity |
leaseExpiresAt |
Recovery deadline for an abandoned acquisition |
createdAt |
Real service-observed or conservatively recorded launch time |
microvmExpiresAt |
Platform maximum-duration deadline |
reuseDeadline |
Last safe time at which another job may acquire the MicroVM |
lastUsedAt |
Reuse and cleanup metadata |
ttl |
Eventual metadata removal after the platform deadline |
Do not store GitHub tokens, JIT configurations, AWS credentials, MicroVM auth tokens, or workflow secrets in DynamoDB.
- Normalize and hash the server name together with repository ID, Region, architecture, image ID and version, execution role, and network fingerprint.
- Read that pool partition consistently and reconcile its expired members.
- Prefer the most recently used healthy
READYmember whosereuseDeadline > now. Conditionally change it toLEASED, set a new lease ID, and increment its generation. - If the lease belongs to the same idempotency identity, return or finish the previous acquisition instead of acquiring a second member.
- If no member is available, evaluate this request's
server-capacity. Omitted capacity permits creation; supplied capacity permits creation only when the active count is lower. - Reserve a
CREATINGmember and increment the active count atomically. The count check and reservation must be one DynamoDB transaction so simultaneous starts cannot overshoot the requesting bound. - Launch the reserved member using a client token derived from its identity and generation.
- Resume a reused member when required, create a new JIT registration, deliver it through the control endpoint, and wait for the exact runner identity.
- On any missing, terminal, incompatible, or otherwise unrecoverable member, conditionally retire it, correct the active count, and continue through the same selection rules.
- Decode the opaque
serverand require the repository identity, server key hash, member identity, lease ID, and generation. - Verify the GitHub JIT runner is gone or unambiguously idle.
- Conditionally change
LEASEDtoSUSPENDINGonly when the lease ID and generation match. - Recalculate remaining useful lifetime. At or beyond
reuseDeadline, change the owned item toDESTROYING, terminate the MicroVM idempotently, and remove or terminally mark the item instead of suspending it. - Otherwise suspend the MicroVM and poll until AWS reports
SUSPENDED. - Conditionally change
SUSPENDINGtoREADY, clear lease ownership, and recordlastUsedAt. - If a condition fails, make no AWS lifecycle call. A newer owner may have acquired the item.
There is no scheduled cleanup workflow. Warm start and stop calls reconcile
only the server pool partition named by their inputs or handle. Ordinary
ephemeral start and stop calls do not scan or mutate warm-cache state.
On each access, the Action:
- repairs an expired
CREATING,SUSPENDING, orDESTROYINGtransition for that item; - treats a missing or platform-terminated MicroVM as stale metadata and replaces it;
- terminates an expired MicroVM if AWS still reports it as present;
- retires entries for incompatible or inactive image versions;
- never mutates an unexpired lease owned by another workflow;
- uses the same conditional generation checks as warm
startandstop.
If no workflow touches a member again, Lambda terminates it at its hard maximum
duration. Its table item can remain temporarily without causing a resource leak.
Set its DynamoDB TTL after microvmExpiresAt with a metadata grace period. TTL
deletion is deliberately eventual: a later start does not wait for it and
instead rejects the member from its recorded deadline.
Create the CREATING item before calling RunMicrovm and derive the AWS client
token from the item identity and generation. If the Action stops after the AWS
call but before recording the returned MicroVM ID, a later start can repeat the
idempotent launch and recover the same result. If the key is never touched
again, the platform still terminates the unrecorded MicroVM at its maximum
duration.
This model deliberately accepts that unused suspended MicroVMs continue to
consume regional MicroVM memory quota until their platform lifetime deadline.
Use a shorter max-lifetime-seconds when a workflow wants a smaller natural
cleanup window without adding a scheduler.
- A later workflow run using the same effective server key prefers a healthy available pool member and receives a new JIT runner identity.
- Different repositories, Regions, architectures, and image versions cannot collide.
- Exactly one workflow can own each member lease generation.
- A stale owner cannot stop, suspend, or rewrite a newer lease.
- Cancellation and expired leases recover on the next access without manual table edits.
- A missing or terminated MicroVM is replaced without returning its stale label.
- A start at or after
reuseDeadlinecreates a replacement instead of resuming the old MicroVM. - A stop at or after
reuseDeadlineterminates instead of suspending. - The Quickstart setup and teardown scripts create and remove only their exact DynamoDB resources and policies.
Allow a server name to behave as a pool when workflows overlap, while keeping capacity deliberately simple and request-local. Capacity is not persisted as pool configuration and no workflow can silently reconfigure another workflow.
- Every start first leases any available healthy member, regardless of its request's capacity value.
- If no member is available and
server-capacityis omitted, start reserves and launches another member without an Action-level bound. - If no member is available and capacity is supplied, start creates a member only when the active count is below that request's bound; otherwise it fails clearly.
- The active-count check, member reservation, and increment happen in one DynamoDB transaction.
- Concurrent starts create distinct members without overshooting the bound evaluated by each successful transaction.
- A request with capacity
3may grow a pool beyond a bound of2used by another request. The request with2neither shrinks nor terminates members. - Twenty concurrent acquisitions with capacity
5produce at most five active reservations. Mixed capacities2and3, omitted capacity, retries, and stale lease generations are covered by deterministic adversarial tests.
Benchmark harnesses, raw measurements, and research notes are maintained outside this product repository. Public articles and reproducible summaries should be linked from the README as they are published. These experiments complement the workflow E2Es below by isolating cache and suspend/resume behavior from GitHub queueing and runner-registration time.
Add deterministic tests for:
- parsing every new mode and rejecting conflicting manual/DynamoDB inputs;
SuspendMicrovm,ResumeMicrovm,CreateMicrovmAuthToken, endpoint parsing, and safe handling ofResourceNotFoundException;- suspend and resume polling across every valid and terminal AWS state;
- control-request payload size, validation, masking, and idempotency;
- cleanup after JIT creation, resume, control delivery, and runner-readiness failures;
- DynamoDB key normalization and repository scoping;
- conditional start, idempotent retry, stop, and on-access reconciliation;
- fencing behavior with stale lease IDs and generations;
- timestamps at maximum duration, the 30-minute reuse margin, lease expiry, and clock boundaries;
- safe user-facing errors that expose no tokens or JIT data.
Use injected clocks, random sources, sleepers, GitHub clients, MicroVM clients, and DynamoDB clients. Unit tests must not depend on wall-clock sleeps or live AWS resources.
Extend the Python suite to prove:
- warm
/runreachesIDLEwithout starting a GitHub runner; - the control endpoint rejects malformed, oversized, duplicate-conflicting, wrong-MicroVM, and non-idle requests;
- an idempotent repeated request does not start a second process;
- JIT runner exit returns warm mode to
IDLEwithout self-termination; - ephemeral mode still self-terminates after runner exit;
- suspend refuses a busy runner and flushes/stops Docker cleanly when idle;
- resume accepts working
overlay2,fuse-overlayfs, and the automaticvfsfallback; - terminate stops runner, Docker, containerd, and the control server;
- process arguments, exceptions, and logs never contain the JIT fixture;
- concurrent control calls cannot create two runner processes.
The packaged ARM64 image test should:
- start Docker and pull a pinned fixture image;
- build a uniquely tagged local image and populate BuildKit cache;
- invoke the suspend hook and verify Docker/containerd stop successfully;
- invoke the resume hook and verify Docker restarts;
- verify the image and unchanged build layers still exist;
- repeat with forced
overlay2failure and confirmfuse-overlayfspreserves functional cache state, then force both copy-on-write drivers to confirm the finalvfsfallback starts; - run a Node container and a Redis service-equivalent container after resume.
Local hook calls do not prove AWS snapshot behavior. They are a fast gate before the AWS tests.
Run in an independent private repository using temporary AWS and GitHub credentials. Record the workflow URL, MicroVM IDs, runner IDs, labels, image version, storage driver, and relevant timestamps without recording secrets.
The primary scenario must prove:
- first warm
startlaunches one MicroVM and one JIT runner; - the first target populates Docker pull, build, package, and tool caches;
- warm
stopreaches AWSSUSPENDED; - second warm
startresumes the same MicroVM and starts a different JIT runner; - the second target proves cache presence and runs container/service-container jobs successfully;
- final legacy
stopreachesTERMINATED; - GitHub contains no leftover self-hosted runner registration.
Repeat with:
- target success, failure, timeout, and workflow cancellation;
- warm stop retry and terminating stop retry;
- resume-hook failure and Docker startup failure;
- expired MicroVM maximum duration;
overlay2, forcedfuse-overlayfs, and forced finalvfsfallback;- Node 24 job container plus Redis service container;
- no-op build and changed-layer build to distinguish a real cache hit from a merely preserved image tag.
Use a temporary table and repository-scoped IAM policy. Test:
- 20 concurrent starts with capacity
5: no more than five members are created and each successful start owns a different member lease; - an idempotent rerun of a winning start returns the same lease;
- two different server keys can proceed independently;
- mixed capacity
2and3: the larger request can create the third member, the smaller request never shrinks it, and both reuse any available member; - omitted capacity can create another member when all existing members are busy;
- supplied capacity at or below the active busy-member count fails clearly;
- concurrent count-and-create transactions do not overshoot the requesting bound;
- stale stop after a newer generation is acquired: the stop is rejected and the newer MicroVM remains running;
- cancellation before JIT delivery, during the target, and during suspend;
- table item exists but MicroVM does not;
- MicroVM exists but table state is stale;
- image version changes between stop and the next start;
- lease expiration followed by on-access reconciliation and another start;
- start one second before, exactly at, and one second after
reuseDeadline; - stop before and after
reuseDeadline; - allow an untouched suspended MicroVM to reach its platform deadline, then verify the next start replaces its stale member;
- expired DynamoDB TTL that has not yet been physically deleted;
- denied suspend/resume/auth-token/DynamoDB permissions with sanitized errors;
- Quickstart teardown with running and suspended test VMs.
- Confirm warm mode is rejected for fork-originated pull requests and document its trusted-workflow requirement.
- Confirm the control port is inaccessible without a valid MicroVM auth token, with an expired token, and with a token scoped to another port.
- Confirm the lifecycle-hook port and shell ingress are not externally exposed.
- Search Action, runner, supervisor, and CloudWatch logs for the JIT fixture, PAT fixture, AWS secret fixture, auth-token fixture, and lease fixture.
- Verify the runtime role cannot create endpoint tokens or read the DynamoDB table unless explicitly required by the final design.
- Verify target workflow credentials cannot operate the warm-cache control plane.
- Demonstrate cache poisoning between two trusted test jobs and document that cache reuse is not an isolation boundary.
- Best-effort cleanup should remove
_workcontents, temporary files, stopped containers, anonymous volumes, and known credential files without deleting Docker layers or configured package caches. Do not claim that cleanup defeats a malicious root-equivalent previous job.
Collect, but do not initially enforce, these metrics:
- cold MicroVM launch to runner online;
- suspended MicroVM resume to runner online;
- suspend duration;
- first and second Docker pull duration;
- first, unchanged, and changed-layer Docker build duration;
- Node/npm dependency installation duration;
- snapshot bytes written and read where AWS exposes them;
- running compute time, suspended storage time, and warm-cache hit rate;
- behavior as Docker cache size grows.
Phase 2 is successful only if the second job demonstrates an actual local cache hit. A faster wall-clock result alone is insufficient evidence.
| Failure | Required result |
|---|---|
| JIT creation fails | Do not resume or mutate an available cached VM |
| Resume fails | Delete unused JIT runner and preserve or destroy VM according to observed state |
| Control delivery fails | Delete unused JIT runner; retry only with the same idempotency identity |
| Runner never becomes online | Delete JIT runner and suspend or destroy the VM |
| Target job fails | Warm stop still suspends through always() |
| Workflow is cancelled | Lease expires; next access reconciles it and platform duration is the resource backstop |
| Suspend fails | Do not mark the item READY |
| Stale stop arrives | Conditional write fails and no AWS lifecycle call is made |
| VM is already terminated | Mark state DEAD and replace on the next start |
| Image version changes | Never resume the old VM for the new key |
Before the feature is described as stable:
- add a warm-cache example with an explicit warning about the trust boundary;
- update installation, IAM, security, operations, teardown, and quota docs;
- document snapshot charges and the eight-hour hard lifetime;
- document request-local
server-capacity, unbounded omission, and pool-at-capacity errors; - preserve the ordinary ephemeral example as the recommended default;
- pass all existing Action and image gates without modifying their expected lifecycle;
- pass the full private-repository Phase 2 and Phase 4 matrices;
- review logs manually for secrets;
- publish Action and runner-image SBOMs and checksums;
- keep warm mode experimental until cancellation, stale leases, forced
fuse-overlayfs, forcedvfs, and adversarial concurrency tests pass.
Do not move the stable major tag based only on the no-DynamoDB proof. Phase 2 is an implementation spike and evidence-gathering release; Phases 3 and 4 must complete before warm-cache mode is marketed as cross-workflow functionality.