fix(agent): strip _db_persisted on rotation compression assembly (salvage #57531, #57491) - #57574
Merged
Merged
Conversation
…transcript (NousResearch#57491) Shallow messages[i].copy() during context compression propagated the _db_persisted marker from cached gateway incremental flushes into the post-rotation compressed list. _flush_messages_to_session_db then skipped every row when writing to the new child session, so gateway restarts lost the compacted transcript (severe amnesia). Strip the marker in _fresh_compaction_message_copy() and add regression tests for rotation flush + compressor assembly. Fixes NousResearch#57491
…ep (NousResearch#57491) Follow-up to the per-site strips from the review gate. The two copy-site strips are correct but positional — a copy site added after the assembly loops would re-leak _db_persisted into the child-session flush. Add a single terminal sweep (_strip_persistence_markers) run once on the fully-assembled compressed list so the invariant 'no compacted message leaves compress() carrying a persistence marker' is structural, not dependent on copy-site order. - agent/context_compressor.py: _strip_persistence_markers() called before compress() returns; helper docstring notes the sweep is the authoritative guard - tests/agent/test_context_compressor.py: structural regression — neuter the per-site helper to a leaking copy, assert the terminal sweep still strips - tests/run_agent/test_compression_persistence.py: pin the fixture assumption behind the exact-equality row-count assertion
Collaborator
Duplicate of #57508 (canonical, earliest of the same-mechanism cluster). This PR is a salvage of #57531 and fixes the same issue #57491 via the same mechanism (strip the |
This was referenced Jul 3, 2026
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Salvage of #57531 (@nankingjing) — cherry-picked to preserve authorship, rebased clean onto current
main, and independently verified end-to-end. Fixes #57491.Rotation compression (
compression.in_place: false) on long-lived cached gateway sessions silently drops the compacted transcript fromstate.db. On restart the child session reloads a near-empty transcript → severe amnesia. Regression introduced by #50372 (e4c6d1b22), which switched flush dedup fromid(msg)to an intrinsic_db_persistedmarker.Root cause:
ContextCompressor.compress()assembles the post-rotation list with shallowmessages[i].copy()for the protected head (context_compressor.py:2837) and tail (:2910). On a cached agent every live message already carries_db_persisted(stamped by incremental flushes), and.copy()propagates it. The child-session flush (run_agent.py:1820,if msg.get(_DB_PERSISTED_MARKER): continue) then skips every copied row — only brand-new post-compression appends reach the child DB.Fix: strip
_db_persistedwhen copying head/tail into the compressed transcript, via a small_fresh_compaction_message_copy()helper applied at both assembly sites. In-place compaction is untouched.Independent verification (not just green units)
Reproduced against the real
_flush_messages_to_session_db+ realSessionDBon a tempHERMES_HOME, both variants:main_merge_summary_into_tail(summary folded into a marked tail dict)[CONTEXT COMPACTION]rowsThe merge-into-tail row exactly matches the WeChat production evidence in the issue ("Child #51: 17 messages, 0 rows containing
[CONTEXT COMPACTION]") — the worst case where the summary itself is lost. The fix covers it because the tail copy is now stripped before the summary is merged into it.Chokepoint check: the two post-assembly passes (
_sanitize_tool_pairs,_strip_historical_media) operate on the already-strippedcompressedlist and never re-copy from the originalmessages, so sites 2837/2910 are the complete chokepoint — no marker can re-enter.Mutation-tested: neutering the strip (revert helper to plain
.copy()) makestest_rotation_child_session_flushes_full_compressed_transcript_with_markersfail — the regression test genuinely guards the fix.Note on the local constant: the helper redefines
_DB_PERSISTED_MARKERlocally rather than importing it fromrun_agent— importing back would be circular (run_agentimportscontext_compressorat import time). Consolidating both literals into a shared constants module is a larger refactor out of scope for this bug.Tests
tests/agent/test_context_compressor.py— unit: assembled compressed list carries no_db_persistedtests/run_agent/test_compression_persistence.py— e2e: cached agent with all source messages pre-marked → rotation → child receives the full compressed row countSupersedes #57531 and #57508 (both independent fixes for the same root cause; credit to @nankingjing and @rayjun).