Skip to content

Fix:goal continuation cadence fix - part a - #5591

Merged
Hmbown merged 9 commits into
Hmbown:mainfrom
M-Maciej:pr/goal-continuation-cadence-fix
Aug 24, 2026
Merged

Fix:goal continuation cadence fix - part a#5591
Hmbown merged 9 commits into
Hmbown:mainfrom
M-Maciej:pr/goal-continuation-cadence-fix

Conversation

@M-Maciej

@M-Maciej M-Maciej commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

[goal] continuation_delay_seconds (the goal-continuation cadence, added in
#5508) was wired into only one of the two goal-continuation dispatch paths.
The within-turn dispatch hook (goal_continuation_message_if_needed in the
engine) had no wait at all, so a model step that ended with the goal still
active immediately emitted the next continuation prompt. That made the
configured cadence dead on two kinds of sessions:

  • CLI-resumed sessions (codewhale resume --last) — they dispatch
    continuation passes through the within-turn hook.
  • Host-managed sessions — the engine yields so the host can create the
    next durable claim, so these sessions never reach the cross-turn scheduler;
    every pass goes through the un-waited hook.

Measured (before split) live with a 300 s delay configured: continuation passes fired at
model latency before the fix (~9 s gaps, instant-fire through the within-turn
hook). After the fix, consecutive passes are separated by the configured
quiet window — the observed cadence became ~5 minutes (300 s quiet window
plus the turn itself), versus ~9 s instant-fire before.

This branch applies the wait on the within-turn path, unconditionally:

  • goal_continuation_message_if_needed now awaits
    goal_loop::await_continuation_wait before dispatching (biased cancel:
    Esc / steer / host cancel / terminal update_goal all win over a racing
    expiry, and the live goal is re-read only after the wait).
  • Host-injected continuation tokens get the same wait in the Op::ContinueGoal
    arm — for host-managed sessions this arm is their only dispatch site.
  • The wait stays cancellable, and pause/clear/terminal updates still cancel a
    pending pass.

The flake-hardening test fixes (env-barrier isolation for the route-budget V4
trigger and the prompt prefix-leak tests, SSH-marker neutralization in the
terminal-motion suite, RUSTUP_HOME preservation in the read-only diagnostic
test) are grouped here per the #5535 review guidance (the cadence fix plus the
flake hardening as one small slice).

Rebase note

Rebased onto current main (v0.9.11, merged in #5542). v0.9.11 rewrote
core/engine/turn_loop.rs and the engine event loop; the fix has been
re-verified against the new loop by the regression tests below.

Split provenance

This PR is one slice of a mechanical split: the original combined branch was
rebased onto v0.9.11 and then partitioned along commit boundaries, then each
slice was re-verified independently with the full gate suite. The combined
rebased tree (the union of all slices) also passes the full suite
(13,441 / 0). Partition boundaries worth review attention: the engine
dispatch paths shared with the outbox emit-site work, and the Windows
cfg-gating.

Split map and merge order

The four pieces of the original combined branch are filed as four PRs. All
four are standalone — each is cut from current main and passes its full
gate suite alone:

One coupling no slice escapes: all four add entries to the same
## [Unreleased] changelog section, so each merge is followed by a small
changelog rebase for the PRs still open. Independently reviewable and
buildable — yes; mergeable with zero follow-up — no.

Testing

<!-- Exact gate commands (including the clippy allow list) are in
CONTRIBUTING.md → "Pre-push verification". -->

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features --locked (warning-free under the CI allow list)
  • cargo test --workspace --all-features --locked

Results: clippy clean on stable 1.98.0 under the CONTRIBUTING.md allow
list; full suite 13,375 passed / 0 failed. Focused goal_continuation
suite 13/13 (host-managed sessions wait, non-host-managed sessions wait,
zero-delay dispatch is immediate, cancellation paths).

Checklist

  • Updated docs or comments as needed
  • Added or updated tests where relevant
  • Verified TUI behavior manually if UI changes — the cadence was
    exercised live on running sessions ( yes before split, not after split )
  • Harvested/co-authored credit uses a GitHub numeric noreply address —
    every commit is authored under
    130112810+M-Maciej@users.noreply.github.com
  • Every commit carries a Signed-off-by (DCO)
  • Changelog entry under Unreleased (and the flake-hardening entry
    moved out of the released 0.9.11 section, where an earlier rebase had
    placed it)
  • No dead-code-budget change
  • The outbox, /relaunch, and control-socket slices from Supervised operation stack: lifecycle outbox, /relaunch, per-session control socket, and the goal-continuation quiet-period fix #5535 follow
    as separate PRs after this one lands

Closes #5534

Host-managed engines have no cross-turn scheduler, so the configured
[goal] continuation_delay_seconds quiet period was never applied: the
intra-turn continuation dispatch in turn_loop.rs continued immediately.

- goal_loop.rs: shared continuation_wait(delay) computation (None =
  immediate, capped at MAX_GOAL_CONTINUATION_DELAY_SECONDS) plus
  await_continuation_wait with biased cancellation — the cancel token
  always wins over a racing expiry (same semantics as Hmbown#5508).
- turn_loop.rs: goal_continuation_message_if_needed now decides before
  spending the quiet period, awaits the cancellable wait for
  host-managed turns only, and re-decides on the live goal state after
  the wait so pause/clear or a terminal update_goal cancels the pending
  pass. Failed or interrupted turns never continue. Interactive engines
  keep their existing cross-turn cadence untouched.

Verified: cargo test -p codewhale-tui goal_loop (15/15, incl. new
delay>0, zero-delay-immediate, cancellation-wins tests), 454 goal-named
engine/tool/UI tests, cargo clippy -p codewhale-tui --lib, cargo fmt.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
The v1 fix awaited the quiet period inside the intra-turn ladder
(turn_loop.rs), which is not the dispatch path for host-managed
sessions: the engine never calls schedule_goal_continuation there
(gated on !host_managed_turns), and the host injects Op::ContinueGoal
(engine_schedule_id None) into the engine mailbox instead. The run()
arm for that token dispatched immediately with no delay.

Now the Op::ContinueGoal arm awaits the shared cancellable quiet
period for host-injected tokens before dispatching: biased
cancellation (Esc/steer/host cancel) always wins over a racing
expiry, and the live goal is re-read only after the wait so
pause/clear/complete/blocked cancels the pass and failures never
continue. Engine-owned tokens (engine_schedule_id Some) keep the
scheduler's ready_at semantics untouched.

Tests: host-injected ContinueGoal with delay>0 waits out the quiet
period before the provider request, delay=0 dispatches immediately,
and cancellation during the wait never dispatches.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
…essions

goal_continuation_message_if_needed gated the between-continuation quiet
period on host_managed_turns(); the else-None branch assumed the
cross-turn scheduler covered non-host-managed sessions, so a session
resumed via `codewhale resume --last` (runtime_services.active_thread_id
None) dispatched goal continuation instantly. This within-turn hook is
the only dispatch site, so the wait is now unconditional:
continuation_wait(goal_continuation_delay_seconds) for every session,
keeping the biased-cancellable await, the post-wait re-decision on live
state, and the GoalContinuationWaiting/WaitEnded events unchanged.

Tests (turn_loop): a non-host-managed engine with a positive delay emits
the wait receipt, does not dispatch before the quiet period elapses, and
dispatches (one recorded continuation) afterwards; a zero delay still
continues immediately with no wait receipt; a host-managed engine keeps
the same wait contract.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
Production intentionally caps terminal motion over SSH (in_ssh_session ->
low_motion), and four tests assert the un-capped state without neutralizing
SSH_CLIENT/SSH_TTY — deterministic failures whenever the suite runs over
SSH (the normal test-bed pattern). Each test now removes SSH_CLIENT,
SSH_CONNECTION, and SSH_TTY via EnvVarGuard under the env lock, restoring
them on drop.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
On hosts with a system-wide rustup (cargo/rustc are rustup proxies), the
env-cleared doctor probe defaults RUSTUP_HOME to the sealed fixture HOME
and materializes toolchain state there, failing the read-only assertion.
Mirror the TUI integration test's preserve_host_rustup_home guard.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
… for V4 trigger and prompt prefix-leak tests

route_budget::tests::v4_trigger_uses_window_percent_when_it_fits_spendable_input
asserts no-override output-budget values but read the process-global
CODEWHALE_MAX_OUTPUT_TOKENS/DEEPSEEK_MAX_OUTPUT_TOKENS without holding
lock_test_env(), so a concurrent sibling writer could flip the value
mid-assertion. The test now holds the barrier and removes both overrides,
matching every sibling test with the same dependency; assertions unchanged.

Full-suite verification under --test-threads=1 exposed the same class of bug
in prompts::tests::system_prompt_prefix_never_leaks_private_content: it read
the real home via HOME/USERPROFILE, so ~/.codewhale/instructions.md leaked its
absolute path into the prompt and failed the no-private-paths assertion unless
a sibling's temporary HOME guard happened to be live. It now holds the barrier
and pins HOME/USERPROFILE to a scratch dir. Assertions unchanged.

Verified: 4 consecutive full cargo test -p codewhale-tui --lib runs green
(10891 passed / 0 failed / 13 ignored each; 2 x default threads, 2 x
--test-threads=1); fmt clean; clippy clean.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
…mbown#5534) and the flake hardening

Move the flake-hardening entry out of the released 0.9.11 section into
Unreleased (0.9.11 already shipped without it) and add the Hmbown#5534 entry.
Regenerated crates/tui/CHANGELOG.md via scripts/sync-changelog.sh.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
docs/changelog/changelog-0076.md is an ecosystem-internal per-session
build log (session-numbered convention); upstream keeps its changelog in
the root CHANGELOG.md, where the flake-hardening entry already lives.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
@M-Maciej
M-Maciej requested a review from Hmbown as a code owner August 24, 2026 00:35
@github-actions

Copy link
Copy Markdown
Contributor

Thanks @M-Maciej for taking the time to contribute.

This repository is observing a maintainer-managed PR intake gate in dry-run mode, so this pull request is staying open. This note helps maintainers prepare the allowlist before any enforcement is considered.

Please read CONTRIBUTING.md for the expected contribution shape. A maintainer can grant recurring PR access by commenting /lgtm on a pull request.

@Hmbown Hmbown left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and independently verified locally on your branch:

  1. The core design is right. Decision split from dispatch (goal_continuation_allowed), decide-before-wait so a terminal goal never spends the quiet period, biased-cancel wait (Esc/steer/host cancel win over a racing expiry), and — the part I'd call load-bearing — the re-decide on live state after the wait, so a /goal pause, /goal clear, or terminal update_goal landing during the quiet period cancels the pass instead of dispatching a provider request. The Op::ContinueGoal arm correctly gates only host-injected tokens (engine_schedule_id.is_none()); engine-owned tokens keep their scheduler semantics.

  2. Ran the focused suite on your branch: goal_continuation 12/12 (host-managed waits, non-host-managed waits, zero-delay immediate, cancellation paths).

  3. Ran the repo's single-turn-loop guard (crates/core/tests/single_turn_loop.rs): 2/2 — the turn_loop.rs changes stay inside the one loop.

  4. Clippy on codewhale-tui --all-targets on your branch: 0 warnings under the CI allow list.

  5. Merge interaction with the 0.9.12 integration branch (#5576): git merge-tree --write-tree between it and this branch exits clean — we also modified turn_loop.rs (compaction decisions, step budget), but in different regions; no conflicts expected either merge order.

The split provenance note and the honest "live-verified before split, not after" checkbox are exactly the review shape we wanted out of #5535 — thank you. Holding the merge for the maintainer's go and for the remaining CI checks to finish.

Refresh the goal-continuation cadence branch on current main. Preserve all eight contributor commits and their authorship; resolve only the expected synchronized Unreleased changelog overlap.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>

@Hmbown Hmbown left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approved at exact head 0903bf6 after the current-main refresh.

Verified locally: all eight M-Maciej commits and their authorship are unchanged; only the expected synchronized Unreleased changelog overlap was resolved. Formatting is clean; workspace/all-targets clippy is clean; goal_continuation passes 12/12; the single-turn-loop guard passes 2/2; the contribution-credit checker passes 11/11 plus the live 8/8 range check. Fresh GitHub CI is green across required Linux, Windows, macOS, lint, and version lanes, with safety, OpenHarmony, DCO, credit/metadata, and advisory checks also green. The PR now closes the exact bug issue #5534.

@Hmbown
Hmbown merged commit c216471 into Hmbown:main Aug 24, 2026
21 of 22 checks passed
@Hmbown Hmbown mentioned this pull request Aug 24, 2026
9 tasks
Hmbown pushed a commit that referenced this pull request Aug 24, 2026
Integrate PR #5591 and current main release/CI documentation changes. Adapt the host-managed continuation regression fixture to the release branch's max_steps=0 unbounded contract by using a deterministic scripted model client instead of a dummy loopback endpoint.

Entire-Checkpoint: 01M0S73RG3JN20J2V8SS9F34GF
Hmbown pushed a commit that referenced this pull request Aug 29, 2026
…ntities

Twenty external contributions landed after the v0.9.11 band closed and none
were credited: the newest band in docs/CONTRIBUTORS.md was still v0.9.11 while
main shipped 0.9.12 work. Adds the v0.9.12 band covering all twenty (#5591,
#5599, #5604, #5610, #5613, #5621-#5624, #5629, #5650, #5657, #5669, #5682,
#5683, #5685, #5687, #5688, #5689, #5692) and demotes v0.9.11 so only the
newest band stays expanded.

AUTHOR_MAP gains numeric-noreply entries for musichen, M-Maciej, and serephus
so future harvested credit is graph-mappable, plus a comment recording that
wangfengcsu@qq.com (21 commits on main via #704) has no resolved login yet.

AUTHOR_MAP is a project convention consumed by scripts/check-coauthor-trailers.py
for new commits; GitHub does not read it, so it grants no retroactive
contribution-graph credit for history already on main.

No-Issue: contributor credit hygiene; no issue tracks it
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Hmbown pushed a commit that referenced this pull request Aug 29, 2026
The contribution gate leaves an unlisted contributor's workflow runs at
`action_required`, so their CI never starts and the PR sits looking stalled
through no fault of theirs. whp233's #5714 and #5716 had SEVEN workflows each
parked that way; I approved those runs by hand, but the allowlist is the actual
fix.

Added (all five have landed or open work):
  whp233    open PRs #5714, #5716 — the runs that were parked
  musichen  merged #5689 (DeepSeek configured-view picker)
  M-Maciej  merged #5591 (goal continuation cadence)
  serephus  merged #5669 (nixpkgs update)
  Pinvou    fork owner behind #5686 (Moonshot/Kimi native search)

Entries use `all:` to match the existing convention for active contributors.

No-Issue: contribution-gate hygiene; no issue tracks it
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Goal-continuation cadence is bypassed on the within-turn dispatch path

2 participants