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.
Metadata
Release files for budgetgate 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| budgetgate-0.3.1.tar.gz | 24.3 kB | Without provenance | Details for budgetgate-0.3.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| budgetgate-0.3.1-py3-none-any.whl | Python 3 | none | any | Details for budgetgate-0.3.1-py3-none-any.whl |
Total release size: 41.2 kB
Release files / budgetgate-0.3.1.tar.gz
| Download URL | budgetgate-0.3.1.tar.gz |
|---|---|
| Size | 24.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c0f2e08450f2ced988eb8ea9c921a785632ea1b5848ebcb224634943c77ac64d
|
|
BLAKE2b-256 checksum How to use checksums |
86ca00b2cb03e079210eb7fa1e73dd74eb78f3affea08c93cd4c9e2a40c41df7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.0
|
Release files / budgetgate-0.3.1-py3-none-any.whl
| Download URL | budgetgate-0.3.1-py3-none-any.whl |
|---|---|
| Size | 16.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bb004c78184a01427cab682ca6e1b0cec1017fd938b5198b5495776f2c23905d
|
|
BLAKE2b-256 checksum How to use checksums |
573112aa07be403e59130134f68236abff4e4ecfb934bda6caec82ec82fd28f5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.0
|
Security for budgetgate 0.3.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.