« Phase 14 · Warmup · Track Overview
Lab 01 — The SRE Console
The problem
It is 03:12. Your phone goes off. Availability is 99.97% against a 99.5% target, latency p99 is normal, error rate is flat, CPU is at 40%.
And the platform is broken:
- One tenant's agent has been in a retry loop since 23:00 and has spent 340% of its monthly budget. Every request succeeded.
- A guardrail change is refusing 60% of retrieval, so answers are being generated from nothing. Every request succeeded.
- The provider quietly moved the model behind a stable version string, and the eval score dropped 0.14. Every request succeeded.
None of the four golden signals moved, because none of these are errors. That is what "tuned for non-deterministic AI workloads" means: correctness is a distribution, cost is a first-class signal, and the thing you page on is not the thing that is wrong.
You build the console that would have caught all three — and, harder, that does not page for the one failure out of two requests on a service nobody uses.
What you build
| # | Component | What it does |
|---|---|---|
| 1 | ValidityPredicate | the denominator, as an explicit design decision |
| 2 | availability_sli, latency_sli | the event-ratio model; latency as a ratio, not a percentile |
| 3 | ErrorBudget, allocate_budget | rolling budgets that clamp at zero and report overspend separately |
| 4 | burn_rate_threshold | derives 14.4 instead of memorizing it |
| 5 | BurnRateAlerting | two windows per rule, plus the minimum-volume guard |
| 6 | SpanTree | an OTel-shaped run tree, and self time with interval union |
| 7 | CardinalityBudget | rejects an unaffordable metric at definition time |
| 8 | DegradationLadder | descend fast, ascend slowly, with hysteresis |
| 9 | cost_report, CostCircuitBreaker | cost per successful action; per-tenant trip |
| 10 | forecast_capacity | provider quota, projected, with procurement lead time |
| 11 | classify_regression | model vs prompt vs corpus — only answerable if pinned |
Key concepts
| Concept | Where | Why it matters |
|---|---|---|
| The denominator is the argument | ValidityPredicate | six events, three predicates, three different SLIs |
| Safety blocks: not good, still valid | GOOD_OUTCOMES | else a guardrail refusing everything shows green |
| Synthetic probes are excluded | exclude_synthetic | otherwise you improve the SLO by probing more |
| An empty window is 1.0 | availability_sli | zero traffic is not an outage |
| Latency as a ratio | latency_sli | percentiles do not average and have no budget |
| Budgets clamp at zero | BudgetState | a dashboard showing −340% helps nobody |
| Overspend is reported separately | overspent_by | "how far past" is still a real question |
| Allocation gives it an owner | allocate_budget | a shared budget is a budget nobody owns |
| 14.4 is derived | burn_rate_threshold | 0.02 / (1/720); not a magic number |
| Two windows | BurnRateAlerting | the short window is the reset |
| The minimum-volume guard | min_events | 1-in-2 is a 100× burn rate and must not page |
| Self time, not total | SpanTree.self_time | total blames the root for everything |
| Union, not sum, of children | self_time | concurrent children would give negative self time |
| GenAI semantic conventions | Span.attributes | so any OTel backend charts tokens without custom queries |
| Series are a product | MetricSpec.series_count | one label multiplies everything; a cliff, not a slope |
| Unbounded labels are forbidden | FORBIDDEN_LABELS | ids belong on traces, not on metrics |
| Rejected at definition time | register | the backend falls over during an incident, not before |
| Descend fast, ascend slowly | DegradationLadder | and hysteresis, or the quality flaps |
| Degradation must be visible | user_visible | a silent quality change is how trust is lost |
| Cost per successful action | cost_report | cost per request improves when you fail faster |
| A cost control is an availability control | CostCircuitBreaker | the runaway looks like success everywhere else |
| Per-tenant trip | check | one loop must not exhaust anyone else's budget |
| The limit is the provider quota | forecast_capacity | you hit TPM long before CPU |
| Alert a lead time early | safety_factor | a 90% alert arrives after the decision point |
| Pinning is the technique | Baseline | "nothing we control changed" is only sayable if you pinned |
Files
| File | Role |
|---|---|
| lab.py | your implementation |
| solution.py | reference; python solution.py runs an eleven-part worked session |
| test_lab.py | 132 tests |
| requirements.txt | pytest |
Run
pip install -r requirements.txt
pytest test_lab.py -v
LAB_MODULE=solution pytest test_lab.py -v
python solution.py
Success criteria
-
All 132 tests green against your
lab.py. - A safety block is valid but not good; a client error is neither.
- An empty window scores 1.0, not 0.0.
-
latency_sliis inclusive at the threshold, and a fast failure is still not good. -
burn_rate_threshold(0.02, 1.0) == 14.4, derived. - An alert fires only when both windows agree.
- A recovered short window stops the alert while the long window is still hot.
- One failure in two requests does not page, and the reason names the minimum.
- The budget never goes negative, and overspend is a separate number.
- Two fully-concurrent children do not produce zero self time for their parent.
- Total self time across all spans equals the root's duration.
-
A metric with
user_idis rejected regardless of its estimated size. - A rejected metric is not registered.
- The ladder jumps straight to level 3 at a 20× burn rate, and ascends one rung at a time.
- Recovery holds for the configured period before ascending.
- Cost per action doubles when half the requests fail.
- One tenant tripping the breaker leaves another allowed.
- A tenant with no configured budget is denied.
- The same growth curve alerts or not depending only on the lead time.
- A regression with every version unchanged yields the provider-or-drift hypothesis.
How this maps to the real stack
| This lab | The real thing | What we simplified |
|---|---|---|
RequestEvent | a metrics pipeline (Prometheus, Azure Monitor) | in-memory events; no scraping, no aggregation, no downsampling |
availability_sli | a recording rule over a counter | no rate(), no window alignment, no staleness handling |
BurnRateAlerting | Prometheus alerting rules, or Azure Monitor | no for: duration, no inhibition, no routing or on-call |
SpanTree | OpenTelemetry + Jaeger/Tempo/App Insights | no context propagation, no sampling, no exporter |
Span.attributes | OTel GenAI semantic conventions | a subset; no events, no links, no resource attributes |
CardinalityBudget | Prometheus scrape_limit, Mimir per-tenant limits | estimates, not measured series |
DegradationLadder | a feature-flag system plus routing policy | no actual shedding; the order is the point |
CostCircuitBreaker | the gateway's quota ledger (Phase 04) | no enforcement path |
forecast_capacity | a capacity model over provider quota metrics | a linear fit; no seasonality, no confidence interval |
classify_regression | an eval pipeline plus a deployment manifest | versions as strings; no actual eval |
Honest limits. The forecaster fits a straight line — which is exactly wrong for the traffic
an AI platform actually sees, where adoption is closer to exponential and demand is strongly diurnal.
A real one needs seasonality and a confidence interval, and reporting a point estimate without one
overstates what it knows. The alerting has no for: duration and no inhibition, so a real deployment
would still fire three alerts for one incident. The cardinality budget works from estimates, and
the estimate for tenant is wrong the day someone onboards a customer per branch. Spans have no
sampling, which is the decision that actually determines whether tracing is affordable — at 100%
sampling an agent platform generates more trace data than log data. And the degradation ladder does
not shed anything; it decides what would be shed and in what order, which is the part that has to
be decided in daylight.
Extensions
- Add sampling. Head-based, then tail-based (keep every errored or slow trace, sample the rest). Then answer: what is the storage cost at 100%, at 10%, and what did you lose?
- Seasonality in the forecaster. Fit weekly and daily components. Compare the projection against the linear one on real-shaped data and see how different the procurement date is.
for:durations and inhibition. Make one incident produce one page instead of three.- A quality SLO, properly. Sampled offline evaluation as a gated objective: it blocks deploys and is reviewed weekly, and it never pages. Then defend that choice.
- Burn rate on cost. The same multi-window machinery against a spend budget rather than an error budget. Most of the code is identical, which is the interesting part.
- Exemplars. Link a metric bucket to a trace id, so "p99 is bad" becomes "here is a p99 trace". It is the single highest-value observability feature most teams have not enabled.
- A real post-mortem template, generated from the console: the timeline from alerts, the budget consumed, the degradation steps taken, the traces at the p99.
- Multi-region SLIs. Compose per-region SLIs into a global one, and discover that the ratio composes cleanly while the percentile does not — which is the argument in §2, demonstrated.
Interview / resume bullets
- "Defined the platform's SLIs on an explicit event-ratio model with a written validity predicate, which turned every SLO argument from a disagreement about numbers into a disagreement about the denominator — where it belongs."
- "Kept answer quality out of the availability SLO and ran it as a gated objective instead: it blocks deploys and is reviewed weekly, but it never pages, because a distribution shift is not an incident."
- "Implemented multi-window multi-burn-rate alerting with derived thresholds and a minimum-volume guard, which cut 3 a.m. pages on low-traffic services to zero without raising a single threshold."
- "Made cost per successful action and safety-block rate first-class signals, and added a per-tenant cost circuit breaker — which caught a runaway agent loop that every traditional signal reported as healthy."
- "Instrumented agent runs as OTel span trees with GenAI semantic conventions and self-time attribution, so 'the platform is slow' became 'the reranker is 690 ms of the 4.1 s' in one query."
- "Enforced a cardinality budget at metric-definition time, which prevented the label that would have taken the metrics backend down during an incident."
- "Forecast capacity against provider quota rather than utilization, with alerts sized to procurement lead time — so a GPU quota conversation started at 55% utilization instead of at a hard 429."