fix(compression): preserve latest actionable user turn - #62747
fix(compression): preserve latest actionable user turn#62747John-Lussier wants to merge 2 commits into
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for isolating a real compaction-anchor failure: current main still anchors any non-summary role="user" row in agent/context_compressor.py:2464, so a blank platform echo can displace the actionable completion.
Problems
agent/context_compressor.py:2285initializes the blank-echo scan withuser_idx + 1. When no actionable user event exists,user_idxis-1, so this scans and removes index 0 if it is blank and followed by an assistant. That is not an echo after a user event and can leave an assistant-leading transcript.agent/context_compressor.py:2254only recognizes structuredtype="text"blanks. Runtime paths also treatinput_textas textual (run_agent.py:4958), so an emptyinput_textecho still becomes the actionable tail anchor.
Suggested changes
- Return no removable indices when
user_idx < 0, with a leading-blank-user regression test. - Treat empty
input_textas blank and cover it in the parameterized echo tests.
Automated hermes-sweeper review.
| placeholder for a transcript still being assembled. | ||
| """ | ||
| indices: set[int] = set() | ||
| idx = user_idx + 1 |
There was a problem hiding this comment.
When _find_last_user_message_idx() returns -1, this starts at index 0 and can remove a leading blank user row before an assistant response. That row is not contiguous after an actionable event and may be needed to keep a provider-valid user-leading transcript. Please return an empty set when user_idx < 0 and add that regression case.
| if part.strip(): | ||
| return False | ||
| continue | ||
| if isinstance(part, dict) and part.get("type") == "text": |
There was a problem hiding this comment.
Please include input_text in the text-only blank classification. Current runtime handling recognizes it as textual (run_agent.py:4958); otherwise an empty structured input_text echo remains actionable and can still displace the real latest user turn.
|
Addressed both sweeper findings in
Strict TDD: the two added cases failed before the source change and now pass. Updated focused compression verification: 214 passed; |
|
The review findings were addressed in
Focused compression verification: 214 passed; |
Summary
Preserve the newest actionable user event across context compaction, including the observed
async completion -> blank platform echo -> long tool runshape.A blank user-role echo could become the last-user tail anchor. The real completion immediately before it then entered the summarized middle, allowing an older task to become active again. A later compaction could also merge a summary into the completion row itself, so the event stopped being actionable.
Fix
input_audio, and unknown structured content as actionable; only empty content shapes are blank.This preserves provider-valid role/tool ordering and existing tool-call/result groups. It also keeps the latest event actionable through a second compaction.
Tests
PYTHONPATH=. HERMES_HOME=/tmp/hermes-p0-compaction-test-home \ python -m pytest \ tests/agent/test_compressor_actionable_tail_anchor.py \ tests/agent/test_context_compressor.py \ tests/agent/test_compressor_assistant_tail_anchor.py \ tests/agent/test_compressor_zero_user_guard.py \ tests/agent/test_context_compressor_summary_continuity.py \ tests/agent/test_compressed_summary_metadata.py \ tests/run_agent/test_compression_persistence.py \ tests/agent/test_resume_stale_active_task.py \ -q -o 'addopts='Result: 212 passed.
git diff --checkalso passes.Regression coverage includes:
input_audio, and unknown structured user content;Compatibility
User asked:summary lines; both touchcontext_compressor.pybut address different failure modes.Scope
Intentionally not included: