mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-19 15:18:03 +00:00
fix(compressor): skip context-summary markers as last-user tail anchor
A context-compaction handoff banner is inserted with role="user" when the protected head ends in an assistant/tool message. On a resumed or multi-compaction session, _find_last_user_message_idx would return that banner as the latest user turn, so _ensure_last_user_message_in_tail anchored the tail to the summary and rolled the genuine last user message into the next compaction — the exact active-task loss the anchor exists to prevent (#10896/#22523). Reuse the existing _is_context_summary_content helper to skip summary banners when locating the last real user message. Salvaged from #36626 by Frank Song (issue #36624). The PR's other two changes (demoting completed tool results inside the protected tail; a preflight compression_exhausted result) are superseded on current main by the min_tail floor (#39170), the no-op compression counting (#40803), and the existing 413/disabled terminal-error paths.
This commit is contained in:
parent
500c2b1e46
commit
ee710db135
2 changed files with 59 additions and 2 deletions
|
|
@ -477,6 +477,51 @@ class TestCompactionRollupReproduction:
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestFindLastUserMessageIdxSkipsSummaryMarker:
|
||||
"""A context-compaction handoff banner is inserted with ``role="user"``
|
||||
when the head ends in an assistant/tool message (see the summary-role
|
||||
selection in ``compress``). ``_find_last_user_message_idx`` must NOT treat
|
||||
that banner as the latest user turn — otherwise, on a resumed or
|
||||
multi-compaction session, ``_ensure_last_user_message_in_tail`` anchors the
|
||||
tail to the summary and rolls the genuine last user message into the next
|
||||
compaction, re-triggering the active-task loss the anchor exists to prevent.
|
||||
(Salvaged from #36626 / issue #36624.)
|
||||
"""
|
||||
|
||||
def test_skips_user_role_context_summary_marker(self, compressor):
|
||||
from agent.context_compressor import SUMMARY_PREFIX
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": "sys"},
|
||||
{"role": "user", "content": "REAL current task"},
|
||||
{"role": "assistant", "content": "working on it"},
|
||||
# A handoff summary re-inserted as a user-role message after resume.
|
||||
{"role": "user", "content": f"{SUMMARY_PREFIX}\n## Active Task\nold"},
|
||||
{"role": "assistant", "content": "continuing from the real task"},
|
||||
]
|
||||
# Latest *real* user message is index 1, not the summary at index 3.
|
||||
assert compressor._find_last_user_message_idx(messages, head_end=1) == 1
|
||||
|
||||
def test_returns_real_user_when_no_summary_present(self, compressor):
|
||||
messages = [
|
||||
{"role": "system", "content": "sys"},
|
||||
{"role": "user", "content": "first"},
|
||||
{"role": "assistant", "content": "reply"},
|
||||
{"role": "user", "content": "second"},
|
||||
]
|
||||
assert compressor._find_last_user_message_idx(messages, head_end=1) == 3
|
||||
|
||||
def test_all_user_messages_are_summaries_returns_minus_one(self, compressor):
|
||||
from agent.context_compressor import SUMMARY_PREFIX
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": "sys"},
|
||||
{"role": "assistant", "content": "reply"},
|
||||
{"role": "user", "content": f"{SUMMARY_PREFIX}\nhandoff"},
|
||||
]
|
||||
assert compressor._find_last_user_message_idx(messages, head_end=1) == -1
|
||||
|
||||
|
||||
class TestSourceGuardrail:
|
||||
@pytest.fixture
|
||||
def source(self) -> str:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue