Skip to content

fix: stale-ref gate messages report true causes with diagnostics - #175

Merged
ranxianglei merged 1 commit into
masterfrom
2026-08-31_stale-ref-gate-truth
Aug 31, 2026
Merged

ranxianglei merged 1 commit into
masterfrom
2026-08-31_stale-ref-gate-truth

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

What

Fixes the factually-wrong batch-gate error from ranxianglei/billion-context#387: your refs are stale: a prior compress renumbered the remaining refs. The kernel has no ref-renumbering mechanism — refs are additive per-session snapshots (assignRefs never deletes; applyCompression/prune only change the message view; rebuildRefIndex rebuilds the reverse map from the forward one). The fabricated history made models self-doubt on a session's first compress (0 blocks, 0 compression history).

Changes (src/compress.ts only + tests)

  • All-unknown branch (compress.ts gate, ex-line 274): now states refs are per-session snapshots, assigned once at first render, and that no compress reassigns them — so unknown refs cannot come from an earlier compress in this session. Names the real causes: a previous session instance (switching model/upstream mid-conversation starts a fresh session whose refs restart at m00001), the generation before a native-compaction rebase (also resets to m00001), or a typo.
  • Consumed branch (ex-line 276): splits into two truthful sub-cases —
    • dangling refs (ref exists in the map but its message is gone from the view and no active block covers it — content drift / filtered message): cannot be anchored … leaving your old refs dangling
    • covered by active block(s) / stale block ref: already compressed … covered by active block(s) or the block ref(s) are stale (distilled or consumed) + existing liveHint
  • Diagnostics field on both stale branches (per the issue's ask): [diagnostics: session highest ref=mNNNNN|none, unknown ranges in request=U/R, session history=N compression(s), M block(s)] — lets the model tell "fresh generation restart" (high requested refs, zero history) from "consumed by block" at a glance.
  • renumber no longer appears in any user-visible text; verified 0 occurrences in the built dist/index.js.

Tests

  • Updated the 2 gate tests that asserted the old wording.
  • Added 3 category tests (issue acceptance: one per true cause):
    1. model switch — fresh session (0 blocks / 0 compressions), foreign refs → new-generation wording + zero-history diagnostics, no "prior compress" claim
    2. rebase — state with 2 compressions replaced by createInitialState, old refs → same branch, diagnostics show zeroed history (current generation, not the past)
    3. content drift — message id changes, old ref dangles → cannot be anchored, asserted NOT "already compressed"
  • All new/updated tests assert doesNotMatch /renumber/i on the gate message.

Pre-flight

  • npm run typecheck — clean
  • npm test — 552/552 pass (baseline 549 + 3 new)
  • npm run build — success; grep -c renumber dist/index.js → 0

Coordination notes

  • Overlapping in-flight kernel work: other agent sessions have uncommitted work in the shared kernel clone touching the same gate area (src/compress.ts gate messages, src/boundaries.ts consumed-reason split, ref reclamation in src/refs.ts/src/sync.ts for the ghost-ref issue). This PR is based on clean master and is self-contained; if that work lands first, expect a small rebase on compress.ts lines ~270-290.
  • Ref-reclamation interaction: if the dead-ref reclamation PR ships, the all-unknown branch gains a fourth cause (drifted message whose old ref was released) — a one-line wording follow-up, flagged here so it isn't lost.

The minCompressRange batch gate claimed 'a prior compress renumbered the
remaining refs' — the kernel has no ref-renumbering mechanism (refs are
additive per-session snapshots; applyCompression/prune only change the
message view). The fabricated history made models self-doubt on a session's
FIRST compress (billion-context#387).

- all-unknown branch: states refs are per-session snapshots that no
  compress reassigns; names the real causes (previous session instance
  after model/upstream switch, pre-rebase generation, typo)
- consumed branch: splits dangling refs (content drift / filtered
  messages — 'cannot be anchored') from ranges covered by active blocks
  ('already compressed')
- both branches carry [diagnostics: session highest ref, unknown ranges
  in request, session history] so the model can tell a fresh-generation
  restart from block consumption at a glance
- 'renumber' no longer appears in any user-visible text or dist bundle
- tests: updated 2 gate tests, added 3 category tests (model switch,
  rebase, content drift) each asserting its wording + diagnostics
@github-actions

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-08-31_stale-ref-gate-truth (864ed94)

Option A — Install from npm PR tag (recommended)

In your adapter project:

npm install acp-kernel@pr-175

Each push to this PR publishes a new version under the pr-175 npm tag.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf acp-kernel-pr175.tgz
npm install ./package

This comment is automatically updated on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 ework agent · vllm-qwen/qwen3.8-27b

[bot] 🏷 Work is complete and verified. PR: #175 (branch 2026-08-31_stale-ref-gate-truth, commit 864ed94)

I independently re-ran the full pre-flight on the branch:

  • npm run typecheck — clean
  • npm test — 552/552 pass (baseline 549 + 3 new)
  • npm run build — success; grep -c renumber dist/index.js → 0 (also zero across all of dist/ and src/; the only remaining occurrences are the doesNotMatch(/renumber/i) test assertions themselves)

Acceptance criteria check against the issue:

  1. All-unknown branch (src/compress.ts:311-315): states refs are per-session snapshots assigned once at first render, that no compress reassigns them, and names the real causes — previous session instance (model/upstream switch restarts refs at m00001), pre-rebase generation (native compaction also resets to m00001), or typo. No "prior compress" claim.
  2. Consumed branch (src/compress.ts:316-319): split into the two truthful sub-cases — dangling refs (cannot be anchored … leaving your old refs dangling, detected by the new danglingMessageRefs helper: ref in the map, message gone from the view, no active block covering it) vs. covered-by-block (already compressed … covered by active block(s) or the block ref(s) are stale (distilled or consumed) + liveHint).
  3. Diagnostics on both stale branches via refGateDiagnostics: [diagnostics: session highest ref=mNNNNN|none, unknown ranges in request=U/R, session history=N compression(s), M block(s)] — "fresh generation restart" (high requested refs, zero history) is distinguishable from block consumption at a glance.
  4. Tests: the 2 old-wording gate tests updated; 3 category tests added (model switch, rebase, content drift), each asserting its wording + exact diagnostics and doesNotMatch /renumber/i.

CI on the PR is green (test 22/24, pr-validation, build-artifact all success) and the PR is mergeable against master.

Coordination notes acknowledged: if #176 (ref reclamation) lands first, expect a small rebase on compress.ts ~lines 270-319; the all-unknown branch would then gain a fourth cause (drifted message whose old ref was released) as a one-line wording follow-up.

Ready for review + human merge.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 ework agent · vllm-qwen/qwen3.8-27b

[bot] 🏷 Minor finding (report-only, not blocking): package-lock.json on master is stale — it still carries "version": "0.0.46" while package.json is 0.0.47 (release commits only touch package.json, so the lockfile version field drifted). Any npm install rewrites it. I did not bundle that sync into this PR to keep the diff scoped to the gate fix — happy to land it as a one-line chore commit on a separate branch if you want.

@ranxianglei
ranxianglei merged commit fa38292 into master Aug 31, 2026
4 checks passed
ranxianglei pushed a commit that referenced this pull request Aug 31, 2026
…undary failures

Rebase onto master (incl. #175): resolve refs.js import conflict
(indexToRef + pruneDeadRefs). Because applyCompression now syncs
(prunes dead refs) before boundary resolution, a drifted ref is
released and reported as unknown rather than the pre-#175 dangling
branch; name edit-drift as an unknown-ref cause in the all-unknown
gate message and update the #387 drift test accordingly.
@ranxianglei ranxianglei mentioned this pull request Aug 31, 2026
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