
Slo Architect
- 407 installs
- 23.8k repo stars
- Updated July 17, 2026
- alirezarezvani/claude-skills
slo-architect is an engineering agent skill (version 2.9.0) that designs SLIs, SLOs, error budgets, and multi-window burn-rate alerting policies following Google SRE Workbook discipline.
About
Acts as an SLO architect for running services: selects meaningful SLIs, sets realistic SLO targets and error budgets, designs burn-rate alerts and dashboards, and aligns on-call response so reliability goals are explicit and enforceable in production.
- SLI and SLO definition workshops
- Error budget policy design
- Burn-rate alerting rules
- Reliability dashboard layout
- Incident response alignment
Slo Architect by the numbers
- 407 all-time installs (skills.sh)
- Ranked #294 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/alirezarezvani/claude-skills --skill slo-architectAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 407 |
|---|---|
| repo stars | ★ 23.8k |
| Last updated | July 17, 2026 |
| Repository | alirezarezvani/claude-skills ↗ |
How do you design SLOs with error budgets and burn-rate alerts?
Design SLIs, SLOs, error budgets, and alerting policies so production reliability targets are measurable and actionable.
Who is it for?
SRE and platform engineers defining measurable SLIs, SLOs, error budgets, and alerting for production services.
Skip if: Generic log dashboards, ad-hoc uptime checks, or teams without historical SLI data to set sustainable targets.
When should I use this skill?
A developer asks to define an SLO, compute error budget, configure burn-rate alerts, or review existing SLO documentation.
What you get
Markdown SLO definitions, error budget calculations, burn-rate alert thresholds, and SLO review findings.
- SLO definition documents
- Error budget calculations
- Burn-rate alert rules
By the numbers
- Skill version 2.9.0
- Bundles 3 Python automation scripts
- Includes 4 SRE reference documents
Files
SLO Architect
Define SLOs that mean something. Most "SLOs" in the wild are arbitrary numbers no one believes — 99.9% on every endpoint, no SLI definition, no error budget, no policy for what happens when budget burns. This skill enforces the discipline from Google's SRE Workbook: pick the right SLI, set a target users actually care about, calculate the error budget, wire multi-window burn-rate alerts, and have a written policy for when budget runs out.
When to use
- Defining a new SLO for a service or feature
- Reviewing existing SLOs for common bugs
- Picking the right SLI (event-based vs time-window based vs request-based)
- Computing error budgets and burn-rate alert thresholds
- Tying SLOs to existing controls — feature flags abort, chaos blast radius, operator capability levels
When NOT to use
- General observability strategy (metrics + logs + traces) → use
observability-designer - Customer-facing SLAs with legal teeth → that's contract drafting, not engineering
- Performance load testing (capacity, not reliability) → use
performance-profiler - Active incident response → use
incident-response
Core principle: an SLO is a promise about user experience
SLI ⟶ measurable signal of user-perceived health (e.g., HTTP 2xx rate)
SLO ⟶ target for the SLI over a window (e.g., 99.9% over 30 days)
SLA ⟶ customer-facing commitment with consequences (separate concern)
EB ⟶ error budget: 100% − SLO target = how much "bad" you can spend
BR ⟶ burn rate: how fast you're consuming the error budgetThe four cardinal mistakes:
1. Target too high (99.99%+ on services that can't support it) — every minor blip violates SLO; alerts become noise. 2. Wrong SLI (CPU usage as proxy for user experience) — system can be "green" while users suffer. 3. No error budget policy — burning budget means nothing if there's no agreed action. 4. Single-window burn-rate alert — either too noisy (page on a 5-min spike) or too slow (notice budget exhausted after the fact).
The 3 tools below catch each of these.
Quick start
SKILL=engineering/slo-architect/skills/slo-architect
# 1. Design an SLO
python "$SKILL/scripts/slo_designer.py" \
--service checkout-svc \
--sli-type request-success-rate \
--target 99.9 \
--window-days 30
# 2. Compute error budget + multi-window burn-rate alerts
python "$SKILL/scripts/error_budget_calculator.py" \
--target 99.9 --window-days 30
# 3. Review existing SLO definitions for common bugs
python "$SKILL/scripts/slo_review.py" --slo-doc docs/slos/The 3 Python tools
All stdlib-only.
slo_designer.py
Generates a structured SLO definition with required fields. Refuses to render if any required field is missing (exit 1).
python scripts/slo_designer.py \
--service checkout-svc \
--sli-type request-success-rate \
--target 99.9 \
--window-days 30 \
--owner team-checkoutSLI types supported:
request-success-rate—(total_requests - bad_requests) / total_requestsrequest-latency—count(requests < threshold) / total_requestsavailability-time—(window - downtime) / windowdata-freshness—count(data_age < threshold) / total_data_pointscorrectness—count(correct_outputs) / total_outputs
Output is markdown by default with all required fields filled or marked <must define>. JSON output (--format json) is consumed by slo_review.py.
error_budget_calculator.py
Given target availability + window, computes:
- Allowed downtime in the window
- Multi-window burn-rate thresholds per Google SRE Workbook (Chapter 5):
- Fast burn — page if 2% of monthly budget consumed in 1 hour
- Slow burn — page if 10% consumed in 6 hours, ticket if 10% in 3 days
- Recommended alerting rules (PromQL-shaped output)
python scripts/error_budget_calculator.py --target 99.9 --window-days 30
python scripts/error_budget_calculator.py --target 99.95 --window-days 7 --format jsonslo_review.py
Audits a directory of SLO definitions (markdown or JSON) for the common bugs.
python scripts/slo_review.py --slo-doc docs/slos/Checks:
target_too_high: target ≥ 99.99% (sustainable only with massive engineering investment)target_too_low: target ≤ 99.0% (probably wrong SLI; users will notice)window_too_short: window < 7 days (statistical noise dominates)window_too_long: window > 90 days (slow feedback)no_sli_definition: SLI section missing or vague ("everything OK")no_error_budget_policy: no documented action when budget burnscpu_as_sli: CPU/memory used as user-experience proxy (wrong signal)
SLI selection cheatsheet
| User experience | SLI type | What you measure |
|---|---|---|
| "Did the request succeed?" | request-success-rate | 2xx / total |
| "Was the response fast?" | request-latency | count(p99 < threshold) / total |
| "Was the service up?" | availability-time | (window - downtime) / window |
| "Is the data current?" | data-freshness | count(data_age < threshold) / total |
| "Was the answer correct?" | correctness | count(correct) / total |
See references/sli_design.md for examples and anti-patterns.
Error budget math (the basics)
For 99.9% SLO over 30 days:
- Allowed unavailability:
0.1% × 30 × 24 × 60 = 43.2 minutes - 1-hour fast-burn threshold (2% of monthly budget burned):
2% × 43.2 / 60 ≈ 1.44 ratio multiplier - 6-hour slow-burn threshold (10% in 6h):
10% × 43.2 / 360 ≈ 0.6 ratio multiplier
error_budget_calculator.py does this math for you and emits ready-to-paste alert rules.
Composition with the rest of the portfolio
This skill explicitly composes with three others:
| Skill | Composition |
|---|---|
feature-flags-architect | Rollout abort criteria reference SLO burn-rate thresholds |
chaos-engineering | Blast-radius calculator already takes monthly error budget as input — define it here |
kubernetes-operator | Operator capability L4 (Deep Insights) requires SLOs + Prometheus rules |
The error_budget_calculator.py output is in the same shape engineering/skills/chaos-engineering/scripts/blast_radius_calculator.py expects on stdin.
Workflows
Workflow 1: Define a new SLO
1. Pick the user journey to protect (e.g., "checkout completion").
2. Choose SLI type (request-success-rate, latency, availability, freshness, correctness).
3. Define the SLI precisely: numerator/denominator with concrete labels.
4. Pick a target by measuring 30 days of historical SLI value:
target = floor(p50 of last 30 days × 100) / 100
This avoids targets the system has never sustained.
5. Pick a window (28 days = 4 calendar weeks, recommended).
6. Run slo_designer.py to render the SLO definition.
7. Run error_budget_calculator.py to get burn-rate alerts.
8. Write the error budget policy (what happens when budget burns).
9. Run slo_review.py — must pass before the SLO is "live".Workflow 2: Quarterly SLO review
1. For every active SLO, run slo_review.py — fix any FAIL findings.
2. Look at last quarter's data:
- Was the SLO too easy (never burned budget)? Tighten target.
- Was it too hard (frequently burned)? Loosen target OR fix the system.
- Did burn-rate alerts fire usefully (not too noisy, not too late)? Adjust thresholds.
3. Audit error budget policies — were they actually followed when budget burned?
4. Commit revised SLOs; archive old versions with date stamps.Workflow 3: SLO-driven rollback
1. New deploy starts burning error budget faster than baseline.
2. Burn-rate alert fires (from error_budget_calculator.py thresholds).
3. Auto-rollback via feature flag (kill switch from feature-flags-architect).
4. Postmortem feeds into next SLO revision.References
references/slo_principles.md— SLI vs SLO vs SLA, Google SRE Workbook canonreferences/sli_design.md— picking the right SLI; 5 types with examplesreferences/error_budget.md— error budget math, burn-rate alerts, budget policyreferences/composition.md— how SLOs feed feature flags, chaos, operators
Slash command
/slo-design — interactive SLO design wizard that runs all 3 tools.
Asset templates
assets/slo_template.yaml— fillable SLO YAMLassets/error_budget_policy.md— fillable policy template
Anti-patterns
- 99.99% on every endpoint — copy-paste SLOs that nobody verified the system can sustain
- CPU usage as SLI — system metrics aren't user experience
- Single-window burn-rate alert — too noisy if 5-min, too slow if 30-day
- No error budget policy — burning budget means nothing without an action
- SLOs without owners — no one is responsible; they bit-rot
- SLOs reviewed once a year — system characteristics change faster than that
- SLAs in the SLO doc — different audience, different stakes; keep them separate
- SLO target = SLA target — SLO must be tighter (you should beat your contract before customers notice)
Verifiable success
A team using this skill should achieve:
- 100% of SLOs pass
slo_review.pywith 0 FAIL findings - Every SLO has a documented owner, error budget, burn-rate alerts, and policy
- Burn-rate alerts fire ≤2 times/month per SLO that's hit (signal, not noise)
- Mean time to detect SLO violation: <30 min (multi-window burn-rate alerts working)
- Quarterly SLO review happens every quarter (not annually)
Error budget policy — <service-name>
This policy says what changes when error budget is burned. Without it, the SLO is theater.
Scope
Applies to: <list of SLO IDs covered by this policy> Owner: <team-name> Review cadence: quarterly Last reviewed: <YYYY-MM-DD>
States and actions
State: HEALTHY (>50% budget remaining)
- Normal operation
- Ship features without extra friction
- Run chaos experiments per the standard cadence
- Roll out feature flags per standard plan
State: CAUTION (25-50% budget remaining)
- Risky changes get extra review (architect or staff sign-off)
- No new chaos experiments outside dedicated windows
- Postpone non-essential migrations
- Daily team check on budget direction
State: CRITICAL (<25% budget remaining)
- Deploy freeze for the affected service: only SLO-improving fixes ship
- All releases require explicit owner sign-off
- Chaos experiments paused
- Feature flag rollouts paused (existing flags continue at current percent)
- Daily standup includes budget status
State: VIOLATED (budget exhausted, SLO target missed)
- Same-day: stop the bleeding (rollback, kill switch, scale up)
- Within 48 hours: blameless postmortem published
- Within 14 days: at least one follow-up action shipped
- Within 30 days: review whether SLO target/window are still right
Recovery
After exiting VIOLATED, the service stays in CRITICAL until:
- Burn rate is sustained at <1× over 7 consecutive days, AND
- All postmortem follow-ups are shipped
Roles
| Role | Responsibility |
|---|---|
| Service owner | Triggers state transitions; communicates to stakeholders |
| On-call | Receives burn-rate alerts; initial triage |
| Engineering manager | Approves deploys during CRITICAL/VIOLATED |
| SRE | Reviews SLO target appropriateness quarterly |
Exceptions
The deploy freeze can be lifted by:
- Service owner + engineering manager joint approval
- Reason documented (security fix, customer escalation, regulatory)
- Logged for postmortem review
Reviewing this policy
This policy is reviewed every quarter. Questions to ask: 1. Did we follow the policy when budget burned? 2. Are the thresholds (50% / 25%) right? 3. Are the actions (freeze, sign-off) actually happening? 4. Did the SLO target need to change?
Answers feed into the next quarter's revision.
Composition references
references/composition.md— how this policy interacts with feature-flags-architect, chaos-engineering, kubernetes-operatorreferences/error_budget.md— the math behind the thresholdsreferences/slo_principles.md— Google SRE Workbook canon
# SLO definition — fill in <PLACEHOLDERS>
# Pass this through slo_review.py before going live.
---
slo_id: slo-<service>-<sli_type>-<unix_ts>
service: <service-name> # e.g., checkout-svc
owner: <team-or-handle@org> # required; named individual or team
created: <YYYY-MM-DD>
review_cadence: quarterly # quarterly | monthly | weekly
# The user journey this SLO protects.
# Be specific. NOT "API works" — instead "User completes checkout in <2s".
user_journey: <describe the user journey>
# The SLI: a measurable signal of user-perceived health.
sli:
type: request-success-rate # request-success-rate | request-latency
# | availability-time | data-freshness | correctness
numerator: count(http_requests_total{job="<service>", status_code=~"2..|3.."})
denominator: count(http_requests_total{job="<service>", source!="bot"})
labels:
- env=prod
- region=us-east-1
# The target value the SLI must hit over the window.
# Pick from data: floor(p50 of last 30d × 100) / 100.
# Don't copy 99.9% blindly.
target_percent: 99.9
window_days: 28 # 7 / 28 / 30 / 90 — default 28
error_budget:
# Computed by error_budget_calculator.py — confirm the math.
minutes_per_window: <40.32 for 99.9% over 28 days>
# Path or URL to the error budget policy.
# The policy must answer: "When budget burns to 25% / 0%, what changes?"
policy_doc: <link required before SLO is live>
# Burn-rate alert thresholds, computed by error_budget_calculator.py.
# Multi-window per Google SRE Workbook Chapter 5.
alerts:
fast_burn:
long_window: 1h
short_window: 5m
burn_rate_threshold: <from error_budget_calculator.py>
severity: page
slow_burn:
long_window: 6h
short_window: 30m
burn_rate_threshold: <from error_budget_calculator.py>
severity: page
ticket_burn:
long_window: 3d
short_window: 6h
burn_rate_threshold: <from error_budget_calculator.py>
severity: ticket
# Composition with other skills.
# Wire-up with feature-flags-architect, chaos-engineering, kubernetes-operator
# is documented in references/composition.md.
references:
monitoring_dashboard: <URL>
policy_doc: <URL>
related_slos:
- <other-slo-id>
Composition with the rest of the portfolio
slo-architect is the keystone. Three other skills in this library already lean on the SLO + error budget concept. This page shows how to wire them together for a coherent reliability stack.
The unified concept: error budget
┌────────────────────────────────────────────────────────────┐
│ slo-architect │
│ defines SLO, error budget, burn rate │
└──────────┬─────────────────┬────────────────┬─────────────┘
│ │ │
▼ ▼ ▼
feature-flags- chaos-engineering kubernetes-
architect (blast-radius operator
(rollout abort) bound by EB) (cap level L4)With feature-flags-architect
feature-flags-architect defines kill switches. Their abort triggers should reference SLO burn-rate, not arbitrary thresholds.
Before:
abort_if: "p99 > 1000ms OR error_rate > 1%"After (SLO-driven):
abort_if: "burn_rate.fast > 14.4 over 1h (per SLO checkout-success)"Wire-up:
1. Define SLO via slo_designer.py 2. Run error_budget_calculator.py to get the burn-rate threshold 3. Use that threshold in the flag's abort criteria 4. The kill_switch_audit.py from feature-flags-architect now has a real signal to verify against
With chaos-engineering
chaos-engineering's blast_radius_calculator.py already takes monthly error budget as input — but the budget should come from the SLO, not be made up.
# 1. Get the budget from the SLO definition
python slo_architect/scripts/error_budget_calculator.py \
--target 99.9 --window-days 30 --format json \
| jq .budget_minutes
# 2. Pass it to the chaos blast-radius calculator
python chaos_engineering/scripts/blast_radius_calculator.py \
--traffic-share 0.05 \
--user-pop 1000000 \
--duration-min 15 \
--monthly-budget-min 43.2 # ← from step 1Now blast radius is bounded by REAL error budget, not a number someone typed in.
With kubernetes-operator
OperatorHub Capability Level 4 ("Deep Insights") requires:
/metricsendpoint- Prometheus alert rules
- SLOs documented for the operator's managed resources
slo-architect provides the SLO definitions; error_budget_calculator.py provides the alert rules. Drop them in the operator's Helm chart or OperatorHub bundle.
End-to-end example
Goal: ship a new checkout flow.
1. Define the SLO (slo-architect):
slo_designer.py --service checkout-svc --sli-type request-success-rate \
--target 99.9 --window-days 28 --owner team-checkout2. Compute burn-rate alerts (slo-architect):
error_budget_calculator.py --target 99.9 --window-days 28
# → fast_burn threshold = 14.43. Define rollout (feature-flags-architect):
rollout_planner.py --population 100000 --target-percent 100 \
--duration-days 14 --strategy ring
# 1% → 5% → 25% → 50% → 100%4. Wire the abort (feature-flags-architect):
abort_if: "burn_rate.fast > 14.4 (per SLO slo-checkout-svc-...)"5. Validate via chaos before going wide (chaos-engineering):
blast_radius_calculator.py --traffic-share 0.05 --user-pop 100000 \
--duration-min 15 --monthly-budget-min 40.32
# → GREEN if <1% of monthly budget6. Audit the operator if the service is operator-managed (kubernetes-operator):
operator_capability_audit.py --operator-dir ./checkout-operator
# → confirm L4 includes the new SLOEach step uses the previous step's output as input. The SLO is the unifying number.
What slo-architect does NOT replace
- observability-designer — broader observability strategy (metrics, logs, traces, dashboards beyond SLO)
- incident-response — SLO violation may trigger an incident, but incident response is a separate discipline
- performance-profiler — capacity planning needs different metrics than SLO does
Use slo-architect for SLO+error-budget; use the others for their specific scopes.
Anti-pattern: SLO without composition
A team defines SLOs in a spreadsheet. Nobody references them in:
- Feature flag rollouts
- Chaos experiment design
- Operator capability audits
- Incident postmortems
The SLOs become a reporting artifact, not an operating tool. The composition story is what makes SLOs change behavior.
Operational checklist
For any service with a new SLO, verify:
- [ ] SLO defined via
slo_designer.py(slo_review.pypasses) - [ ] Burn-rate alerts deployed via
error_budget_calculator.pyoutput - [ ] If using feature flags: rollout abort references the SLO burn-rate threshold
- [ ] If running chaos: blast radius bounded by SLO error budget
- [ ] If operator-managed: operator audit confirms L4 includes the SLO
- [ ] Postmortem template (when SLO violated) includes "SLO revision needed?" question
Error budget
The most important number in your SLO.
Computation
error_budget_fraction = 1 − (target_percent / 100)
error_budget_minutes = error_budget_fraction × window_days × 24 × 60
error_budget_requests = error_budget_fraction × total_requests_in_windowReference table
| SLO target | 7-day budget (min) | 28-day budget (min) | 30-day budget (min) | 90-day budget (min) |
|---|---|---|---|---|
| 99% | 100.8 | 403.2 | 432 | 1296 |
| 99.5% | 50.4 | 201.6 | 216 | 648 |
| 99.9% | 10.08 | 40.32 | 43.2 | 129.6 |
| 99.95% | 5.04 | 20.16 | 21.6 | 64.8 |
| 99.99% | 1.008 | 4.032 | 4.32 | 12.96 |
| 99.999% | 0.1008 | 0.4032 | 0.432 | 1.296 |
99.999% over 30 days = 26 seconds of allowed downtime. Sustainable only with multi-region, sub-second failover, dedicated SRE team.
Burn-rate alerts (Google SRE Workbook canon)
The single most useful artifact this skill produces. From Chapter 5: "Alerting on SLOs."
Why multi-window
Single-window alerts fail in opposite directions:
| Window | Failure mode |
|---|---|
| 5 minutes | Fires on every blip; alert fatigue |
| 30 days | Fires when budget is already exhausted; too late |
| 1 hour alone | Fires too often; misses sustained slow burn |
Multi-window combines:
- Long window filters noise
- Short window speeds detection
The alert fires only when BOTH windows show high burn. This filters spikes (only short window high) and only fires on sustained burn (both windows high).
Recommended thresholds
| Alert | Long window | Short window | Burn rate threshold | % budget at fire | Severity |
|---|---|---|---|---|---|
| Fast burn | 1h | 5m | 14.4 | 2% in 1h | page |
| Slow burn | 6h | 30m | 6 | 5% in 6h | page |
| Ticket | 3d | 6h | 1 | 10% in 3d | ticket |
The numbers come from: burn_rate × bad_event_rate > slo_target_violation_rate.
error_budget_calculator.py computes these for any target+window. Output is PromQL-shaped:
# fast_burn (page)
# Burn rate threshold: 14.4
(
sli:rate1h > 14.4 * (1 - 0.999)
AND
sli:rate5m > 14.4 * (1 - 0.999)
)Paste into your Prometheus rules; adjust label selectors to match your environment.
Error budget policy
A policy without consequences is theater. The policy says: "When budget is in state X, action Y happens automatically."
Standard 4-state policy
| State | Trigger | Action |
|---|---|---|
| Healthy | >50% budget remaining | Normal operation; ship features, run experiments |
| Caution | 25-50% budget remaining | Reduce risk on changes; no chaos experiments |
| Critical | <25% remaining | Freeze risky deploys; reliability work prioritized |
| Violated | Budget exhausted | Postmortem; SLO revision; blameless review |
What "freeze" means
Specifically:
- No deploys to production except for SLO-improving fixes
- All releases require explicit owner sign-off
- Chaos experiments paused
- Feature flag rollouts paused
This is real, not aspirational. Engineering teams that don't follow through erode the credibility of the SLO.
Recovery path
After SLO is violated: 1. Same-day: stop bleeding (rollback, kill switch, scale up) 2. Within 48h: postmortem published 3. Within 14 days: at least one follow-up action shipped 4. At 30 days: review whether SLO is still right
If burns are frequent, the SLO is wrong (too tight) OR the system needs investment.
Burn-rate vs uptime alerting
Old-school: "Page if any 5xx rate >5%." New-school: "Page if budget burns 14.4× faster than sustainable."
Why burn-rate is better:
- Stays calibrated as traffic grows (5% of low traffic = noise; of high traffic = real)
- Auto-adjusts for SLO target (99.99% needs sharper alerts than 99%)
- Aligns alerts with the SLO they protect
When to skip burn-rate alerts
- For SLOs that aren't "always on" (batch jobs, async pipelines) — measure SLI per execution instead
- For SLOs in development (no historical data yet)
- For internal tools where ticket-only is enough — don't page the team for non-paging issues
The error budget conversation
The SLO + error budget is meant to enable a conversation, not replace it.
Engineering: "We want to ship the new payment provider this sprint."
SRE: "We're at 35% budget remaining for the month. If this rolls back twice, we'll exhaust it."
Eng: "Fine, we'll ship behind a feature flag and ramp 1% → 5% → 50% with a 24-hour bake at each stage."
SRE: "OK. Set the flag's auto-abort to fire on the burn-rate alert."
That's the conversation the SLO + budget enables. Without numbers, both sides argue from gut feel.
SLI design
The SLI is the foundation. Get it wrong and the SLO is meaningless — green dashboard, angry users.
The user-experience test
Before defining ANY SLI, answer:
When this signal turns red, will a user notice?
If the answer is "maybe" or "depends," it's not an SLI — it's an internal metric.
| Signal | User notices? | Use as SLI? |
|---|---|---|
| HTTP 5xx rate | Yes | YES |
| p99 latency at the user's edge | Yes | YES |
| Successful login rate | Yes | YES |
| CPU usage on backend | No | NO |
| Memory usage on backend | No | NO |
| Pod restart count | No (until it's too late) | NO |
| Database query duration | Indirect | Maybe (if it dominates user latency) |
CPU and memory are LEADING indicators of trouble — useful for capacity planning, useless for SLO.
The 5 SLI types
1. Request-success-rate (most common)
Numerator: "good" requests Denominator: total requests
sli = (total - 5xx - timeouts - protocol_errors) / totalUse when:
- Service is request-driven (HTTP, gRPC, queue handler)
- Each request is independent
- Success/failure is well-defined
Edge cases:
- 4xx is usually NOT counted as bad (they're client errors), EXCEPT 429 (rate limiting) and 401/403 if those are operator-caused
- Time out at p99 of expected latency; treat anything beyond as bad
- Cancelled requests are tricky — define explicitly
2. Request-latency
Numerator: requests with latency below threshold Denominator: total requests
sli = count(latency_p99 < 500ms) / count(all)Use when:
- Performance is part of user experience (most user-facing services)
- A success that takes 30 seconds is effectively a failure
Pick the threshold from data: measure p50/p95/p99 over 30 days, then set the threshold at p95 of typical good operation.
3. Availability-time
Numerator: window minus total downtime Denominator: window length
sli = (window - sum(downtime_seconds)) / windowUse when:
- Service is "always-on" (DNS, infrastructure, control plane)
- "Up" or "down" is binary
- No clear request unit
Define "up" precisely: is one health check failure "down"? Three consecutive? Per-region or per-cluster?
4. Data-freshness
Numerator: data points younger than threshold Denominator: total data points
sli = count(data_age < 5min) / count(all_data)Use when:
- Service's value depends on recency (analytics dashboards, fraud detection, search index)
- "Stale data" is the user-facing failure mode
5. Correctness
Numerator: outputs that are correct Denominator: total outputs
sli = count(correct_predictions) / count(predictions)Use when:
- Output quality matters more than speed (ML models, search ranking, fraud scoring)
- You have ground truth (labels, customer feedback, A/B comparison)
Hardest SLI to maintain because "correct" requires labeled data.
SLI vs SLO target — concrete examples
Example 1: Checkout API
- SLI:
(2xx + 3xx requests) / total requests, excluding 4xx (client errors) - SLO target: 99.9% over 28 days
- Error budget: 40.32 minutes/window of unavailability
Example 2: Search latency
- SLI:
count(latency < 200ms) / count(all_searches) - SLO target: 99.5% over 28 days
- Error budget: 3.36 hours/window where >0.5% of queries are slow
Example 3: Internal API uptime
- SLI:
(window - downtime) / window, downtime measured by pingdom-style probes - SLO target: 99% over 28 days
- Error budget: 6.72 hours/window of allowed outage
Common SLI mistakes
"We just count errors"
Errors are useful but incomplete. A request that returns 200 OK in 30 seconds is a failure even though it's not an error. Use latency SLI for performance-sensitive services.
Conflating SLIs across user journeys
If checkout and browsing are different user experiences, they get different SLIs. A 99.9% on "the API" averages over journeys with very different criticality.
Counting bot traffic
Bots can dominate request volume. Filter them out (or have a separate SLI for them) — your error budget shouldn't be spent on synthetic traffic.
Counting internal traffic
If your service is hit by other internal services, those requests have different reliability requirements than user requests. Separate SLIs.
Using ratios that go backward
WRONG: sli = errors / total
(lower is better — confusing)
RIGHT: sli = (total - errors) / total
(higher is better, matches SLO target convention)Defining the numerator/denominator precisely
Every SLI must specify:
1. What's being counted (requests? events? checks?) 2. What "good" means (the numerator filter) 3. What's excluded (filters: bot traffic, internal traffic, health checks, etc.) 4. Where it's measured (LB? service edge? client side?)
Bad: "request success rate" Good: count(http_requests_total{job="checkout-api", status_code=~"2..|3.."}) / count(http_requests_total{job="checkout-api", source!="bot"})
The second one is testable, debuggable, and unambiguous.
Review the SLI as the system evolves
System change → SLI change. When:
- A new failure mode appears (e.g., circuit breaker that returns 5xx) → update what's "bad"
- A dependency moves (e.g., from synchronous to async) → re-examine what users feel
- A new endpoint is added → does it belong in this SLO or its own?
Stale SLIs are worse than no SLIs — they create false confidence.
SLO principles
The Google SRE Workbook canon, distilled to what matters in practice.
SLI vs SLO vs SLA
| Term | What it is | Audience | Stakes |
|---|---|---|---|
| SLI (Service Level Indicator) | A measurable signal of user-perceived health (e.g., HTTP success rate) | Engineering | None directly — it's the input |
| SLO (Service Level Objective) | A target value or range for the SLI over a window (e.g., 99.9% over 28 days) | Engineering, internal | Engineering action when burning budget |
| SLA (Service Level Agreement) | A customer-facing commitment with consequences (refunds, credits) | Customers, legal, sales | Contractual; costs money to break |
Cardinal rule: SLA target < SLO target < SLI baseline.
If SLA = 99.9%, SLO must be tighter (e.g., 99.95%) so engineering action triggers BEFORE customer-impacting violation.
The error budget
error_budget = 100% − SLO_target
For 99.9% SLO over 30 days:
error_budget = 0.1% × 30d × 24h × 60min = 43.2 minutes/month
That's the maximum unavailability you can spend without violating SLO.The whole point of SLOs: error budget makes reliability a numeric resource you can spend deliberately. Spending it on:
- New feature rollouts (some risk)
- Chaos experiments (intentional learning)
- Migrations (necessary instability)
is GOOD. Wasting it on:
- Avoidable bugs
- Bad deploys
- Unmonitored regressions
is BAD. Error budget reframes "should we ship this?" from gut feel to a budget question.
Multi-window burn-rate alerts (the canon)
Google SRE Workbook Chapter 5: "Alerting on SLOs." The recommended structure:
| Alert | Long window | Short window | % budget burned | Severity |
|---|---|---|---|---|
| Fast burn | 1h | 5m | 2% | page |
| Slow burn | 6h | 30m | 5% | page |
| Ticket burn | 3d | 6h | 10% | ticket (no page) |
Why two windows per alert?
- Long window filters noise (random spikes don't fire)
- Short window speeds detection (alert fires the moment burn is sustained)
Single-window burn-rate alerts are either too noisy (5-min only) or too slow (30-day only).
The error_budget_calculator.py tool emits these thresholds for any target+window combination.
Choosing a target
Bad: copy-paste 99.9% on every endpoint. Good: measure 30 days of historical SLI, then:
target = floor(p50 of last 30 days × 100) / 100This guarantees the system has actually sustained the target. Tightening later is fine; loosening after announcing a target is embarrassing.
Reality-check ranges:
| User-perceived service | Typical target |
|---|---|
| Internal tool, occasional use | 99% |
| Standard customer-facing app | 99.9% |
| Commerce / payments | 99.95% |
| Critical infrastructure | 99.99% |
| Hyperscale (Google, AWS) | 99.999% (and only for tiny scope) |
99.99%+ requires multi-region, automatic failover, no single points of failure, and a team paid to maintain that. Don't write it on a whim.
Choosing a window
| Window | Use when | Trade-off |
|---|---|---|
| 7 days | Need fast feedback; system changes weekly | High noise, fast learning |
| 28 days | Default for most services | Balanced |
| 30 days | Calendar-month aligned (board reports) | Slightly more noise than 28 |
| 90 days | Slow-changing systems, contract reporting | Too slow for engineering feedback |
28 days = 4 calendar weeks. Recommended unless you have a specific reason otherwise.
Error budget policy (the missing half)
An SLO without a policy is a wish. The policy answers:
When the error budget is burned, what changes?
Standard policy options:
| State | Action |
|---|---|
| Budget healthy (>50% remaining) | Normal operation; ship features, run experiments |
| Budget at 50% | Heightened review on risky changes |
| Budget exhausted (<10%) | Freeze risky deploys; focus on reliability work |
| Budget violated | Postmortem; SLO revision; blameless review |
Without an agreed policy, burning budget is just a number.
SLO ownership
Every SLO has exactly one owning team. The owner is responsible for:
- Keeping the SLI definition correct as the system evolves
- Making sure burn-rate alerts route to the right team
- Quarterly review and revision
- Writing the postmortem when SLO is violated
Without an owner, SLOs bit-rot (SLI definitions drift, alerts route to wrong teams, reviews never happen).
When NOT to define an SLO
- For internal tooling that breaks rarely and doesn't gate revenue
- For experimental features that may be removed in 30 days
- For systems where you can't measure user experience (revisit when you can)
- As performance theater — measuring without acting on burn
Review cadence
- Quarterly — minimum for any active SLO
- Monthly — recommended for systems under active development
- Weekly — only during incident-recovery windows
The point of review: "is this SLO still right?" Tightening, loosening, or removing an SLO is a normal outcome. SLOs are not contracts; they are calibration knobs.
Reading
- Google SRE Workbook (Beyer, Murphy, Rensin et al.) — Chapter 2 (SLO design), Chapter 5 (alerting on SLOs). Free at sre.google/workbook.
- Implementing Service Level Objectives (Alex Hidalgo) — covers operationalization beyond Google's frame.
- The SLO Reference Architecture (slo.dev) — community-maintained.
#!/usr/bin/env python3
"""Compute error budget and multi-window burn-rate alert thresholds.
Per Google SRE Workbook (Chapter 5: Alerting on SLOs), reliable burn-rate
alerting uses TWO windows: a fast window (1h) for catastrophic burn and a
slow window (6h) to filter false positives. Optionally a 3-day window for
ticket-only (non-paging) alerts.
Outputs:
- Allowed downtime in the SLO window
- Burn-rate thresholds for fast/slow/ticket alert windows
- PromQL-shaped alert rules ready to paste
References:
https://sre.google/workbook/alerting-on-slos/
"""
import argparse
import json
import sys
# Per Google SRE Workbook Chapter 5: Table 5-3 recommended thresholds
# (severity, percent_of_monthly_budget, long_window, short_window_ratio)
DEFAULT_BURN_RATE_RULES = [
{
"name": "fast_burn",
"severity": "page",
"long_window_hours": 1,
"short_window_hours": 1 / 12,
"budget_pct_consumed": 2.0,
"rationale": "2% of monthly budget burned in 1h => system on fire",
},
{
"name": "slow_burn",
"severity": "page",
"long_window_hours": 6,
"short_window_hours": 0.5,
"budget_pct_consumed": 5.0,
"rationale": "5% of monthly budget burned in 6h => sustained degradation",
},
{
"name": "ticket_burn",
"severity": "ticket",
"long_window_hours": 72,
"short_window_hours": 6,
"budget_pct_consumed": 10.0,
"rationale": "10% of monthly budget burned in 3d => trending bad",
},
]
def compute(target_percent, window_days):
if not 50 <= target_percent <= 100:
raise ValueError(f"target must be between 50 and 100, got {target_percent}")
if window_days < 1:
raise ValueError("window-days must be >= 1")
bad_fraction = (100 - target_percent) / 100
window_minutes = window_days * 24 * 60
budget_minutes = round(bad_fraction * window_minutes, 4)
rules = []
for rule in DEFAULT_BURN_RATE_RULES:
burn_rate_threshold = (rule["budget_pct_consumed"] / 100) / (rule["long_window_hours"] / (window_days * 24))
rules.append({
"name": rule["name"],
"severity": rule["severity"],
"long_window": _fmt_hours(rule["long_window_hours"]),
"short_window": _fmt_hours(rule["short_window_hours"]),
"budget_pct_consumed": rule["budget_pct_consumed"],
"burn_rate_threshold": round(burn_rate_threshold, 3),
"rationale": rule["rationale"],
"promql": _promql_rule(rule, burn_rate_threshold, target_percent),
})
return {
"target_percent": target_percent,
"window_days": window_days,
"bad_fraction": round(bad_fraction, 6),
"budget_minutes": budget_minutes,
"budget_hours": round(budget_minutes / 60, 4),
"alert_rules": rules,
}
def _fmt_hours(hours):
if hours < 1:
return f"{int(round(hours * 60))}m"
if hours < 24:
return f"{int(round(hours))}h"
return f"{int(round(hours / 24))}d"
def _promql_rule(rule, burn_rate, target_pct):
long_w = _fmt_hours(rule["long_window_hours"])
short_w = _fmt_hours(rule["short_window_hours"])
return (
f"# {rule['name']} ({rule['severity']})\n"
f"# Burn rate threshold: {round(burn_rate, 3)}\n"
f"(\n"
f" sli:rate{long_w} > {round(burn_rate, 3)} * (1 - {target_pct / 100})\n"
f" AND\n"
f" sli:rate{short_w} > {round(burn_rate, 3)} * (1 - {target_pct / 100})\n"
f")"
)
def render_text(result):
print(f"Error Budget — target={result['target_percent']}%, window={result['window_days']}d")
print("=" * 60)
print(f"Allowed bad events: {result['bad_fraction'] * 100:.4f}% of total")
print(f"Allowed downtime: {result['budget_minutes']:.2f} min ({result['budget_hours']:.2f} hours)")
print("")
print("Multi-window burn-rate alerts (Google SRE Workbook):")
print("")
for r in result["alert_rules"]:
print(f" [{r['severity'].upper():6}] {r['name']}")
print(f" windows: {r['long_window']} long / {r['short_window']} short")
print(f" burn rate: {r['burn_rate_threshold']}")
print(f" consumed: {r['budget_pct_consumed']}% of monthly budget")
print(f" rationale: {r['rationale']}")
print("")
print("PromQL-shaped rules:")
print("")
for r in result["alert_rules"]:
print(r["promql"])
print("")
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--target", type=float, help="Target percent (e.g., 99.9)")
ap.add_argument("--window-days", type=int, default=28, help="Window in days (default: 28)")
ap.add_argument("--format", choices=["text", "json"], default="text")
ap.add_argument("--sample", action="store_true", help="Run with embedded sample inputs (99.9%% / 28d)")
args = ap.parse_args()
if args.sample:
target, window_days = 99.9, 28
elif args.target is not None:
target, window_days = args.target, args.window_days
else:
ap.error("--target is required (or use --sample)")
try:
result = compute(target, window_days)
except ValueError as e:
print(f"ERROR: {e}", file=sys.stderr)
return 2
if args.format == "json":
print(json.dumps(result, indent=2))
else:
render_text(result)
return 0
if __name__ == "__main__":
sys.exit(main())
#!/usr/bin/env python3
"""Generate a structured SLO definition.
Enforces required fields (service, SLI type + definition, target, window,
owner, error budget policy reference). Refuses to render if required fields
are missing — exit 1 forces the caller to provide them.
Output is markdown by default. JSON output is consumed by slo_review.py.
"""
import argparse
import json
import sys
from datetime import datetime, timezone
SLI_TYPES = {
"request-success-rate": {
"numerator": "count(http_requests_total{status=~\"2..|3..\"})",
"denominator": "count(http_requests_total)",
"user_question": "Did the request succeed?",
},
"request-latency": {
"numerator": "count(http_request_duration_seconds < 0.5)",
"denominator": "count(http_request_duration_seconds)",
"user_question": "Was the response fast enough?",
},
"availability-time": {
"numerator": "(window_seconds - sum(up_down_seconds))",
"denominator": "window_seconds",
"user_question": "Was the service up?",
},
"data-freshness": {
"numerator": "count(data_age_seconds < freshness_threshold)",
"denominator": "count(data_age_seconds)",
"user_question": "Is the data current?",
},
"correctness": {
"numerator": "count(correct_outputs)",
"denominator": "count(total_outputs)",
"user_question": "Was the answer correct?",
},
}
def build_slo(args):
sli_meta = SLI_TYPES.get(args.sli_type, {})
slo = {
"slo_id": f"slo-{args.service}-{args.sli_type}-{int(datetime.now(timezone.utc).timestamp())}",
"created": datetime.now(timezone.utc).isoformat(),
"service": args.service,
"owner": args.owner or "<must define before SLO is live>",
"user_journey": args.user_journey or f"<{sli_meta.get('user_question', 'describe the user journey this SLO protects')}>",
"sli": {
"type": args.sli_type,
"numerator": args.sli_numerator or sli_meta.get("numerator", "<must define>"),
"denominator": args.sli_denominator or sli_meta.get("denominator", "<must define>"),
"labels": args.sli_labels.split(",") if args.sli_labels else [],
},
"target_percent": args.target,
"window_days": args.window_days,
"error_budget": {
"minutes_per_window": _budget_minutes(args.target, args.window_days),
"policy_doc": args.policy_doc or "<link to error budget policy required before SLO is live>",
},
"alerts": {
"fast_burn_threshold": "see error_budget_calculator.py",
"slow_burn_threshold": "see error_budget_calculator.py",
},
"review_cadence": args.review_cadence,
}
return slo
def _budget_minutes(target_pct, window_days):
bad_fraction = max(0.0, (100 - target_pct) / 100)
return round(bad_fraction * window_days * 24 * 60, 2)
def _missing_required(slo):
missing = []
if not slo["owner"] or slo["owner"].startswith("<"):
missing.append("owner")
if not slo["error_budget"]["policy_doc"] or slo["error_budget"]["policy_doc"].startswith("<"):
missing.append("error_budget.policy_doc")
if slo["sli"]["numerator"].startswith("<") or slo["sli"]["denominator"].startswith("<"):
missing.append("sli.numerator/denominator")
return missing
def render_markdown(slo):
lines = []
lines.append(f"# SLO: {slo['slo_id']}")
lines.append("")
lines.append(f"- **Service:** `{slo['service']}`")
lines.append(f"- **Owner:** {slo['owner']}")
lines.append(f"- **Created:** {slo['created']}")
lines.append(f"- **User journey:** {slo['user_journey']}")
lines.append("")
lines.append("## SLI")
lines.append(f"- **Type:** {slo['sli']['type']}")
lines.append(f"- **Numerator:** `{slo['sli']['numerator']}`")
lines.append(f"- **Denominator:** `{slo['sli']['denominator']}`")
if slo["sli"]["labels"]:
lines.append(f"- **Labels:** {', '.join(slo['sli']['labels'])}")
lines.append("")
lines.append("## Target")
lines.append(f"- **Target:** {slo['target_percent']}% over {slo['window_days']} days")
lines.append(f"- **Error budget:** {slo['error_budget']['minutes_per_window']} minutes per window")
lines.append(f"- **Policy:** {slo['error_budget']['policy_doc']}")
lines.append("")
lines.append("## Alerts")
lines.append("Run `error_budget_calculator.py --target {} --window-days {}` for burn-rate thresholds.".format(
slo["target_percent"], slo["window_days"]
))
lines.append("")
lines.append(f"## Review cadence: {slo['review_cadence']}")
return "\n".join(lines)
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--service", required=True, help="Service name (e.g., checkout-svc)")
ap.add_argument("--sli-type", required=True, choices=list(SLI_TYPES.keys()))
ap.add_argument("--target", type=float, required=True, help="Target percent (e.g., 99.9)")
ap.add_argument("--window-days", type=int, default=28, help="Compliance window in days (default: 28)")
ap.add_argument("--user-journey", help="The user journey this SLO protects")
ap.add_argument("--sli-numerator", help="Override default SLI numerator expression")
ap.add_argument("--sli-denominator", help="Override default SLI denominator expression")
ap.add_argument("--sli-labels", help="Comma-separated labels (e.g., env=prod,region=us-east-1)")
ap.add_argument("--owner", help="Owning team / handle")
ap.add_argument("--policy-doc", help="URL or path to error budget policy")
ap.add_argument("--review-cadence", default="quarterly", help="How often to review (default: quarterly)")
ap.add_argument("--format", choices=["markdown", "json"], default="markdown")
args = ap.parse_args()
if not 50 <= args.target <= 100:
print(f"ERROR: --target must be between 50 and 100, got {args.target}", file=sys.stderr)
return 2
if args.window_days < 1:
print(f"ERROR: --window-days must be >= 1", file=sys.stderr)
return 2
slo = build_slo(args)
missing = _missing_required(slo)
if args.format == "json":
print(json.dumps(slo, indent=2))
else:
print(render_markdown(slo))
if missing:
print("")
print(f"WARNING: missing required fields: {', '.join(missing)}", file=sys.stderr)
print("SLO is NOT live until these are filled.", file=sys.stderr)
return 1 if missing else 0
if __name__ == "__main__":
sys.exit(main())
#!/usr/bin/env python3
"""Audit existing SLO definitions for the common bugs.
Reads markdown or JSON SLO docs and reports:
FAIL — definitely wrong (target ≥ 99.99 with no engineering investment plan,
no SLI definition, no error budget policy, CPU-as-SLI)
WARN — probably wrong (target ≤ 99.0, window outside 7-90 days)
Use as a pre-merge gate before SLOs go live.
"""
import argparse
import json
import os
import re
import sys
CPU_AS_SLI_PATTERNS = [
r"\bcpu_usage\b",
r"\bcpu_utilization\b",
r"\bmemory_usage\b",
r"\bmem_used\b",
r"\bdisk_usage\b",
r"\bdisk_full\b",
]
SLI_KEYWORDS = ("numerator", "denominator", "sli")
POLICY_KEYWORDS = ("policy", "error_budget", "error budget")
def _read(path):
try:
with open(path, "r", encoding="utf-8", errors="replace") as f:
return f.read()
except OSError:
return ""
def _parse_target(text):
m = re.search(r"target[:\s\"]+(\d+(?:\.\d+)?)\s*%?", text, re.IGNORECASE)
if m:
return float(m.group(1))
return None
def _parse_window_days(text):
m = re.search(r"window[_\-\s]?days?[:\s\"]+(\d+)", text, re.IGNORECASE)
if m:
return int(m.group(1))
m = re.search(r"window[:\s\"]+(\d+)\s*days?", text, re.IGNORECASE)
if m:
return int(m.group(1))
return None
def _has_any(text, keywords):
low = text.lower()
return any(k in low for k in keywords)
def _has_cpu_as_sli(text):
for pat in CPU_AS_SLI_PATTERNS:
if re.search(pat, text, re.IGNORECASE):
return True
return False
# Embedded sample SLO doc — intentionally flawed (target too high, CPU-as-SLI,
# no error budget policy) so --sample exercises several finding paths.
SAMPLE_SLO_DOC = """# Checkout API SLO
target: 99.995%
window_days: 28
sli: cpu_usage below 80%
"""
def audit_text(text):
findings = []
target = _parse_target(text)
window_days = _parse_window_days(text)
if target is None:
findings.append(("FAIL", "no_target", "no SLO target (X%) found in document"))
else:
if target >= 99.99:
findings.append(("FAIL", "target_too_high",
f"target {target}% ≥ 99.99% — sustainable only with massive engineering investment; document the investment plan or lower"))
elif target <= 99.0:
findings.append(("WARN", "target_too_low",
f"target {target}% ≤ 99% — likely wrong SLI; users will notice"))
if window_days is None:
findings.append(("WARN", "no_window", "no compliance window found"))
else:
if window_days < 7:
findings.append(("FAIL", "window_too_short",
f"window {window_days}d < 7d — statistical noise dominates"))
elif window_days > 90:
findings.append(("WARN", "window_too_long",
f"window {window_days}d > 90d — feedback too slow"))
if not _has_any(text, SLI_KEYWORDS):
findings.append(("FAIL", "no_sli_definition",
"no SLI definition (numerator/denominator) found"))
if not _has_any(text, POLICY_KEYWORDS):
findings.append(("FAIL", "no_error_budget_policy",
"no error budget policy reference found"))
if _has_cpu_as_sli(text):
findings.append(("FAIL", "cpu_as_sli",
"CPU/memory/disk-usage referenced — system metrics aren't user experience; pick a request-level SLI"))
return findings
def audit_one(path):
return audit_text(_read(path))
def _walk(target):
if os.path.isfile(target):
yield target
return
for r, _, files in os.walk(target):
for f in files:
if f.endswith((".md", ".json", ".yaml", ".yml")):
yield os.path.join(r, f)
def audit(target):
results = []
for path in _walk(target):
findings = audit_one(path)
if findings:
results.append({"path": path, "findings": findings})
return results
def render_text(results):
fails = sum(1 for r in results for f in r["findings"] if f[0] == "FAIL")
warns = sum(1 for r in results for f in r["findings"] if f[0] == "WARN")
print(f"SLO Review — {len(results)} doc(s) with findings, {fails} FAIL, {warns} WARN")
print("")
if not results:
print("PASS: no issues detected.")
return 0
for r in results:
print(f"== {r['path']}")
for level, key, msg in r["findings"]:
print(f" [{level}] {key}: {msg}")
print("")
return 1 if fails else 0
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--slo-doc", help="Path to SLO doc or directory of docs")
ap.add_argument("--format", choices=["text", "json"], default="text")
ap.add_argument("--sample", action="store_true", help="Audit an embedded sample SLO doc")
args = ap.parse_args()
if args.sample:
results = [{"path": "<embedded sample>", "findings": audit_text(SAMPLE_SLO_DOC)}]
else:
if not args.slo_doc:
ap.error("--slo-doc is required (or use --sample)")
if not os.path.exists(args.slo_doc):
print(f"ERROR: not found: {args.slo_doc}", file=sys.stderr)
return 2
results = audit(args.slo_doc)
if args.format == "json":
print(json.dumps(results, indent=2))
return 1 if any(f[0] == "FAIL" for r in results for f in r["findings"]) else 0
return render_text(results)
if __name__ == "__main__":
sys.exit(main())
Related skills
How it compares
Choose slo-architect for SLO discipline and error budgets; use generic observability skills when you only need dashboards without reliability targets.
FAQ
What scripts does slo-architect include?
slo-architect version 2.9.0 ships slo_designer.py for SLO definitions, error_budget_calculator.py for downtime and burn-rate thresholds, and slo_review.py to audit SLO documents. Together they produce markdown SLOs with PromQL-shaped alert rules.
What SLO mistakes does slo-architect catch?
slo-architect slo_review.py flags issues like missing SLI definitions, targets that are too aggressive or too low, windows shorter than 7 days or longer than 90 days, and absent error budget policies when budget burns.