Description
#5508 (commit 7eb4650b0a67, "feat(tui): add cancellable goal
continuation cadence") added [goal] continuation_delay_seconds and a
cancellable quiet period, but wired the wait into only one of the two
goal-continuation dispatch paths:
- The cross-turn scheduler (
schedule_goal_continuation in
crates/tui/src/core/engine.rs, the ready_at quiet period) — used
when the interactive engine re-dispatches after a completed turn, and
gated on if !self.host_managed_turns().
- The within-turn dispatch hook (
goal_continuation_message_if_needed
in crates/tui/src/core/engine/turn_loop.rs) — which has no wait at
all. Whenever a model step ends with the goal still active it
immediately emits the next continuation prompt.
Consequences:
- Within-turn continuation passes never honor the quiet period, on
any session: up to per_turn_max passes fire back-to-back at model
latency.
- Host-managed sessions never reach the cross-turn scheduler at all
(the engine yields so the host can create the next durable claim), so
for those sessions the quiet period is effectively dead — every pass
is dispatched through the un-waited hook.
Steps to reproduce
- Set the quiet period, e.g.
[goal] continuation_delay_seconds = 300.
- Start a session with an active goal, or resume one via
codewhale resume --last (a non-host-managed CLI resume).
- Watch the continuation passes: they fire at model-latency intervals
(~9 s observed), never 300 s apart.
Expected behavior
Consecutive goal-continuation passes are separated by the configured quiet
period (cancellable with Esc), on every dispatch path.
Actual behavior
Passes fire instantly through the within-turn dispatch hook; the quiet
period applies only to the cross-turn scheduler path, which is not the
path these sessions dispatch through.
Impact
Every supervised/unattended run that relies on continuation_delay_seconds
to pace autonomous goal work (cost control, provider rate limits,
checkpoint cadence) gets instant-fire passes instead of the configured
cadence. The bypass was measured live in both dispatch modes — host-
injected continuation tokens dispatching immediately (~9 s end→start
gaps), and instant-fire passes on a CLI-resumed session.
Environment
- OS: Linux
- codewhale version: 0.9.10
- Install method: release binary (also reproduced on a source build
tracking v0.9.10)
- Model/provider: Deepseek/any
- Shell: zsh
Logs, screenshots, or recordings
Live measurements against a supervised deployment tracking v0.9.10:
- Before: host-injected continuation tokens dispatched immediately,
~9 s end→start gaps; a CLI-resumed session fired passes instantly.
- After applying the fix: two consecutive quiet→pass gaps of ~331 s
and ~323 s, with zero user input in each window. The bypass is gone.
The fix in the accompanying PR applies the same cancellable quiet period
at the within-turn dispatch site, unconditionally (the hook is the
only dispatch site for non-host-managed sessions, so gating the wait on
host-managed turns does not work — a CLI-resumed session would still fall
into the no-wait branch). Semantics preserved: the wait stays cancellable
(the Esc cancel token wins biased over the timer), a pause/clear or a
terminal update_goal observed after the wait cancels the pending pass
before anything is recorded or dispatched, and the
GoalContinuationWaiting / GoalContinuationWaitEnded events are
unchanged. New regression tests cover the non-host-managed wait, the
host-managed wait, and the zero-delay-immediate case.
Description
#5508 (commit
7eb4650b0a67, "feat(tui): add cancellable goalcontinuation cadence") added
[goal] continuation_delay_secondsand acancellable quiet period, but wired the wait into only one of the two
goal-continuation dispatch paths:
schedule_goal_continuationincrates/tui/src/core/engine.rs, theready_atquiet period) — usedwhen the interactive engine re-dispatches after a completed turn, and
gated on
if !self.host_managed_turns().goal_continuation_message_if_neededin
crates/tui/src/core/engine/turn_loop.rs) — which has no wait atall. Whenever a model step ends with the goal still active it
immediately emits the next continuation prompt.
Consequences:
any session: up to
per_turn_maxpasses fire back-to-back at modellatency.
(the engine yields so the host can create the next durable claim), so
for those sessions the quiet period is effectively dead — every pass
is dispatched through the un-waited hook.
Steps to reproduce
[goal] continuation_delay_seconds = 300.codewhale resume --last(a non-host-managed CLI resume).(~9 s observed), never 300 s apart.
Expected behavior
Consecutive goal-continuation passes are separated by the configured quiet
period (cancellable with Esc), on every dispatch path.
Actual behavior
Passes fire instantly through the within-turn dispatch hook; the quiet
period applies only to the cross-turn scheduler path, which is not the
path these sessions dispatch through.
Impact
Every supervised/unattended run that relies on
continuation_delay_secondsto pace autonomous goal work (cost control, provider rate limits,
checkpoint cadence) gets instant-fire passes instead of the configured
cadence. The bypass was measured live in both dispatch modes — host-
injected continuation tokens dispatching immediately (~9 s end→start
gaps), and instant-fire passes on a CLI-resumed session.
Environment
tracking v0.9.10)
Logs, screenshots, or recordings
Live measurements against a supervised deployment tracking v0.9.10:
~9 s end→start gaps; a CLI-resumed session fired passes instantly.
and ~323 s, with zero user input in each window. The bypass is gone.
The fix in the accompanying PR applies the same cancellable quiet period
at the within-turn dispatch site, unconditionally (the hook is the
only dispatch site for non-host-managed sessions, so gating the wait on
host-managed turns does not work — a CLI-resumed session would still fall
into the no-wait branch). Semantics preserved: the wait stays cancellable
(the Esc cancel token wins biased over the timer), a pause/clear or a
terminal
update_goalobserved after the wait cancels the pending passbefore anything is recorded or dispatched, and the
GoalContinuationWaiting/GoalContinuationWaitEndedevents areunchanged. New regression tests cover the non-host-managed wait, the
host-managed wait, and the zero-delay-immediate case.