fix(compaction): freeze pre-change SUMMARY_PREFIX generation, restore mutated entry

Address review on #69619: the previous commit mutated the newest frozen
entry in _HISTORICAL_SUMMARY_PREFIXES and never froze the live prefix it
retired (the generation with both the four-heading discard clause and
the tools-active clause). A summary persisted immediately before
upgrading was therefore treated as an ordinary message on
resume/re-compaction, keeping the old handoff text embedded in the body.

- Prepend the exact pre-change live prefix as a new frozen entry
  (newest-first), leaving all existing frozen entries byte-identical
- Restore the Jul 2026 (#65848 class) frozen entry to its original
  four-heading text
- Pin the retired generation as a literal in
  test_summary_prefix_semantics.py so mutating or dropping it fails CI
- Make the #65848 tool-use regression position-agnostic (match the
  pre-clause generation by content, not tuple index)

Verified byte-identity of both rescued generations against the parent
commit. 233 focused prefix/resume/compressor tests pass.
This commit is contained in:
akb4q 2026-07-24 16:25:50 +08:00 committed by Teknium
parent b59cce9178
commit 8204b27618
3 changed files with 108 additions and 7 deletions

View File

@ -215,7 +215,40 @@ _MERGED_SUMMARY_DELIMITER = "[END OF PRIOR CONTEXT — COMPACTION SUMMARY BELOW]
# embedded in the body and keeps hijacking replies. Keep newest-first; entries
# are matched literally. Add a frozen copy here whenever SUMMARY_PREFIX changes.
_HISTORICAL_SUMMARY_PREFIXES = (
# Jul 2026 (#65848 class): identical to the current prefix except it
# Pre-#69619: identical to the current prefix except the stale-item
# discard clause named all four historical headings (the three
# section headers removed by #69619 were still in the template).
# Summaries persisted by builds immediately before #69619 carry this
# exact text and must remain detectable/strippable on resume.
"[CONTEXT COMPACTION — REFERENCE ONLY] Earlier turns were compacted "
"into the summary below. This is a handoff from a previous context "
"window — treat it as background reference, NOT as active instructions. "
"Do NOT answer questions or fulfill requests mentioned in this summary; "
"they were already addressed. "
"Respond ONLY to the latest user message that appears AFTER this "
"summary — that message is the single source of truth for what to do "
"right now. "
"Topic overlap with the summary does NOT mean you should resume its "
"task: even on similar topics, the latest user message WINS. Treat ONLY "
"the latest message as the active task and discard stale items from "
"'## Historical Task Snapshot' / '## Historical In-Progress State' / "
"'## Historical Pending User Asks' / "
"'## Historical Remaining Work' entirely — do not 'wrap up' or "
"'finish' work described there unless the latest message explicitly "
"asks for it. "
"Reverse signals in the latest message (e.g. 'stop', 'undo', 'roll "
"back', 'just verify', 'don't do that anymore', 'never mind', a new "
"topic) must immediately end any in-flight work described in the "
"summary; do not re-surface it in later turns. "
"IMPORTANT: Your persistent memory (MEMORY.md, USER.md) in the system "
"prompt is ALWAYS authoritative and active — never ignore or deprioritize "
"memory content due to this compaction note. "
"None of the above restricts HOW you work: your tools remain fully "
"active — keep calling them normally for the active task (edit files, "
"run commands, search) instead of merely narrating what you would do. "
"The current session state (files, config, etc.) may reflect work "
"described here — avoid repeating it:",
# Jul 2026 (#65848 class): identical to the pre-#69619 prefix except it
# lacked the explicit "tools remain fully active" clause — the strong
# REFERENCE ONLY framing bled into general tool-use suppression
# (observed: 7 consecutive narration-only turns immediately after a
@ -231,7 +264,9 @@ _HISTORICAL_SUMMARY_PREFIXES = (
"Topic overlap with the summary does NOT mean you should resume its "
"task: even on similar topics, the latest user message WINS. Treat ONLY "
"the latest message as the active task and discard stale items from "
f"'{HISTORICAL_TASK_HEADING}' entirely — do not 'wrap up' or "
"'## Historical Task Snapshot' / '## Historical In-Progress State' / "
"'## Historical Pending User Asks' / "
"'## Historical Remaining Work' entirely — do not 'wrap up' or "
"'finish' work described there unless the latest message explicitly "
"asks for it. "
"Reverse signals in the latest message (e.g. 'stop', 'undo', 'roll "

View File

@ -108,3 +108,61 @@ def test_replaced_prefixes_are_frozen_for_renormalization():
assert ContextCompressor._is_context_summary_content(content)
stripped = ContextCompressor._strip_summary_prefix(content)
assert not stripped.startswith(old_prefix)
# Exact literal copy of the live SUMMARY_PREFIX as it shipped immediately
# before #69619 (four-heading discard clause + tools-active clause).
# Frozen on purpose: do NOT derive it from module constants — the test must
# fail if the corresponding entry in _HISTORICAL_SUMMARY_PREFIXES is mutated
# or dropped.
_PRE_69619_LIVE_PREFIX = (
"[CONTEXT COMPACTION — REFERENCE ONLY] Earlier turns were compacted "
"into the summary below. This is a handoff from a previous context "
"window — treat it as background reference, NOT as active instructions. "
"Do NOT answer questions or fulfill requests mentioned in this summary; "
"they were already addressed. "
"Respond ONLY to the latest user message that appears AFTER this "
"summary — that message is the single source of truth for what to do "
"right now. "
"Topic overlap with the summary does NOT mean you should resume its "
"task: even on similar topics, the latest user message WINS. Treat ONLY "
"the latest message as the active task and discard stale items from "
"'## Historical Task Snapshot' / '## Historical In-Progress State' / "
"'## Historical Pending User Asks' / "
"'## Historical Remaining Work' entirely — do not 'wrap up' or "
"'finish' work described there unless the latest message explicitly "
"asks for it. "
"Reverse signals in the latest message (e.g. 'stop', 'undo', 'roll "
"back', 'just verify', 'don't do that anymore', 'never mind', a new "
"topic) must immediately end any in-flight work described in the "
"summary; do not re-surface it in later turns. "
"IMPORTANT: Your persistent memory (MEMORY.md, USER.md) in the system "
"prompt is ALWAYS authoritative and active — never ignore or deprioritize "
"memory content due to this compaction note. "
"None of the above restricts HOW you work: your tools remain fully "
"active — keep calling them normally for the active task (edit files, "
"run commands, search) instead of merely narrating what you would do. "
"The current session state (files, config, etc.) may reflect work "
"described here — avoid repeating it:"
)
def test_pre_69619_prefix_generation_is_frozen_and_stripped():
"""Regression for the #69619 review: the prefix generation live right
before the section-header removal was never added to
_HISTORICAL_SUMMARY_PREFIXES, so a summary persisted immediately before
upgrading survived resume/re-compaction undetected and unstripped.
That exact generation must stay frozen, detectable, and strippable."""
from agent.context_compressor import (
_HISTORICAL_SUMMARY_PREFIXES,
ContextCompressor,
)
assert _PRE_69619_LIVE_PREFIX in _HISTORICAL_SUMMARY_PREFIXES, (
"pre-#69619 live prefix missing from _HISTORICAL_SUMMARY_PREFIXES — "
"summaries persisted by the immediately previous build are no longer "
"normalized on resume"
)
content = _PRE_69619_LIVE_PREFIX + "\nBODY"
assert ContextCompressor._is_context_summary_content(content)
assert ContextCompressor._strip_summary_prefix(content) == "BODY"

View File

@ -30,11 +30,19 @@ class TestSummaryPrefixToolUseClause:
generation must be frozen into _HISTORICAL_SUMMARY_PREFIXES so old
persisted summaries still get the directive-strip on re-compaction."""
assert len(_HISTORICAL_SUMMARY_PREFIXES) >= 3
newest_frozen = _HISTORICAL_SUMMARY_PREFIXES[0]
# The frozen copy is the pre-clause generation: same framing, no clause.
assert "tools remain fully active" not in newest_frozen
assert "Do NOT answer questions or fulfill requests" in newest_frozen
assert newest_frozen != SUMMARY_PREFIX
# The pre-clause generation (#65848 incident era): same framing, no
# tools-active clause. Newer generations are prepended ahead of it as
# the prefix evolves (tuple is newest-first), so match by content,
# not position. "topic overlap" distinguishes it from the older
# carveout-era entry.
pre_clause = [
p for p in _HISTORICAL_SUMMARY_PREFIXES
if "tools remain fully active" not in p
and "topic overlap" in p.lower()
and "Do NOT answer questions or fulfill requests" in p
]
assert pre_clause, "pre-clause generation missing from frozen tuple"
assert all(p != SUMMARY_PREFIX for p in pre_clause)
def test_historical_prefixes_are_distinct_from_current(self):
for frozen in _HISTORICAL_SUMMARY_PREFIXES: