Repository navigation
Conversation
…nt-Field#1546) railBlockLocked skipped the daily budget check for InTask runners and the home Errand, and the crew guard that also enforces it is absent under --one-model and for an InTask agent with no crew. So a chat /task past the day's limit, and a task under --one-model, kept spending with no refusal while headless codeaf do and standing firings honoured the limit. Remove the InTask/Errand carve-out so the daily ceiling is checked on every turn-starting path. It is a ceiling read of the shared ledger, never a tally, so a path already held to the crew's own day cap is refused in two places, never charged twice.
ZeroPoint95
left a comment
There was a problem hiding this comment.
Hi @7vignesh, thanks for closing the ordinary task and errand budget gap. Two cases still need addressing around internal/session/rail.go:75–83:
-
Preserve the selected daily-budget profile in workers.
Both task construction paths omit
ProfileDir, so the new check reads the default profile. With a selected limit of $1,000 and $600 spent, a worker incorrectly refuses at the default $500. With a selected $1 limit and $1.50 spent, it still runs.The first case is a new regression; the second leaves the existing bypass unresolved. Please carry the selected budget authority into workers without enabling settings changes. Add regressions through both the production task-child constructor and current run-worker factory: the higher selected limit must allow dispatch, and the lower selected limit must refuse before any provider call.
-
Preserve explicit spending preauthorization.
codeaf do --yes-spendandCODEAF_PREAUTHORIZE_SPEND=1deliberately allow spending past the daily limit, but the new worker check applies that limit again.An actual run worker with preauthorization, a $1 daily limit and $1.50 spent now stops before calling its provider; the exact base proceeds. Please propagate that authorization into the daily check and cover ordinary refusal alongside preauthorized execution. Separate task limits should remain in force.
These requests preserve the existing turn-admission policy; no per-call budget redesign is needed.
…ation into workers ZeroPoint95's review on Agent-Field#1842 caught that the daily check read the default profile for workers (which carry no ProfileDir), so a worker refused under a higher selected limit and spent past a lower one, and that it re-applied the limit --yes-spend deliberately lifts. - Resolve the daily ceiling once at the root (where ProfileDir is set) and carry it as Config.DailyBudgetUSD; dailyCeilingUSD reads live from the profile for a root and the carried figure for a worker, never a profile at an empty path. - Carry Config.SpendPreauthorized (--yes-spend / CODEAF_PREAUTHORIZE_SPEND=1) into workers; it lifts the daily check and nothing else, matching the crew guard. - Thread both through all three worker constructors: the task-child (task_run.go), the adaptive-run worker (orchestrate.go) and the plan-run bash worker (run.CrewFactory, which gains a preauthorized parameter). Adds regressions: a worker held to its carried selected budget (higher allows, lower refuses) not the default, and preauthorization lifting the daily ceiling while a conversation limit stays in force.
|
Thanks @ZeroPoint95, both real. The daily check read the default profile for workers and ignored preauthorization. Fixed by resolving both at the root and carrying them down, since a worker is handed no 1. The selected budget travels to workers.
2. Preauthorization is honoured.
Verified by running |
What changed
The daily spend ceiling (
CODEAF_DAILY_BUDGET, the machine-wide limit over a day) was not enforced on every execution path.Agent.railBlockLockedskipped the daily check for anInTaskrunner (a task node, including one under--one-model) and the homeErrand, and the crew guard that also enforces the day cap is absent under--one-modeland for anInTaskagent with no crew. So a chat/taskor a task under--one-modelkept spending past the day's limit with no refusal, while headlesscodeaf doand standing firings honoured it (#1546).The remaining gap after #1627, which enabled the chat/crew daily check but kept the
InTask/Errandcarve-out (noted as still-open in that PR's review).The fix removes the carve-out in
internal/session/rail.go: the daily ceiling is now checked on every turn-starting path (every caller ofrailBlockLocked- chat submit, resume, harness, image, team-wake, and the task node runner). It is a ceiling READ of the shared ledger, never a tally, so a path already held to the crew's own day cap is refused in two places and never charged twice. The rail still never cuts a turn in half - it stops the next turn, exactly as the conversation rail does.How it was checked
Verified with real runs in a Linux container (
golang:1.26), sinceinternal/sessiondoes not test-build on my Windows box (it transitively imports the//go:build !windowsinternal/seniordev/engine/steploop):go test ./internal/session/ -run TestTheDailyCeilingBindsTaskAndErrandTurnsToo- new test, passes for all three postures: anInTasktask node runner, a task node under--one-model, and the homeErrand, each refused past the ceiling withdaily limit reached, and a task turn under the ceiling still allowed. It FAILS on all three with the carve-out restored (verified by reverting), so it is a real regression guard.go test ./internal/session/ -run TestSpendRailRefusesTurnWhenDailyBudgetExceededand the fullRail|Spend|Budget|Dailyset - all pass.go build ./internal/session/on Windows andGOOS=linux go test -c ./internal/session/both succeed.Pre-existing failures on clean
dev(unrelated to this change, verified by stashing):TestTheTaskPromptTeachesTheMeasureIsTheLoop,TestTheFixedPrefixStaysUnderItsBudget,TestTheLeanPrefixStaysUnderItsBudget- all about system-prompt text and byte budgets, not spend.Checklist
docs/changes/unreleased/1842-daily-ceiling-every-path.md./budget daywording are unchanged, this only extends which paths honour an existing limit. The manual already says new work waits at the daily limit..github/known-red.txt.Closes #1546.