Automating Azure Cost Optimization with an AI Agent
Open Azure Advisor's Cost tab in any tenant more than a year old and you'll find the same thing: a wall of recommendations, impact estimates attached, some of them months stale. Advisor did its job. Nobody worked the list. That backlog is the reason to automate Azure cost optimization rather than audit harder. Detection was never the bottleneck. Every fix still needs a person to validate the finding, check it against context Advisor doesn't have ("is this VM idle, or is it the quarterly batch job?"), open a ticket and wait for a sprint slot. The backlog refills faster than tickets close.
Part one mapped the seven places Azure subscriptions leak money: orphaned disks and snapshots, idle and oversized VMs, reservation coverage gaps, idle networking, oversized SQL tiers, bandwidth, and always-on dev and test VMs. Part two audited all of them with native tools: Cost Management, Advisor, Azure Monitor and az CLI sweeps.
This part covers running those checks continuously with CloudThinker Optimize, and what it won't do without your explicit approval.
Why Advisor recommendations pile up
01Context
Signals only
Advisor can't tell an idle VM from a warm standby, so a person triages every line.
02Action
No path to fix
Advisor recommends. Every fix is still a CLI command or portal session behind a ticket.
03Time
Decay
Every deploy can orphan a disk. A quarterly audit races daily waste and loses.
No context. Advisor's cost recommendations come from utilization signals alone. It can't tell a genuinely idle VM from a warm standby or a month-end job, so every line needs human triage before anyone acts.
No remediation path. Advisor recommends. It doesn't execute. Outside VM auto-shutdown, every fix from part two is a manual az CLI command or portal session, which means a ticket and a queue.
Decay. The findings from a part-two audit start going stale the day you export them. Every deploy can orphan a disk, every scale-out can open a coverage gap, and a quarterly audit races daily waste.
The point of putting an agent on this work is continuous detection with evidence attached, plus a remediation path that runs only at the level you've granted.
Connecting Azure: a read-only service principal, about five minutes
01Cloud
Azure
- Cost ManagementCost Management Reader
- Resource inventoryReader at subscription scope
- Azure Monitor metricsReader at subscription scope
Read-only service principal
02Investigation
CloudThinker
- Scan all seven leaksContinuously, per subscription
- Check the contextMetrics window, activity log, tags
- Propose the fixExact change, saving range, rollback
No agents on your VMs
03Where the team works
Chat and tickets
SlackFindings and approvals in the thread
Microsoft TeamsSame flow for Microsoft shops
- JiraFollow-ups with the evidence linked
Nothing new to watch
Optimize connects the way your security review would ask for anyway: a service principal holding read-only roles at subscription scope. No agents on VMs, no stored write credentials, no keys in a spreadsheet. At connection time it can see your resources and billing data and change nothing.
Setup is two commands:
az ad sp create-for-rbac \
--name "cloudthinker-costops" \
--role Reader \
--scopes /subscriptions/<your-subscription-id>
az role assignment create \
--assignee <appId-from-previous-output> \
--role "Cost Management Reader" \
--scope /subscriptions/<your-subscription-id>
Paste the tenant ID, client ID and secret into the connection screen and the first scan starts. The connection guide in the docs lists the exact permissions requested, so your security team can review the grant before anyone clicks connect. For multi-subscription tenants, repeat per subscription or assign both roles once at management-group scope.
Continuous scanning across all seven Azure leaks
Once connected, the agent scans the same seven leaks you mapped in part one, including the ones a manual audit tends to skip because the evidence is tedious to assemble:
- Unattached managed disks and snapshots.
diskStateof Unattached, cross-referenced with age and activity log history, plus snapshots of disks that no longer exist. - Idle or oversized VMs. Sustained low CPU and network over a 30-day window, with a one-size-down proposal where the maximums support it.
- Reservation and savings plan coverage gaps. Coverage tracked against your stable baseline. When on-demand usage has held steady above coverage, the agent proposes a purchase with break-even math. When an existing reservation's utilization drops, or one is about to expire, it flags that too.
- Unused public IPs, idle load balancers and Application Gateways. Unassociated Standard IPs, and load balancers or gateways with near-zero backend traffic.
- Over-provisioned Azure SQL. Databases sitting far below their provisioned vCores or DTUs over a sustained window.
- Bandwidth. Cross-region and egress charges growing out of proportion to workload, the line almost nobody audits by hand.
- Non-production running 24/7. Dev and test VMs with business-hours usage that bill around the clock.
The checks are the ones you ran by hand in part two. What changes is cadence and evidence. A disk orphaned by Tuesday's deploy is a finding on Tuesday, and it arrives with the metrics window, the current monthly cost and the projected saving as a range: the case you'd have built manually, minus the hours.
One finding, end to end
Detection is the easy half. The useful work is deciding what a number means. Here's an illustrative case.
01Signal
Cost jump
Production compute up about 30% day over day, with no deploy.
02Readings
Three suspects
More instance hours, a lapsed commitment, or a SKU or price change.
03Check
Pricing model
The Query API splits the same days by On Demand and Reservation.
04Output
Finding
A reservation expired. Renewal proposed with break-even math.
1. The signal. Daily amortized compute cost on the production subscription rises about 30% overnight and stays there. No deploy, no scaling event in the change log.
2. Name the readings. Three explanations fit a step change like this, and each predicts something different:
- More usage. A scale set didn't scale in, or a new workload landed. Predicts: instance hours up, price per hour flat.
- A lapsed commitment. A reservation expired or stopped applying. Predicts: instance hours flat, and the cost moves from the Reservation line to the On Demand line.
- A SKU or price change. Someone resized a pool to a more expensive family. Predicts: hours flat, the meter or SKU mix changes.
3. Check the one that splits them. Grouping the same days by pricing model separates all three. The agent calls the Cost Management Query API, the same data behind Cost analysis:
az rest --method post \
--url "https://management.azure.com/subscriptions/<sub-id>/providers/Microsoft.CostManagement/query?api-version=2023-03-01" \
--body '{
"type": "AmortizedCost",
"timeframe": "Custom",
"timePeriod": {"from": "2026-09-01", "to": "2026-09-30"},
"dataset": {
"granularity": "Daily",
"aggregation": {"totalCost": {"name": "Cost", "function": "Sum"}},
"grouping": [{"type": "Dimension", "name": "PricingModel"}]
}
}'
The Reservation line drops to near zero on the 14th, and On Demand rises by almost exactly the same amount. Instance hours from Azure Monitor are flat across the change. That rules out more usage and a SKU change.
4. Confirm and propose. The Reservations blade shows a three-year D-series reservation that expired on the 13th with auto-renew off. The finding in chat says that in plain words, with the daily split, the expired reservation and the baseline it used to cover. The proposal is a renewal sized to the last 90 days of stable baseline (not the old quantity, since the fleet has changed), with the break-even month and a note on whether a savings plan fits better for the part of the fleet that moved families.
5. Wait for a person. A purchase is a commitment, so it waits for approval. Someone approves in chat, and the agent checks the next days' split to confirm coverage came back.
That's the loud failure from part one, caught on day two instead of at invoice time. The same pattern (readings, the query that splits them, the evidence) applies to an idle-looking VM: the agent reads the max column and the activity log before calling it idle, and tells you when the "idle" box is the quarterly close.
What a first analysis typically returns
The numbers below are illustrative, a composite of what first scans tend to surface on a mid-market Azure footprint around $40K to $45K a month. Your tenant will differ, but probably not in shape.
| Finding | Detail | Est. monthly impact |
|---|---|---|
| Reservation coverage gap | Compute coverage at 28%; D/E-series baseline stable for 90 days | ~$5,200 |
| Non-prod running 24/7 | 19 dev/test VMs and 2 AKS node pools, no shutdown schedules | ~$2,700 |
| Idle and oversized VMs | 9 VMs under 8% average CPU over 30 days, one size step of headroom | ~$1,400 |
| Over-provisioned Azure SQL | 4 databases provisioned roughly 4x above sustained load | ~$980 |
| Unattached disks and snapshots | 44 disks (6.2 TB, mostly Premium SSD) plus 3,100 stale snapshots | ~$900 |
| Bandwidth | Cross-region replication between paired regions, unreviewed | ~$450 |
| Idle networking | 12 unassociated public IPs, 1 App Gateway at near-zero traffic | ~$260 |
Roughly $11,900 a month identified on day one, a bit over a quarter of the bill. Look at where the weight sits: the reservation gap alone is bigger than the bottom five rows combined, and coverage plus scheduling is nearly two-thirds of the total. That matches what a careful manual audit finds on Azure, where reservation coverage tends to lag furthest behind baseline. The value isn't exotic findings. It's that this table rebuilds itself every day and every row carries a gated fix.
Why this beats the traditional approach
| Monthly review with native tools | With CloudThinker | |
|---|---|---|
| When a leak is found | At the next review, about 15 days later on average | The day it appears |
| Context | A person checks tags, metrics and history by hand | Metrics window, activity log and tags attached |
| Expired reservations | Noticed on the invoice | Caught from the pricing-model split |
| Remediation | A ticket in a queue | A proposed change, approved in chat |
| Audit trail | Spreadsheet and ticket comments | Every finding, approval and change logged |
The deeper difference is who holds the context. In the native workflow, the knowledge that a VM is a warm standby or that a reservation is up for renewal lives in someone's head, and the audit is only as good as who's in the room. The agent writes that context down with every finding, so the next person to look at the same resource starts where the last one finished.
Getting started
Connect one subscription read-only, then ask in plain language. Each answer cites the Cost Management and Azure Monitor data behind it, so you verify rather than trust.
"Find unattached managed disks across all subscriptions, oldest first, with monthly cost for each. Propose only, change nothing."
"Analyze our reservation and savings plan coverage against the last 90 days of stable compute. Flag anything expiring in the next 60 days and recommend purchases with break-even months."
"Which non-production VMs ran overnight last week, and what would a weekday 8-to-8 schedule save per resource group?"
Follow-ups work the way you'd expect: ask for the break-even on one proposed reservation, or limit the disk sweep to one resource group, and the answer narrows.
What the agent won't do
"An AI agent connected to our Azure tenant" should raise questions. Here are the answers:
- Read-only by default. The connection roles grant no write access. Remediation needs scoped write permission you grant on purpose.
- No unchecked changes. CloudThinker acts within Auto Mode, so you decide what runs on its own and what waits for approval.
- Nothing off the record. Every proposed and executed action is in the audit trail with its evidence and its approver.
- Architecture stays yours. It will show you that an Application Gateway serves no traffic. Whether that environment should exist is your call.
If you did the part-two audit by hand, the right mental model is that audit running daily with a remediation queue attached. Running AWS too? The same agent covers it, and the parallel series ends with automating AWS cost optimization.
Related reading
- Optimize: continuous cloud cost optimization
- Auto Mode
- Automating GCP cost optimization with an AI agent
- Manual vs continuous rightsizing
Found, approved, fixed
The native stack tells you where Azure money leaks. What it can't give you is closure: findings that stay fresh and fixes that don't wait for a sprint. Teams that move from periodic manual reviews to continuous, approval-gated optimization across the full list typically see a 30 to 50% reduction over the following months. That's a typical range, not a promise, and most of it comes from the two structural rows at the top of the table.
Try CloudThinker free with 100 premium credits and no card required. The connection guide walks your security team through the read-only setup, and your own first findings table is about an hour away.
