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.

  • Observability & SRE
  • $slo-error-budgets
  • 154 lines
  • 3 files
  • ~1,750 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- slo-error-budgets

Installs to ~/.agents/skills/slo-error-budgets/ for every project on this machine.

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

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.

~/.agents/skills/slo-error-budgets/SKILL.md

--- name: slo-error-budgets description: Defines service level indicators and objectives from user journeys, computes error budgets, and implements multiwindow multi-burn-rate alerts and… license: MIT ---

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

  1. Read AGENTS.md and existing alert rules, dashboards and any SLO definitions (rg -n "slo|error_budget|burn" --glob '*.{yml,yaml,json,jsonnet}').
  2. Identify the critical user journeys: log in, search, checkout, API write. Two to five per service is plenty.
  3. Find which data can express good/total events: load balancer or gateway metrics, service histograms, synthetic checks, queue processing metrics.
  4. Ask for or estimate current performance over the last 30 days before choosing a target.

Choose SLIs

Journey typeSLI (good events / valid events)Source
Request/response APINon-5xx responses / all responses (exclude 4xx caused by clients)Gateway or service counters
Latency-sensitiveRequests faster than threshold / all requestsHistogram bucket at the threshold
Pipeline / batchRecords processed within freshness target / all recordsJob metrics, watermark timestamps
Async jobsJobs completed successfully within N minutes / jobs enqueuedQueue and worker counters
Browser experiencePage loads with LCP <= 2.5 s / all page loadsRUM

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

TargetBudget per 30 daysImplication
99%7.2 hManual recovery is fine
99.5%3.6 hOn-call within an hour
99.9%43.2 minPaging, fast rollback, no single points of failure
99.95%21.6 minAutomated rollback and multi-zone
99.99%4.3 minMulti-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:

SeverityLong windowShort windowBurn rateBudget spent when it fires
page1 h5 m14.42%
page6 h30 m65%
ticket3 d6 h110%

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

SituationUse
Plain Prometheus, few SLOsThe script above, rules in the repo
Many SLOs, PrometheusSloth (generates rules from a spec) or Pyrra (Kubernetes CRDs plus UI)
Vendor-neutral spec neededOpenSLO YAML, converted by a tool that supports it
DatadogMetric-based SLOs plus burn-rate monitors (Terraform datadog_service_level_objective)
Grafana CloudGrafana 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 rules and a promtool test rules case 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 up for 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.yamlgenerated from this page

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.py112 lines, 4.7 KB
#!/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.

esc