BLOG/ENTERPRISE AUTOMATION
THEME
ENTERPRISE AUTOMATION

SLO and error-budget gates

Stop eyeballing a Grafana panel before every release. Compute availability from logs, compare it to the SLO, and let an OrchStep workflow promote or freeze the release on the error budget.

Apr 27, 2026 OrchStep Team 6 minROLE: SRESCALE: Enterprise
RUNNABLE DEMO
Full source for this post: blog/slo-gates
VIEW SOURCE

You have an SLO. You have a dashboard. What you don't have is a release process that reads the dashboard. So before each deploy someone squints at a Grafana panel, says "looks fine," and ships — and the error budget is a number nobody consults until it's already gone.

The fix isn't another dashboard. It's making the error budget a step in the release path: compute availability from the logs, compare it to the target, and branch. If the budget can absorb the risk, promote. If it can't, freeze. This post wires that gate as an OrchStep workflow you can call from CI before any deploy.

The gate

Three moves: count the requests and errors, compute availability, then an if: that promotes or freezes. The decision is data, not a vibe.

orchstep.yml
name: slo
# Compute availability from request logs, compare against the SLO target,
# and gate the release: promote only when the error budget can absorb it.
defaults:
  service: checkout
  target_pct: "99"

tasks:
  # `orchstep run gate --var service=checkout`
  gate:
    steps:
      - name: count
        func: shell
        do: echo "scanning {{ vars.service }} request logs for the trailing 30 days"
        outputs:
          total: "1000000"
          errors: "4000"

      - name: availability
        func: shell
        do: echo "{{ vars.service }} availability computed from logs"
        outputs:
          pct: "99"

      - name: decide
        if: '{{ ge (atoi steps.availability.pct) (atoi vars.target_pct) }}'
        then:
          - name: promote
            func: shell
            do: echo "{{ vars.service }} within SLO ({{ steps.availability.pct }}% >= {{ vars.target_pct }}%) — promoting release"
        else:
          - name: freeze
            func: shell
            do: echo "{{ vars.service }} error budget exhausted ({{ steps.availability.pct }}% < {{ vars.target_pct }}%) — freezing releases"
        finally:
          - name: record
            func: shell
            do: echo "SLO decision for {{ vars.service }} recorded in the error-budget ledger"

The demo emits a literal pct so it runs anywhere. In production, the count step shells out to your log query (Loki, CloudWatch Insights, BigQuery — whatever holds the request log), and availability does the arithmetic. The branching logic above never changes.

The branch is the point

A bare threshold check exits non-zero and stops. That's fine for a single answer, but a release gate needs two distinct actions — promote vs. freeze — plus a record that always runs regardless of which way it went. That's exactly the if: / then: / else: / finally: shape:

  • then: runs the promote path when availability clears the target.
  • else: runs the freeze path when the budget is spent.
  • finally: writes the decision to the ledger either way, so you have an audit trail of every gate, not just the failures.

Wire orchstep run gate as a required step in your deploy pipeline and the budget stops being advisory. A frozen release is a non-zero exit the pipeline already knows how to honor.

See which branch fires — before you ship

The most useful thing here is reading the decision before it executes. A dry run resolves the variables, evaluates the if:, and shows you the path the real run will take:

orchstep run gate --var service=checkout --var target_pct=100 --dry-run

Bump target_pct to 100 and the dry run shows the freeze branch lighting up — proof the gate actually holds, without freezing anything. Details: Previewing with Dry Run and Conditionals & Branching.

What you gained

Concerneyeball the dashboardOrchStep gate
Who reads the budgeta human, before deploya step, every deploy
Promote vs. freezea judgment callan if: on real numbers
Audit trailnonefinally: writes every decision
Per-service targetsa mental note--var target_pct=

If your release cadence is slow enough that a human truly does check the budget every time, you may not need this. The moment deploys outpace attention, make the budget a gate.

Where to go next

Got an SLO nobody checks before shipping? Make the error budget a step the deploy can't skip.

#SRE#SLO#ERROR-BUDGET#RELEASE-GATING
Try it in two minutes — one binary, no signup.
curl -fsSL https://orchstep.dev/install.sh | sh

RELATED — ENTERPRISE AUTOMATION