A thermostat doesn't ask permission to nudge the temperature two degrees — someone already decided the acceptable range. The Thermostat pattern applies that to capacity and cost: an agent continuously measures utilization, detects drift and waste, and proposes corrections provably inside a pre-approved policy envelope enforced in code.
The rollout is staged — observe, then recommend, then auto only for the reversible — and savings arrive as a stream of small PRs instead of a quarterly purge.
"The cost project." Finance flags that cloud spend is up 34% year over year, and Dana, the platform engineer who drew the short straw, is handed it.
She exports utilization data into a spreadsheet, cross-referencing instance types against a rightsizing report the FinOps tool has been emitting — unread — since March. The report lists 61 recommendations; nobody has actioned any, because each one carries an implicit risk assessment someone would have to do, and nobody owns doing it.
Under quarter-end pressure, the team batches 40 resizes into one change window. Most go fine.
report-runner — average CPU 16%, which looked like textbook waste — turns out to run the nightly finance batch that spikes to 85% at 02:00. Downsized, it OOM-kills; the finance batch misses its morning deadline, and the retro consumes more engineering hours than the resize saved that month.
The episode poisons the well: for the next year every rightsizing suggestion is met with "remember report-runner." By Q1 the estate has drifted back — new services launch overprovisioned "to be safe" — and the same 34% conversation is waiting in next October's calendar.
This is the quarterly fire-drill loop: heroic, risky, and with a savings half-life of about two quarters.
It runs nightly, reads 14 days of per-service utilization and the current cost rates, and operates inside an envelope the team wrote down once, in a reviewed file: rightsizing only within an instance family, one step at a time; replica changes of at most one; services tagged stateful untouchable; every change must leave 40% headroom above the observed peak, not the average; auto-execute off.
catalog-api m5.2xlarge → m5.xlarge across its 6 replicas — 14-day average CPU 12%, peak 22%, projected peak after resize 44%, still under the 60% ceiling the headroom rule implies — about $840 a monthimage-resizer trimmed — about $250 a monthreport-runner — proposed nothing, logging "peaks require the headroom; do not downsize"Merging a PR triggers the normal CI path — the change ships like any other change, because it is one.
The Thermostat saw the same 16% average that fooled the humans — and also the 85% nightly peaks. The trap that caused last year's outage is now caught twice, mechanically.
Six weeks in, the team promotes one action class to auto: replica ±1 scale-downs in the dev environment, because they are reversible with a single command and the worst case is a five-minute blip nobody pays for. Everything else stays at recommend. There is no October fire drill, because there is nothing to purge — waste is skimmed weekly, in increments small enough that no single change can ruin a Friday, and the audit log can show finance exactly which merged PR saved which dollars.
Client-safe by design — the analysis runs against an approved tenancy (Claude on AWS Bedrock, Azure OpenAI) or locally via Ollama, with service names, account IDs, and absolute dollar figures replaced by stable placeholders before prompting while the utilization percentages that drive the decision stay intact. Full setup is in the build guide below.
Blue = AI-added step · Amber = human control point · Gray = your existing tools.
| Step | Who acts | What changes | Why it matters |
|---|---|---|---|
| Measure | Your existing tools | Metrics and cost APIs are read on a schedule — nothing new to instrument | The loop runs on data you already collect; peaks and averages both captured |
| Detect drift and waste | Agent | 14-day utilization windows compared against allocation, peaks weighted over averages | The unread rightsizing report becomes an active process; spiky services are recognized, not flagged |
| Recommend | Agent | Each finding lands as a small PR: two-line diff, projected saving, cited evidence, risk note | Savings arrive weekly in 3-minute reviews instead of quarterly in 40-change batches |
| Policy check | Code you wrote | Every recommendation is validated against the envelope — allowed actions, headroom, forbidden tags | The model is never trusted to self-police; violations are rejected with printed reasons |
| Approve | Human | Merge the PR — or close it with a comment the agent's next run can see | The merge is the approval artifact; normal CI and review culture apply unchanged |
| Act | Agent (inside envelope) | The change ships through your existing pipeline; auto-execute only for reversible action classes | Blast radius is capped by the envelope, not by hope |
| Verify | Agent + your dashboards | Next cycle's measurements confirm spend dropped and peaks still fit under the headroom ceiling | Closed loop: a resize that hurt latency shows up in the same data that proposed it |
| Audit | Your existing tools | Every recommendation, rejection reason, merge, and outcome is logged under the agent's identity | Finance sees which PR saved what; trust survives personnel changes |
| Component | In this lesson's build | In your stack |
|---|---|---|
| Event source | You run thermostat.py by hand (a nightly run in spirit) | Scheduler/cron, Kubecost alerts, AWS Cost Anomaly Detection, Azure Cost Management alerts |
| Context stores (read) | usage/metrics.json — 14 days of per-service utilization | Prometheus/CloudWatch/Datadog metrics; Kubecost, Cast AI, CloudZero, or cloud cost/billing APIs |
| Action interface (allowlisted writes) | PR-style diff files written to recommendations/ | Real PRs against your IaC repo (Terraform, Helm values, Kubernetes manifests); later, a narrow rightsize API allowlist |
| Approval channel | You, reading the diffs | PR review with CODEOWNERS on the capacity paths; policy check wired into CI via OPA/Conftest |
| Verification | Re-run against updated metrics after a change | Post-change utilization and SLO dashboards; billing export deltas confirming the projected saving materialized |
| Audit sink + identity | The printed pass/reject log plus diffs committed to git | Agent runs under its own identity with scoped credentials; recommendations, rejections, and merges retained in git and your audit tooling |
Never trust the model to self-police. The envelope lives in a reviewed file, and enforcement is a deterministic policy check in code that runs on every recommendation — the model proposes, the code disposes, the human approves. If your only guardrail is a sentence in the system prompt, you have a wish, not a policy.
The Thermostat is the pattern where staged autonomy must be explicit, because its whole point is eventually acting without a human in the loop — and rushing that step is how you get the 02:14 OOM. Run the ladder on the calendar, not on vibes:
The agent runs nightly and only logs what it would recommend. You read the log and count false positives — above all, whether it ever wants to downsize your spiky services. No PRs, no pressure.
Recommendations land as PRs through the policy check. Humans merge every change. Track two numbers: percentage of PRs merged without modification (your precision), and realized savings versus projected (your calibration).
Promote one narrow action class at a time — start with replica ±1 in non-production — and only where rollback is a single command with instant effect. Instance resizes of stateful anything, cross-environment changes, and anything without a tested one-step rollback stay at recommend indefinitely.
Any one of those triggers drops the affected action class a full tier immediately. Review the tier assignments monthly with the same seriousness as an access review — an autonomy grant is a standing credential.
Averages lie. A service at 16% average CPU with an 85% nightly peak is not overprovisioned — it is correctly provisioned for its peak, and every naive rightsizer in existence will try to break it. Weight peaks over averages in the analysis, enforce headroom against peaks in the policy check, and treat "the spiky service was left alone" as the first test of any Thermostat you build.