Per-user AI budgets
The tx_ table caps per-backend-user AI spend
independently of the per-configuration daily limits on
tx_. A user request must clear BOTH layers:
any limit on the preset they chose AND any limit on their personal
budget record.
What a budget caps
Each row in tx_ binds to exactly one
be_user and defines six independent ceilings. 0 on any axis
means "unlimited on this axis".
| Field | Unit | Reset cadence |
|---|---|---|
| Max Requests/Day | count | Every day at 00:00 server-local time. |
| Max Tokens/Day | count | Every day at 00:00 server-local time. |
| Max Cost/Day ($) | USD | Every day at 00:00 server-local time. |
| Max Requests/Month | count | First of the month, 00:00 server-local time. |
| Max Tokens/Month | count | First of the month, 00:00 server-local time. |
| Max Cost/Month ($) | USD | First of the month, 00:00 server-local time. |
Usage is aggregated on demand from tx_ — the
same table the UsageTracker already writes to per request — so there
is no second write per request and no way for a separate counter to
drift away from the source of truth.
Creating a budget
Budget records have rootLevel = -1, so admins can create them at
the TYPO3 root (pid = 0) or on any regular page. Keeping them at
the root is the convention because budgets are site-wide admin
concerns, not page-scoped content; the recipe below follows that
convention.
- Open Web > List in the root (page UID 0) — or on the page where you keep other cross-site configuration records.
- Click Create new record.
- Choose LLM User Budget.
- Pick the backend user, set the ceilings, toggle Enforce this budget on.
- Save.
Note
Only one budget row per backend user. The be_ column
is unique. Re-editing the existing row is the correct way to
tighten or relax limits.
How the check runs
Before dispatching a request the consuming extension calls
Netresearch. The service:
- Returns allowed when the user has no budget record, when
Enforce this budget is off, or when every ceiling
is
0. - Aggregates today's usage and this month's usage in a single database roundtrip.
- Evaluates the daily window first; the monthly window only if the daily window passes.
- Adds
+1request and+plannedCostto the usage figures before comparing, so a user at exactly the limit is still allowed one more call.
The returned Budget names which bucket was tripped
(exceededLimit as a stable machine key, plus a human-friendly
reason string suitable for log output or caller-side wrapping).
Important
The check is best-effort, not a transactionally-safe gate.
Two concurrent requests for the same user can both pass
check before either updates
tx_, temporarily allowing a one-request
overshoot. Full serialisation would hot-path every AI request.
If strict enforcement matters, layer a per-user lock on top.
Budgets vs. configuration limits
Both layers persist but cap different things:
| Axis | Configuration daily limits | Per-user budgets |
|---|---|---|
| Bound to | a preset (tx_) | a backend user (tx_) |
| Question answered | "Can ANY editor keep using this preset today?" | "Can THIS editor keep spending this month?" |
| Windows | daily | daily AND monthly |
| Dimensions | requests, tokens, cost | requests, tokens, cost |
| Both must pass | yes | yes |
See ADR-025: Per-User AI Budgets for the full design rationale, including the alternatives (counter table, group-level budgets, auto-throttling) we considered and why they were rejected.