hermes - ✅(Solved) Fix Bug: context_compressor drops messages when summarization fails [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#11585Fetched 2026-04-18 06:00:12
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Participants
Timeline (top)
cross-referenced ×2referenced ×1

Error Message

Additionally, when _generate_summary() returns None explicitly (an exception was caught), there's no early return — it falls through to the same code path, losing everything.

Root Cause

In agent/context_compressor.py, the code path at line ~1087:

if not summary:
    n_dropped = compress_end - compress_start
    summary = (
        f"{SUMMARY_PREFIX}\n"
        f"Summary generation was unavailable. {n_dropped} conversation turns were "
        ...
    )
# then continues to DELETE the middle turns and replace with this static text

The if not summary: check catches both None AND empty string — but even for a fallback static summary, all the original middle messages get deleted.

Additionally, when _generate_summary() returns None explicitly (an exception was caught), there's no early return — it falls through to the same code path, losing everything.

Fix Action

Fixed

PR fix notes

PR #11651: fix: preserve context when summary generation fails

Description (problem / solution / changelog)

Summary

  • preserve the original conversation when context summary generation fails with None
  • stop injecting a misleading fallback marker that implied middle turns were removed without a usable summary
  • add regression coverage for both the direct failure path and the pre-prune rollback path

Root cause

ContextCompressor.compress() treated any falsy summary as a signal to continue compression with a static fallback marker. When _generate_summary() returned None, Hermes still mutated the system prompt, dropped middle turns, and incremented compression_count even though no valid summary existed.

Fix

  • return the original messages unchanged when _generate_summary() returns None
  • keep compression_count unchanged on this failure path
  • align docs/logging with the preserve-on-failure behavior
  • add regression tests for summary failure behavior

Test plan

  • uv run pytest tests/agent/test_context_compressor.py -q -o addopts=
  • uv run pytest tests/run_agent/test_compression_boundary.py tests/run_agent/test_compression_feasibility.py tests/run_agent/test_compressor_fallback_update.py -q -o addopts=
  • uv run pytest tests/agent/test_compress_focus.py tests/cli/test_compress_focus.py -q -o addopts=
  • uv run python -m py_compile agent/context_compressor.py tests/agent/test_context_compressor.py

Closes #11585

Changed files

  • agent/context_compressor.py (modified, +14/-7)
  • tests/agent/test_context_compressor.py (modified, +49/-6)

PR #11662: fix(compressor): preserve messages when summarization fails

Description (problem / solution / changelog)

Summary

  • When _generate_summary() returns None (summarization failure), the compressor now preserves the original messages instead of dropping middle conversation turns and replacing them with a useless static marker
  • Tool-result pruning (cheap pre-pass, no LLM) is still applied before the early return, so some space savings still occur
  • Updated 3 existing tests that were asserting the old buggy behavior (dropping messages on failure) and added 4 new regression tests

Closes #11585

Changes

  • agent/context_compressor.py: In compress(), when _generate_summary() returns None, return the (pruned) original messages immediately instead of assembling a compressed list with a static fallback marker
  • tests/agent/test_context_compressor.py: Fixed test_truncation_fallback_no_client, test_compression_increments_count, and test_none_content_in_system_message_compress to expect message preservation on summary failure. Added TestSummaryFailurePreservesMessages class with 4 tests covering Exception, RuntimeError, compression count behavior, and a sanity check that successful summaries still compress.

Test plan

  • All 44 tests in tests/agent/test_context_compressor.py pass
  • Verify no regressions in related test files (test_compression_boundary.py, test_compress_focus.py, etc.)
  • Manual test: trigger compression with no OPENROUTER_API_KEY set — conversation should continue normally without losing context

🤖 Generated with Claude Code

Changed files

  • agent/context_compressor.py (modified, +14/-14)
  • agent/context_engine.py (modified, +9/-1)
  • tests/agent/test_context_compressor.py (modified, +100/-9)
  • tests/agent/test_context_engine.py (modified, +15/-1)
  • tests/run_agent/test_plugin_context_engine_init.py (modified, +1/-1)
  • website/docs/developer-guide/context-engine-plugin.md (modified, +2/-1)

Code Example

if not summary:
    n_dropped = compress_end - compress_start
    summary = (
        f"{SUMMARY_PREFIX}\n"
        f"Summary generation was unavailable. {n_dropped} conversation turns were "
        ...
    )
# then continues to DELETE the middle turns and replace with this static text

---

# Case 1: _generate_summary returned None — preserve everything
if summary is None:
    logger.warning("Context compression skipped — summary generation failed. All %d original messages preserved.", n_messages)
    return messages

# Case 2: summary is empty fallback — keep original middle turns
if not summary:
    # Instead of deleting middle turns, keep them intact
    for i in range(compress_start, compress_end):
        compressed.append(messages[i].copy())
    # ... then tail
    return compressed
RAW_BUFFERClick to expand / collapse

Bug Description

When the context compressor's _generate_summary() returns None (summarization failure), the original code would:

  1. Set summary to a static fallback string (still non-empty)
  2. Continue to delete the middle conversation turns and only keep head + tail + static marker

This means even when the LLM didn't crash but simply returned an empty/unusable summary, all the middle conversation history was lost and replaced with a useless "Summary generation was unavailable" marker.

Root Cause

In agent/context_compressor.py, the code path at line ~1087:

if not summary:
    n_dropped = compress_end - compress_start
    summary = (
        f"{SUMMARY_PREFIX}\n"
        f"Summary generation was unavailable. {n_dropped} conversation turns were "
        ...
    )
# then continues to DELETE the middle turns and replace with this static text

The if not summary: check catches both None AND empty string — but even for a fallback static summary, all the original middle messages get deleted.

Additionally, when _generate_summary() returns None explicitly (an exception was caught), there's no early return — it falls through to the same code path, losing everything.

Fix (Preserve-on-Failure Principle)

Two cases need fixing:

  1. When summary is None (explicit failure): Return all original messages unchanged — preserve everything.
  2. When summary is empty/fallback: Keep the original middle messages as-is rather than deleting them.
# Case 1: _generate_summary returned None — preserve everything
if summary is None:
    logger.warning("Context compression skipped — summary generation failed. All %d original messages preserved.", n_messages)
    return messages

# Case 2: summary is empty fallback — keep original middle turns
if not summary:
    # Instead of deleting middle turns, keep them intact
    for i in range(compress_start, compress_end):
        compressed.append(messages[i].copy())
    # ... then tail
    return compressed

Impact

  • Severity: High — silent data loss in long conversations
  • Frequency: Every time the summarization LLM returns unusable output (which happens with certain model/provider combinations)
  • User impact: Previous conversation context completely lost, only static failure message remains

extent analysis

TL;DR

To fix the issue, modify the context_compressor.py to preserve the original conversation messages when the summarization fails or returns an empty string.

Guidance

  • Identify the cases where the summarization fails or returns an empty string and handle them separately to preserve the original messages.
  • When _generate_summary() returns None, return all original messages unchanged to prevent data loss.
  • When the summary is an empty fallback, keep the original middle messages intact instead of deleting them.
  • Review the code at line ~1087 in agent/context_compressor.py to ensure the if not summary: check is updated to handle both None and empty string cases correctly.

Example

if summary is None:
    logger.warning("Context compression skipped — summary generation failed. All %d original messages preserved.", n_messages)
    return messages

if not summary:
    for i in range(compress_start, compress_end):
        compressed.append(messages[i].copy())
    # ... then tail
    return compressed

Notes

The provided fix assumes that the messages list contains all the original conversation messages and that the compressed list is used to store the compressed conversation. The code modifications should be applied to the context_compressor.py file at the specified line number.

Recommendation

Apply the workaround by modifying the context_compressor.py file to preserve the original conversation messages when the summarization fails or returns an empty string. This will prevent silent data loss in long conversations and ensure that the user's previous conversation context is retained.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix Bug: context_compressor drops messages when summarization fails [2 pull requests, 1 participants]