Background
Two defect classes surfaced in the omp adapter (users on weak local models — vllm/qwen3.8-27b — and long agentic turns) were fixed adapter-side. Both are host-agnostic model-behavior problems; the pi adapter lacks the equivalent fixes and every adapter reimplementing them is drift. This issue tracks moving both into acp-kernel so all adapters (omp / pi / opencode) inherit them by bumping the kernel pin.
1. Mid-band nudge cadence (omp PR #45, issue #22)
Defect: the kernel's over-limit branch (usage >= maxContextLimitPct, the 75–95% pressure band from #70) applies NO growth cadence — shouldInject re-fires on every processTurn. In an agentic turn that fires dozens of LLM calls back-to-back, the nudge is injected repeatedly (observed: twice in a row within seconds at ~21% real usage; omp issue #22).
omp-side fix (adapter patch): at the injection point, re-apply the kernel's own growth floor — suppress unless the sent view grew >= growthFloor since the last nudge the model actually saw, and ROLL BACK the stamps (lastNudgeShownTokens / lastShownByTier) that processTurn already wrote for the suppressed event. Emergency (>= emergencyThresholdPct) stays unguarded by design (#70's load-bearing choice — the overflow reminder must keep firing on every call).
Kernel-side proposal: apply the same per-tier growth floor inside the over-limit branch of processTurn, so the stamps stay truthful (no adapter rollback needed). Care needed not to regress #70's arbitration: pressure-band argmax selects the tier with max effective pending; suppression must only trigger when bestPending exists but growth since last shown is below the floor — never suppress the emergency tier when usage >= emergencyThresholdPct.
2. Compress rejection escalation (omp PR #48, issue #47)
Defect: rejection feedback is a flat, identical error every time. Weak models treat it as a fresh prompt and re-issue doomed compress calls — observed 33 rejections over 48 minutes ("Total compressible content too small (95 chars, min 5000)"). The error text itself ("Combine more messages into your range(s)") reads as a retry invitation; the model obeys it and loops. Physical delivery was verified (the rejection reaches the model every turn) — this is a behavior problem, not a visibility problem.
omp-side fix: per-session rejectStreak on the runtime slot — streak >= 3 appends an explicit STOP directive to the rejection, streak >= 4 replaces the kernel detail with a short stop message (the detail itself proved to be loop fuel). Success resets to 0. omp additionally carries the streak across view-flip re-folds (its session-level bookkeeping, fold-architecture-specific — stays adapter-side).
Kernel-side proposal: add compressRejects: number to CompressionState (next to state.nudge) and export a helper, e.g. rejectionFeedback(state, baseText): string, that adapters call when formatting rejections. Success paths (applyCompression with no errors) reset it. Adapters keep ownership of WHERE the streak lives across their state lifecycle (pi sidecar: free; omp fold slot: preserved on re-fold).
Why now
- pi (0.1.38) lacks both fixes; its users are exposed to both defect classes.
- omp carries both as adapter patches that become dead weight once the kernel owns the semantics.
- opencode will need the same when it catches up on kernel pins.
Acceptance (suggested)
Downstream refs: omp #45, #48, #50 (context), issues #22/#47; pi #150 (related scale fix, already shipped).
Background
Two defect classes surfaced in the omp adapter (users on weak local models — vllm/qwen3.8-27b — and long agentic turns) were fixed adapter-side. Both are host-agnostic model-behavior problems; the pi adapter lacks the equivalent fixes and every adapter reimplementing them is drift. This issue tracks moving both into acp-kernel so all adapters (omp / pi / opencode) inherit them by bumping the kernel pin.
1. Mid-band nudge cadence (omp PR #45, issue #22)
Defect: the kernel's over-limit branch (
usage >= maxContextLimitPct, the 75–95% pressure band from #70) applies NO growth cadence —shouldInjectre-fires on everyprocessTurn. In an agentic turn that fires dozens of LLM calls back-to-back, the nudge is injected repeatedly (observed: twice in a row within seconds at ~21% real usage; omp issue #22).omp-side fix (adapter patch): at the injection point, re-apply the kernel's own growth floor — suppress unless the sent view grew
>= growthFloorsince the last nudge the model actually saw, and ROLL BACK the stamps (lastNudgeShownTokens/lastShownByTier) thatprocessTurnalready wrote for the suppressed event. Emergency (>= emergencyThresholdPct) stays unguarded by design (#70's load-bearing choice — the overflow reminder must keep firing on every call).Kernel-side proposal: apply the same per-tier growth floor inside the over-limit branch of
processTurn, so the stamps stay truthful (no adapter rollback needed). Care needed not to regress #70's arbitration: pressure-band argmax selects the tier with max effective pending; suppression must only trigger whenbestPendingexists but growth since last shown is below the floor — never suppress the emergency tier when usage >= emergencyThresholdPct.2. Compress rejection escalation (omp PR #48, issue #47)
Defect: rejection feedback is a flat, identical error every time. Weak models treat it as a fresh prompt and re-issue doomed compress calls — observed 33 rejections over 48 minutes ("Total compressible content too small (95 chars, min 5000)"). The error text itself ("Combine more messages into your range(s)") reads as a retry invitation; the model obeys it and loops. Physical delivery was verified (the rejection reaches the model every turn) — this is a behavior problem, not a visibility problem.
omp-side fix: per-session
rejectStreakon the runtime slot — streak >= 3 appends an explicit STOP directive to the rejection, streak >= 4 replaces the kernel detail with a short stop message (the detail itself proved to be loop fuel). Success resets to 0. omp additionally carries the streak across view-flip re-folds (its session-level bookkeeping, fold-architecture-specific — stays adapter-side).Kernel-side proposal: add
compressRejects: numbertoCompressionState(next tostate.nudge) and export a helper, e.g.rejectionFeedback(state, baseText): string, that adapters call when formatting rejections. Success paths (applyCompressionwith no errors) reset it. Adapters keep ownership of WHERE the streak lives across their state lifecycle (pi sidecar: free; omp fold slot: preserved on re-fold).Why now
Acceptance (suggested)
Downstream refs: omp #45, #48, #50 (context), issues #22/#47; pi #150 (related scale fix, already shipped).