fix: break semantic-level compress loops at low temp (#330) - #331
fix: break semantic-level compress loops at low temp (#330)#331ranxianglei wants to merge 1 commit into
Conversation
Follow-up to #308's generic breaker: that breaker stops the TOOL from doing damage but cannot stop the MODEL from generating another ~10K-token repetitive compress turn under a low-temp attractor (99.7%-identical thinking). Adds the input-side counter-signals that actually break the semantic attractor: - messages.ts: drop dangling tool_calls from interrupted (aborted/error) turns so the sent view carries no unmatched tool_use — invalid for OpenAI-compat providers and the hook that drags the model back into re-issuing the call. Keyed off stopReason, not a missing-result scan, so OMP execution roles and evicted/undo fixtures are untouched. - index.ts + runtime.ts: nudge suppression now engages on the FIRST failed/no-op compress attempt this turn (not only at MAX_COMPRESS_ATTEMPTS), and an independent [ACP:compress-loop] user-role stop-signal is injected once in-turn failures reach COMPRESS_LOOP_CORRECT_THRESHOLD (2). Self-clears per turn. - compress-tool.ts: capped-rejection text now points at acp_status explicitly. - system-prompt.ts: document how to interpret [ACP:compress-loop]. Fixes #330
📦 Built Extension ArtifactBranch: Option A — Install from npm PR tag (recommended)pi install npm:billion-context-pi@pr-331Each push to this PR publishes a new version under the Option B — Download artifact
tar xzf billion-context-pi-pr331.tgz
pi install ./packageThis comment is automatically updated on each push. |
[bot] 🏷 PR #331 reviewed — LGTM, ship-ready. I checked out the branch ( Verification (all performed locally against the PR head)
Factor-by-factor audit#1 dangling toolCall after abort — confirmed real, fix is correctly scoped. Two things I verified beyond the diff:
#2 PAUSED text — confirmed: #4 mid-loop nudge injection — confirmed fixed. Traced the counting path end-to-end: Root-cause layering — agreed: #308/#6/#250 are tool-side breakers (limit damage); a low-temp semantic attractor needs input-side counter-signals, and both additions here are input-side. Deferred items (#3 orphan-hiding, P2 tool removal, #4b targetBlocks consistency) are appropriately scoped out. Non-blocking observations
One housekeeping check before merge: the three "follow-ups filed separately" — worth confirming those issues exist so nothing gets lost. Per repo rules I won't merge; ready for human merge once you're satisfied. |
[bot] 🏷 Follow-up on my housekeeping check — the three claimed "filed separately" items did not exist in either repo (verified via API: newest bcp item was #331/#330; newest acp-kernel item was #219). I've filed them now, each with a
Nothing else outstanding from my side — review stands as posted (LGTM), merge is yours. |
Summary
Follow-up to #308 — that generic breaker correctly counted failures and paused the nudge, but the loop in session
01a07b3cstill ran to manual abort because the existing breakers stop the tool from doing damage, not the model from generating another ~10K-token repetitive compress turn under a low-temp attractor (99.7%-identical thinking across 5 rounds). This PR adds the input-side counter-signals that actually break the semantic attractor.Triage (verified against v0.1.59 / acp-kernel 0.0.56)
tool_use(invalid for OpenAI-compatible providers, and the hook that drags the model back into re-issuing it)cappedRejectionTextalready embeds the correct remaining ranges + snapshot; only missing an explicitacp_statuspointerMAX_COMPRESS_ATTEMPTScap, so it kept injecting at failure 1–2Root-cause layering: #308/#6/#250 are tool-side breakers; a semantic-level attractor needs an input-side counter-signal. This is the "breaker fired but the loop continued" successor form of #308.
Changes
src/messages.ts— drop danglingtool_callsfrom interrupted turns (stopReason ∈ {aborted, error}) so the sent view carries no unmatchedtool_use. Keyed offstopReason, not a missing-result scan, so OMP execution roles and evicted/undo fixtures are untouched.src/index.ts+src/runtime.ts— nudge suppression now engages on the first failed/no-op compress attempt this turn (not only at the cap); an independent[ACP:compress-loop]user-role stop-signal is injected once in-turn failures reachCOMPRESS_LOOP_CORRECT_THRESHOLD(2). Both self-clear per user turn.src/compress-tool.ts— capped-rejection text now explicitly points atacp_status.src/system-prompt.ts— documents how to interpret[ACP:compress-loop].Tests
+10 new assertions across 4 files: dangling-toolCall drop (incl. the deliberate scope guard proving we key off
stopReason, not result-presence),compress-loopunit (sentinel/threshold/text), thecompressFailCountForcounting contract, and end-to-end correction injection + self-clear through the real context handler. Full suite 633 pass / 0 fail, typecheck clean, build OK.Not addressed here (follow-ups filed separately)
compressfrom the tools list mid-session after repeated same-arg failures (feasibility TBD against Pi's dynamic tools API).KEEP_LAST_ORPHANED) swallowing the actionable rejection diagnostics.targetBlocksvsacp_statusblock-list consistency (suspected acp-kernel side).Model-side mitigation (temp 0.1 → 1.0) was applied separately by the owner on 09-08.
Fixes #330