Gateway compression and hygiene transcript rotation/persistence issues
Cluster 21301 · gateway. Data as of
1 open item. Fix merged in #52628 on 25 Jun 2026. Check it covers your case.
The short answer
- Known?
- Yes. 25 related items grouped here, 6 issues already closed.
- Fix event
- A fix was merged to main in PR #52628. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PRs #48584, #52628, #60981. That is separate from the fix event. - Still open
- 0 issues and 1 PR.
Most members concern gateway session hygiene auto-compress and manual /compress, particularly temporary compression agents lacking a session_db, session rotation not happening, and transcripts being rewritten or overwritten. They include bug reports and proposed fixes (passing session_db, guarding rewrites, in-place compression), though the exact outcomes are not established here.
Open issues0
Open PRs1
- PR#78401fix(gateway): hygiene compression must force-shrink over-threshold sessions
Fixes on record
-
#48584 fix(compression): preserve messages across session rotation (flush-before-rotate + /compress rewrite guard)
files:
agent/conversation_compression.py,cli.py,gateway/slash_commands.pyShow the diff
-
agent/conversation_compression.py@@ -512,6 +512,16 @@ def _release_lock() -> None: old_title = agent._session_db.get_session_title(agent.session_id) # Trigger memory extraction on the old session before it rotates. agent.commit_memory_session(messages) + # Flush any un-persisted messages from the current turn to the + # old session *before* rotating. compress_context() can be + # called mid-turn (auto-compress when context exceeds threshold) + # at a point when _flush_messages_to_session_db() has not yet + # run. Without this, messages generated during the current turn + # are silently lost on session rotation (#47202). + try: + agent._flush_messages_to_session_db(messages) + except Exception: + pass # best-effort — don't block compression on a flush error agent._session_db.end_session(agent.session_id, "compression") old_session_id = agent.session_id agent.session_id = f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:6]}" -
cli.py@@ -5957,6 +5957,18 @@ def new_session(self, silent=False, title=None): old_session_id = self.session_id if self._session_db and old_session_id: + # Flush any un-persisted messages from the current turn to the + # old session *before* rotating. /new can be called mid-turn + # when _flush_messages_to_session_db() has not yet run — without + # this, messages generated during the current turn are silently + # lost on session rotation (#47202). + if self.agent: + try: + self.agent._flush_messages_to_session_db( + self.conversation_history + ) + except Exception: + pass # best-effort try: self._session_db.end_session(old_session_id, "new_session") except Exception: -
gateway/slash_commands.py@@ -2588,14 +2588,29 @@ async def _handle_compress_command(self, event: MessageEvent) -> str: # session_id for the continuation. Write the compressed messages # into the NEW session so the original history stays searchable. new_session_id = tmp_agent.session_id - if new_session_id != session_entry.session_id: + rotated = new_session_id != session_entry.session_id + if rotated: session_entry.session_id = new_session_id self.session_store._save() self._sync_telegram_topic_binding( source, session_entry, reason="compress-command", ) - self.session_store.rewrite_transcript(new_session_id, compressed) + # Only rewrite the transcript when rotation actually produced a + # NEW session id. If _compress_context could not rotate (e.g. + # _session_db unavailable, or the DB split raised), session_id + # is unchanged and rewrite_transcript() would DELETE the + # original messages and replace them with only the compressed + # summary — permanent data loss (#44794, #39704). In that case + # leave the original transcript intact. + if rotated: + self.session_store.rewrite_transcript(new_session_id, compressed) + else: + logger.warning( + "Manual /compress: session rotation did not occur " + "(session_id unchanged) — preserving original transcript " + "instead of overwriting it (#44794)." + ) # Reset stored token count — transcript changed, old value is stale self.session_store.update_session( session_entry.session_key, last_prompt_tokens=0
-
-
#49925 fix(gateway): preserve transcript when hygiene auto-compress can't rotate (#21301)
files:
gateway/run.py,tests/gateway/test_session_hygiene.py -
#52628 fix(gateway): pass session_db to compress temp agents so persistence works
files:
gateway/run.py,gateway/slash_commands.py,tests/gateway/test_compress_command.py,tests/gateway/test_compress_focus.py,tests/gateway/test_compress_plugin_engine.pyShow the diff
-
gateway/run.py@@ -9550,14 +9550,15 @@ async def _handle_message_with_agent(self, event, source, _quick_key: str, run_g skip_memory=True, enabled_toolsets=["memory"], session_id=session_entry.session_id, + session_db=self._session_db, ) try: # The hygiene agent rotates the session # forward to a continuation id that becomes # the gateway session's live row. It must - # never finalize on close() (today it has no - # session_db so close() no-ops, but this - # guards a future where one is wired in). + # never finalize on close() — close() would + # end the newly rotated session the gateway + # entry now points at. _hyg_agent._end_session_on_close = False _hyg_agent._print_fn = lambda *a, **kw: None -
gateway/slash_commands.py@@ -2818,9 +2818,14 @@ async def _handle_compress_command(self, event: MessageEvent) -> str: skip_memory=True, enabled_toolsets=["memory"], session_id=session_entry.session_id, + session_db=self._session_db, ) try: tmp_agent._print_fn = lambda *a, **kw: None + # Prevent close() from ending the newly rotated session — + # the gateway session entry now points at the new id and + # must remain open for the next user turn. + tmp_agent._end_session_on_close = False # Estimate with system prompt + tool schemas included so the # figure reflects real request pressure, not a transcript-only -
tests/gateway/test_compress_command.py@@ -54,6 +54,7 @@ def _make_runner(history: list[dict[str, str]]): runner.session_store.rewrite_transcript = MagicMock() runner.session_store.update_session = MagicMock() runner.session_store._save = MagicMock() + runner._session_db = None return runner @@ -247,3 +248,60 @@ def _estimate(messages, **_kwargs): assert "intact" in result agent_instance.shutdown_memory_provider.assert_called_once() agent_instance.close.assert_called_once() + + [redacted:email] +async def test_compress_command_passes_session_db_and_persists_rotated_session(): + """session_db must be wired into the /compress temp agent so that + _compress_context can actually rotate the session and persist the + compressed transcript — without it compression is a silent no-op.""" + history = _make_history() + compressed = [ + history[0], + {"role": "assistant", "content": "compressed summary"}, + history[-1], + ] + runner = _make_runner(history) + runner._session_db = object() + agent_instance = MagicMock() + agent_instance.shutdown_memory_provider = MagicMock() + agent_instance.close = MagicMock() + agent_instance._cached_system_prompt = "" + agent_instance.tools = None + agent_instance.context_compressor.has_content_to_compress.return_value = True + agent_instance.compression_in_place = False + agent_instance.session_id = "sess-1" + + def _compress(messages, *_args, **_kwargs): + agent_instance.session_id = "sess-2" + return compressed, "" + + agent_instance._compress_context.side_effect = _compress + + def _estimate(messages, **_kwargs): + if messages == history: + return 100 + if messages == compressed: + return 60 + raise AssertionError(f"unexpected transcript: {messages!r}") + + with ( + patch("gateway.run._resolve_runtime_agent_kwargs", return_value={"api_key": "***"}), + patch("gateway.run._resolve_gateway_model", return_value="test-model"), + patch("run_agent.AIAgent", return_value=agent_instance) as mock_agent_cls, + patch("agent.model_metadata.estimate_request_tokens_rough", side_effect=_estimate), + ): + result = await runner._handle_compress_command(_make_event()) + + assert "Compressed:" in result + mock_agent_cls.assert_called_once() + assert mock_agent_cls.call_args.kwargs["session_db"] is runner._session_db + runner.session_store._save.assert_called_once() + runner.session_store.rewrite_transcript.assert_called_once_with( + "sess-2", compressed + ) + runner.session_store.update_session.assert_called_once_with( + build_session_key(_make_source()), last_prompt_tokens=0 + ) + agent_instance.shutdown_memory_provider.assert_called_once() + agent_instance.close.assert_called_once() -
tests/gateway/test_compress_focus.py@@ -54,6 +54,7 @@ def _make_runner(history: list[dict[str, str]]): runner.session_store.rewrite_transcript = MagicMock() runner.session_store.update_session = MagicMock() runner.session_store._save = MagicMock() + runner._session_db = None return runner -
tests/gateway/test_compress_plugin_engine.py@@ -101,6 +101,7 @@ def _make_runner(history: list[dict[str, str]]): runner.session_store.rewrite_transcript = MagicMock() runner.session_store.update_session = MagicMock() runner.session_store._save = MagicMock() + runner._session_db = None return runner
-
-
#52658 feat(compression): flip in_place default to True + guard fix (#38763) [salvage #51959]
files:
gateway/run.py,gateway/slash_commands.py,hermes_cli/config.py,scripts/release.py,tests/agent/test_compression_concurrent_fork.py,tests/agent/test_compression_logging_session_context.py,tests/agent/test_compression_rotation_state.py,tests/gateway/test_compression_concurrent_sessions.py,tests/gateway/test_session_hygiene.py,tests/run_agent/test_compression_boundary_hook.py,tests/run_agent/test_in_place_compaction.py -
#60981 fix(gateway): compact hygiene transcripts in place (#60947)
files:
gateway/run.py,scripts/release.py,tests/gateway/test_session_hygiene.py
13 PRs closed without merging
From the close comments: done elsewhere 5 · merged via another change 5 · duplicate 2 · superseded by another PR 1
-
#21313 fix(gateway): preserve transcript on /compress and hygiene auto-compress · merged via another change →
#49925 -
#39731 fix(gateway): prevent session hygiene compression from overwriting original transcript · done elsewhere →
#52658,#50098,#38763 -
#39745 fix(gateway): pass session_db to hygiene agent for proper session rotation (#39704) · merged via another change →
#49925 -
#40112 fix: guard compress rewrite_transcript on session rotation (#39704) · done elsewhere →
4c349e8 -
#41000 Fix #39704: Guard session transcript rewrite on successful rotation · done elsewhere →
4c349e8
Show 8 more
-
#44867 fix(gateway): only rewrite transcript when session rotation happened (#44794) · duplicate →
#35905,#39704,#40112,#41000,#44794 -
#50935 fix(gateway): pass session_db to hygiene and manual compress agents for proper session rotation · merged via another change →
#52628,#49925 -
#51624 fix(gateway): preserve session rotation for manual /compress · merged via another change →
#52628 -
#60956 fix(gateway): compact hygiene transcripts in place · merged via another change →
4d611ba,#60981,#60947 -
#60964 fix(gateway): set compression_in_place on session hygiene agent · duplicate →
#60956,#60947,#60328,#60963,#60964,#60965 -
#71940 fix(gateway): compact hygiene transcripts in place · done elsewhere →
d6a275b - #78389 fix(gateway): hygiene compression must force-shrink over-threshold sessions · superseded by another PR
-
#80669 fix: wire gateway SessionDB into the hygiene compression agent (#21301) · done elsewhere →
1a38a8f,73c8d5a,d6a275b,#21301
Closed issues · 6
- Closed#21301[bug] /compress and gateway hygiene compression destroy original transcript — tmp_agent missing session_db kwarg
- Closed#39704Bug: Session Hygiene compression overwrites original messages when _session_db is None
- Closed#44794/compress deletes original messages from state.db when session rotation fails
- Closed#51469Manual gateway /compress succeeds but no-ops because temporary agent never rotates session without session_db
- Closed#58452Hygiene compression never persists: summary discarded when hygiene agent has no session_db
- Closed#60947Gateway session hygiene can no-op on oversized Telegram transcripts, leaving users stuck on spinning replies
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