From 8f8cad7ec5a69a6d4ca9ebdde2213498c37ddc3d Mon Sep 17 00:00:00 2001 From: kyssta-exe Date: Mon, 8 Jun 2026 01:02:57 +0000 Subject: [PATCH] fix(agent): strengthen compression preamble against stale task execution (#41607) --- agent/context_compressor.py | 24 ++++++++++++-------- tests/agent/test_resume_stale_active_task.py | 8 ++++++- tests/agent/test_summary_prefix_semantics.py | 2 +- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/agent/context_compressor.py b/agent/context_compressor.py index 86352a633..c7ca134b1 100644 --- a/agent/context_compressor.py +++ b/agent/context_compressor.py @@ -7,7 +7,7 @@ protecting head and tail context. Improvements over v2: - Structured summary template with Resolved/Pending question tracking - Filter-safe summarizer preamble that treats prior turns as source material - - "Remaining Work" replaces "Next Steps" to avoid reading as active instructions + - "Historical Work (DO NOT EXECUTE)" replaces "Next Steps" and "Remaining Work" to avoid reading as active instructions - Clear separator when summary merges into tail message - Iterative summary updates (preserves info across multiple compactions) - Token-budget tail protection instead of fixed message count @@ -49,14 +49,18 @@ SUMMARY_PREFIX = ( "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. " - f"If the latest user message is consistent with the '{HISTORICAL_TASK_HEADING}' " - "section, you may use the summary as background. If the latest user " - "message contradicts, supersedes, changes topic from, or in any way " - f"diverges from '{HISTORICAL_TASK_HEADING}' / " + "Any topic overlap between the summary and the latest user message does " + "NOT mean you should resume the summary's task. The latest user message " + "always takes priority and WINS, even on similar topics. " + "If the latest user message is a new question, request, or instruction " + "that relates to a topic also mentioned in " + f"'{HISTORICAL_TASK_HEADING}' / " f"'{HISTORICAL_IN_PROGRESS_HEADING}' / " f"'{HISTORICAL_PENDING_ASKS_HEADING}' / " - f"'{HISTORICAL_REMAINING_WORK_HEADING}', the latest message WINS — " - "discard those stale items entirely and do not 'wrap up the old task first'. " + f"'{HISTORICAL_REMAINING_WORK_HEADING}', treat ONLY the " + "latest message as the active task — discard those stale items entirely. " + "Do NOT 'wrap up' or 'finish' any work described in the summary 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 " @@ -1380,13 +1384,13 @@ Be specific with file paths, commands, line numbers, and results.] [Questions the user asked that were ALREADY answered — include the answer so it is not repeated] {HISTORICAL_PENDING_ASKS_HEADING} -[Questions or requests from the user that have NOT yet been answered or fulfilled. If none, write "None."] +[Questions or requests from the user that have NOT yet been answered or fulfilled. These are STALE — they were from the compacted turns. Write them here for reference only. The agent must NOT act on them unless the latest user message explicitly requests it. If none, write "None."] ## Relevant Files [Files read, modified, or created — with brief note on each] {HISTORICAL_REMAINING_WORK_HEADING} -[What remains to be done — framed as context, not instructions] +[What remains to be done — framed as STALE context for reference only. The agent must NOT resume this work unless the latest user message explicitly asks for it.] ## Critical Context [Any specific values, error messages, configuration details, or data that would be lost without explicit preservation. NEVER include API keys, tokens, passwords, or credentials — write [REDACTED] instead.] @@ -1761,7 +1765,7 @@ The user has requested that this compaction PRIORITISE preserving all informatio Context compressor bug (#10896): ``_align_boundary_backward`` can pull ``cut_idx`` past a user message when it tries to keep tool_call/result groups together. If the last user message ends up in the *compressed* - middle region the LLM summariser writes it into "Pending User Asks", + middle region the LLM summariser writes it into "Historical User Asks", but ``SUMMARY_PREFIX`` tells the next model to respond only to user messages *after* the summary — so the task effectively disappears from the active context, causing the agent to stall, repeat completed work, diff --git a/tests/agent/test_resume_stale_active_task.py b/tests/agent/test_resume_stale_active_task.py index 9f6488076..a2dfd529a 100644 --- a/tests/agent/test_resume_stale_active_task.py +++ b/tests/agent/test_resume_stale_active_task.py @@ -56,6 +56,10 @@ def test_latest_message_wins_over_inherited_active_task(): # Conflict-resolution must be explicit, not implied. assert "wins" in lower or "supersede" in lower assert "discard" in lower + # The "consistent -> use as background" carveout licensed stale-task + # resumption on topic overlap (#41607, #38364) — it must stay gone. + assert "you may use the summary as background" not in lower + assert "topic overlap" in lower def test_no_resume_exactly_directive_can_hijack(): @@ -87,7 +91,9 @@ def test_resumed_stale_handoff_gets_renormalized_to_current_prefix(): # current latest-message-wins framing. assert "resume exactly" not in renormalized.lower() assert renormalized.startswith(SUMMARY_PREFIX) - assert "wins" in renormalized.lower() + assert ("wins" in renormalized.lower() + or "priority" in renormalized.lower() + or "supersede" in renormalized.lower()) def test_legacy_prefix_handoff_also_renormalized(): diff --git a/tests/agent/test_summary_prefix_semantics.py b/tests/agent/test_summary_prefix_semantics.py index 6b3756bf1..42047142f 100644 --- a/tests/agent/test_summary_prefix_semantics.py +++ b/tests/agent/test_summary_prefix_semantics.py @@ -40,7 +40,7 @@ def test_latest_message_wins_on_conflict(): assert HISTORICAL_PENDING_ASKS_HEADING.lower() in lower assert HISTORICAL_REMAINING_WORK_HEADING.lower() in lower # Must have an explicit conflict-resolution rule. - assert "wins" in lower or "supersede" in lower or "discard" in lower + assert "wins" in lower or "supersede" in lower or "discard" in lower or "priority" in lower def test_handoff_sections_are_framed_as_historical():