fix(gateway): preserve archived compaction history on /retry - #80218
fix(gateway): preserve archived compaction history on /retry#80218Adolanium wants to merge 2 commits into
Conversation
/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.
|
For #80216, I reproduced the current CI failure on the exact PR head There is also a data-safety issue in the probe design itself:
I prepared a narrow follow-up commit directly on this PR head:
This is equivalent for ordinary sessions: Evidence:
Public commit (based directly on The commit's parent is this PR's head ( git remote add poisdahl https://github.com/poisdahl/hermes-agent.git
git fetch poisdahl codex/pr-80218-retry-archive-safety
git cherry-pick 476e4ee33072a5c675099dbc34955d43c70c706cThis matches |
|
Verified all three points locally, including the CI failure on our head. Cherry-picked your commit as-is (3d03e56), full retry/session/compress/hygiene/discord gate passes 90/0. Thanks for the catch and the ready-made fix. |
|
Merged via #80913 (salvage). Your commits cherry-picked with authorship preserved — all three contributors (@Adolanium, @poisdahl, @Kshitij) credited in git history. Your PR's approach (explicit |
What does this PR do?
On messaging platforms,
/retrytruncates the live transcript to before the last user message and persists it throughSessionStore.rewrite_transcript, which callsreplace_messages()with the defaultactive_only=False. That DELETEs every row for the session, including the soft-archivedactive=0/compacted=1rows that in-place compaction keeps on disk (#38763). Any/retryafter a compaction permanently wiped the archived pre-compaction history.#57803 named this call site as a residual gap after its global-default approach was rejected in favor of per-caller guards, and the TUI sibling (
prompt.submittruncation) was fixed in #80195. This is the gateway half of that gap.The handler now probes
has_archived_messages()and passesactive_only=Truewhen archives exist, so only the live rows are replaced. The default stays False because yuanbao recall redaction deliberately purges. Same contract the ACP adapter's_persistand the gateway/compresscomments already document (#61145, #44794, #39704).Related Issue
Fixes #80216
Type of Change
Changes Made
gateway/slash_commands.py:_handle_retry_commandprobeshas_archived_messages()and passesactive_only=has_archivedtorewrite_transcript.gateway/session.py:rewrite_transcriptgains anactive_onlyparameter (default False, forwarded toreplace_messages), plus aSessionStore.has_archived_messageswrapper thatAsyncSessionStoreexposes automatically through its__getattr__offload. Also corrects therewrite_transcriptdocstring, which still listed/undoas a caller even though/undosoft-archives viarewind_session.tests/gateway/test_retry_replacement.py: new regression test drives_handle_retry_commandagainst a real SessionStore and SessionDB seeded with soft-archived compaction rows and asserts the archives survive (active=0/compacted=1) and the live set reflects the truncation plus the retried exchange.Coordination note: #62031 (open, yuanbao recall) also adds an
active_onlyparameter torewrite_transcriptfor its own caller. The two are complementary. If it merges first this PR shrinks to the call site; if this merges first its rebase is trivial.How to Test
scripts/run_tests.sh tests/gateway/test_retry_replacement.py -qscripts/run_tests.sh 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 -qactive=0), send/retry, thenSELECT count(*) FROM messages WHERE session_id = ? AND active = 0. Before the fix: 0. After: unchanged.Checklist
Code
fix(scope):,feat(scope):, etc.)Documentation & Housekeeping
docs/, docstrings) - therewrite_transcriptdocstring is updated as part of the fix (stale/undoreference removed,active_onlysemantics documented)cli-config.yaml.exampleif I added/changed config keys - N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows - N/AScreenshots / Logs
$ scripts/run_tests.sh 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 -q === Summary: 5 files, 86 tests passed, 0 failed (100% complete) in 8.3s (32 workers) ===