Context compression failures silently losing conversation history
NousResearch/hermes-agent · cluster 10719 · agent
Data as of
2 open items. Fix merged in #16771. Check it covers your case.
The short answer
- Known?
- Yes. 22 related items grouped here, 8 issues already closed.
- Fix event
- A fix was merged to main in PR #16771. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PRs #16771, #51881. That is separate from the fix event. - Still open
- 2 issues and 0 PRs.
Next step: read #16771, then check whether your report is one of the 2 still open.
Read #16771Pick up a Lab taskThe majority of these issues and fixes concern a pattern where context compression failures (network errors, auth failures, LLM errors) silently discard conversation history instead of preserving it. A smaller number address related concerns like user notification on failure, pre-compression hooks, and an unrelated DeFi risk analysis skill addition.
Open issues2
Open PRs0
Fixes on record
-
#16771 fix(compression): notify gateway users when summary generation fails (salvage #16333)
files:
agent/context_compressor.py,gateway/run.py,tests/agent/test_context_compressor.py,tests/gateway/test_compress_command.py,tests/gateway/test_session_hygiene.py -
#51881 fix(compression): abort + preserve context on transient network summary failure (#29559, #25585)
files:
agent/context_compressor.py,tests/agent/test_context_compressor.pyShow the diff
-
agent/context_compressor.py@@ -890,7 +890,15 @@ def __init__( # This is independent of the abort_on_summary_failure config flag: # rotating on a broken credential is never the right behavior. self._last_summary_auth_failure: bool = False - # When a user-configured summary model fails and we recover by + # Set when summary generation ultimately fails due to a transient + # network/connection error (httpx/httpcore connection drop, premature + # stream close, etc.) — distinct from auth failures but treated the + # same way by compress(): ABORT and preserve the session unchanged + # rather than destroy the middle window for a deterministic + # "summary unavailable" marker. Retrying once the network recovers is + # strictly better than discarding context for a transient blip + # (#29559, #25585). Independent of abort_on_summary_failure. + self._last_summary_network_failure: bool = False # retrying on the main model, record the failure so gateway / # CLI callers can still warn the user even though compression # succeeded. Silent recovery would hide the broken config. @@ -1687,6 +1695,7 @@ def _generate_summary( self._summary_model_fallen_back = False self._last_summary_error = None self._last_summary_auth_failure = False + self._last_summary_network_failure = False return self._with_summary_prefix(summary) except Exception as e: # ``call_llm`` raises ``RuntimeError`` for two very different cases: @@ -1819,6 +1828,15 @@ def _generate_summary( if len(err_text) > 220: err_text = err_text[:217].rstrip() + "..." self._last_summary_error = err_text + # A terminal connection/network failure (we reach this branch only + # after any main-model fallback has already been tried or is + # unavailable). Flag it so compress() ABORTS and preserves the + # session unchanged instead of destroying the middle window for a + # placeholder marker — retrying once the network recovers is + # strictly better than dropping context (#29559, #25585). Mirrors + # the auth-failure carve-out; independent of abort_on_summary_failure. + if _is_streaming_closed: + self._last_summary_network_failure = True logger.warning( "Failed to generate context summary: %s. " "Further summary attempts paused for %d seconds.", @@ -2382,6 +2400,7 @@ def compress(self, messages: List[Dict[str, Any]], current_tokens: int = None, f self._last_aux_model_failure_model = None self._last_compress_aborted = False self._last_summary_auth_failure = False + self._last_summary_network_failure = False # Manual /compress (force=True) bypasses the failure cooldown so the # user can retry immediately after an auto-compress abort. Without @@ -2498,15 +2517,21 @@ def compress(self, messages: List[Dict[str, Any]], current_tokens: int = None, f # surface a warning. # Default is False (historical behavior). # - # EXCEPTION — auth failures always abort. A 401/403 from the summary - # call means the credential or endpoint is broken (invalid/blocked - # key, or a token pointed at the wrong inference host). Rotating into + # EXCEPTION — auth AND transient network failures always abort. A + # 401/403 from the summary call means the credential or endpoint is + # broken (invalid/blocked key, or a token pointed at the wrong + # inference host). A connection/stream-close error means the network + # blipped at the compaction moment (#29559). In BOTH cases rotating into # a child session with a placeholder summary on a broken credential # strands the user on a degraded session for zero benefit — every # subsequent call fails the same way. So when the failure was an auth # error we abort regardless of abort_on_summary_failure, preserving # the conversation unchanged until the credential is fixed. - if not summary and (self.abort_on_summary_failure or self._last_summary_auth_failure): + if not summary and ( + self.abort_on_summary_failure + or self._last_summary_auth_failure + or self._last_summary_network_failure + ): n_skipped = compress_end - compress_start self._last_summary_dropped_count = 0 # nothing actually dropped self._last_summary_fallback_used = False @@ -2521,6 +2546,15 @@ def compress(self, messages: List[Dict[str, Any]], current_tokens: int = None, f "with /compress or start fresh with /new.", n_skipped, ) + elif self._last_summary_network_failure: + logger.warning( + "Summary generation failed with a network/connection " + "error — aborting compression. %d message(s) preserved " + "unchanged; the session was NOT rotated. This is " + "transient: retry with /compress once connectivity " + "recovers, or continue the conversation as-is.", + n_skipped, + ) else: logger.warning( "Summary generation failed — aborting compression " -
tests/agent/test_context_compressor.py@@ -683,6 +683,47 @@ def test_non_auth_failure_still_uses_fallback_path(self): assert c._last_compress_aborted is False assert len(result) < len(msgs) # middle window dropped + def test_generate_summary_flags_network_failure(self): + """A connection/network error on the summary call flags + _last_summary_network_failure (#29559).""" + with patch("agent.context_compressor.get_model_context_length", return_value=100000): + c = ContextCompressor(model="test", quiet_mode=True) + with patch( + "agent.context_compressor.call_llm", + side_effect=ConnectionError("Connection error."), + ): + result = c._generate_summary(self._msgs()) + assert result is None + assert c._last_summary_network_failure is True + assert c._last_summary_auth_failure is False + + def test_compress_aborts_on_network_failure_despite_flag_false(self): + """#29559/#25585: abort_on_summary_failure=False (default), but a + transient connection error must ABORT — messages returned unchanged, + _last_compress_aborted=True — NOT drop the middle window. Retrying once + the network recovers beats discarding context for a transient blip.""" + with patch("agent.context_compressor.get_model_context_length", return_value=100000): + c = ContextCompressor( + model="test", + quiet_mode=True, + protect_first_n=2, + protect_last_n=2, + abort_on_summary_failure=False, + ) + msgs = self._msgs(12) + with patch( + "agent.context_compressor.call_llm", + side_effect=ConnectionError("Connection error."), + ): + result = c.compress(msgs, current_tokens=[redacted:secret-assignment] force=True) + # Session must NOT be compressed/rotated — same messages back. + assert result == msgs + assert len(result) == len(msgs) + assert c._last_compress_aborted is True + assert c._last_summary_network_failure is True + # Did NOT fall through to the static-fallback (drop-the-middle) path. + assert c._last_summary_fallback_used is False + def test_aux_model_auth_failure_recovers_on_main_no_abort(self): """A 401 from a DISTINCT auxiliary summary_model retries on the main model; if main succeeds, the auth flag is cleared and compression is
-
10 PRs closed without merging
From the close comments: done elsewhere 3 · merged via another change 3 · superseded by another PR 2 · other 2
-
#11651 fix: preserve context when summary generation fails · superseded by another PR →
#14514 -
#11662 fix(compressor): preserve messages when summarization fails · done elsewhere →
#11585 -
#13034 feat(optional-skills/security): add defi-risk-analysis skill · other →
#11649,#11660,#11662,#11585,#12091,#12092,#12093 -
#14514 fix(agent): preserve messages when compression summary generation fails · other →
#10719,#12131,#11914,#11585 -
#16333 fix(compression): notify gateway users when summary generation fails · merged via another change →
#16771
Show 5 more
-
#25588 fix(compression): preserve context when summary fails · superseded by another PR →
#10719,#14514,#16771 - #29581 fix(compress): retry summary on connection error and preserve recent turns on fallback (#29559) · done elsewhere
-
#46587 fix(agent): flush messages before compression session rotation (#46567) · done elsewhere →
#46567,#47215,#47202,#44794,#39704 -
#47215 fix(agent): flush un-persisted messages before session rotation (#47202) · merged via another change →
81ff916,#47202,#46567 -
#60956 fix(gateway): compact hygiene transcripts in place · merged via another change →
4d611ba,#60981,#60947
Closed issues · 8
- Closed#10719Context compression silently drops all conversation turns when summary generation fails
- Closed#11585Bug: context_compressor drops messages when summarization fails
- Closed#11753[Feature]: Pre-compression task state preservation hook
- Closed#11914[Bug]: Context compression causes permanent task loss — model forgets in-progress work after MiniMax 529
- Closed#12131[Bug]: Context compression failure uses static placeholder instead of preserved message tail — context permanently lost
- Closed#16650Gateway users not notified when compression summary generation fails and history is dropped
- Closed#25585bug(compression): failed summaries should not discard conversation context
- Closed#29559[Bug] Context compression silently drops summary on connection error, causing total context loss for long tasks
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.08: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab