Scheduler and warm pools
How placement decisions are made and how warm pools hold each tier's start-latency promise.
Start latency is the feature this subsystem exists to deliver. Everything here is in service of one number per tier: the probability that a session create can claim a pre-started runtime instead of booting one.
The claim path versus the cold path
A claim is a compare-and-swap on a Redis set of available pool instances, followed by a node instruction to bind the session. The CAS is what prevents two concurrent creates taking the same instance; a lost CAS retries against the next instance, and after three failures falls through to cold start rather than spinning.
| Tier | Claim cost | Cold cost | Ratio |
|---|---|---|---|
| 1 | ~40 ms | ~600 ms | 15× |
| 2 | ~60 ms | ~2 s | 33× |
| 3 | ~700 ms | ~8 s | 11× |
| 4 | ~1.8 s | ~45 s | 25× |
The ratio is why pool hit rate, not raw capacity, is the metric the infra team watches.
Pool identity
A pool is keyed by four dimensions, and all four must match for a claim:
(tier, template@digest, size_class, zone)
template@digest— the content hash, not the friendly name. A template update creates a new pool; the old pool drains as its instances are claimed or expire.size_class— a discrete bucket (s= 1 vCPU / 2 GiB,m= 2 / 4,l= 4 / 8,xl= 8 / 16,2xl= 16 / 32), not an arbitrary request. Continuous sizing would make pools uncachable; requests are rounded up to the next class and billed on the class.zone— a session with an attached volume is constrained to the volume's zone, which is the most common cause of a miss in an otherwise healthy region.
Every distinct template digest is a separate pool needing its own depth. An organization with forty private templates across five size classes and three zones is asking for six hundred pools. Pool depth is allocated by demand, so rarely used templates get depth zero and always cold start. Consolidate templates, or pin the few that are latency-critical.
Pool sizing
pool-mgr recomputes targets every 15 seconds per pool.
target = clamp(
ceil( burn_rate × refill_latency × safety ) + baseline,
floor,
ceiling
)
burn_rate claims/second, EWMA over 5 min, plus a seasonal term from
the same slot last week and yesterday
refill_latency p90 seconds to bring one instance to ready for this pool
safety 1.5 for tiers 1–2, 2.0 for tiers 3–4 (slower refill, costlier miss)
baseline minimum depth so the first request of a quiet period is warm
floor contractual reserved depth for orgs that bought it
ceiling budget guard: pooled capacity cost cap per tier per zone
The seasonal term matters because agent traffic is extremely diurnal and weekly. A purely reactive EWMA always under-provisions the Monday morning ramp; the previous week's same-slot value gets the pool ahead of it.
The economics
Every pooled instance occupies real capacity. Pool cost is a direct platform expense, not a
customer charge — except where a customer has bought reserved depth, which is billed as
pool_reservation_s.
pool cost ≈ depth × hourly cost of one idle instance miss cost ≈ p(miss) × creates × (cold latency − warm latency) × value of latency
Tier 4 makes this concrete: a pooled desktop costs roughly a running desktop, so depth 20 costs twenty desktops' worth of capacity continuously. That is why tier 4 pool depth beyond a small platform baseline is a purchased reservation rather than a free platform guarantee.
Refill
Refill is asynchronous and rate-limited. A claim schedules a refill immediately, but refills are throttled per host so a burst of claims does not saturate every host's NVMe and network simultaneously — which would slow the cold starts those same claims are falling back to.
Refill priority: pools below floor, then pools furthest below target as a fraction, then pre-emptive refill for predicted demand.
Placement
When there is no claim to make, placer chooses a host.
Filters — hard constraints
| Filter | Reason |
|---|---|
| Residency allows the region | Contractual, non-negotiable |
| Zone matches the attached volume | Volumes are zonal |
| Host is in the tier's placement class | Tiers never mix on a host |
| Host runtime and kernel satisfy the template | A template declares its requirements |
| Host not cordoned or draining | Would be evicted shortly |
| Host health score above threshold | Degraded hosts get no new work |
| Allocatable resources cover the request | Including the pool's reserved headroom |
Scoring — soft preferences
| Signal | Weight | Why |
|---|---|---|
| Warm pool hit | 100 | Dominates everything; a claim beats any packing gain |
| Template layers already cached locally | 30 | Removes the pull from the cold path |
| Resulting headroom | 20 | Mild bin-packing, biased toward leaving room |
| Zone balance | 15 | Keeps failure domains even |
| Contention risk (recent steal time, memory bandwidth pressure) | −25 | Noisy-neighbour avoidance |
| Proximity to scheduled drain | −40 | Do not place work about to be evicted |
Every decision is recorded with its scores and the filter that eliminated each rejected host, which is what makes "why did this cold start" answerable from the infra console.
Spread and affinity
Callers can request placement hints, which are preferences and never hard constraints unless the plan includes reserved capacity:
spread: "zone"— place replicas of a workload across zonesaffinity: { session_id }— co-locate with another session, for low-latency inter-session workanti_affinity: { label }— keep labelled sessions apart
Oversubscription
| Placement class | vCPU ratio | Memory ratio | Rationale |
|---|---|---|---|
sandbox (T1) | 4:1 | 1.2:1 | Short, bursty, often idle between runs |
micro (T2, T3) | 2:1 | 1.1:1 | Longer-lived, more sustained CPU |
desktop (T4) | 1:1 | 1:1 | Latency-sensitive interactive, side-channel isolation |
gpu | 1:1 | 1:1 | GPU memory cannot be oversubscribed usefully |
Memory is barely oversubscribed anywhere because memory pressure is not gracefully degradable — it
becomes an OOM kill, which to a customer is an unexplained failure. CPU oversubscription shows up as
slower execution, which is recoverable and visible in cpu_ms.
Steal time per session is tracked and exposed. Sustained steal above a threshold triggers placement rebalancing for new sessions and marks the host as contended in the scoring function.
Eviction
Sessions can be evicted only when the plan marks them preemptible, or when the host is failing.
| Trigger | Notice | Customer effect |
|---|---|---|
| Host failure | None possible | Session fails, webhook session.failed with reason: host_failure, billing stops at last meter |
| Planned drain | 5 min webhook | Non-preemptible sessions run to the drain deadline; preemptible ones evicted after notice |
| Preemptible reclaim | 30 s webhook | Session stopped, prorated bill, snapshot taken if snapshot_on_evict is set |
| Policy violation | Immediate | Session killed, audit entry, webhook with the violated rule |
| Spend cap breach | Immediate or 60 s, per the cap's setting | Session stopped, quota.exceeded webhook |
Non-preemptible sessions are never evicted for capacity. If the fleet is short, new creates are
refused with 503 capacity_unavailable rather than killing running work — failing to start is
recoverable, failing mid-run usually is not.
Metrics the infra team watches
| Metric | Target | Meaning when it moves |
|---|---|---|
pool_hit_ratio{tier} | > 0.97 | The headline. Below target, start latency SLOs break |
pool_depth{pool} / pool_target{pool} | ≈ 1.0 | Sustained below means refill cannot keep up |
refill_latency_p90{pool} | Tier-dependent | Rising means storage or network pressure on hosts |
cold_start_p95{tier} | Per the tier table | The fallback path degrading |
placement_latency_p99 | < 25 ms | placer itself becoming the bottleneck |
placement_failures{reason} | ~0 | no_capacity means buy hardware; no_zone_match means volume placement is fragmenting the fleet |
steal_time_p95{class} | < 3% | Oversubscription is too aggressive for the class |
pool_waste_ratio | < 0.15 | Fraction of pooled instances expiring unclaimed — depth is too high |
pool_hit_ratio and pool_waste_ratio are the two numbers in tension. Tuning the fleet is the
continuous process of holding the first above target while keeping the second low enough that pooled
capacity is not the dominant cost line. See capacity planning.