mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-15 14:22:43 +00:00
fix(gateway): prevent hygiene compression from destroying archived transcript
When gateway session-hygiene auto-compression fires with in-place compaction, the flow was: 1. _compress_context() calls archive_and_compact() — soft-archives old rows (active=0, compacted=1) and inserts compacted messages as the new active set. This is the non-destructive, durable path. 2. The hygiene handler then called rewrite_transcript() — which calls replace_messages(active_only=False) — DELETEing ALL rows including the just-archived turns. Silent permanent data loss (#61145). The interactive /compress handler had the same bug. Fix: only call rewrite_transcript() when session rotation produced a new session id (legacy path). When in-place compaction succeeded, skip the rewrite — archive_and_compact() already handled persistence. Closes #61145.
This commit is contained in:
parent
b298fd5db1
commit
549b87c9aa
2 changed files with 49 additions and 21 deletions
|
|
@ -11240,7 +11240,17 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
)
|
||||
|
||||
# Only rewrite the transcript when rotation produced
|
||||
# a NEW session id OR in-place compaction succeeded.
|
||||
# a NEW session id. In-place compaction does NOT
|
||||
# need a rewrite: archive_and_compact() has already
|
||||
# soft-archived the previous active rows and inserted
|
||||
# the compacted messages as the new active set inside
|
||||
# _compress_context(). Calling rewrite_transcript()
|
||||
# after in-place compaction would invoke
|
||||
# replace_messages(active_only=False) which DELETEs
|
||||
# ALL rows — including the archived turns that
|
||||
# archive_and_compact() deliberately preserved
|
||||
# (silent data loss, #61145).
|
||||
#
|
||||
# The danger this guards against (mirrors the
|
||||
# /compress fix #44794/#39704): if _compress_context
|
||||
# returns a summary but neither rotates nor completes
|
||||
|
|
@ -11249,7 +11259,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
# rewrite_transcript() would DELETE the original
|
||||
# messages and replace them with only the compressed
|
||||
# summary (permanent data loss, #21301).
|
||||
if _hyg_rotated or _hyg_in_place:
|
||||
if _hyg_rotated:
|
||||
self.session_store.rewrite_transcript(
|
||||
session_entry.session_id, _compressed
|
||||
)
|
||||
|
|
@ -11260,6 +11270,16 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
_new_tokens = estimate_messages_tokens_rough(
|
||||
_compressed
|
||||
)
|
||||
elif _hyg_in_place:
|
||||
# archive_and_compact() already persisted the
|
||||
# compacted transcript inside _compress_context.
|
||||
# Reset counts to match the new active set.
|
||||
session_entry.last_prompt_tokens = 0
|
||||
history = _compressed
|
||||
_new_count = len(_compressed)
|
||||
_new_tokens = estimate_messages_tokens_rough(
|
||||
_compressed
|
||||
)
|
||||
else:
|
||||
# No rewrite happened — transcript preserved
|
||||
# unchanged, so the post-compression counts equal
|
||||
|
|
|
|||
|
|
@ -3262,18 +3262,23 @@ class GatewaySlashCommandsMixin:
|
|||
# at it; in place the original transcript is untouched) and lets
|
||||
# the outer handler surface a "compress failed" banner instead.
|
||||
#
|
||||
# The rewrite runs when EITHER rotation produced a new id OR
|
||||
# in-place compaction succeeded. It is skipped in the THIRD
|
||||
# case: _compress_context could NOT rotate AND was not in-place
|
||||
# (e.g. legacy mode but _session_db unavailable / the DB split
|
||||
# raised) — there session_id is unchanged for a FAILURE reason,
|
||||
# and rewrite_transcript() would DELETE the original messages and
|
||||
# replace them with only the compressed summary (permanent data
|
||||
# loss #44794, #39704). In in-place mode the unchanged id is
|
||||
# SUCCESS, so the rewrite is exactly right (and is the durable
|
||||
# write when the throwaway /compress agent has no _session_db of
|
||||
# its own).
|
||||
if rotated or _in_place:
|
||||
# Only rewrite the transcript when rotation produced a NEW
|
||||
# session id. In-place compaction does NOT need a rewrite:
|
||||
# archive_and_compact() has already soft-archived the previous
|
||||
# active rows and inserted the compacted messages as the new
|
||||
# active set inside _compress_context(). Calling
|
||||
# rewrite_transcript() after in-place compaction would invoke
|
||||
# replace_messages(active_only=False) which DELETEs ALL rows —
|
||||
# including the archived turns that archive_and_compact()
|
||||
# deliberately preserved (silent data loss, #61145).
|
||||
#
|
||||
# The third case: _compress_context could NOT rotate AND was
|
||||
# not in-place (e.g. legacy mode but _session_db unavailable /
|
||||
# the DB split raised) — there session_id is unchanged for a
|
||||
# FAILURE reason, and rewrite_transcript() would DELETE the
|
||||
# original messages and replace them with only the compressed
|
||||
# summary (permanent data loss #44794, #39704).
|
||||
if rotated:
|
||||
if not self.session_store.rewrite_transcript(
|
||||
new_session_id, compressed
|
||||
):
|
||||
|
|
@ -3281,13 +3286,16 @@ class GatewaySlashCommandsMixin:
|
|||
f"failed to persist compressed transcript for "
|
||||
f"session {new_session_id}"
|
||||
)
|
||||
if rotated:
|
||||
session_entry.session_id = new_session_id
|
||||
self.session_store._save()
|
||||
await asyncio.to_thread(
|
||||
self._sync_telegram_topic_binding,
|
||||
source, session_entry, reason="compress-command",
|
||||
)
|
||||
session_entry.session_id = new_session_id
|
||||
self.session_store._save()
|
||||
await asyncio.to_thread(
|
||||
self._sync_telegram_topic_binding,
|
||||
source, session_entry, reason="compress-command",
|
||||
)
|
||||
elif _in_place:
|
||||
# archive_and_compact() already persisted the compacted
|
||||
# transcript inside _compress_context — nothing to do.
|
||||
pass
|
||||
else:
|
||||
logger.warning(
|
||||
"Manual /compress: session rotation did not occur "
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue