P-d: budgets with warn, downgrade and stop, admin overrides, usage over time
Spec: docs/DESIGN.md part P, item 4. This is based on develop (after #24).
Tables
All are assistant_* tables, gated by assistant.admin, with AI exposure NONE.
assistant_budget (in the nav as Budgets):
scope: INSTANCE, USER, ROLE (a permission), PROVIDER or MODEL;
period DAY or MONTH (UTC), and metric COST_USD or TOKENS;
warn % (default 80), and what happens at 100%: DOWNGRADE (optionally to a named model) or STOP;
a hard ceiling (default 120%), at which the turn always stops.
assistant_budget_override (child list on the budget): an extra allowance or "unlimited until", with a required reason and expiry. The grantor is taken from the session. Only assistant.admin can create one.
assistant_budget_event (child list): WARNED, DOWNGRADED or STOPPED, deduplicated per budget, scope and period by a unique key, so it holds across replicas.
assistant_budget_usage:
Usage rows are owner-locked, so a user's session cannot sum other users' usage. This table holds only counters, time, user, provider and model; no conversation or request data.
It lets shared scopes (instance, provider, model) be summed under the caller's own session. No session is elevated.
It is read-only and visible only to admins. UPGRADE.md has the backfill.
Behaviour
Budgets are checked at turn start (the existing 429 BUDGET path) and before every model call. The strictest outcome wins.
At the warn %:
a panel notice to the user;
an optional Alerts signal assistant.budget.warning, through the reflective Alerts producer SPI pattern from qbit-webhooks. The Alerts qbit turns it into the bell.
At 100% with DOWNGRADE: the turn switches to the named model, or the cheapest enabled model (LOCAL first), and the user gets a notice. The PHI rule still applies: if no eligible target exists, the turn stops.
STOP, or the hard ceiling: the turn ends with BUDGET and the budget's name.
The existing config budgets and provider monthly caps are unchanged, and overrides never waive them.
App home
A Budgets card: used / limit, a percent bar, the state, and the active overrides.
30-day cost and token bars, broken down by user, model, provider and tool.
Flow layout only, inherited colors, and empty states.
Tests
205 in total, all green in ./mvnw verify with the JaCoCo gates (core: 133).
Warnings: none at 79%; one warning at 80%, and only once per period.
Downgrade: to the named model; to the cheapest LOCAL model; PHI with no eligible target stops.
Stops: the hard ceiling stops; the turn-start 429 and the mid-turn BUDGET.
Overrides: extra allowance, unlimited until, expired, and admin-only.
Scopes: ROLE, PROVIDER, MODEL and INSTANCE, the TOKENS metric, and the UTC month boundary with a fixed clock.
Precedence and home: the strictest outcome wins; the home renders with data and empty.
## P-d: budgets with warn, downgrade and stop, admin overrides, usage over time
Spec: `docs/DESIGN.md` part P, item 4. This is based on `develop` (after #24).
### Tables
All are `assistant_*` tables, gated by `assistant.admin`, with AI exposure NONE.
- **`assistant_budget`** (in the nav as Budgets):
- scope: INSTANCE, USER, ROLE (a permission), PROVIDER or MODEL;
- period DAY or MONTH (UTC), and metric COST_USD or TOKENS;
- warn % (default 80), and what happens at 100%: DOWNGRADE (optionally to a named model) or STOP;
- a hard ceiling (default 120%), at which the turn always stops.
- **`assistant_budget_override`** (child list on the budget): an extra allowance or "unlimited until", with a required reason and expiry. The grantor is taken from the session. Only `assistant.admin` can create one.
- **`assistant_budget_event`** (child list): WARNED, DOWNGRADED or STOPPED, deduplicated per budget, scope and period by a unique key, so it holds across replicas.
- **`assistant_budget_usage`:**
- Usage rows are owner-locked, so a user's session cannot sum other users' usage. This table holds only counters, time, user, provider and model; no conversation or request data.
- It lets shared scopes (instance, provider, model) be summed under the caller's own session. No session is elevated.
- It is read-only and visible only to admins. UPGRADE.md has the backfill.
### Behaviour
- Budgets are checked at turn start (the existing 429 `BUDGET` path) and before **every** model call. The strictest outcome wins.
- **At the warn %:**
- a panel notice to the user;
- an optional Alerts signal `assistant.budget.warning`, through the reflective Alerts producer SPI pattern from qbit-webhooks. The Alerts qbit turns it into the bell.
- **At 100% with DOWNGRADE:** the turn switches to the named model, or the cheapest enabled model (LOCAL first), and the user gets a notice. The PHI rule still applies: if no eligible target exists, the turn stops.
- **STOP, or the hard ceiling:** the turn ends with `BUDGET` and the budget's name.
- The existing config budgets and provider monthly caps are unchanged, and overrides never waive them.
### App home
- A Budgets card: used / limit, a percent bar, the state, and the active overrides.
- 30-day cost and token bars, broken down by user, model, provider and tool.
- Flow layout only, inherited colors, and empty states.
### Tests
205 in total, all green in `./mvnw verify` with the JaCoCo gates (core: 133).
- **Warnings:** none at 79%; one warning at 80%, and only once per period.
- **Downgrade:** to the named model; to the cheapest LOCAL model; PHI with no eligible target stops.
- **Stops:** the hard ceiling stops; the turn-start 429 and the mid-turn `BUDGET`.
- **Overrides:** extra allowance, unlimited until, expired, and admin-only.
- **Scopes:** ROLE, PROVIDER, MODEL and INSTANCE, the TOKENS metric, and the UTC month boundary with a fixed clock.
- **Precedence and home:** the strictest outcome wins; the home renders with data and empty.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
P-d: budgets with warn, downgrade and stop, admin overrides, usage over time
Spec:
docs/DESIGN.mdpart P, item 4. This is based ondevelop(after #24).Tables
All are
assistant_*tables, gated byassistant.admin, with AI exposure NONE.assistant_budget(in the nav as Budgets):assistant_budget_override(child list on the budget): an extra allowance or "unlimited until", with a required reason and expiry. The grantor is taken from the session. Onlyassistant.admincan create one.assistant_budget_event(child list): WARNED, DOWNGRADED or STOPPED, deduplicated per budget, scope and period by a unique key, so it holds across replicas.assistant_budget_usage:Behaviour
BUDGETpath) and before every model call. The strictest outcome wins.assistant.budget.warning, through the reflective Alerts producer SPI pattern from qbit-webhooks. The Alerts qbit turns it into the bell.BUDGETand the budget's name.App home
Tests
205 in total, all green in
./mvnw verifywith the JaCoCo gates (core: 133).BUDGET.🤖 Generated with Claude Code