Deterministic, pre-execution spend limiting for semantic actions in agent systems.
Project description
BudgetGate
Deterministic, pre-execution spend limiting for semantic actions in agent systems.
Source of Truth
The canonical source is github.com/actiongate-oss/budgetgate. PyPI distribution is a convenience mirror.
Vendoring encouraged. This is a small, stable primitive. Copy it, fork it, reimplement it. See SEMANTICS.md for the behavioral contract if you reimplement.
Quick Start
from decimal import Decimal
from budgetgate import Engine, Ledger, Budget, BudgetExceeded
engine = Engine()
@engine.guard(
Ledger("openai", "gpt-4", "user:123"),
Budget(max_spend=Decimal("10.00"), window=3600), # $10/hour
cost=Decimal("0.03"), # fixed cost per call
)
def call_gpt4(prompt: str) -> str:
return openai.chat(prompt)
try:
response = call_gpt4("Hello")
except BudgetExceeded as e:
print(f"Budget exceeded: {e.decision.spent_in_window} spent")
Two Cost Modes
Fixed Cost (pre-execution)
When cost is known before execution:
@engine.guard(
Ledger("openai", "embedding"),
Budget(max_spend=Decimal("5.00"), window=3600),
cost=Decimal("0.0001"), # fixed cost per call
)
def embed(text: str) -> list[float]:
return openai.embed(text)
Bounded Dynamic Cost (pre-execution with estimate)
When cost depends on the result but has a known upper bound:
@engine.guard_bounded(
Ledger("anthropic", "claude", "user:123"),
Budget(max_spend=Decimal("5.00"), window=3600),
estimate=Decimal("0.50"), # max possible cost (reserved before execution)
actual=lambda r: Decimal(str(r.usage.total_cost)), # actual cost (committed after)
)
def call_claude(prompt: str) -> Response:
return anthropic.messages.create(...)
The estimate is reserved before execution. If it doesn't fit the budget, the action is blocked. After execution, the actual cost is committed and unused budget is recovered.
Core Concepts
Ledger
Identifies a spend-tracked stream:
Ledger(namespace, resource, principal)
Ledger("openai", "gpt-4", "user:123") # per-user
Ledger("anthropic", "claude", "team:eng") # per-team
Ledger("infra", "compute", "global") # global
Budget
Budget(
max_spend=Decimal("10.00"), # max spend in window
window=3600, # rolling window (seconds)
mode=Mode.HARD, # HARD raises, SOFT returns result
on_store_error=StoreErrorMode.FAIL_CLOSED,
)
Decision
Every check returns a Decision with:
decision.allowed # bool
decision.spent_in_window # Decimal - current spend
decision.remaining # Decimal - budget remaining
decision.requested # Decimal - amount requested
Decorator Styles
| Decorator | Cost Mode | Returns | On Block |
|---|---|---|---|
guard |
Fixed | T |
Raises BudgetExceeded |
guard_bounded |
Dynamic | T |
Raises BudgetExceeded |
guard_result |
Fixed | Result[T] |
Returns blocked result |
guard_bounded_result |
Dynamic | Result[T] |
Returns blocked result |
# Raises on block
@engine.guard(ledger, budget, cost=Decimal("0.01"))
def fixed_action(): ...
@engine.guard_bounded(ledger, budget, estimate=Decimal("0.50"), actual=lambda r: r.cost)
def dynamic_action(): ...
# Never raises - returns Result[T]
@engine.guard_result(ledger, budget, cost=Decimal("0.01"))
def fixed_action(): ...
@engine.guard_bounded_result(ledger, budget, estimate=Decimal("0.50"), actual=lambda r: r.cost)
def dynamic_action(): ...
Relation to ActionGate
BudgetGate complements ActionGate:
| Primitive | Limits | Use case |
|---|---|---|
| ActionGate | calls/time | Rate limiting |
| BudgetGate | cost/time | Spend limiting |
Both are:
- Deterministic
- Pre-execution
- Decorator-friendly
- Store-backed
Use together:
from decimal import Decimal
@actiongate_engine.guard(Gate("api", "search"), Policy(max_calls=100))
@budgetgate_engine.guard(Ledger("api", "search"), Budget(max_spend=Decimal("1.00")), cost=Decimal("0.01"))
def search(query: str) -> list:
...
API Reference
| Type | Purpose |
|---|---|
Engine |
Core spend tracking |
Ledger |
Spend stream identity |
Budget |
Spend policy |
Decision |
Evaluation result |
Result[T] |
Wrapper for guard_result |
BudgetExceeded |
Exception from guard |
| Enum | Values |
|---|---|
Mode |
HARD, SOFT |
StoreErrorMode |
FAIL_CLOSED, FAIL_OPEN |
Status |
ALLOW, BLOCK |
BlockReason |
BUDGET_EXCEEDED, STORE_ERROR |
Numeric Precision
All spend amounts use Decimal to avoid floating-point drift. See SEMANTICS.md §9.
License
Apache License 2.0. See LICENSE for the full text.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file budgetgate-0.2.1.tar.gz.
File metadata
- Download URL: budgetgate-0.2.1.tar.gz
- Upload date:
- Size: 16.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d126592bed5754a5ee989412f0e603c69ba25c4f74c4c91faf86e6eec173f12
|
|
| MD5 |
d879e8c68f2c69b4cd0420839581ce9a
|
|
| BLAKE2b-256 |
96da5c84fde0cf6e245f7f2fa604cc84056a5dd89f64be095190772b45adabbc
|
File details
Details for the file budgetgate-0.2.1-py3-none-any.whl.
File metadata
- Download URL: budgetgate-0.2.1-py3-none-any.whl
- Upload date:
- Size: 14.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
16ddac057cfe7227aab136f0a205d9aed073172a8ab9f87032306ec7ae2b4949
|
|
| MD5 |
a3c943c417dbcc2babce0a60940992fb
|
|
| BLAKE2b-256 |
6978035a10d3abecb3a2714e28741d2fb573884d4ca86af93adf8d02d70f4bad
|