Skip to content

Record late streaming content after compression rebuild (Fixes #3338) - #3372

Merged
acoliver merged 4 commits into
dev/0.12.0from
issue3338
Aug 30, 2026
Merged

Record late streaming content after compression rebuild (Fixes #3338)#3372
acoliver merged 4 commits into
dev/0.12.0from
issue3338

Conversation

@acoliver

@acoliver acoliver commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

TLDR

This stacked change makes compression rebuild work explicit. Rebuilt history still emits while recording suppression is active, while ordinary streaming content queued before or after the rebuild emits after compressionLockReleased and remains recoverable from the session recording.

This PR is based on issue3264, the head of PR #3336. It should remain stacked until #3336 lands. Reviewers should focus on HistoryService.rebuildWith(), phase classification in CompressionOperationQueue.flush(), and the recording/replay regression test.

Dive Deeper

PR #3336 partitions queued compression work by position at the first clear(). That preserves streaming content queued before the clear, but a streaming add() queued after the rebuild block is still placed in the rebuild phase. Its contentAdded event fires while RecordingIntegration is suppressing rebuilt entries, so live history contains the entry while replay loses it.

This change adds one synchronous rebuild scope to HistoryService. Adds and clears made inside that scope are tagged rebuild; ordinary queued mutations outside it are tagged streaming. The queue runs rebuild operations first, emits the existing release/end lifecycle events, then runs streaming operations. FIFO within each phase, failure aggregation, high-water diagnostics, and the never-drop contract are unchanged.

The three production rebuild paths now use the scope:

  • cache-anchor compression application
  • pending-context fallback reconstruction
  • provider-content restore and retry reconstruction

The callback type is () => undefined, which rejects async callbacks at type-check time. A compile-time regression test protects that synchronous contract.

Behavior tests cover clear-only rebuilds, multiple rebuilt entries, streaming entries on both sides of the scope, multiple clear/retry sequences, no-lock synchronous use, all three agent callers under an active compression lock, exact lifecycle event order, final history order, and a real recording/replay round trip. The persisted sequence is content(original) -> compressed -> content(late), and replay yields [summary, late entry] without duplicating retained entries.

TDD RED logs, review triage, mutation checks, test-audit comparison, and verification details are recorded in project-plans/issue3338/plan.md. Compliance and local Open Code Review findings are resolved or explicitly rejected with rationale there.

Reviewer Test Plan

Run the focused behavior suites:

bun test packages/core/src/services/history/ packages/core/src/recording/
bun test packages/agents/src/compression/cacheAnchor.test.ts \
  packages/agents/src/compression/__tests__/pendingContextWindowEnforcement.toolTruncation.test.ts \
  packages/agents/src/compression/__tests__/compression-retry-provider-hardlimit.test.ts

The core set passes 1,068 tests across 68 files. The focused agent compression set passes 580 tests. Mutation checks confirm that removing any migrated rebuildWith wrapper breaks its caller test, and forcing all queued work into the streaming phase breaks 12 boundary rows.

Local verification on macOS:

  • npm run typecheck, npm run format, and npm run build pass.
  • Full-tree ESLint passes with a 16,384 MB heap. The package script's fixed 12,288 MB heap exhausted during the same traversal; changed-file lint and the ESLint policy guard pass.
  • A clean npm run test completed every workspace. Four unrelated agents files reached their isolated-process timeouts after 372 of 376 files passed; all four passed together in isolation, 47 tests in 7.17 seconds.
  • The test-audit baseline and candidate each report 2,013 existing findings, with no new finding in a touched test.
  • bun scripts/start.ts --profile-load zai "write me a haiku and nothing else" passes against zai:glm-5.3.

Testing Matrix

🍏 🪟 🐧
npm run ⚠️
npx
Docker
Podman - -
Seatbelt - -

The macOS npm result is marked with a warning because the full test orchestrator had the four load-sensitive timeouts described above and the fixed-heap lint script exhausted its heap. All affected files and the equivalent full lint traversal pass separately.

Linked issues / bugs

Fixes #3338

Depends on #3336. Related: #3264, #3132, #3263, #2852.

Summary by CodeRabbit

  • Bug Fixes
    • Improved conversation history handling during compression and context-window recovery.
    • Preserved content added while compression is in progress, including late-streaming entries.
    • Prevented restored or compressed history from losing entries or appearing in the wrong order.
    • Improved reliability when compression retries or fallback recovery are required.
    • Ensured recorded session history accurately reflects compressed content and subsequent additions.

#3264)

HistoryService.add()/clear() queue operations while compression holds the
history lock, and endCompression() used to flush that queue in plain FIFO
order. A streaming add() queued before the compression rebuild's clear() was
therefore destroyed by that clear, and because RecordingIntegration suppresses
contentAdded until compressionLockReleased, the wiped content was never
recorded either: mid-compression streaming output silently vanished from
history and from replay.

Flush is now two-phase, partitioned at the FIRST queued clear: the rebuild
slice runs first (its contentAdded stays inside the recording suppression
window, so rebuilt entries are not double-recorded), then
compressionLockReleased/compressionEnded, routed through
runSynchronousHistoryMutation so they share the mutation FIFO with deferred
closures and the drain order rebuild, events, streaming holds even while
a replaceAll is in flight, then the streaming slice, whose contentAdded now
fires after the lock release so recording captures it and replay can recover
it. The queue attempts every unit under its own failure capture and combines
failures with combineMutationFailures, so a throwing listener can no longer
discard queued operations or suppress the release events, and a thrown
undefined propagates truthfully.

The queue lives in a new CompressionOperationQueue class preserving the
#2852 never-drop contract and its one-shot high-water diagnostics (re-armed
by both flush() and clear()).
@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 849e6d1d-9347-4a1a-b1ad-23a5b87ad3f8

📥 Commits

Reviewing files that changed from the base of the PR and between 6cc7a04 and 924bd1c.

⛔ Files ignored due to path filters (1)
  • project-plans/issue3338/plan.md is excluded by !project-plans/**
📒 Files selected for processing (11)
  • packages/agents/src/compression/__tests__/compression-retry-provider-hardlimit.test.ts
  • packages/agents/src/compression/__tests__/pendingContextWindowEnforcement.toolTruncation.test.ts
  • packages/agents/src/compression/cacheAnchor.test.ts
  • packages/agents/src/compression/cacheAnchor.ts
  • packages/agents/src/compression/pendingContextWindowEnforcement.ts
  • packages/agents/src/compression/providerContentEnforcement.ts
  • packages/core/src/recording/RecordingIntegration.core.test.ts
  • packages/core/src/services/history/HistoryService.ts
  • packages/core/src/services/history/compression-locking.test.ts
  • packages/core/src/services/history/compression-rebuild-scope.test.ts
  • packages/core/src/services/history/historyCompressionQueue.ts

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

The change adds synchronous rebuildWith scopes to HistoryService. Queued rebuild and streaming operations now flush in separate phases. Compression callers and tests use the new scope. Recording tests verify late streaming content during replay.

Changes

Compression rebuild handling

Layer / File(s) Summary
Explicit rebuild and streaming queue phases
packages/core/src/services/history/HistoryService.ts, packages/core/src/services/history/historyCompressionQueue.ts
HistoryService tags queued mutations as rebuild or streaming. The queue flushes rebuild operations before streaming operations and release events.
Compression caller rebuild scopes
packages/agents/src/compression/cacheAnchor.ts, packages/agents/src/compression/pendingContextWindowEnforcement.ts, packages/agents/src/compression/providerContentEnforcement.ts, packages/agents/src/compression/__tests__/*
Compression restoration, truncation, and anchored rebuilds now run inside rebuildWith. Tests verify restored content, event order, cache-anchor reset, and late streaming content.
HistoryService ordering and failure coverage
packages/core/src/services/history/compression-rebuild-scope.test.ts, packages/core/src/services/history/compression-locking.test.ts
Tests cover rebuild boundaries, FIFO ordering, synchronous callbacks, callback failures, event sequencing, and queue tagging.
Recording and replay validation
packages/core/src/recording/RecordingIntegration.core.test.ts
Recording tests verify that content added after an explicit rebuild is recorded after compression and restored during replay.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Merge Risk: 🔵 Low · up to 924bd

The change preserves late streaming content in history recordings and replay, but a failing compression lifecycle listener could prevent completion notification and leave downstream state inconsistent. The PR is mergeable with explicit owner awareness and follow-up to isolate listener failures.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 11 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #3338 by adding an explicit synchronous rebuild scope, separating rebuild and streaming queue phases, applying the scope to all three compression rebuild paths, preserving ev…
Out of Scope Changes check ✅ Passed The production changes and tests remain focused on issue #3338 and its stated compression rebuild, streaming ordering, recording, replay, and API-contract requirements. No unrelated code changes are e…
Description check ✅ Passed The description includes all required sections. It explains the change, implementation, test plan, testing results, platform limitations, and linked issues. The incomplete testing matrix entries are c…
Title check ✅ Passed The title clearly identifies the primary change: preserving late streaming content after a compression rebuild. It is concise, specific, and related to issue #3338.
Full details: Linked Issues check

Explanation

The changes satisfy issue #3338 by adding an explicit synchronous rebuild scope, separating rebuild and streaming queue phases, applying the scope to all three compression rebuild paths, preserving event and history ordering, and adding event-order and recording/replay regression tests.

Full details: Out of Scope Changes check

Explanation

The production changes and tests remain focused on issue #3338 and its stated compression rebuild, streaming ordering, recording, replay, and API-contract requirements. No unrelated code changes are evident.

Full details: Description check

Explanation

The description includes all required sections. It explains the change, implementation, test plan, testing results, platform limitations, and linked issues. The incomplete testing matrix entries are clearly marked as unverified, while the macOS results and known test limitations are documented.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue3338

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the maintainer:e2e:ok Trusted contributor; maintainer-approved E2E run label Aug 27, 2026
@acoliver

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Before this change, compression rebuilds could erase or misorder late streaming content because history rebuild and streaming mutations were not scoped separately, and cache-anchor state could survive a truncating fallback even though the preserved prefix had been destroyed. After this PR, the core history service tags queued operations as rebuild or streaming and flushes them in a defined rebuild-then-streaming order so queued streaming content is reapplied after the rebuild and recorded after the lock is released. The compression and agent enforcement paths now wrap fallback and cache-anchor rebuilds in the new rebuild scope, and the cache-anchor helper explicitly resets or advances the anchor based on whether the prefix survived.

Release Notes

Bug Fixes

Refactor

  • Introduced rebuild-scope guarding for compression queue operations, separating rebuild clears from streaming adds during lock-held flushes.
  • Wrapped compression fallback and cache-anchor history rebuilds in rebuild-scoped execution so history mutations follow a consistent rebuild-then-streaming order.

Tests

  • Added regression and unit tests for rebuild scope boundaries, compression queue event ordering, cache-anchor reset/advance behavior, and recording of late streaming content after compression.

Changes

Layer File(s) Summary
docs project-plans/issue3338/plan.md Plan document for issue #3338 defining rebuild scope, acceptance criteria, and verification approach.
core packages/core/src/services/history/HistoryService.ts, packages/core/src/services/history/historyCompressionQueue.ts Introduces rebuildWith() scope guard and tags compression queue operations as rebuild vs streaming to preserve late streaming content.
agents packages/agents/src/compression/providerContentEnforcement.ts, packages/agents/src/compression/pendingContextWindowEnforcement.ts, packages/agents/src/compression/cacheAnchor.ts Wraps compression fallback and cache-anchor history rebuilds in rebuildWith() so late streaming content is recorded correctly.
tests packages/agents/src/compression/tests/compression-retry-provider-hardlimit.test.ts, packages/core/src/services/history/compression-rebuild-scope.test.ts, packages/agents/src/compression/tests/pendingContextWindowEnforcement.toolTruncation.test.ts, packages/core/src/services/history/compression-locking.test.ts, packages/core/src/recording/RecordingIntegration.core.test.ts, packages/agents/src/compression/cacheAnchor.test.ts Regression and unit tests validating rebuild scope boundaries, event ordering, cache anchor reset, and recording of late streaming content after compression.

Sequence Diagram

sequenceDiagram
  participant Agent as Agent Enforcer
  participant History as HistoryService
  participant Queue as CompressionOperationQueue
  participant Recorder as RecordingIntegration
  Agent->>History: startCompression()
  Agent->>History: rebuildWith(clear/add/anchor updates)
  History->>Queue: tag operations as rebuild
  Agent->>History: late streaming add()
  History->>Queue: tag operation as streaming
  Agent->>History: endCompression(summary)
  History->>Queue: flush rebuild operations first
  Queue-->>History: rebuilt contentAdded events
  History->>Recorder: compressionLockReleased
  History->>Recorder: compressionEnded(summary)
  Queue-->>History: flush streaming operations
  History->>Recorder: late streaming contentAdded
Loading

Magnitude

🎯 3 (L)
1072 additions, 190 deletions, 12 changed files across 2 packages, 18 acceptance criteria

Related

Pre-merge Checks

Check Status Note
Title Clear and descriptive title that identifies the fix and references issue #3338.
Description All required template sections present: TLDR, Dive Deeper, Reviewer Test Plan, Testing Matrix, and Linked issues / bugs.
Linked Issues Changes fulfill #3338 acceptance criteria by introducing HistoryService.rebuildWith() to tag operations as rebuild vs streaming, ensuring late streaming content fires after compressionLockReleased and is recorded/recovered correctly. Tests cover event ordering, recording/replay round trips, and all three agent compression callers.
Out of Scope PR is stacked on #3336 and should not be merged until that base PR lands. No other out-of-scope items identified.

Walkthrough generated by LLxprt PR Review. Planner issue: #2256

Comment thread packages/core/src/recording/RecordingIntegration.core.test.ts
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

OpenCodeReview — PR #3372

  • Reviewed head SHA: 575b6ed667eb689835f0fd578bded302be7df639
  • Merge base: 6cc7a0404a692dfd6605df97254c9312fbd631c6
  • Range: full from 6cc7a0404a692dfd6605df97254c9312fbd631c6
  • Range fallback: checkpoint-missing
  • Scope: selected 12 file(s), +1072/-190; cumulative 12 file(s), +1072/-190
  • Tokens: 2001617 total (1667951 input, 333666 output, 1145600 cache)
  • OCR version: open-code-review v1.8.4 (e78474478) linux/amd64 built at: 2026-08-01T03:27:37Z https://github.com/alibaba/open-code-review
  • Phase: review
  • Exit code: 0
  • Run: https://github.com/vybestack/llxprt-code/actions/runs/33067495984
  • Partial review: 0 of 11 files completed (1 failed); 1 finding(s) (1 posted inline).
  • Artifacts: ocr-review-output contains raw JSON, stdout, stderr, preview, phase, and exit-code diagnostics.
  • WARNING: Changed-file coverage 1/11 preview files covered is below the 90% threshold. WARNING: 1 file read/review failure(s) detected.

@acoliver

Copy link
Copy Markdown
Collaborator Author

OCR finding ocr-33065394029-0-6fd89a6e is rejected as factually incorrect. ProviderContentEnforcer.restoreHistory() opens one rebuildWith callback at line 647 and closes it at line 680. The initial restore, backup restore, final retry, and cleanup clears at lines 648, 656, 666, and 670 all execute inside that callback, so they are all tagged as rebuild work. The inline comment was also attached to RecordingIntegration.core.test.ts, not the source it described. No code change is needed. The triage is recorded in project-plans/issue3338/plan.md.

@acoliver

Copy link
Copy Markdown
Collaborator Author

OCR finding ocr-33067495984-0-70743cc4 is rejected as factually incorrect. ProviderContentEnforcementDeps.performFallbackCompression is explicitly asynchronous and returns Promise<boolean> (providerContentEnforcement.ts, lines 44-47). Its separate applyResult argument is the synchronous callback that reaches restoreHistory() and rebuildWith(). The test’s async implementation matches the production dependency contract and calls applyResult synchronously before resolving. Changing it to a synchronous function would violate the declared production type rather than protect it. No code change is needed.

@acoliver

Copy link
Copy Markdown
Collaborator Author

CodeRabbit’s docstring-coverage warning is classified Reject. It is a generic finishing-touch metric, not an identified behavior defect. The touched functions include test helpers and small private methods whose names and types state their contracts; adding docstrings to reach an arbitrary percentage would conflict with this repository’s sparse-comment style. CodeRabbit generated no actionable code comments, and its status check passed.

@acoliver acoliver added this to the 0.12.0 milestone Aug 27, 2026
@acoliver
acoliver changed the base branch from issue3264 to dev/0.12.0 August 27, 2026 14:54
@acoliver
acoliver merged commit 20feaac into dev/0.12.0 Aug 30, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer:e2e:ok Trusted contributor; maintainer-approved E2E run

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant