fix(goal): spend the budget per judged turn and never leave a goal active on an idle session - #208
Merged
Merged
Conversation
…tive on an idle session A goal turn is one full agent turn ending in a judge cycle. `beginTurn` ran before every provider attempt — each tool round-trip, each retry — and charged one turn each time, so the default budget of 20 was gone after a turn that read fifteen files, while every surface still spoke of turns. The counter now moves in `SessionGoal.apply`, once per judged turn and never on a WAIT; `beginTurn` only admits. The judge prompt, the budget reason, the TUI dialog and toasts and the spec all say "turns". Three ways a goal was left active with nothing recorded are closed. A `/goal-budget` or `/goal-resume` landing while the judge decided made the compare in `afterTurn` fail and the turn end silently; the decision is now taken again on the fresh record, and a second loss pauses the goal with a reason. The step-ceiling stop broke out before `afterTurn`, and the stall watchdog cancelled through `state.cancel` without pausing; both now pause the goal with their own reason. Evidence for the judge is scoped to the current turn — everything since the last spent turn (`goal.judged`), so a `read` on turn one no longer stands as evidence for every later claim, while a turn parked on background work and the report that re-enters it stay one turn. Gate results are placed last so the tail cut keeps them. Claude-Session: https://claude.ai/code/session_01KAcoL6wpgVEs2ebUrUGpHS
|
Found 2 test failures on Blacksmith runners: Failures
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Four defects in goal mode (
/goal) left the budget meaningless or the goal stranded:GoalRuntime.beginTurnran before every provider attempt — each tool round-trip and each retry — and charged one turn each time, so the default of 20 was exhausted by a goal that read fifteen files and ran five commands. Meanwhile the goal block said "Turn 3 of 20", the continuation said "turn N of M", the TUI toast said "20 turns" and the budget dialog said "provider attempt budget"./goal-budgetand/goal-resumebumpupdated; if that landed between thefreshread inprompt.tsand the compare inafterTurn,afterTurnreturnedundefined, the loop broke, nothing was recorded and the goal stayedactiveon an idle session.afterTurn; the stall watchdog calledstate.cancelrather thanSessionPrompt.cancel, bypassinggoals.pause.goal.created, so onereadon turn 1 satisfiedevidence.length > 0forever; and[...gates, ...observed].join().slice(-24000)cut gate results first.Fix
turns.usedis incremented inSessionGoal.apply(once per judged turn, never on WAIT);beginTurnis an admission gate only.decidetreats the turn being judged as spending one, so the budget check isused + 1 >= max. The judge prompt,budgetReason, the TUI budget dialog, the resume toast andspecs/goal-modes.mdall say "turns". Default stays 20; runaway protection remains the retry policy, loop guard, step ceiling andMAX_JUDGE_FAILURES.afterTurntakes the decision on a record and writes it only if that record is still stored. On a lost race it re-reads and decides again on the fresh record (a raised budget can turn a stop back into a continue); a second loss logs and pauses the goal with a reason.goals.pausewith their own reason (stopped at the step ceiling after N steps; ...,stalled: no output for ...) before ending the turn.goal.judged, new optional field, falling back togoal.created). Anchoring on the last spent turn rather than the last user message keeps a turn parked on background work and the report that re-enters it as one turn.SessionGoal.evidenceputs gate results last so the tail cut keeps them.Test
Regression tests fail on
mainand pass here (run with the source stashed and the new tests in place, then again after restoring):test/session/goal.test.ts: budget check (used: 2, max: 3is the last turn),applyspends one per judged turn and none on WAIT and stampsjudged,evidencekeeps gate results through truncation — 3 fail before (SessionGoal.evidence is not a function, wrong counts), 20/20 pass after.test/session/prompt.test.ts: retry inside a turn spends nothing (before: retry refused, 1 hit); many tool steps are one judged turn (before: paused after the first attempt); terminal failure spends 0; background WAIT spends 0;/goal-budgetlanding during judgement is recorded against the fresh record (before:active); step ceiling pauses the goal (before:active); stall pauses the goal (before:active); a tool result from an earlier turn is not evidence for a later claim.test/sessiondirectory green;bun run typecheckclean inpackages/redcodeandpackages/tui.Not done:
packages/sdk/openapi.json/types.gen.tsnot regenerated for the optionaljudgedfield (the build script regenerates the whole client; no client reads the field). Spec line about SessionV2 charging per provider attempt left as is — that is a different runtime inpackages/core. The uncommitted.changeset/repair-goal-attempts.mdin the main checkout says retries count against budgets; this PR supersedes that behaviour and does not touch that file.https://claude.ai/code/session_01KAcoL6wpgVEs2ebUrUGpHS
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.