Problem
A goal/operate run is documented as unbounded by default: [goal] max_continuations defaults to 0 = unlimited, and the only terminal stops are completion, blocked, or the opt-in continuation backstop (goal_loop.rs: "Goals are unlimited by default: completion, blocked status, or explicit user control ends the run", DEFAULT_MAX_GOAL_CONTINUATIONS = 0; docs/CONFIGURATION.md "Goal loop").
But a single goal pass is still one turn, and every turn is capped by the interactive [tui].max_model_steps (default 200 — the R1 runaway guard). The turn loop itself says the intra-turn goal-continuation hook is "bounded by the step budget" (turn_loop.rs ~4912). So when a model enters a long tool loop inside one pass and does not close the turn, that pass is cut at 200 model steps with Maximum model steps reached before completion (limit: N) and the turn ends Failed — even though the goal is meant to run until done.
Result: an autonomous goal run is simultaneously unbounded (no pass/step cap at the goal level by default) and arbitrarily chopped mid-pass at the interactive ceiling. The knob designed to catch an interactive runaway loop is what stops an intentional autonomous run.
Current behavior
- Goal run started (operate mode); the model works a long tool loop inside one pass without emitting a tool-less closing answer → the turn never closes →
max_steps (200) fires mid-pass → TurnOutcomeStatus::Failed with Maximum model steps reached before completion (limit: 200). Work done up to that point stays in the transcript, but the run is over.
- Goal-level budgets already exist but do not gate:
GoalBudget { token_budget, time_budget_seconds, max_continuations } (goal_loop.rs), and "token/time accounting stays visible as telemetry but does not gate continuation".
[goal] max_continuations counts passes, not steps, and defaults off (0).
- The only way to give an autonomous run more room today is to raise
[tui].max_model_steps globally — which loosens the interactive runaway guard for everyone, including non-goal turns that a human is watching.
Desired behavior (options — maintainer's choice)
Give goal runs a step budget of their own, independent of the interactive max_steps:
- Option A —
[goal] max_steps: a model-step ceiling scoped to goal runs (total across the run, or per pass), separate from [tui].max_model_steps. Default could stay generous/unbounded at the goal layer (matching the goal "unlimited" contract) or inherit a large finite default — but it must not be the interactive 200.
- Option B — surface the governing budget: wherever the 80% soft-landing / exhaustion notice is shown, name which budget governs (interactive
max_steps vs goal budget), so "200" is not ambiguous.
- Either way, exhaustion of a goal step budget should end the run with the existing honesty contract (soft landing at ~80%, one bounded final-report turn) rather than a bare
Failed that looks like a crash.
The goal layer already owns a natural home for this: GoalBudget in goal_loop.rs gains a step dimension alongside token_budget / time_budget_seconds / max_continuations.
Motivation
Autonomous/operate runs (including overnight or long unattended work) are an intended use of the goal loop. Today a long single pass inside such a run dies at the interactive guard, which the operator did not expect to govern autonomous work — the goal documentation says "unlimited by default". Splitting the budgets keeps the interactive runaway protection intact (default 200 untouched for watched turns) while letting autonomous runs have their own honest, visible limit.
Acceptance criteria
- A goal run that would previously die at 200 model steps inside one pass now runs under its own goal step budget and ends with the soft-landing/final-report honesty contract, not a bare
Failed.
- Interactive
[tui].max_model_steps default stays 200 for non-goal turns (guard unchanged).
- The effective governing budget is visible/disclosed where the exhaustion notice is shown (Option B).
docs/CONFIGURATION.md goal section and config.example.toml document the new key.
Environment
- codewhale 0.9.12 (dev), Linux, tmux. Reproduced in an operate-mode goal run.
Problem
A goal/operate run is documented as unbounded by default:
[goal] max_continuationsdefaults to0= unlimited, and the only terminal stops are completion, blocked, or the opt-in continuation backstop (goal_loop.rs: "Goals are unlimited by default: completion, blocked status, or explicit user control ends the run",DEFAULT_MAX_GOAL_CONTINUATIONS = 0;docs/CONFIGURATION.md"Goal loop").But a single goal pass is still one turn, and every turn is capped by the interactive
[tui].max_model_steps(default 200 — the R1 runaway guard). The turn loop itself says the intra-turn goal-continuation hook is "bounded by the step budget" (turn_loop.rs~4912). So when a model enters a long tool loop inside one pass and does not close the turn, that pass is cut at 200 model steps withMaximum model steps reached before completion (limit: N)and the turn endsFailed— even though the goal is meant to run until done.Result: an autonomous goal run is simultaneously unbounded (no pass/step cap at the goal level by default) and arbitrarily chopped mid-pass at the interactive ceiling. The knob designed to catch an interactive runaway loop is what stops an intentional autonomous run.
Current behavior
max_steps(200) fires mid-pass →TurnOutcomeStatus::FailedwithMaximum model steps reached before completion (limit: 200). Work done up to that point stays in the transcript, but the run is over.GoalBudget { token_budget, time_budget_seconds, max_continuations }(goal_loop.rs), and "token/time accounting stays visible as telemetry but does not gate continuation".[goal] max_continuationscounts passes, not steps, and defaults off (0).[tui].max_model_stepsglobally — which loosens the interactive runaway guard for everyone, including non-goal turns that a human is watching.Desired behavior (options — maintainer's choice)
Give goal runs a step budget of their own, independent of the interactive
max_steps:[goal] max_steps: a model-step ceiling scoped to goal runs (total across the run, or per pass), separate from[tui].max_model_steps. Default could stay generous/unbounded at the goal layer (matching the goal "unlimited" contract) or inherit a large finite default — but it must not be the interactive 200.max_stepsvs goal budget), so "200" is not ambiguous.Failedthat looks like a crash.The goal layer already owns a natural home for this:
GoalBudgetingoal_loop.rsgains a step dimension alongsidetoken_budget/time_budget_seconds/max_continuations.Motivation
Autonomous/operate runs (including overnight or long unattended work) are an intended use of the goal loop. Today a long single pass inside such a run dies at the interactive guard, which the operator did not expect to govern autonomous work — the goal documentation says "unlimited by default". Splitting the budgets keeps the interactive runaway protection intact (default 200 untouched for watched turns) while letting autonomous runs have their own honest, visible limit.
Acceptance criteria
Failed.[tui].max_model_stepsdefault stays 200 for non-goal turns (guard unchanged).docs/CONFIGURATION.mdgoal section andconfig.example.tomldocument the new key.Environment