Insight

2026-08-19

Why Your AI Agent Bill Will Surprise You, and the Architecture That Stops It

Authored by: Scott Weber, MegazoneCloud CTO, and Reynold Nathaniel Tanton, MegazoneCloud Data & AI Architect

 

Part 1 of 2: The hidden cost curve of AI agents, and a serverless AWS architecture that enforces budgets before the money is spent. In Part 2, we benchmark three models on 40 real tasks and find a 13× cost difference that bought nothing.



A single LLM call is almost free. That's the trap.

When your team demos a chatbot that answers a question for $0.003, everyone rounds the cost to zero. Then the chatbot becomes an agent: it plans, calls tools, reads results, and decides what to do next. And here is the detail that surprises finance three months later: on every step of that loop, the agent re-sends its entire conversation history. Step 1 sends the prompt. Step 10 sends the prompt along with 9 rounds of tool calls, results, and reasoning. Step 50 sends all of that again, plus everything since.

Token spend does not increase linearly with conversation length. It grows roughly with the square of the step count. A 50-step agent run can use hundreds of times as many tokens as the single call you priced in the demo. Multiply that by every user, every workflow, and every retry, and the number that looked like a rounding error becomes a line item the CFO wants explained.
 

Input token growth.png


The two failures that make it worse

Working with enterprise GenAI programs, we kept seeing the same two gaps, and neither of them is a model problem. Both are management problems.

Gap 1: Nobody knows whether a cheaper model would work. Teams pick the premium model for everything because it feels safe, and there is no easy way to prove that a cheaper one would do the job. Is the flagship model actually better than one that costs 1/20th as much for your specific tasks? Almost nobody can answer that with data. So everyone pays the premium price, everywhere, all the time.

Gap 2: Nobody owns the number. On a shared AWS account, Amazon Bedrock spend appears as a single combined line item. No team attribution, no tenant attribution, no workload attribution. When the bill spikes, someone has to dig through old logs and guess. And when nobody owns a number, nobody improves it.

The usual company response to a surprise AI bill - freeze usage and require approvals - saves on costs but destroys value. The better response is an engineering one:

1.     Measure which model configuration is actually cheapest per unit of delivered value - not per token.

2.    Route each request to the cheapest configuration that can handle it.

3.    Enforce budgets before the money is spent, not in a report three weeks later.

4.    Attribute every dollar to a team and a model tier, automatically.

We built a reference system on AWS that does all four. This post covers the architecture and the governance mechanics (points 2–4). Part 2 covers the measurement system and its findings, including a model that costs 13× as much per successful task as its sibling, with zero quality gain.

The system in one picture

Reference architecture.png

Reference architecture: tenant apps flow through an LLM Gateway into Amazon Bedrock, with a benchmarking band and a FinOps attribution path


Three cooperating subsystems, all serverless, all deployed by Terraform from one repository:

  • The LLM Gateway is the single entry point for application model calls. It authenticates tenants, routes each prompt to the lowest-cost model tier likely to handle it, enforces per-tenant daily token budgets, and invokes Bedrock via cost-tagged inference profiles.
     

  • The AI Agent Power Tuner is a Step Functions benchmarking harness that runs candidate model configurations against a golden dataset and ranks them by cost per successful task. (This is Part 2's story.)
     

  • A results dashboard, a CloudFront-served web app behind Cognito sign-in, where anyone on the team can build a benchmark, run it, and read the results.

Everything is pay-per-request: Lambda, API Gateway HTTP APIs, Step Functions, DynamoDB on-demand, S3, CloudFront. Idle cost is close to zero, which matters; a governance layer that costs more than it prevents in waste is a hard sell.
 

Governance mechanism #1: Budgets are enforced before the spend

The naive budget guard reads a usage counter, compares it to the budget, calls the model, and writes the updated counter back. It works in the demo and fails in production, in two ways:

1.     Lost updates. Two concurrent requests read the same counter, each adds its own usage, and the last write wins. Some spending is never recorded, the budget counter falls behind reality, and never catches up.

2.    The race past the limit. Ten concurrent requests all pass the "under budget?" check at the same moment, and all ten go through. The tenant ends up far past the cap.

The fix is a reserve-and-settle pattern on a DynamoDB per-tenant-per-day counter item, using conditional atomic updates:

def _reserve_budget(tenant_id, budget, reserve):
    """Atomically reserve worst-case tokens BEFORE invoking the model.

    The condition admits the request only while the pre-update counter
    leaves room for the full reservation, so N concurrent requests
    cannot collectively overshoot the budget."""
    try:
        resp = table.update_item(
            Key={"tenant_id": f"usage#{tenant_id}#{today()}"},
            UpdateExpression="ADD tokens_used :r "
                             "SET expires_at = if_not_exists(expires_at, :ttl)",
           ConditionExpression="attribute_not_exists(tokens_used) "
                                "OR tokens_used <= :cap",
            ExpressionAttributeValues={
                ":r": reserve,                  # est. input + max_tokens
                ":cap": budget - reserve,
                ":ttl": int(time.time()) + 3 * 86400,
            },
           ReturnValues="UPDATED_NEW",
        )
    except ClientError as exc:
        if exc.response["Error"]["Code"] == "ConditionalCheckFailedException":
            return None                        # budget exhausted -> HTTP 429
        raise
    return int(resp["Attributes"]["tokens_used"])


The flow per request:

1.     Reserve the worst case, estimated input tokens plus the request's max_tokens, with the conditional ADD above. If the condition fails, the caller gets HTTP 429 before a single token is billed.

2.    Invoke Bedrock.

3.    Settle - a second atomic ADD adjusts the counter from the reservation down to the exact metered usage from the response. If the invocation failed, the whole reservation is released.
 

Reserve-and-settle.png

Reserve-and-settle: the gateway atomically reserves worst-case tokens in DynamoDB before invoking Bedrock, then settles to exact usage


Because both writes are atomic, no update is ever lost. Because the reservation is conditional on the pre-update value, concurrent requests cannot race past the cap. And because the counter item's key includes the date, budgets reset daily automatically, and DynamoDB TTL deletes old counters.

One design detail worth copying: the daily counter is a separate item from the tenant's registry record. The registry stays in a static configuration; the counters are a short-lived runtime state. Terraform manages one and never fights the application over the other.
 

Governance mechanism #2: Every dollar lands with a name on it

Bedrock has a very useful feature for this: Application Inference Profiles. Instead of invoking a model by its raw ID, you create a profile per tenant-and-tier combination, tag it with Tenant=acmeTier=pro, and invoke it through the profile ARN.

The billing effect: once those tags are activated as cost-allocation tags, Cost Explorer can break down Bedrock spend by tenant and by model tier with zero changes to application code. Chargeback no longer needs manual spreadsheets. In Terraform, the profiles are a simple for_each over tenants × tiers, so onboarding a tenant automatically creates their cost attribution as well.

Two layers, two jobs: the DynamoDB counters give you real-time enforcement (stop the spend now), the tags give you financial attribution (explain the spend later). You need both; billing data arrives hours later, far too slow to stop a runaway loop; counters enforce instantly, but do not show up on the invoice.
 

Governance mechanism #3: Route to the cheaper model first

The gateway also picks which model serves each request. Ours currently uses a deliberately simple rule: short, simple prompts go to the cheapest tier; longer or reasoning-heavy prompts go to the next tier. Routing is isolated behind a single function, so it can be swapped for embedding-based classification or Amazon Bedrock's Intelligent Prompt Routing without touching anything else.

But here is the honest question that the rule cannot answer: how do you know the cheap tier is good enough? Routing policies are guesses until you can measure quality for each model and task type.

That measurement system, a benchmarking harness that replays a golden dataset through candidate configurations, grades every answer with an LLM judge, and ranks configurations by cost per successful task, is what we cover in Part 2. The results genuinely surprised us: the most expensive model in our test cost 13× as much per successful task as the mid-tier model and delivered exactly the same success rate. It even failed a logic case that the cheaper models passed.
 

Continue to Part 2 → We benchmarked 3 models on 40 real tasks, here's what cost 13× more for nothing.

ACT ACERTi

ISO/IEC 42001:2023
ISO/IEC 27001:2022

ISO/IEC 27018:2019
ISO/IEC 27017:2015

ISO/IEC 27701:2019
ISO 45001:2018