A spent completion budget stops reading as a backend outage #807
No reviewers
Labels
No labels
move-to-repo
coilyco-bridge-deploy
move-to-repo
coilyco-flight-deck-agent-compose
move-to-repo
coilyco-gaming-eco-app
move-to-repo
coilysiren-inbox
move-to-repo
unknown
🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
c#
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-gaming/sirens-echo!807
Loading…
Reference in a new issue
No description provided.
Delete branch "aos/claude/xh55-budget-spent-cause"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #549 by taking its second acceptance branch, which needs no ceiling decision.
What was actually broken
The issue said the runner is right to report an empty-content turn as a failure, and that is correct. What was wrong is what the failure said.
formatBudgetExhaustedreturned a barefmt.Errorfwith no sentinel, so nothing downstream could identify the condition.failureCausecould not see it and it fell through tostage_failed. The stage ismodel, soturnFailureNoticehanded the member:Every model call returned 200. That notice sends an operator to a working backend and tells a member to retry a question that will fail identically. It is the same defect #258 fixed for spent tool rounds and #651 fixed for a refused reply, arriving from a third direction — and per @Quail's capture on this issue it reaches members, not only batteries.
The change
ErrBudgetExhaustedsentinel, wrapped byformatBudgetExhaustedso the rendered numbers survive.budget_spentjoins thefailure_causeclosed set.ran out of room to answer, ask for something narrower.rounds_spentandbudget_spentstay separate causes. Both mean a ceiling this service chose ended the turn, but they are different numbers with different owners (tool_roundsagainstmax_completion_tokens), and #367 is a ceiling decision that has to be answerable from the failure series.failure_causeis a log attribute rather than a metric label, so the set grows without touching a cardinality bound. Its doc had listed five values and been missingreply_refusedsince #651; it now lists all seven.Addressing @Angie's objection directly
Agreed, and nothing here goes green. The turn still fails, the battery still goes red, and
TestAnAlwaysTruncatingProxyEndsTheTurnAsBudgetSpentasserts the error is still returned. Only what the red says changes. That is what the issue's own acceptance asked for: "characterised as its own behaviour... because a substrate event is not the agent failing a check."What this does not do
prompt-leakage, which truncates 34 times a day. @Quail was explicit about not having read that family's reasoning to the end, and a model that deliberates without converging still exhausts its budget. This changes the report, not the behaviour.model.budget.raisedcontradiction. I read the plumbing:finish_reasondecodes correctly atproxy.go:977and carries intotruncated()intact, so the harness side is sound and @Quail's agent-proxy inference survives my reading. One correction to that handoff though:model.chatspans are marked error only on transport and decode failures, so a truncated response is a successful span. The 20-errors-against-366-truncations comparison was never a contradiction, which leaves fewer facts needing one explanation.Verification
ward exec build,vet,test,policy-check,test-skips, andpre-commit run --all-filesall pass.Six new tests. The end-to-end one drives
Completeagainst a proxy that only ever returnsfinish_reason: lengthwith empty content, and asserts it makes exactlyBudgetRaises + 1calls before failing — so it genuinely climbs the ladder rather than passing vacuously.