
Build an Agent Weekend Challenge: Billsense
An AWS bill-explainer agent that tells you whether to worry first, then why, with a Panic Meter, plain-English answers and charts from Cost Explorer data.
This year I've built a handful of hackathon projects on my own AWS account: multi-agent systems, RAG pipelines, things running on ECS and Lambda. Every build ends the same way. The demo is done, the submission is in, and a small worry stays with me for days: did I leave something running?
Checking means opening Cost Explorer, switching filters, grouping by service, then by region, then trying to remember what "EC2-Other" means this time. It's tedious enough that I usually put it off, which is exactly how forgotten resources turn into surprise bills. Nothing is hacked or broken. A NAT Gateway or a stray EBS volume just keeps billing by the hour.
So this weekend I built Billsense, an agent that does that check for me and answers the one question I actually care about first: should I worry?
The first time I pointed it at my own account, it answered 🟢 Relax. I'll admit that was the most satisfying test result of the whole build.
What My Agent Does
You ask Billsense something like "why did my bill go up?" or "am I paying for anything idle?". It checks your Cost Explorer data and scans for the usual suspects: running NAT Gateways, unattached EBS volumes, Elastic IPs that aren't attached to anything, and EC2 instances. Then it answers in plain English, with charts built from the real numbers.
It's for people who use AWS but aren't FinOps people: students, hackathon builders, community members running workshop accounts, and small teams without a dedicated cloud cost person. The console has all the data. What these users are missing is someone to tell them what it means.
How I Built It
I built it in one morning, in four rough stages:
- Verified Bedrock access to Amazon Nova Pro through the APAC inference profile.
- Wrote the two tools and a demo dataset, and tested them without the model.
- Wrapped them in a Strands agent and tuned the prompt until the answer format held.
- Deployed to Lambda, then spent the rest of the time on the experience: first run, error cards, glossary and charts.
Stack
- Agent framework: Strands Agents SDK (Python)
- Model: Amazon Nova Pro on Amazon Bedrock
- Backend: one AWS Lambda function (Python 3.12) with a Function URL
- Frontend: a single HTML page with Chart.js, served by the same Lambda
- Infrastructure: AWS SAM
- Observability: Amazon CloudWatch Logs and Logs Insights
AWS Services I Used
- Amazon Bedrock: runs Nova Pro, which reasons over the tool output and writes the answer.
- AWS Cost Explorer API: daily cost grouped by service and region, current period vs previous.
- Amazon EC2 APIs: find NAT Gateways, EBS volumes, Elastic IPs and instances.
- AWS Lambda: hosts both the UI and the agent API in one function, exposed through a Function URL.
- AWS IAM: a read-only role limited to ce:GetCostAndUsage, ec2:Describe* and Bedrock invoke.
- Amazon CloudWatch: request logs and feedback counts.
- AWS Budgets: a $10 monthly alert on the demo account.
- AWS SAM: infrastructure as code for the deploy.
Architecture

- The browser sends the question to a Lambda Function URL in ap-south-1.
- An input guard checks length, blocks anything that looks like an AWS access key, and applies a per-IP rate limit.
- A Strands agent sends the question to Amazon Nova Pro on Bedrock.
- Nova Pro calls two read-only tools: one reads Cost Explorer (us-east-1), the other scans EC2 APIs for idle resources.
- Lambda builds the KPIs, charts and idle-resource table directly from the tool output.
- The browser gets back the answer, the 🟢🟡🔴 verdict and the chart data.
Key Decisions
- Numbers come from the tools, not the model, so every figure on screen matches what AWS reports.
- One Lambda serves both the page and the API. No API Gateway means no 29-second timeout and fewer moving parts.
- An owner passcode instead of AWS keys. Users never paste credentials into the app.
Challenges I Overcame
- sam build on Windows pulled Windows wheels that crash on Lambda. I wrote a build.ps1 script that installs Linux (manylinux x86_64) wheels instead.
- Strands calls Bedrock with streaming, so the role also needed bedrock:InvokeModelWithResponseStream.
- My account's concurrency limit (10) didn't allow reserved concurrency, so I used a per-IP request limit plus a budget alarm.
- Cost Explorer only works in us-east-1 and costs $0.01 per call, so I pinned the client there and cache results for 10 minutes.
- Chart labels were truncated ("Elastic Compute Cloud - Comput"), so I mapped service names to short labels.
- Charts kept light colors after switching to dark mode, so the report re-renders on theme change.
Security
Cost data is sensitive, and the Function URL is public, so I spent real time here:
- No AWS keys, ever. Billsense reads the account it's deployed in through its own IAM role. Real-data mode is unlocked by an owner passcode, which is not an AWS credential.
- Credential-leak guard. If you paste something that looks like an AWS access key, both the browser and the server block it before it reaches the model or the logs, and you're told to rotate it.
- Masking. Account IDs and resource IDs are masked in the answer and in the chart data.
- Limits. Questions are capped at 500 characters, there's a per-IP request limit, and a budget alarm covers the account.
- Headers and logs. The page sets a Content Security Policy and HSTS. Logs keep only the request ID, mode, verdict and latency, never the question text.
The Experience: The Panic Meter
Every answer starts with a one-line verdict before any detail:
- 🟢 Relax. Your bill rose $4.10, and $3.80 of it is a NAT Gateway running in ap-south-1.
- 🟡 Worth a look. Something is costing money that probably shouldn't be.
- 🔴 Act now. Spend jumped in a region you don't normally use.
After the verdict come at most three short bullets, then exactly one next step. When people see an unexpected bill, the first thing they want to know is whether something is seriously wrong, not a breakdown by usage type. The Panic Meter answers that in the first second, and the details are there if you want them.
I enforce the format in code, not just in the prompt. If the model's reply doesn't start with 🟢, 🟡 or 🔴, the agent asks it once more. The verdict also controls the whole card: its color, the line above it ("Nothing to worry about." / "Worth five minutes." / "Take a breath. Here's what to do first."), and whether you see charts or a calm "All clear" screen.
A few smaller things support it:
- First run: a welcome card explains the three verdict levels and has a "Show me with demo data" button, so you see a real answer before you trust it with anything.
- Plain-English glossary: tap "NAT Gateway" and it tells you what it is and how it bills.
- Error cards that help: every error says what happened, why, and what to do, with a button that does it (Retry, Try demo data, Re-enter passcode).
How I Know It Worked
I judged it on three things I could actually check.
The format holds. In every test run, demo and real, the answer opened with a verdict. The code checks this and asks the model again if it doesn't, so a user never has to dig for the conclusion.
The answer comes before the detail. In Cost Explorer, finding the forgotten NAT Gateway in the demo scenario means grouping by service, then by region, then working out what the line item is. In Billsense it's the first sentence. That gap is the whole point of the Panic Meter.
The bad paths are friendly too. I deliberately broke things: a wrong passcode, too many requests, and a fake access key pasted into the question. Each one showed a clear card with what happened and one button to fix it, never a stack trace.
Under every answer there's also a "Was this clear? 👍 👎" button that logs only the verdict and the vote, with no question or answer text. I've just shared the demo with my local AWS User Group, and I'll update this post with real numbers as they come in.
To be honest about the limits: this is a weekend sample, not a study. My own account is nearly idle, so the real-data path only ever produced 🟢 answers. The 🟡 and 🔴 paths were tested with demo data.
Proof It Works
- Live demo: https://e7v3k3fkrzhawoqicaiwaayxze0upkij.lambda-url.ap-south-1.on.aws/ (choose "Demo data", no AWS access needed)
- Source code: https://github.com/Thirumalaiboobathi/billsense/tree/main/billsense



Cost
For a personal demo this costs very little. Lambda sits inside the free tier, each Cost Explorer call is $0.01 (cached), and a Nova Pro answer is a fraction of a cent. The $10 budget alarm is there because the demo link is public, not because I expect to reach it.
What I Learned
- Where the experience really comes from. Most of the experience work happened outside the model, in the first-run card, the error mapping, and deciding what to hide.
- Format enforcement. Forcing a strict answer format made the model more useful, not less. One verdict line is harder to get wrong than five paragraphs.
- Small security features build trust. The credential guard took about 20 lines of code and made the whole app feel safer to use.
What's Next
- Connect other accounts the way AWS recommends: a read-only cross-account IAM role with a per-customer External ID, instead of anything involving keys.
- A weekly check-in that only messages you when the verdict isn't 🟢.
- More "usual suspects": idle RDS instances, old snapshots, and unused load balancers.
Try It Yourself
1
2
3
git clone https://github.com/Thirumalaiboobathi/billsense.git
cd billsense/billsense
uv run local_test.py --demoThe README covers deploying with build.ps1 and sam deploy.
If AWS bills have ever made your stomach drop, try the demo and tell me whether the Panic Meter made it any easier. That's the whole point of it.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article