Skip to content
Console
Menu

Queues

Workflows

Getting Started

Authentication

KV Store

Lease lifecycle

How a lease lives, why it ends, and how to watch it happen

A lease is granted, runs, and ends. Everything in between is visible: the state is on the lease, the ends are typed, and every step is an event you can read in order or long-poll for.

#The states

status.state is one of seven:

  • requested — the call was accepted and a machine is being assigned.
  • granted — a machine is assigned, and the guest is booting.
  • ready — the guest answers. Everything in the data plane needs this state.
  • paused — the disk is kept and the machine is destroyed.
  • ending — the end has begun, and status.end_reason is set.
  • ended — the lease is finished. It never returns to another state.
  • refused — no machine was granted, and status.refusal says why.

status.conditions carries the platform's own view beside the state: Ready, Reconciling and Stalled, each with status, reason and a customer-safe message.

#The two clocks

A lease has two ways to run out, and either ends it.

ttl is the maximum wall time from the grant, and it is required. It runs from one minute up to the longest lease the shape allows — every shape in the catalog today caps it at 24 hours, and each one reports its own bounds in min_lease_duration and max_lease_duration. Reaching it ends the lease with the expired reason. renew moves expire_time out, never past the shape's longest lease, and does not change the lease's generation:

Shell
sylphx sandboxes leases renew orgs/acme/projects/shop/envs/production/leases/lease --ttl 1h

idle_timeout is the other clock: no data-plane call, no stream input and no exec for that long ends the lease IDLE. Leave it unset and an unused lease lives until its ttl.

The two are complementary rather than redundant. A ttl bounds what a lease may cost; an idle_timeout bounds what an abandoned one may cost. A lease that serves an interactive user wants both.

#Pause and resume

pause destroys the machine and keeps the disk. Process memory is not kept, so a resumed lease starts its processes again. The lease's generation increases on the pause and again on the resume, which is what invalidates every stream token and in-flight Cell command from before.

resume asks for a machine now, exactly as create does — including the possibility of a refusal when nothing can be granted.

#Budgets

Beyond the clocks, a lease can carry a budget, and reaching it ends the lease for that reason:

FieldTypeWhat it is
max_cpu_secondsint64Ends the lease CPU_BUDGET after this many vCPU-seconds of guest CPU time.
max_cost_microsint64Ends the lease COST_BUDGET once its accrued cost at the shape’s list price reaches the cap. [The price list](/pricing) is where those prices live.

A budget is a bound, not a target: a lease that hits one is ended, and the stream carries budget_warning before the end so a caller can react first.

#Why a lease ended

status.end_reason is typed, and the event stream carries it too. A lease ended for one of ten reasons:

  • released — a caller ended it.
  • expired — it reached its ttl.
  • idle — it went unused past its idle_timeout.
  • cpu_budget — it reached max_cpu_seconds.
  • cost_budget — it reached max_cost_micros.
  • abuse — the platform stopped it.
  • quota — an org limit stopped it.
  • org_deleted — the org the lease belonged to went away.
  • machine_lost — the machine it ran on was lost.
  • boot_failed — the guest never became ready.

The distinction between them is the point of the field: a caller that retries on machine_lost and reports a bug on boot_failed behaves differently from one that retries everything. usage on the ended lease says what it consumed — wall seconds, guest vCPU-seconds, egress bytes and the rest.

#The event stream

Every step is a LeaseEvent, in order, and the stream is the lease's own history rather than a log you have to reconstruct:

Shell
sylphx sandboxes lease-events list orgs/acme/projects/shop/envs/production/leases/lease
Shell
curl "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/leases/lease/lease_events" \
  -H "Authorization: Bearer $SYLPHX_API_KEY"
TypeScript
const response = await sylphx.sandboxes.leaseEvents.list({
  parent: 'orgs/acme/projects/shop/envs/production/leases/lease',
})

An event's kind is one of twelve: granted, ready, refused, ended, renewed, network_changed, control_acquired, control_released, control_expired, budget_warning, paused or resumed. sequence is the position in the lease's own sequence, from 1, so a reader can tell a gap from a quiet spell. Each event also carries the state and the lease_generation after it, the principal that caused it when a caller did, and — on an ended event — the end_reason and the final usage.

list is a long poll. With wait it blocks for an event after the page_token you already hold, for at most 60 seconds, which turns the stream into a loop that reacts instead of one that polls on a timer:

Shell
sylphx sandboxes lease-events list \
  orgs/acme/projects/shop/envs/production/leases/lease \
  --wait 30s

A lease can also deliver its own events as signed webhooks: spec.webhook names an HTTPS uri and, optionally, the kinds to receive. The URI earns its keep when the thing waiting for an event is not the thing holding the connection.

#Generations

Two numbers guard against acting on a lease that has moved on.

meta.generation increases on every change to spec; status.lease_generation increases on grant, pause, resume and end. Every stream token and every Cell command carries the latter, and a call that names a generation the lease has left is refused STALE_GENERATION rather than applied to a machine that is no longer the one you meant.

The practical shape of it: a token minted before a pause cannot be replayed against the machine after the resume, and a command written for generation 3 cannot land on generation 4. Conditional writes work the same way through meta.etag — send it back and a stale one fails ETAG_MISMATCH instead of overwriting a change you have not seen.