Skip to content

fix: preserve growth baseline when nothingToCompress - #207

Merged
ranxianglei merged 3 commits into
masterfrom
2026-07-27_fix-baseline-reset
Jul 27, 2026
Merged

ranxianglei merged 3 commits into
masterfrom
2026-07-27_fix-baseline-reset

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Problem

When the growth threshold was met (nudgeAllowed=true) but there was nothing to compress (nothingToCompress=true), inject.ts:368 reset lastPerMessageNudgeTokens = currentTokens. This "ate" accumulated growth, creating a feedback loop in short sessions (e.g., subagent sessions):

  1. Context grows → growth ≥ 50K → nudgeAllowed = true
  2. But nothingToCompress = true (all ranges protected by preserveRecentMessages: 20) → shouldInject = false
  3. Baseline reset to current context → growth forgotten
  4. Need another 50K growth before next attempt
  5. Cycle repeats → model NEVER sees a nudge

Observed in ses_0604a0a1affebNnnFMo5KiM7GD: 31 API calls, context grew 719→142K, baseline ended at 132K (near peak).

Fix

Remove lastPerMessageNudgeTokens = currentTokens from the nothingToCompress path. Only clear lastNudgeShownTokens. Growth baseline is preserved so it accumulates until there IS something to compress.

The baseline is still correctly reset via the compress-detection path (inject.ts:108-156) when the model actually compresses.

Files

  • lib/messages/inject/inject.ts — 1 line removed from nothingToCompress block
  • tests/baseline-reset.test.ts — 3 regression tests
  • tests/inject.test.ts — 3 existing tests updated for new behavior
  • devlog/2026-07-27_fix-baseline-reset/ — REQ.md + WORKLOG.md

Test Results

  • 922 tests pass (919 existing + 3 new), 0 failures
  • typecheck ✅, build ✅

ranxianglei and others added 3 commits July 27, 2026 09:13
When growth threshold was met (nudgeAllowed) but all compressible ranges were filtered out (nothingToCompress), the old code reset lastPerMessageNudgeTokens = currentTokens. This ate accumulated growth every cycle, creating a feedback loop in short sessions (e.g., subagents) where the baseline chased the current context and the model never saw a nudge. Fix: remove the baseline reset; growth accumulates until there IS something to compress.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Removed 3 tautological tests from baseline-reset.test.ts (didn't call injectCompressNudges — violated AGENTS.md §5.6). Real regression coverage comes from updated inject.test.ts tests. Added E2E growth scenario to inject.test.ts that verifies: baseline preserved through nothingToCompress turns, growth accumulates, nudge fires when content exits protected zone.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
… test

E2E test now uses 25+ messages in array (not just byRawId) with 200K-char bash outputs so compressible ranges pass the 100K floor. Turn 3 now asserts shouldInjectThisTurn=true (nudge fires when content exits protected zone) AND baseline=200K (growth accumulated from original). Also added baseline assertion to existing 'nudge suppressed when all content is protected' test.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@ranxianglei
ranxianglei merged commit da590e1 into master Jul 27, 2026
5 checks passed
ranxianglei added a commit that referenced this pull request Jul 27, 2026
Bundle two PRs merged to master since v1.14.0:
- PR #205 feat(log): add ACP version to daily logs and context logs
  (tsup define ACP_VERSION from package.json; logger.ts appends
  '| v={version}' to each daily log line and writes a one-shot
  _version file under each per-request context-log directory)
- PR #207 fix: preserve growth baseline when nothingToCompress
  (remove lastPerMessageNudgeTokens=currentTokens reset that ate
  accumulated growth in short sessions / subagents, causing a
  baseline-chases-context feedback loop where nudges never fired)

Release-only: no source changes. Source identical to master @ da590e1.

Files: package.json (1.14.0 -> 1.14.1), README.md, README.zh-CN.md,
devlog/2026-07-27_release-v1.14.1/{REQ,WORKLOG}.md.
ranxianglei added a commit that referenced this pull request Jul 27, 2026
Self-review caught two factual errors in the v1.14.1 changelog entry
(propagated from PR #207's commit message without verification):

1. Claimed '3 tautological tests removed from tests/baseline-reset.test.ts'.
   That file does not exist on master. The actual PR #207 test diff in
   tests/inject.test.ts is 3 tests added + 2 removed (reworked baseline
   tests with assertions flipped advance->preserve, plus 1 new E2E growth
   scenario). Corrected to describe what actually changed.

2. Chinese changelog had typo 'taautological' (extra 'a').

Also dropped the editorial 'tautological' characterization since the
actual change was legitimate assertion rework, not removal of trivial tests.

No source code changes.
ranxianglei added a commit that referenced this pull request Jul 27, 2026
…e verification (#208)

* docs: AGENTS.md §5.7 nudge/growth test requirements + Docker E2E nudge verification

Added mandatory testing requirements after baseline-reset bug (PR #207) survived 900+ tests. §5.7 requires: multi-turn tests, side-effect assertions (lastPerMessageNudgeTokens), production config (preserveRecentMessages>0), Docker E2E nudge state verification. New E2E scenario 06-nudge-growth-compress + verify.ts nudgeBaselineSet/nudgeBaselineNotEquals fields.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(review): address dual-agent review findings on PR #208

Removed scenario 06 (used scripted compress, violated §5.7.2 nudge-triggered requirement). Removed nudgeBaselineNotEquals (dead code, never used). Reworded §5.7.2: nudge-triggered compression noted as future work (fake-llm-server limitation). Removed shouldInjectThisTurn from §5.7.2 (can't verify post-hoc). Fixed 'three' → 'five' gaps. Added scenario 05 to README table.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* feat(e2e): real nudge→compress flow with detectNudge + nudge-compress step type

Fake LLM server now detects ACP nudge injections in messages. When nudge detected, emits compress call (not scripted). Scenario 06 grows context past 20K threshold via repeated text turns, ACP auto-injects nudge, server detects it and compresses. Tests the real nudge→compress flow end-to-end.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(e2e): detectNudge uses nudge-unique phrases + fake LLM reports real input tokens

C1: detectNudge now scans only user-role messages for phrases that ONLY appear in actual nudge injections (efficiency nudge, Context limit reached, since last nudge) — not system prompt documentation markers that are always present. C2: computeInputTokens() calculates prompt_tokens from actual input message sizes so ACP sees realistic token counts for threshold evaluation. H1: §5.7.2 updated to describe detectNudge as implemented capability. H2: fake model config now includes limit.context=100000.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(e2e): fix JSON brace structure + increase growth text to exceed growthFloor

C1: opencode.json had broken brace nesting — models block indentation change caused agent/model/permission to be nested inside provider.fake instead of root. Fixed brace structure. C2: Growth text was ~1K tokens total, far below the 5000-token growthFloor. Increased to ~4K chars per turn (~1K tokens), 3 substantive turns = ~3K tokens growth. Combined with system prompt baseline this should approach the growthFloor threshold.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(e2e): add limit.output to fake model config — all 6 scenarios pass

opencode requires limit.output in model config. Without it, configuration validation fails: 'Missing key provider.fake.models.fake-model.limit.output'. Added output: 4096. All 6 E2E scenarios now pass: 01-basic-compress, 02-quality-reject, 03-quality-acknowledge, 04-batch-compress, 05-subagent-compress, 06-nudge-triggered.

Verified: scenario 06 nudge fires on turn 2 (inputTok=17.6K, growth exceeds growthFloor). blockCount=1, nudgeBaselineSet=true.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
ranxianglei added a commit that referenced this pull request Jul 27, 2026
E2E tests had severe gaps that made them ineffective:
- ALL protection disabled (preserveRecentMessages:0) — the mechanism
  fixed 3 times (PR #210/#211/#212) was never exercised
- CI ran only 4/6 scenarios (05 subagent, 06 nudge missing)
- verify.ts only checked blockCount, not which messages were protected

Changes:
- run-e2e.sh: per-scenario config override via 'acpConfig' JSON field
- verify.ts: compressedCount/min/max checks (byMessageId inspection)
- scenario 07: protection-filtered (production config, compress succeeds
  via soft-filter, protected msgs excluded from compressed set)
- scenario 08: nudge-with-protection (nudge→compress with protection
  enabled — tests exact bug from PR #207/#210/#212)
- ci.yml: all 8 scenarios now in CI

All 8 scenarios verified locally (8/8 pass).
ranxianglei pushed a commit that referenced this pull request Aug 29, 2026
…ted .gitignore change

Post-PR review of #351:
- §5.7.1 required at least one test per PR using preserveRecentMessages > 0;
  all 17 original tests ran on the preserveRecentMessages: 0 default, which
  disables the protected-zone / nothingToCompress path (the #207 bug scenario).
  New 5-turn test: per-model floor suppression → fire → compress → new
  baseline → fire again, plus a fully-protected turn locking the #207
  baseline-reset regression. Mutation-verified (reintroducing the #207 bug
  fails this test).
- Reverted the node_modules/ → node_modules .gitignore edit bundled into
  e14193c (unsubstantiated 'symlink fix'; no tracked symlink exists).

1054/1054 green (1036 baseline + 18 new). Typecheck + build pass.
ranxianglei added a commit to drexb-ops/opencode-acp that referenced this pull request Sep 12, 2026
…DE candidates

- lib/config.ts: CompressConfig.candidates (default false, 3-layer merge)
- inject.ts: candidatesEnabled gates planning/candidateText/nothingToCompress/
  actionHint/breakdown/maxLimit copy — off mode byte-exact master behavior,
  zero planning cost
- prompts: master base restored; candidate guidance split into separate
  CANDIDATE_GUIDANCE constants, appended only when enabled
- status.ts: scope:uncompressed falls back to ranges when disabled;
  config-neutral tool description
- schema + config-validation + CONFIGURATION.md/.zh-CN docs
- tests: switch test file (5 tests, §5.7 growth cycle, preserveRecentMessages=20,
  ranxianglei#207 baseline anti-regression); existing candidate tests opt in explicitly
- 1261/1261 pass
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.

1 participant