Budget Enforcement
Govrix Scout enforces daily spend limits for each agent and for the entire deployment. When a budget is exceeded, the proxy returns HTTP 429 and stops forwarding requests until the next calendar day (UTC).
How it works
Budget counters are stored in the budget_daily table in PostgreSQL. At proxy startup, today’s counters are loaded into memory for every known agent. From that point on, all budget checks use in-memory counters — no database reads on the hot path.
After each successful request, token usage and cost are added to the in-memory counters and written to the database fire-and-forget (the proxy does not await the write).
startup
|
v
load today's counters from budget_daily into memory (per-agent + global)
|
inbound request
|
v
interceptor: agent_counter + request_estimated_tokens > agent_cap?
|
+-- yes --> return HTTP 429
|
v
global_counter + request_estimated_tokens > global_cap?
|
+-- yes --> return HTTP 429
|
v
forward to upstream
|
v
(response received)
|
v
update in-memory counters
|
v
record_usage_with_db() -- fire-and-forget, not awaitedBudget counters reset at midnight UTC. The reset is handled by checking whether the date stored in memory matches today’s date at each request. If it does not match, counters are zeroed in memory and a new row is inserted into budget_daily.
Configuration
Budget limits are set in govrix.toml:
[budget]
# Global daily cap across all agents combined
global_daily_token_limit = 10_000_000
global_daily_cost_usd = 500.00
# Default per-agent daily cap (applied to any agent without an explicit override)
default_agent_daily_token_limit = 500_000
default_agent_daily_cost_usd = 25.00
# Per-agent overrides
[budget.agents.billing-agent]
daily_token_limit = 1_000_000
daily_cost_usd = 50.00
[budget.agents.support-agent]
daily_token_limit = 200_000
daily_cost_usd = 10.00If no per-agent override is set, the default_agent_* values apply. If no default is set, the agent has no cap.
HTTP 429 response format
When a budget is exceeded, the proxy returns:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: <seconds until midnight UTC>
{
"error": "budget_exceeded",
"message": "Daily token budget for agent 'billing-agent' has been exceeded.",
"agent_id": "billing-agent",
"limit": 1000000,
"used": 1000214,
"resets_at": "2026-03-06T00:00:00.000Z"
}The Retry-After header tells the caller how many seconds remain until the daily reset.
Querying current usage
Agent usage
curl -X GET http://localhost:4001/api/v1/agents/billing-agent/budget \
-H "Authorization: Bearer $GOVRIX_API_KEY"Response:
{
"agent_id": "billing-agent",
"date": "2026-03-05",
"tokens_used": 423819,
"tokens_limit": 1000000,
"cost_usd_used": 21.19,
"cost_usd_limit": 50.00,
"resets_at": "2026-03-06T00:00:00.000Z"
}Database schema
CREATE TABLE budget_daily (
agent_id TEXT NOT NULL,
date DATE NOT NULL,
tokens_used BIGINT NOT NULL DEFAULT 0,
cost_usd_used NUMERIC(12,6) NOT NULL DEFAULT 0,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (agent_id, date)
);The global budget row uses agent_id = '__global__'.
Implementation
| File | Role |
|---|---|
crates/govrix-scout-proxy/src/policy/budget.rs | In-memory counter management, cap checks, HTTP 429 logic |
crates/govrix-scout-store/src/budget.rs | record_usage_with_db() — fire-and-forget DB write; startup load |
In-memory counters are per-process. If you run multiple proxy instances, each instance maintains its own counters and they are not synchronized in real time. The budget_daily table converges eventually (all instances write to it), but between writes, two instances could each believe they are under the cap when the combined usage has exceeded it. For strict enforcement across multiple instances, reduce the batch-write interval or run a single proxy instance.