# Cloud cost actions for AI agents need a hard boundary

An AI agent can read a cloud bill, group idle resources, and estimate a savings opportunity with little danger. It crosses into a different class of work when it resizes a database, buys a reservation, moves a billing relationship, or closes an account. Those calls change money, service capacity, contractual commitments, or recovery options. They need a boundary that a useful report does not.

Teams often make this mistake because cloud APIs put reporting and mutations behind the same identity. An agent receives a broad token so it can inspect costs, then someone tells it to "apply the obvious savings." The resulting permission model asks the agent to decide where analysis ends and authority begins. Do not delegate that distinction to a model.

## Reporting must not carry permission to spend

Cost reporting answers what happened or what might happen. A spend-changing operation creates a new fact in the provider account. Put those two jobs behind separate credentials, separate tools, or both.

An agent can safely produce a candidate list when it reads billing exports, usage metrics, inventory, and pricing data. Its output should identify its evidence and its gaps. It should not silently convert a candidate into an API request because a projected saving passes some threshold.

The distinction gets blurred by commands with harmless names. A "recommendation" endpoint may create an export. A "commitment" endpoint may quote, purchase, modify, or cancel depending on one field. A capacity request may be an estimate at one provider and a binding change at another. Read the provider API reference for the exact operation, not the label that a console puts above a button.

The FinOps Foundation's Cloud FinOps guidance separates informing, optimizing, and operating activities. That is a useful business model, but it does not create an authorization model. A person or agent may inform without permission to operate. Treat that gap as deliberate design, not paperwork.

Give the reporting side a constrained contract. It can request a bounded time range, named accounts, named regions, and a report type. It returns figures with their units and provenance. A tool should reject an unbounded query such as "show every billing record" when the task concerns a single production account.

A report also needs to carry enough context to prevent false confidence. At minimum, retain:

- the billing account or project scope
- the time range and data freshness
- currency, price basis, and whether taxes or credits appear
- the resource identifiers behind each recommendation
- the assumptions used for a forecast

This is not bureaucracy. A monthly amortized cost and a daily cash charge can both be accurate while supporting opposite conclusions about a proposed commitment.

## Classify the action by consequence, not API verb

A verb such as `update` tells you almost nothing about risk. Classify a cloud cost action by what it changes, how far the effect reaches, and how hard it is to undo.

I use four buckets. The first bucket contains observations: listing resources, retrieving invoices, reading utilization, and generating a forecast. The second contains reversible local changes: adjusting an autoscaling floor or changing a noncritical worker size where the team has a tested rollback. The third contains constrained commitments: buying a reservation, altering a savings plan, moving a resource to a different pricing arrangement, or changing a budget alert. The fourth contains destructive or governance actions: deleting billing exports, removing payment controls, transferring ownership, deleting shared resources, and closing an account.

The middle two buckets cause the most bad decisions. Teams call a resize reversible because they can send another resize request. That ignores restart time, capacity limits, instance-family availability, local disks, and applications that assume the old shape. Teams call a reservation a purchase because the console says "buy." It is also a forecast about future eligible usage, with a term and payment obligation attached.

Use a consequence record before the agent can invoke any mutation. The record should name the target, the expected cost effect, the service effect, the recovery method, and the human approval required. It can be simple JSON:

```json
{
  "action": "resize_compute_group",
  "scope": {
    "billing_account": "finance-prod",
    "region": "eu-west-1",
    "resource_group": "batch-workers"
  },
  "before": {"instance_type": "c6i.2xlarge", "minimum": 6},
  "after": {"instance_type": "c6i.xlarge", "minimum": 6},
  "expected_monthly_delta": {"currency": "USD", "amount": -412},
  "service_effect": "rolling replacement of batch workers",
  "rollback": "restore c6i.2xlarge and wait for replacements",
  "approval": "per_call"
}
```

This artifact prevents a recurring failure: an agent submits a valid request against a target that a reviewer never saw. The provider only validates whether the payload can run. It does not validate whether the requester meant `finance-prod`, whether the forecast uses the right price, or whether the worker group has enough spare capacity.

Do not infer risk from the expected dollar amount alone. A small resize may interrupt a revenue service. A large but bounded purchase may be acceptable if finance has already allocated it. Scope, reversibility, and operational dependency belong beside projected savings.

## A recommendation is evidence, not an instruction

An agent should make cost recommendations in a form that a reviewer can disprove. If it cannot state why a resource looks wasteful, which measurements it used, and what would make the recommendation wrong, it has written a guess with a table around it.

For a compute resize, require utilization across a relevant operating cycle, not a convenient sample from a quiet afternoon. Include CPU, memory if available, queue depth, latency, error rate, and scheduled peaks. CPU alone often lies. Many services wait on memory, storage, network, a connection pool, or a licensing limit.

For a storage change, distinguish allocated size from consumed bytes, provisioned performance from observed IOPS, and snapshot retention from current volume cost. A proposal to shrink a volume can be nonsense when the provider cannot shrink it in place. A proposal to delete old snapshots can destroy the only recoverable copy of a database that someone stopped testing years ago.

The same discipline applies to commitment recommendations. The agent needs eligible usage, not total spend. A commitment that applies only to a particular family, region, operating system, tenancy, or purchase option cannot cover everything in a broad service category. The apparent discount matters less than the amount of stable usage that actually qualifies.

Ask the agent to return an explicit abstention when evidence falls short. Good abstentions sound like this:

> I found low CPU use on these workers, but no memory metrics and no record of their scheduled peak. I can prepare a resize request after the owner confirms the peak load and rollback window.

That answer is more useful than a confident recommendation that forces an operator to reconstruct the missing premises. Models tend to complete patterns. Your action interface must give them a sanctioned way to stop.

## Commitments deserve a separate purchase review

Reservations, savings commitments, capacity blocks, and similar discounts require a purchase review even when the agent's arithmetic is correct. They turn a usage forecast into an obligation.

The usual weak rule says, "approve commitments above a dollar threshold." It is popular because it is easy to explain and easy to automate. It fails because a low-cost commitment can fragment coverage across many teams, while a larger one may match a documented baseline that finance already approved. A threshold measures ticket size, not decision quality.

Require the proposal to state the following in plain terms:

- the eligible hourly or daily usage baseline
- the coverage amount the agent proposes to buy
- term, payment option, and scope
- workloads that could lose eligibility after a planned migration
- the person responsible for the forecast

A sound proposal also separates three numbers that people routinely collapse: on-demand spend, discounted spend for covered use, and unused commitment cost. The first two make the discount attractive. The third tells you how wrong the forecast can be before savings disappear.

Suppose an agent sees steady usage across several compute groups and proposes coverage for the aggregate. That may be reasonable, or it may be a trap. One team may retire its group next quarter, another may move regions, and a third may use a platform type that does not qualify. The agent should report each contributor and any upcoming change it found. A reviewer can then exclude uncertain demand rather than arguing with a single blended chart.

Provider documentation usually describes eligibility rules precisely and billing consoles often summarize them loosely. Use the API and billing documentation as the source of truth when the two views differ. A console's "estimated savings" is a scenario, not a contract review.

Do not give a general cost-optimization agent authority to purchase because it has permission to create reports. Create a purchase-specific route that accepts only a fully specified proposal and requires an approver who understands both the budget and the workload plan.

## Resizing capacity can break services before it saves money

A resize changes a running system, even when the provider treats it as routine. The agent must show the operational consequence before anyone approves the lower bill.

The failure pattern is familiar. The agent finds instances with low average CPU use, selects a smaller shape, and submits a rolling update. The new instances have less memory. The service begins swapping under its daily traffic spike, queue latency climbs, autoscaling adds more nodes, and the monthly bill rises. In another version, the old nodes used local scratch storage and the replacement process discards unfinished work. The cost report never contained either fact.

A resize proposal needs a service owner, a maintenance condition, and a rollback trigger. "Rollback if errors increase" is vague because every service has some error rate. Define the signal and the observation window. For example, the owner may require a queue age limit, a latency objective, or successful completion of a batch cycle before the agent can report success.

The execution request should bind all target selectors. Never permit a free-form command such as `resize all nonproduction workers` to cross an authorization boundary. Resolve the target list first, present it, and send identifiers rather than a tag query that can match new resources after approval.

A useful execution sequence has four parts:

1. The agent gathers metrics and resolves the exact resources.
2. It creates a change record with current configuration, proposed configuration, rollback, and test condition.
3. A human approves that immutable record for the named resources.
4. The action runner sends the request, records the provider operation identifier, then reports only the checks the contract asked it to run.

The word "immutable" matters. If the agent can alter the target list after approval, the approval card becomes theater. If a change must expand, create a new record and request another approval.

## Account closure needs a different credential and a human confirmation

Account closure belongs in its own class because it affects billing, identity, support access, retained data, and recovery paths at the same time. Do not place it behind an optimization workflow.

A provider may use a sequence rather than a single API call. It may require owner credentials, a payment check, a waiting period, or separate actions for projects and organization members. Your agent must never treat a successful first response as proof that closure has finished or that data has disappeared. It should record the provider's state and tell the operator what remains to do.

Give account closure a dedicated credential with no ordinary read or mutation duties. Require a typed confirmation or an approval that displays the exact account identifier, legal or billing name if your records contain it, and a plain statement of the expected effect. A request that says "close the test account" is not enough. Names get reused, and tags get copied.

Separate closure from cleanup. An agent may inventory idle resources and prepare a deletion plan. It should not infer that deleting those resources grants permission to close the account. The savings from cleanup and the governance decision to terminate an account have different owners.

Recovery planning also changes the answer. If a team can export records, verify backups, transfer domains, preserve audit evidence, and document dependency removal, it can approve a closure with confidence. If it cannot, the agent should produce a readiness report and stop. A failed closure workflow is annoying. A completed closure with an overlooked dependency is much worse.

## Approval must bind to the exact call

A broad approval such as "allow cloud optimization for this session" is appropriate for gathering evidence. It is poor protection for actions that change spend or destroy access. An agent can make dozens of reasonable reads, then issue one unreasonable mutation after the operator has stopped watching.

Bind approval to a canonical request. That request includes action type, account, region, resource identifiers, desired values, and any purchase term or payment option. Display the expected cost effect as context, but do not use it as the identity of the request. Forecasts change; a provider call must remain unambiguous.

The authorization layer should reject meaningful differences between the approved request and the outgoing request. These differences include a different account, a wider selector, a changed quantity, a different region, or a different commitment term. Treat an omitted field carefully too. Providers often assign defaults, and defaults can change between API versions or accounts.

Per-call confirmation has a cost: it interrupts people. Use it for the narrow group of operations where interruption is cheaper than regret. Allow a session-level authorization for the agent process to inspect scoped data, then require a separate confirmation for each commitment purchase, capacity mutation, account governance action, or use of a specially marked credential.

Sallyport follows this shape with session authorization for a new agent process and an optional per-key approval for every use of that credential. The distinction is useful because an approved agent run still needs a human decision before it invokes a credential that can change spend.

Approval text should show the person reviewing it what the provider will receive. Do not ask them to approve a tool name such as `cloud.execute`. Show `resize_compute_group`, the exact target group, old and new values, and the rollback statement. If the interface cannot fit that information, the action contract is too broad.

## Keep an audit record the agent cannot rewrite

A successful cloud response is not an audit trail. It tells you that the provider accepted a request, but it may not preserve the intent, authorization, resolved targets, or evidence that led to the call.

Store an append-only record before the request leaves the control point. Record the proposal, evidence references, canonical request, authorization decision, caller identity, timestamp, provider response, and operation identifier. Store error responses too. A rejected request often explains a later workaround or shows that an agent probed for broader permission.

Hash chaining gives the record a practical integrity check. Each entry includes a digest of the previous entry and its own content. If someone alters, removes, or reorders a historical entry, verification fails at the break. It does not prove that the original request was wise. It proves that the recorded sequence has not changed without detection.

NIST SP 800-92, Guide to Computer Security Log Management, calls for protecting log integrity and making logs available for review. The advice is old because the failure is old: teams collect logs where the same compromised process can edit them. For agent actions, keep the audit writer outside the agent's direct filesystem and credential access.

Sallyport projects its Sessions and Activity journals from a write-blind encrypted, hash-chained audit log, and `sp audit verify` can check the chain offline without a vault key. That is the kind of property to demand from any action gateway: the agent can receive results, but it cannot revise the record of what it asked to do.

Review the record after a mutation, not only during an incident. A weekly sample of agent proposals and completed actions exposes bad scopes, weak rollback statements, and approvals that people click without reading. You will find the boundary leaks faster in routine work than in a postmortem.

## Build narrow action routes instead of a cloud superuser

A cloud superuser credential behind a friendly chat interface remains a cloud superuser credential. The agent's reasoning may improve, but the authority does not become safer.

Create action routes that match real decisions. One route can retrieve cost and usage records for a named scope. Another can prepare a resize request but cannot execute it. A purchase route can submit only a commitment proposal after a dedicated approval. A closure route should exist only if your organization truly needs automated preparation for closure, and it should end at human confirmation.

Keep credential injection inside the action runner. The agent should receive structured results, not bearer tokens, SSH private keys, temporary command output that exposes secrets, or placeholders it can accidentally echo into a transcript. This protects the credential and reduces the chance that an agent carries authority into unrelated tools.

Test the routes with failure cases before you trust the happy path. Attempt a region substitution after approval. Attempt a selector that expands to a newly created resource. Attempt a commitment request without a term. Attempt a close request that names an alias rather than an account identifier. The route should reject each one with an explanation that points to the missing or mismatched field.

The first control worth building is usually not an autonomous resize. Build a reporting route that produces an evidence packet, then a proposal route that cannot call the provider. Once reviewers can reliably accept, reject, and amend those proposals, add one narrow mutation with a bound approval and a tested rollback. Cloud savings that require you to explain an outage or an unwanted purchase were never savings.
