Skip to content

fix(gateway): preserve archived compaction history on /retry and yuanbao recall - #80913

Merged
kshitijk4poor merged 3 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/80218-archived-history
Aug 7, 2026
Merged

fix(gateway): preserve archived compaction history on /retry and yuanbao recall#80913
kshitijk4poor merged 3 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/80218-archived-history

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Summary

After in-place compaction, rewrite_transcript() called replace_messages() with the default active_only=False, DELETEing every row for the session — including soft-archived (active=0) pre-compaction history that archive_and_compact() deliberately keeps on disk (#38763). After any /retry or yuanbao recall redaction on a compacted session, the archived history was gone permanently.

Root cause

gateway/session.py rewrite_transcript called replace_messages(session_id, messages) with active_only defaulting to False. The DELETE had no AND active = 1 clause, so it took the archived rows too. Same data-loss class as #61145; #57803 named this call site as a residual gap after rejecting the auto-detect-inside-rewrite_transcript approach in favor of per-caller guards.

Changes

  • gateway/session.py: rewrite_transcript gains an active_only parameter (default False, forwarded to replace_messages). Docstring updated to remove stale /undo reference and document the active_only contract.
  • gateway/slash_commands.py: /retry passes active_only=True unconditionally — no probe needed (when no archived rows exist, AND active = 1 matches the same rows as no clause). Avoids TOCTOU race and fail-open exception path that a probe would introduce.
  • gateway/platforms/yuanbao.py: both yuanbao recall redaction call sites now pass active_only=True. load_transcript only returns active rows, so the redacted content is in the active set and archived pre-compaction history should survive the rewrite.
  • tests/gateway/test_retry_replacement.py: new regression test seeds archived compaction rows, simulates a probe failure, and asserts archives survive /retry and the live set reflects the truncation + retried exchange.

Attribution

Cherry-picked from PR #80218 by @Adolanium (original fix + regression test) and @poisdahl (fail-safe follow-up). Sibling-site fix (yuanbao callers + docstring cleanup) added on top.

Closes #80216. Closes #80218. Supersedes #80695.

Test plan

  • tests/gateway/test_retry_replacement.py tests/gateway/test_retry_response.py tests/gateway/test_session.py tests/gateway/test_compress_command.py tests/gateway/test_session_hygiene.py — 87 passed, 0 failed
  • tests/gateway/ -k 'yuanbao' — 16 passed, 1 skipped
  • E2E: real SessionDB + SessionStore, seeded with compaction-archived rows → rewrite_transcript(active_only=True) preserves all 4 archived rows; active_only=False destroys them (confirms the bug)
  • ruff: all checks passed
  • py_compile: all files compile

Adolanium and others added 3 commits August 7, 2026 13:45
/retry truncates the live transcript to before the last user message
and persists it via SessionStore.rewrite_transcript, which calls
replace_messages() with the default active_only=False. That DELETEs
every row for the session, including the soft-archived
active=0/compacted=1 rows that in-place compaction keeps on disk
(NousResearch#38763), so any /retry after a compaction permanently wiped the
archived history. NousResearch#57803 named this call site as a residual gap after
its global-default approach was rejected; the TUI sibling was fixed
in NousResearch#80195.

The handler now probes has_archived_messages() (new SessionStore
wrapper, auto-exposed through AsyncSessionStore) and passes
active_only=True when archives exist, so only the live rows are
replaced. rewrite_transcript gains an active_only parameter that
defaults to False, keeping the destructive semantics yuanbao recall
redaction depends on. Also corrects the rewrite_transcript docstring,
which still listed /undo as a caller even though /undo soft-archives
via rewind_session.

The regression test drives _handle_retry_command against a real
SessionStore and SessionDB seeded with archived compaction rows and
asserts the archives survive.
Sibling-site fix for NousResearch#80216: yuanbao recall redaction also calls
rewrite_transcript() and was subject to the same archived-history
data loss when active_only defaulted to False. Pass active_only=True
at both yuanbao call sites — load_transcript only returns active
rows, so the redacted content is in the active set and the archived
pre-compaction history should survive the rewrite.

Also drops the stale 'callers that mean to purge (e.g. yuanbao
recall redaction) keep the default' note from the rewrite_transcript
docstring — no caller intentionally purges archived rows.
@kshitijk4poor
kshitijk4poor merged commit 2d9b809 into NousResearch:main Aug 7, 2026
43 checks passed
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery P1 High — major feature broken, no workaround area/compression Context compression and continuation sessions sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 7, 2026
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/gateway Gateway runner, session dispatch, delivery P1 High — major feature broken, no workaround 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.

[Bug]: /retry permanently deletes archived compaction history on messaging platforms

4 participants