From c7205040c3c9b21dbbb143a2f75969fdd69f6b0c Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Fri, 17 Jul 2026 06:49:42 -0700 Subject: [PATCH] fix(compression): affirm tool use stays active in the compaction handoff prefix (#66291) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The REFERENCE ONLY framing ('treat as background reference, NOT as active instructions... Do NOT answer questions or fulfill requests') was observed bleeding into general tool-use suppression: a production session went narration-only for 7 consecutive turns immediately after a compression event, describing edits instead of calling tools (#65848 report). Fix is additive: one clause stating the note does not restrict HOW the agent works — tools remain fully active for the active task. Every anti-resumption protection stays intact; the previous prefix generation is frozen into _HISTORICAL_SUMMARY_PREFIXES per the module contract so persisted summaries still get the directive-strip on re-compaction. The #65848 rewrite was not taken: dropping the 'Do NOT answer questions' line and the four-heading discard directive risks re-opening the stale-task-resumption class those clauses exist to prevent (the carveout era regressions #41607/#38364/#42812 documented in this file). Report and root-cause analysis: @yasserbousrih (#65848). --- agent/context_compressor.py | 33 +++++++++++++ tests/agent/test_summary_prefix_tool_use.py | 53 +++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 tests/agent/test_summary_prefix_tool_use.py diff --git a/agent/context_compressor.py b/agent/context_compressor.py index 7c7b5ec330d6..e023aecba9bd 100644 --- a/agent/context_compressor.py +++ b/agent/context_compressor.py @@ -107,6 +107,9 @@ SUMMARY_PREFIX = ( "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:" ) @@ -193,6 +196,36 @@ _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 + # 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 + # compression event on a production deployment). + "[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 " + f"'{HISTORICAL_TASK_HEADING}' / '{HISTORICAL_IN_PROGRESS_HEADING}' / " + f"'{HISTORICAL_PENDING_ASKS_HEADING}' / " + f"'{HISTORICAL_REMAINING_WORK_HEADING}' 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. " + "The current session state (files, config, etc.) may reflect work " + "described here — avoid repeating it:", # Carveout era (#41607/#38364/#42812): "consistent → use as background" # licensed stale-task resumption on topic overlap. "[CONTEXT COMPACTION — REFERENCE ONLY] Earlier turns were compacted " diff --git a/tests/agent/test_summary_prefix_tool_use.py b/tests/agent/test_summary_prefix_tool_use.py new file mode 100644 index 000000000000..2a289b86f9bf --- /dev/null +++ b/tests/agent/test_summary_prefix_tool_use.py @@ -0,0 +1,53 @@ +"""Regression tests for the SUMMARY_PREFIX tool-use clause (#65848 class). + +The REFERENCE ONLY framing must keep its anti-resumption protections while +explicitly NOT restricting tool use — the strong wording was observed bleeding +into general tool-use suppression (narration-only turns after compression). +""" + +from agent.context_compressor import ( + _HISTORICAL_SUMMARY_PREFIXES, + LEGACY_SUMMARY_PREFIX, + SUMMARY_PREFIX, +) + + +class TestSummaryPrefixToolUseClause: + def test_prefix_affirms_tools_remain_active(self): + assert "tools remain fully active" in SUMMARY_PREFIX + assert "narrating" in SUMMARY_PREFIX + + def test_prefix_keeps_anti_resumption_protections(self): + """The clause is additive — every load-bearing protection stays.""" + assert "REFERENCE ONLY" in SUMMARY_PREFIX + assert "Do NOT answer questions or fulfill requests" in SUMMARY_PREFIX + assert "the latest user message WINS" in SUMMARY_PREFIX + assert "Reverse signals" in SUMMARY_PREFIX + assert "ALWAYS authoritative" in SUMMARY_PREFIX + + def test_previous_generation_frozen_in_historical_prefixes(self): + """Per the module contract: whenever SUMMARY_PREFIX changes, the prior + 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 + + def test_historical_prefixes_are_distinct_from_current(self): + for frozen in _HISTORICAL_SUMMARY_PREFIXES: + assert frozen != SUMMARY_PREFIX + assert LEGACY_SUMMARY_PREFIX != SUMMARY_PREFIX + + def test_strip_recognizes_current_and_frozen_prefixes(self): + """Re-compaction normalization must strip both the live prefix and the + newly frozen one (the incident generation).""" + from agent.context_compressor import ContextCompressor + + for prefix in (SUMMARY_PREFIX, _HISTORICAL_SUMMARY_PREFIXES[0]): + text = f"{prefix}\nsummary body here" + stripped = ContextCompressor._strip_summary_prefix(text) + assert "summary body here" in stripped + assert "REFERENCE ONLY" not in stripped