Skip to main content

A spend firewall for AI agents โ€” prices a cloud command before it runs and blocks the over-budget ones.

Project description

๐Ÿฆ budgie

A spend firewall for AI agents. budgie is a Claude Code hook that prices an agent's cloud command before it runs โ€” folding in storage, node groups, whole loops, and the session's cumulative burn, not just the headline instance โ€” and blocks the ones that breach your budget. Billing alerts fire after the money's gone; budgie stops the command at the door.

tests license: MIT python deps

budgie pricing an agent's command before it runs โ€” a cheap db class blocked by 20 TB of storage, node groups priced, and the session's live burn

The depth is in what it sees: below, a cheap db.t3.micro is blocked โ€” not for the instance, but for the 20 TB of storage attached to it. That's the difference between "block the big box" and understanding what a command costs.

Demo

$ budgie check 'aws rds create-db-instance --db-instance-class db.t3.micro --allocated-storage 20000 --storage-type gp2'
[BLOCK] 1ร— rds db.t3.micro โ‰ˆ $3.17/hr ($2,312/mo) โ€” over the $2.00/hr session cap. Blocked.  [+20000GB gp2]

$ budgie check 'aws eks create-nodegroup --instance-types m5.24xlarge --scaling-config desiredSize=40'
[BLOCK] 40ร— eks m5.24xlarge โ‰ˆ $184.32/hr ($134,554/mo) โ€” over the $2.00/hr session cap. Blocked.

$ budgie check 'aws ec2 run-instances --instance-type p5.48xlarge --count 2'
[BLOCK] 2ร— ec2 p5.48xlarge โ‰ˆ $196.64/hr ($143,547/mo) โ€” over the $2.00/hr session cap. Blocked.

$ budgie check 'aws ec2 run-instances --instance-type t3.micro'
[ALLOW] โ‰ˆ $0.01/hr โ€” within budget.

$ budgie session
  agent-8f2: burning $4.61/hr  ยท  accrued $13.83 so far

Regenerate the animation with vhs: vhs demo/demo.tape โ†’ writes docs/demo.gif.

Architecture โ€” a gate and a ledger, kept apart

budgie is two hooks with one discipline between them: anticipate to decide, account to record โ€” and never let the two cross.

  agent (Claude Code) โ”€โ”€ Bash: aws / terraform / gcloud โ€ฆ
        โ”‚
        โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ PreToolUse โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   the GATE โ€” anticipation
        โ”‚  budgie hook                                  โ”‚   prices a resource that
        โ”‚    parse   every aws cmd (loops, &&, ;, xargs)โ”‚   doesn't exist yet; the
        โ”‚    price   static table | AWS Price List API  โ”‚   estimate is used only to
        โ”‚    gate    accrued + running + this cmd > cap?โ”‚   decide, then discarded โ€”
        โ”‚      โ”œโ”€ over  โ†’ exit 2  โœ—  blocked (never runs)   it commits NOTHING
        โ”‚      โ””โ”€ under โ†’ allow (exit 0), command runs  โ”‚
        โ”‚                                               โ–ผ
        โ”‚                       session ledger  ($BUDGIE_HOME)
        โ”‚                       active_rate $/hr ยท accrued_cost $
        โ”‚                                               โ–ฒ
        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ PostToolUse โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   the LEDGER โ€” accounting
           budgie posthook                                 fires ONLY on success, so
             create succeeded โ†’ COMMIT its cost + record id  only real spend is ever
             delete succeeded โ†’ credit the cost back         written down

  * pricing via AWS Price List API is opt-in: pip install "budgie-firewall[aws]"

Why the split matters. The gate anticipates โ€” it prices a command that hasn't run and may be blocked, fail, or be a dry-run, so it must not write anything down. The ledger accounts โ€” Claude Code fires PostToolUse only when a command succeeds, so a create counts only once it's real; a failed create fires no hook and never touches the total. (Verified live on Claude Code 2.1.121: a failed command fires no PostToolUse โ€” and no PostToolUseFailure either.) Keeping a prediction out of the factual ledger is what stops phantom spend โ€” money that was never spent โ€” from wrongly blocking later, legitimate commands.

The gate itself (command โ†’ parse โ†’ price โ†’ verdict) is a pure function over the stdlib. Pricing and the ledger are seams around it.


Why

Unsupervised agents now run real aws / terraform / gcloud commands โ€” and they loop, retry, and over-provision. One went viral for running up a $6,531 AWS bill. Every existing guardrail is too late or too coarse:

  • Billing alerts / FinOps dashboards (Vantage, CloudZero) โ€” report after the spend.
  • AWS Budgets โ€” fires after cumulative spend crosses a line, org-wide, blocks a whole service.
  • Infracost โ€” great, but CI/PR-time and Terraform-only.
  • Token-budget tools (Waxell, loop-cost) โ€” cap LLM tokens, not infra.

None block the specific money-spending command, at agent runtime, before it runs. That's budgie.

The decision

agent about to run:  aws ec2 run-instances --instance-type p5.48xlarge --count 2
        โ”‚
        โ–ผ   PreToolUse hook (budgie) โ€” no execution, no network
   parse โ†’ find every aws command (loops, &&, ;, xargs, /path/aws)
   price โ†’ 2 ร— $98.32/hr = $196.64/hr  ($143,547/mo)
   gate  โ†’ dollars spent + running + this command  >  the cap?
        โ”‚
        โ”œโ”€ yes โ†’ exit code 2  โœ—   Claude Code blocks it; the command never runs
        โ””โ”€ no  โ†’ allow (exit 0); the command runs, and PostToolUse commits its
                 cost to the session โ€” only once it has actually succeeded

budgie inspects and decides allow/deny โ€” it never runs the command itself (that stays with the agent, only if budgie allows). A hard block uses exit code 2 (the version-proof deny); a warn is a non-blocking hint; allow is silent. No AWS SDK, no network on the decision path.

Session accrual (cumulative cost)

budgie keeps two separate numbers per session โ€” the current burn and the money already spent โ€” because a rate is not a cost:

  • active_rate ($/hr) โ€” what the session is burning right now. A create raises it; a teardown lowers it.
  • accrued_cost ($) โ€” the time-integral of the burn: the area under the active-rate line.
on every event (create / teardown):
    accrued_cost += active_rate ร— (now โˆ’ last_ts)   # ฮ”t capped at 24h
    last_ts       = now
    create   โ†’ active_rate += resource_rate
    teardown โ†’ active_rate โˆ’= resource_rate          # past dollars stay accrued

 active_rate $/hr
   5 โ”‚                 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ”‚                 โ”‚  burning $5  โ”‚
   3 โ”‚        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”        โ† teardown A: rate drops,
     โ”‚  $3    โ”‚            $5                $2 โ”‚          accrued does NOT
   0 โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ  time
     create A     create B              teardown A
                                                     accrued = ฮฃ (rate ร— ฮ”t)

Both are enforced, and you choose which:

  • BUDGIE_HOURLY checks active_rate (max concurrent burn) โ€” two individually-cheap boxes can still trip it together.
  • BUDGIE_BUDGET checks the cumulative total โ€” accrued_cost (real dollars spent) plus what's still running, projected over BUDGIE_HORIZON. Because torn-down resources keep their spent dollars in accrued_cost, create/tear-down churn is counted, not laundered.

budgie session prints both. This mirrors KML's cost_estimator accrual โ€” rate and cost kept distinct, with active vs torn-down resources tracked separately.

Install

Name: the PyPI package is budgie-firewall and the command is budgie. (The bare budgie name on PyPI is an unrelated 2015 SSH tool โ€” not this project.)

# from PyPI (once published) โ€” package is budgie-firewall, the command is budgie
uvx --from budgie-firewall budgie check "aws ec2 run-instances --instance-type p5.48xlarge --count 2"
# [BLOCK] 2ร— ec2 p5.48xlarge โ‰ˆ $196.64/hr ($143,547/mo) โ€” over the $2.00/hr cap.

# or straight from GitHub, no PyPI needed:
uvx --from git+https://github.com/TanishkaMarrott/budgie budgie check "aws ec2 run-instances --instance-type p5.48xlarge"

Wire both hooks โ€” PreToolUse blocks before the spend, PostToolUse commits it after success (required for cumulative budgets):

// .claude/settings.json
{ "hooks": {
    "PreToolUse":  [ { "matcher": "Bash", "hooks": [
        { "type": "command", "command": "budgie hook" } ] } ],      // the gate โ€” blocks (exit 2)
    "PostToolUse": [ { "matcher": "Bash", "hooks": [
        { "type": "command", "command": "budgie posthook" } ] } ]   // the ledger โ€” commits succeeded spend
} }

Two caps, use either or both:

  • BUDGIE_HOURLY=2.0 โ€” a rate ceiling: never let the session burn faster than $2/hr at once.
  • BUDGIE_BUDGET=1.0 โ€” a cumulative total: the net sum of dollars already spent plus everything still running (projected over BUDGIE_HORIZON, default 1h) may never cross $1. This is what catches slow accrual and create/tear-down churn that a rate cap alone misses.

No silent allow. Anything that provisions but can't be priced โ€” an un-enumerated service, an unknown SKU, a hidden config โ€” warns, never falls through to allow; a dynamic quantity (--count $N) or an unbounded loop blocks. Flip BUDGIE_STRICT=1 to turn every can't-price warn into a hard block (the zero-escape-boat posture).

Configuration

Every knob is an environment variable; all are optional.

Variable Default What it does
BUDGIE_HOURLY 2.0 Rate ceiling ($/hr) โ€” the session may never burn faster than this at once.
BUDGIE_BUDGET off Cumulative total ($) โ€” dollars spent + still-running, projected over the horizon. Needs both hooks wired.
BUDGIE_HORIZON 1.0 Hours the running rate is projected over for BUDGIE_BUDGET.
BUDGIE_STRICT off 1 escalates every can't-price warn โ†’ block โ€” nothing unpriceable runs.
BUDGIE_FAIL closed On internal error / corrupt state: closed blocks spend, open allows through.
BUDGIE_OK off 1 overrides the gate for the command (also .budgie/allow.txt, a substring allowlist).
BUDGIE_PRICING static static = bundled offline table; aws = live Price List API (pip install "budgie-firewall[aws]").
BUDGIE_HOME .budgie Directory for the session ledger + price cache.

Commands

budgie check "<command>"     # price + gate one command
budgie hook                  # PreToolUse hook entry โ€” prices + gates (exit 2 blocks)
budgie posthook              # PostToolUse hook โ€” commits succeeded spend, credits deletes
budgie tf-plan plan.json     # price a `terraform show -json` plan
budgie session               # show each session's burn ($/hr) and accrued ($)
budgie ledger                # recent decisions + total spend stopped

What it does today

  • โœ… Composite pricing โ€” RDS = instance + storage + Multi-AZ; EKS = control plane + node groups; EBS volumes ($/GB-mo). Plus EC2, NAT, ElastiCache, Redshift, ELB, SageMaker, OpenSearch. Warns (never silent-allow) when cost is hidden โ€” Fargate, Aurora, EMR, MSK, --cli-input-json, terraform apply.
  • โœ… Extracts every aws invocation from loops / && / ; / xargs / full paths, including ones behind leading global flags (aws --region โ€ฆ --profile โ€ฆ ec2 run-instances) and inside $(โ€ฆ) / backticks โ€” the common forms that must not slip past. Disambiguates service-ambiguous actions: only eks create-cluster is the flat $0.10 control plane; kafka/emr create-cluster warn (bill-shock), ecs create-cluster is free. Handles --dry-run, spot (~70% off), region, robust --count 1:5.
  • โœ… Prices the whole loop, not one iteration โ€” for i in $(seq 100); do โ€ฆ is costed as 100ร—, so the $6,531 runaway-loop pattern blocks. A loop with no bounded count (while / xargs / a dynamic range) that creates billable resources is refused outright โ€” cost can't be bounded.
  • โœ… Blocks via exit code 2 (version-proof hard deny); warns are non-blocking hints; allow is silent. Crash-proof, fail-closed on spend commands.
  • โœ… Two enforced caps โ€” a rate ceiling (BUDGIE_HOURLY, $/hr) and a cumulative total (BUDGIE_BUDGET, $): the net sum of dollars spent + running, projected over a horizon. Rate and accrued cost are tracked separately (the KML model); budgie session shows both.
  • โœ… PostToolUse accounting โ€” commits a create's cost only once it succeeds, records the resource id, and credits it back when the agent deletes it. A failed create fires no hook, so it never counts โ€” and as a belt-and-braces check it also refuses to commit an interrupted command or one whose output carries a botocore error, so no phantom spend even if a future version fires PostToolUse on failure. Resources are recorded by their user-assigned identifier too, so a teardown still credits back under --output text. A resize (modify-*) is gated but not re-committed (no double-count). No AWS polling.
  • โœ… Durable session state โ€” the budget ledger is written atomically (os.replace) and guarded by a file lock, so a crash mid-write or two parallel hooks can't corrupt it or lose a commit. A ledger that can't be read fails closed (blocks spend) rather than silently resetting the budget to $0.
  • โœ… Terraform โ€” budgie tf-plan prices a terraform show -json plan.
  • โœ… Ledger + override (BUDGIE_OK=1 / .budgie/allow.txt).
  • โœ… Live AWS pricing โ€” region-aware, disk-cached (AWS Price List API); zero-dep static table is the default.

Roadmap

  • EC2 root/data volumes โ€” fold --block-device-mappings EBS into the instance estimate (RDS storage + EKS nodes are already composite).
  • Out-of-band deletes โ€” reconciliation is agent-driven; console / TTL / autoscaling deletes would need resource-snapshot polling (a platform's job).
  • Multi-cloud โ€” GCP / Azure pricing tables.
  • Hard enforcement โ€” mint a scoped credential / SCP from the budget (the un-bypassable tier).

Limitations (honest)

budgie is a fast advisory guard, not an un-bypassable control. Know its edges:

  • Bash-tool only. An agent using an AWS MCP server (boto3 directly) bypasses it. The un-bypassable tier is a scoped credential / SCP minted from the budget โ€” on the roadmap.
  • Cumulative budgets need both hooks. BUDGIE_BUDGET accrues through the PostToolUse hook, so wire both budgie hook and budgie posthook (the setup above does). PreToolUse alone still enforces the per-command rate cap, but won't accumulate spend across commands.
  • EKS nodegroup counts desiredSize (or 1 when only min/max is given); autoscaling beyond desired isn't priced.
  • Composite only where the data's in the command. RDS storage/Multi-AZ and EKS node groups are folded in; an EC2 instance's own EBS volumes (--block-device-mappings) are not yet, so a big root volume can be under-counted. The cumulative session total partly compensates.
  • Agent-driven reconciliation. budgie credits deletes it sees (the agent's own commands). A delete done from the console / by TTL / by autoscaling won't be caught without snapshot polling โ€” out of scope for a hook.
  • Provisioning cost, not usage cost. Usage-based services โ€” S3, Lambda, DynamoDB on-demand, data transfer, NAT data processing โ€” can't be known before the fact and are not priced.
  • Estimates, not invoices. It ignores Reserved Instances / Savings Plans; spot is a rough ~70% off. A covered account may see over-estimates.
  • Curated pricing, but no silent allow. The static table prices the big bill-shock services exactly; anything else that provisions (an unknown SKU, an un-enumerated service, a hidden config) still warns rather than passing through โ€” only genuinely free creates (security groups, tags, IAM, log groupsโ€ฆ) are silent. So coverage gaps cost you a warning to review, never an unnoticed create.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

budgie_firewall-0.1.0.tar.gz (310.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

budgie_firewall-0.1.0-py3-none-any.whl (36.8 kB view details)

Uploaded Python 3

File details

Details for the file budgie_firewall-0.1.0.tar.gz.

File metadata

  • Download URL: budgie_firewall-0.1.0.tar.gz
  • Upload date:
  • Size: 310.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for budgie_firewall-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b249470ebabce5c9279231f2d30f91df4a25c0c29938bd5eb8f72fef8e0f849e
MD5 315552eda195f0e36fefb1797a347e39
BLAKE2b-256 6f395907a8495feb4fc83574a7af054b76cb08ba12692de75a430141a29ff81f

See more details on using hashes here.

Provenance

The following attestation bundles were made for budgie_firewall-0.1.0.tar.gz:

Publisher: publish.yml on TanishkaMarrott/budgie

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file budgie_firewall-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: budgie_firewall-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 36.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for budgie_firewall-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 554019c55e4a05d482df51ba071fc56baf3f874808c94bb5b917a49a9de78252
MD5 0eb475c245aecaf4b46dc3c5c9f073b8
BLAKE2b-256 a09756c13ca5e2573e9aa6329eb8d0ff9c90096ce36901321b54088c997a4329

See more details on using hashes here.

Provenance

The following attestation bundles were made for budgie_firewall-0.1.0-py3-none-any.whl:

Publisher: publish.yml on TanishkaMarrott/budgie

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page