Skip to content

fix(compression): preserve latest actionable user turn - #62747

Closed
John-Lussier wants to merge 2 commits into
NousResearch:mainfrom
John-Lussier:fix/compaction-actionable-event
Closed

fix(compression): preserve latest actionable user turn#62747
John-Lussier wants to merge 2 commits into
NousResearch:mainfrom
John-Lussier:fix/compaction-actionable-event

Conversation

@John-Lussier

Copy link
Copy Markdown
Contributor

Summary

Preserve the newest actionable user event across context compaction, including the observed async completion -> blank platform echo -> long tool run shape.

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

  • Distinguish actionable user turns from compaction summaries and genuinely empty platform echoes.
  • Treat image, audio, input_audio, and unknown structured content as actionable; only empty content shapes are blank.
  • Remove a blank echo only when it is contiguous after the latest actionable user event and an assistant turn follows it.
  • Preserve the immediately preceding assistant/tool group as a role bridge when necessary, keeping the latest user event as a separate, exact, non-summary row.
  • If no older safe window remains, take the existing ineffective-compression path rather than summarizing or rewriting the latest event.

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 --check also passes.

Regression coverage includes:

  • async completion followed by blank echo and long tool activity;
  • exact boundary with no safely compressible older turn;
  • second compaction preserving the same event;
  • image-only, audio, input_audio, and unknown structured user content;
  • tool-call head/tail groups and visible assistant replies;
  • truthful no-op compression accounting.

Compatibility

Scope

Intentionally not included:

  • changes to summary prompting or fabricated-request validation;
  • generic message-role normalization;
  • changes to gateway/delegation delivery;
  • unrelated context-budget or tool-result pruning changes.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:2285 initializes the blank-echo scan with user_idx + 1. When no actionable user event exists, user_idx is -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:2254 only recognizes structured type="text" blanks. Runtime paths also treat input_text as textual (run_agent.py:4958), so an empty input_text echo 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_text as 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread agent/context_compressor.py Outdated
if part.strip():
return False
continue
if isinstance(part, dict) and part.get("type") == "text":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@John-Lussier

John-Lussier commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Addressed both sweeper findings in aacd1eed2:

  • _blank_echo_indices_after() now returns immediately when there is no actionable user (user_idx < 0), with a leading-blank regression so it cannot produce an assistant-leading transcript.
  • Empty input_text blocks now use the same blank classification as empty text blocks.

Strict TDD: the two added cases failed before the source change and now pass. Updated focused compression verification: 214 passed; git diff --check is clean.

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 11, 2026
@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 11, 2026
@John-Lussier

Copy link
Copy Markdown
Contributor Author

The review findings were addressed in aacd1eed2 and remain on the current PR head:

  • _blank_echo_indices_after() returns immediately when user_idx < 0, with a leading-blank regression so it cannot produce an assistant-leading transcript.
  • Empty input_text blocks now use the same blank classification as empty text blocks, covered by parameterized echo tests.

Focused compression verification: 214 passed; git diff --check clean. Ready for re-review.

@teknium1 teknium1 added the area/compression Context compression and continuation sessions label Jul 19, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Merged via #69291 (commit 2ee50c6). Both your commits were cherry-picked with authorship preserved — cleanest salvage of the batch. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/compression Context compression and continuation sessions comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants