Product

Automating Azure Cost Optimization with an AI Agent

Part three of the Azure cost series: CloudThinker connects through a read-only service principal, scans all seven Azure leaks continuously, and works each finding from the evidence. For example, it can trace an overnight cost jump to an expired reservation with one Cost Management query. Every delete, resize or purchase goes through Auto Mode.

9 min read
Cover Image for Automating Azure Cost Optimization with an AI Agent

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.

A tool that only detects is a second Advisor. The agent has to solve all three.

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

AzureAzure

  • Cost ManagementCost Management Reader
  • Resource inventoryReader at subscription scope
  • Azure Monitor metricsReader at subscription scope

Read-only service principal

02Investigation

CloudThinkerCloudThinker

  • 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

  • SlackSlackFindings and approvals in the thread
  • Microsoft TeamsMicrosoft TeamsSame flow for Microsoft shops
  • JiraFollow-ups with the evidence linked

Nothing new to watch

Two built-in roles, both read-only. Your security team can review the grant in five minutes.

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:

  1. Unattached managed disks and snapshots. diskState of Unattached, cross-referenced with age and activity log history, plus snapshots of disks that no longer exist.
  2. 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.
  3. 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.
  4. Unused public IPs, idle load balancers and Application Gateways. Unassociated Standard IPs, and load balancers or gateways with near-zero backend traffic.
  5. Over-provisioned Azure SQL. Databases sitting far below their provisioned vCores or DTUs over a sustained window.
  6. Bandwidth. Cross-region and egress charges growing out of proportion to workload, the line almost nobody audits by hand.
  7. 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.

Illustrative example. Each reading predicts a different shape in the data, so one query rules two of them out.

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.

FindingDetailEst. monthly impact
Reservation coverage gapCompute coverage at 28%; D/E-series baseline stable for 90 days~$5,200
Non-prod running 24/719 dev/test VMs and 2 AKS node pools, no shutdown schedules~$2,700
Idle and oversized VMs9 VMs under 8% average CPU over 30 days, one size step of headroom~$1,400
Over-provisioned Azure SQL4 databases provisioned roughly 4x above sustained load~$980
Unattached disks and snapshots44 disks (6.2 TB, mostly Premium SSD) plus 3,100 stale snapshots~$900
BandwidthCross-region replication between paired regions, unreviewed~$450
Idle networking12 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 toolsWith CloudThinker
When a leak is foundAt the next review, about 15 days later on averageThe day it appears
ContextA person checks tags, metrics and history by handMetrics window, activity log and tags attached
Expired reservationsNoticed on the invoiceCaught from the pricing-model split
RemediationA ticket in a queueA proposed change, approved in chat
Audit trailSpreadsheet and ticket commentsEvery 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

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.

azurecostoptimizationfinopscostopsaiagent