You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[epic] AI budget & circuit-breaker limits are user-configurable via UI + MCP (today they are hardcoded constants) #478
Every AI budget/limit a user can hit should be viewable and settable by the user via the UI and via MCP — not a hardcoded constant that requires a code edit + deploy to change.
Background (verified in code)
Account circuit breaker = two bare constants in workers/api/src/lib/delegation-budget-store.ts: DAILY_CEILING_MICROS ($50 charged/24h, line 95) and DAILY_TOKEN_CEILING (250M tokens/24h, line 112). Enforced in reserve(); nothing reads an env var, per-account column, or admin setting.
The only existing platform-AI setting is a boolean kill switch (PUT /v1/admin/settings/platform-ai).
The breaker is invisible until it fires — the number only shows in the refusal message, and the agent-facing guidance even implies a nonexistent "contact support to raise it" lever.
A user can see their rolling-24h consumption against every ceiling and raise/lower each limit from both the console and MCP, with a clamped safe maximum — no code change required.
Reframed by #485: rather than calibrate the ceilings now, AI budget enforcement is demoted to observe-only (BUDGET_ENFORCE, default off) until paid launch — usage is metered, structural caps stay, nothing hard-blocks. The D1 config resolver from #474 stays in place for when enforcement returns. Children #475/#476/#477 parked.
Verdict: delivered — two small remainders, then close
All four children are closed and I have read the code that delivers each, plus confirmed the surfaces in production. This epic's premise ("today they are hardcoded constants") has expired.
resolveAccountCeilings, lib/delegation-budget-store.ts:130-172 — per-account row → '__platform__' row → env → constant, clamped to MAX_OVERRIDE_*, fails open on a D1 error. Read by reserve() (:305) and openBudget
GET/PUT /v1/budget/limits, routes/budget.ts; console BudgetPanel, store/console/src/pages/Usage.tsx:566-780. Both are live — the deployed console bundle contains the panel, and production GET /v1/budget/limits returns effective + per-field tier + consumption + remaining + maxOverride + enforced
perTreeCostMicros / perTreeDelegations / perTreeMaxDepth / loopMaxIterations in the same four-tier stack; loopMaxIterations reaches sanitizeMaxIterations via lib/agent-loop.ts:60-64
Live proof of "no code change required", production, 2026-08-11 — every tier resolving, distance-to-limit present:
1. MCP write parity — 2 of 6 fields, and it clears the other 4.set_budget_limits (account.ts:122-151) sends only tokenCeiling and chargedMicrosCeiling; because PUT /v1/budget/limits is a full replace (routes/budget.ts:250-274), calling it wipes any per-tree or loopMaxIterations override set from the console. Filed as #501 with the fix. Read parity is fine — get_budget_limits proxies the GET, so it already returns all six.
2. The refusal message still points at a lever that is now the user's own.lib/delegation-budget-store.ts:321 and :331 both end:
To raise this limit, ask your platform operator.
This epic's own background objected to exactly this — "the agent-facing guidance even implies a nonexistent 'contact support to raise it' lever" — and #474 listed "fix the refusal message" as a deliverable. The denomination half was fixed (the message now names what was counted, over what window, and why subscription tokens are excluded — #343's finding). The lever half was not: since #475 the owner raises it themselves in Usage → Budget limits, or with set_budget_limits. A one-line copy change to something like "Raise it in Usage → Budget limits, or ask your platform operator." finishes it. Not worth its own issue; fold it into whichever of #501/#502 lands first, or do it standalone.
Correction to the 2026-08-10 08:15 comment on this issue
That comment says #475/#476/#477 are "parked" under #485. They are not — all three were closed delivered between 08:20 and 08:56 the same morning, i.e. within 46 minutes of the parking decision being written. Anyone reading this epic top-to-bottom would conclude three quarters of it is deferred when in fact all of it shipped. The Children checklist above the comment is also still unticked.
Recommendation
Tick the four children, correct or strike the "parked" comment, and close this epic as delivered, tracking the two remainders under #501 (MCP write parity) and the one-line message fix. The enforcement posture question is separate and is answered on #485 — nothing in this epic depends on it, and the machinery it built is what makes the recommended split-enforcement option there a config change rather than a code change.
Verified vs inferred
Verified: every file:line, the migrations, the production /v1/budget/limits payload, and the presence of the budget panel in the deployed console bundle. Inferred: nothing load-bearing. Not checked: whether any other client sends a partial PUT and relies on the clearing behaviour — I grepped store/, workers/, packages/ and found only the console (all six fields) and MCP (two).
Seven days on: the "close as delivered" verdict still holds, and one of the two named remainders is done. The other is still shipping a false statement to the agent.
Every tier still resolving, distance-to-limit present, all four children still closed. The epic's premise — "today they are hardcoded constants" — is still expired.
Remainder 1 is done
#501 is closed — MCP set_budget_limits no longer wipes the four fields it does not expose.
Remainder 2 is not, and it is now false in three places, not two
workers/api/src/lib/delegation-budget-store.ts:
:321 — "…it resets as older usage ages out. To raise this limit, ask your platform operator."
:331 — "…It resets as older usage ages out. To raise this limit, ask your platform operator."
:377 — "Work already done is intact; nothing new can draw on this pool. There is no self-service way to raise it — ask your platform operator."
The third one is the sharpest, and it was not in the previous comment's list. It fires on an exhausted per-tree pool — and routes/budget.ts:61-64 makes every field of that pool owner-settable:
So :377 tells the owner "there is no self-service way to raise it" about four values he can raise from Usage → Budget limits or with set_budget_limits, both of which #477 shipped. That is not stale copy — it is the agent asserting the absence of a lever the same release added, which is exactly the defect this epic's own background objected to:
"the agent-facing guidance even implies a nonexistent 'contact support to raise it' lever"
It has been inverted rather than fixed: the lever now exists and the message denies it.
Recommendation, unchanged and now cheaper
Tick the four children, strike the stale "parked" comment, fix the three strings, close. The string fix is one commit with no design question in it:
:321 / :331 → "Raise it in Usage → Budget limits, or ask your platform operator."
:377 → "Raise the per-run pool in Usage → Budget limits (per-tree budget, delegations, depth), or ask your platform operator."
Worth pinning with a test, because this is the third time these two halves have drifted: assert that no refusal string in delegation-budget-store.ts claims a limit cannot be raised while routes/budget.ts's FIELDS table lists the corresponding column. That is a small mute-invariant.test.ts-shaped scan and it would have caught :377 on the day #477 landed.
Verified: the live payload; delegation-budget-store.ts:321, :331, :377; routes/budget.ts:61-64; the closed states of #474–#477 and #501. Not attempted: I set no limit and flipped no flag.
Closing as superseded by its own premise expiring. This epic asks for AI budget and circuit-breaker limits to be user-configurable because they are hardcoded constants. They are no longer enforced at all — d951b5fb demoted them to observe-only (#485, now closed), so there is nothing whose hardcoding currently constrains anyone.
Measured today: 24h spend $2.07 against a $50 ceiling, enforced:false, both tiers "default".
The right time to build configurable limits is when enforcement returns, which #56 (cost governance, gating the public open) already owns and which is correctly deferred behind #68. Re-filing then against live numbers will produce a better issue than progressing this one against numbers from before enforcement was switched off.
Activity
serge-ivo commentedon Aug 10, 2026
Reframed by #485: rather than calibrate the ceilings now, AI budget enforcement is demoted to observe-only (
BUDGET_ENFORCE, default off) until paid launch — usage is metered, structural caps stay, nothing hard-blocks. The D1 config resolver from #474 stays in place for when enforcement returns. Children #475/#476/#477 parked.feat(budget): demote AI circuit breakers to observe-only (BUDGET_ENFO…
feat(mcp): add get_budget_limits + set_budget_limits tools (#476)
serge-ivo commentedon Aug 11, 2026
Verdict: delivered — two small remainders, then close
All four children are closed and I have read the code that delivers each, plus confirmed the surfaces in production. This epic's premise ("today they are hardcoded constants") has expired.
32f48a1+ migration0113resolveAccountCeilings,lib/delegation-budget-store.ts:130-172— per-account row →'__platform__'row → env → constant, clamped toMAX_OVERRIDE_*, fails open on a D1 error. Read byreserve()(:305) andopenBudget2d8f9d5(+5805332forenforced)GET/PUT /v1/budget/limits,routes/budget.ts; consoleBudgetPanel,store/console/src/pages/Usage.tsx:566-780. Both are live — the deployed console bundle contains the panel, and productionGET /v1/budget/limitsreturnseffective+ per-field tier +consumption+remaining+maxOverride+enforcedca476cdget_budget_limits/set_budget_limits,workers/mcp/src/instance-tools/account.ts:108-153c363fa9+ migration0115perTreeCostMicros/perTreeDelegations/perTreeMaxDepth/loopMaxIterationsin the same four-tier stack;loopMaxIterationsreachessanitizeMaxIterationsvialib/agent-loop.ts:60-64Live proof of "no code change required", production, 2026-08-11 — every tier resolving, distance-to-limit present:
What remains
1. MCP write parity — 2 of 6 fields, and it clears the other 4.
set_budget_limits(account.ts:122-151) sends onlytokenCeilingandchargedMicrosCeiling; becausePUT /v1/budget/limitsis a full replace (routes/budget.ts:250-274), calling it wipes any per-tree orloopMaxIterationsoverride set from the console. Filed as #501 with the fix. Read parity is fine —get_budget_limitsproxies the GET, so it already returns all six.2. The refusal message still points at a lever that is now the user's own.
lib/delegation-budget-store.ts:321and:331both end:This epic's own background objected to exactly this — "the agent-facing guidance even implies a nonexistent 'contact support to raise it' lever" — and #474 listed "fix the refusal message" as a deliverable. The denomination half was fixed (the message now names what was counted, over what window, and why subscription tokens are excluded — #343's finding). The lever half was not: since #475 the owner raises it themselves in Usage → Budget limits, or with
set_budget_limits. A one-line copy change to something like "Raise it in Usage → Budget limits, or ask your platform operator." finishes it. Not worth its own issue; fold it into whichever of #501/#502 lands first, or do it standalone.Correction to the 2026-08-10 08:15 comment on this issue
That comment says #475/#476/#477 are "parked" under #485. They are not — all three were closed delivered between 08:20 and 08:56 the same morning, i.e. within 46 minutes of the parking decision being written. Anyone reading this epic top-to-bottom would conclude three quarters of it is deferred when in fact all of it shipped. The
Childrenchecklist above the comment is also still unticked.Recommendation
Tick the four children, correct or strike the "parked" comment, and close this epic as delivered, tracking the two remainders under #501 (MCP write parity) and the one-line message fix. The enforcement posture question is separate and is answered on #485 — nothing in this epic depends on it, and the machinery it built is what makes the recommended split-enforcement option there a config change rather than a code change.
Verified vs inferred
Verified: every
file:line, the migrations, the production/v1/budget/limitspayload, and the presence of the budget panel in the deployed console bundle. Inferred: nothing load-bearing. Not checked: whether any other client sends a partial PUT and relies on the clearing behaviour — I greppedstore/,workers/,packages/and found only the console (all six fields) and MCP (two).serge-ivo commentedon Aug 18, 2026
Seven days on: the "close as delivered" verdict still holds, and one of the two named remainders is done. The other is still shipping a false statement to the agent.
Re-verified 2026-08-18.
The delivered half is unchanged
GET /v1/budget/limitson production today:{"enforced":false, "effective":{"tokenCeiling":250000000,"tokenCeilingTier":"default", "chargedMicrosCeiling":50000000,"chargedMicrosCeilingTier":"default"}, "consumption":{"chargedMicros":2073017,"tokens":56743055}, "remaining":{"chargedMicros":47926983,"tokens":193256945}}Every tier still resolving, distance-to-limit present, all four children still closed. The epic's premise — "today they are hardcoded constants" — is still expired.
Remainder 1 is done
#501 is closed — MCP
set_budget_limitsno longer wipes the four fields it does not expose.Remainder 2 is not, and it is now false in three places, not two
workers/api/src/lib/delegation-budget-store.ts::321— "…it resets as older usage ages out. To raise this limit, ask your platform operator.":331— "…It resets as older usage ages out. To raise this limit, ask your platform operator.":377— "Work already done is intact; nothing new can draw on this pool. There is no self-service way to raise it — ask your platform operator."The third one is the sharpest, and it was not in the previous comment's list. It fires on an exhausted per-tree pool — and
routes/budget.ts:61-64makes every field of that pool owner-settable:So
:377tells the owner "there is no self-service way to raise it" about four values he can raise from Usage → Budget limits or withset_budget_limits, both of which #477 shipped. That is not stale copy — it is the agent asserting the absence of a lever the same release added, which is exactly the defect this epic's own background objected to:It has been inverted rather than fixed: the lever now exists and the message denies it.
Recommendation, unchanged and now cheaper
Tick the four children, strike the stale "parked" comment, fix the three strings, close. The string fix is one commit with no design question in it:
:321/:331→ "Raise it in Usage → Budget limits, or ask your platform operator.":377→ "Raise the per-run pool in Usage → Budget limits (per-tree budget, delegations, depth), or ask your platform operator."Worth pinning with a test, because this is the third time these two halves have drifted: assert that no refusal string in
delegation-budget-store.tsclaims a limit cannot be raised whileroutes/budget.ts'sFIELDStable lists the corresponding column. That is a smallmute-invariant.test.ts-shaped scan and it would have caught:377on the day #477 landed.Verified: the live payload;
delegation-budget-store.ts:321,:331,:377;routes/budget.ts:61-64; the closed states of #474–#477 and #501. Not attempted: I set no limit and flipped no flag.serge-ivo commentedon Aug 18, 2026
Closing as superseded by its own premise expiring. This epic asks for AI budget and circuit-breaker limits to be user-configurable because they are hardcoded constants. They are no longer enforced at all —
d951b5fbdemoted them to observe-only (#485, now closed), so there is nothing whose hardcoding currently constrains anyone.Measured today: 24h spend $2.07 against a $50 ceiling,
enforced:false, both tiers"default".The right time to build configurable limits is when enforcement returns, which #56 (cost governance, gating the public open) already owns and which is correctly deferred behind #68. Re-filing then against live numbers will produce a better issue than progressing this one against numbers from before enforcement was switched off.