SLOs and Error Budgets skill for Codex
Targets users feel, alerts on burn rate. Codex picks event-based SLIs close to the user, proposes targets from measured performance with the budget spelled out in minutes, and generates multiwindow multi-burn-rate recording and alerting rules with a bundled standard-library script. It also drafts an error budget policy for owners to adopt and backtests the alerts against past incidents.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- slo-error-budgets Installs to ~/.agents/skills/slo-error-budgets/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install slo-error-budgets The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project slo-error-budgets Installs to .agents/skills/slo-error-budgets/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/slo-error-budgets.zipunzip -o slo-error-budgets.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) slo-error-budgets PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $slo-error-budgets, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Defines service level indicators and objectives from user journeys, computes error budgets, and implements multiwindow multi-burn-rate alerts and budget policies in Prometheus (or Sloth, Pyrra, OpenSLO, Datadog and Grafana SLOs), with a bundled script that prints budget arithmetic and generates recording and alerting rules. Use when asked to set SLOs, choose SLIs, replace threshold alerts with burn-rate alerts, report error budget status, or decide whether to freeze releases.
Use it when
- A team wants SLOs but has only uptime percentages or threshold alerts
- You need burn-rate alerts with correct thresholds and windows
- Leadership asks how much reliability budget is left this month
- Release decisions should depend on reliability data
Use something else when
- You need ordinary symptom alerts without SLOs: use alerting-rules
- You need to add the underlying request and latency metrics first: use prometheus-metrics
How to check the result
Generated rules pass promtool check and a unit test where a 2% error ratio for an hour pages and a small slow leak only tickets, and a 90-day backtest matches known incidents.
The mistake it prevents
Setting a target above what the service or its dependencies have ever achieved, so the budget is always exhausted and the alerts are ignored.
Done well means: each SLO measures something a user would notice, the target is one the team has agreed to defend (not 100%), budget consumption is visible, and alerts fire on burn rate so fast outages page within minutes while slow leaks open tickets.
Inspect first
- Read
AGENTS.mdand existing alert rules, dashboards and any SLO definitions (rg -n "slo|error_budget|burn" --glob '*.{yml,yaml,json,jsonnet}'). - Identify the critical user journeys: log in, search, checkout, API write. Two to five per service is plenty.
- Find which data can express good/total events: load balancer or gateway metrics, service histograms, synthetic checks, queue processing metrics.
- Ask for or estimate current performance over the last 30 days before choosing a target.
Choose SLIs
| Journey type | SLI (good events / valid events) | Source |
|---|---|---|
| Request/response API | Non-5xx responses / all responses (exclude 4xx caused by clients) | Gateway or service counters |
| Latency-sensitive | Requests faster than threshold / all requests | Histogram bucket at the threshold |
| Pipeline / batch | Records processed within freshness target / all records | Job metrics, watermark timestamps |
| Async jobs | Jobs completed successfully within N minutes / jobs enqueued | Queue and worker counters |
| Browser experience | Page loads with LCP <= 2.5 s / all page loads | RUM |
Rules:
- Measure as close to the user as possible (edge > service > database).
- Use ratios of events, not “minutes the dashboard was green”; event-based SLIs weigh busy hours correctly.
- Latency SLIs need a histogram bucket exactly at the threshold (
le="0.3"for 300 ms). - Decide and document what is excluded: health checks, internal synthetic traffic, client errors, rate-limited abusive traffic.
Choose targets
| Target | Budget per 30 days | Implication |
|---|---|---|
| 99% | 7.2 h | Manual recovery is fine |
| 99.5% | 3.6 h | On-call within an hour |
| 99.9% | 43.2 min | Paging, fast rollback, no single points of failure |
| 99.95% | 21.6 min | Automated rollback and multi-zone |
| 99.99% | 4.3 min | Multi-region, automated failover; expensive |
Start slightly below current measured performance, never above what dependencies can deliver (a service cannot be more available than its database without redundancy). Use a rolling 28 or 30-day window. Calculate exact numbers:
python3 scripts/burn_rate.py --target 99.9 --window-days 30
Burn-rate alerts
Burn rate = observed error ratio / allowed error ratio. A burn rate of 1 spends exactly the whole budget over the window. Use the multiwindow, multi-burn-rate policy:
| Severity | Long window | Short window | Burn rate | Budget spent when it fires |
|---|---|---|---|---|
| page | 1 h | 5 m | 14.4 | 2% |
| page | 6 h | 30 m | 6 | 5% |
| ticket | 3 d | 6 h | 1 | 10% |
The long window proves the burn is significant; the short window makes the alert reset quickly once the problem is fixed. Generate recording and alerting rules:
python3 scripts/burn_rate.py --target 99.9 --name checkout_availability \
--errors 'sum(rate(http_requests_total{service="checkout",code=~"5.."}[{w}]))' \
--total 'sum(rate(http_requests_total{service="checkout"}[{w}]))' > rules/slo-checkout.yml
For a latency SLI, express bad events as total minus fast:
--errors 'sum(rate(http_request_duration_seconds_count{service="checkout"}[{w}])) - sum(rate(http_request_duration_seconds_bucket{service="checkout",le="0.3"}[{w}]))'
--total 'sum(rate(http_request_duration_seconds_count{service="checkout"}[{w}]))'
Then fill in runbook_url and lint with promtool check rules. Recording 3-day rate
windows over raw counters is expensive at high cardinality; aggregate away instance
labels first or record a 5 m ratio and average it (Sloth and Pyrra do this for you).
Tool choice
| Situation | Use |
|---|---|
| Plain Prometheus, few SLOs | The script above, rules in the repo |
| Many SLOs, Prometheus | Sloth (generates rules from a spec) or Pyrra (Kubernetes CRDs plus UI) |
| Vendor-neutral spec needed | OpenSLO YAML, converted by a tool that supports it |
| Datadog | Metric-based SLOs plus burn-rate monitors (Terraform datadog_service_level_objective) |
| Grafana Cloud | Grafana SLO, which generates recording rules and alerts |
Error budget policy
An SLO without a policy is a dashboard. Write a short policy and get the service owner and product owner to agree to it:
## Error budget policy: <service>
- SLOs: <list with SLI definition, target, window>
- Budget remaining > 0: ship normally.
- Budget exhausted in the current window: freeze non-critical feature releases; next
sprint priority goes to the top reliability items from recent postmortems.
- A single incident consumed > 20% of budget: mandatory postmortem with action items.
- Exceptions: security fixes and changes that improve reliability always ship.
- Review: SLO targets revisited every quarter against user feedback and cost.
- Owners: <service owner>, <product owner>. Escalation: <director>.
Codex can draft this, but release freezes are an organisational decision; present it for the humans to adopt rather than enforcing it in CI unilaterally.
Workflow
- List journeys and pick one SLI each
- Measure the last 30 days of the SLI
- Propose targets with budget minutes, agree with owners
- Generate recording and burn-rate rules; add runbooks
- Add a budget panel: remaining budget %, burn rate, SLI over window
- Remove threshold alerts the burn-rate alerts make redundant
- Draft the error budget policy
Verification
promtool check rulesand apromtool test rulescase where errors at 2% for an hour trigger the 1 h page and 0.05% for a day does not.- Backtest: run the long-window expressions over the last 90 days and check they would have fired for known incidents.
- Budget panel query:
1 - (avg_over_time(slo:<name>:error_ratio_rate5m[30d]) / <allowed ratio>)(an unweighted average; fine at steady traffic, use summed counters if traffic is very uneven). Check it against the script’s arithmetic for a known bad day.
Output
A table per service: journey, SLI definition (good/valid query), target, window, budget in minutes, current 30-day performance, alert rules file, dashboard link, policy link.
Gotchas
- Low-traffic services make ratios noisy; a handful of errors burns a day of budget. Add synthetic traffic or use longer windows, and require a minimum event count.
- Excluding too much (all 4xx, all timeouts “caused by clients”) makes the SLO look great while users suffer.
- A 100% target leaves no budget and no room to deploy; nobody will defend it.
- Counter resets and missing scrapes make long-window rates drift; prefer recording
rules and check
upfor the source.
What the zip contains
Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "SLOs and Error Budgets"
short_description: "Targets users feel, alerts on burn rate"
default_prompt: "$slo-error-budgets Define SLOs for our checkout API and generate burn-rate alerts in Prometheus" scripts/burn_rate.py
#!/usr/bin/env python3
"""Error-budget arithmetic and multiwindow burn-rate Prometheus rules for one SLO.
Examples:
python3 scripts/burn_rate.py --target 99.9 --window-days 30
python3 scripts/burn_rate.py --target 99.9 --name checkout_availability \
--errors 'sum(rate(http_requests_total{service="checkout",code=~"5.."}[{w}]))' \
--total 'sum(rate(http_requests_total{service="checkout"}[{w}]))' > slo-rules.yml
{w} in --errors/--total is replaced by each window (5m, 30m, 1h, 6h, 3d).
Standard library only. Exits 2 on bad input.
"""
import argparse
import sys
# (severity, long window, short window, burn rate, budget fraction consumed when it fires)
POLICY = [
("page", "1h", "5m", 14.4, 0.02),
("page", "6h", "30m", 6.0, 0.05),
("ticket", "3d", "6h", 1.0, 0.10),
]
def human(minutes: float) -> str:
if minutes >= 1440:
return f"{minutes / 1440:.1f} days"
if minutes >= 60:
return f"{minutes / 60:.1f} hours"
if minutes >= 1:
return f"{minutes:.1f} minutes"
return f"{minutes * 60:.0f} seconds"
def summary(target: float, days: int) -> str:
budget = 1 - target / 100
window_min = days * 1440
lines = [
f"SLO target: {target}% over {days} days",
f"Error budget: {budget:.5%} of events = {human(budget * window_min)} of full outage",
"",
f"{'severity':8} {'long':>5} {'short':>6} {'burn':>6} {'error-ratio threshold':>22} {'budget used':>12} {'time to exhaust':>16}",
]
for sev, long_w, short_w, rate, frac in POLICY:
lines.append(
f"{sev:8} {long_w:>5} {short_w:>6} {rate:>6.1f} {rate * budget:>22.5%} {frac:>12.0%} {human(window_min / rate):>16}"
)
return "\n".join(lines)
def rules(name: str, target: float, errors: str, total: str) -> str:
budget = 1 - target / 100
windows = sorted({w for _, l, s, _, _ in POLICY for w in (l, s)}, key=lambda w: {"m": 1, "h": 60, "d": 1440}[w[-1]] * int(w[:-1]))
out = ["groups:", f" - name: slo-{name}-recording", " rules:"]
for w in windows:
out += [
f" - record: slo:{name}:error_ratio_rate{w}",
" expr: |",
f" ({errors.replace('{w}', w)})",
" /",
f" ({total.replace('{w}', w)})",
]
out += [f" - name: slo-{name}-alerts", " rules:"]
for sev, long_w, short_w, rate, frac in POLICY:
thr = f"{rate} * {budget:.6g}"
out += [
f" - alert: {''.join(p.capitalize() for p in name.split('_'))}BudgetBurn{long_w}",
" expr: |",
f" slo:{name}:error_ratio_rate{long_w} > ({thr})",
" and",
f" slo:{name}:error_ratio_rate{short_w} > ({thr})",
f" for: {'2m' if sev == 'page' else '15m'}",
" labels:",
f" severity: {sev}",
f" slo: {name}",
" annotations:",
f' summary: "{name}: burning error budget at {rate}x ({frac:.0%} of {target}% budget in {long_w})"',
' runbook_url: "TODO: link the runbook for this SLO"',
]
return "\n".join(out) + "\n"
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--target", type=float, required=True, help="SLO target in percent, e.g. 99.9")
ap.add_argument("--window-days", type=int, default=30)
ap.add_argument("--name", help="snake_case SLO name; with --errors/--total prints Prometheus rules")
ap.add_argument("--errors", help="PromQL for bad-event rate, with {w} for the window")
ap.add_argument("--total", help="PromQL for all-event rate, with {w} for the window")
a = ap.parse_args()
if not 0 < a.target < 100:
print("error: --target must be between 0 and 100 (exclusive)", file=sys.stderr)
return 2
if a.window_days <= 0:
print("error: --window-days must be positive", file=sys.stderr)
return 2
want_rules = any([a.name, a.errors, a.total])
if want_rules and not all([a.name, a.errors, a.total]):
print("error: --name, --errors and --total must be given together", file=sys.stderr)
return 2
if want_rules:
if "{w}" not in a.errors or "{w}" not in a.total:
print("error: --errors and --total must contain {w}", file=sys.stderr)
return 2
print(rules(a.name, a.target, a.errors, a.total), end="")
print("# " + summary(a.target, a.window_days).replace("\n", "\n# "), file=sys.stderr)
else:
print(summary(a.target, a.window_days))
return 0
if __name__ == "__main__":
sys.exit(main())
About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.