Skip to content

fix(tui): preserve archived compaction history on prompt.submit truncation - #80195

Closed
Adolanium wants to merge 1 commit into
NousResearch:mainfrom
Adolanium:fix/tui-truncation-preserve-archived-history
Closed

fix(tui): preserve archived compaction history on prompt.submit truncation#80195
Adolanium wants to merge 1 commit into
NousResearch:mainfrom
Adolanium:fix/tui-truncation-preserve-archived-history

Conversation

@Adolanium

Copy link
Copy Markdown
Contributor

What does this PR do?

After a successful in-place compaction (compression.in_place, the default since #38763), the pre-compaction transcript stays on disk as soft-archived active=0/compacted=1 rows under the same session id, and SessionDB keeps them searchable. The prompt.submit truncation path in tui_gateway (desktop/TUI edit, regenerate, rewind via truncate_before_user_ordinal) called replace_messages() with the default active_only=False, which DELETEs every row for the session and reinserts only the truncated live tail. Any edit after a compaction permanently destroyed the archived history: hard ID gaps, empty archive queries, missing in_place_committed history.

This mirrors the ACP adapter's _persist: probe has_archived_messages() and pass active_only so only the live rows are replaced when archives exist on disk. Same durability contract the gateway /compress path documents in gateway/slash_commands.py (#61145, #44794, #39704) and that replace_messages states in its own docstring.

Likely a partial explanation for #79391, not the whole story. The explicit_interrupt branch in agent/conversation_compression.py restores the in-memory transcript and aborts before archive_and_compact runs, so an interrupt alone does not DELETE rows. A later edit/regenerate wiping the archives matches the reported "earlier successful in_place_committed archives are missing". Refs rather than fixes.

Related Issue

Refs #79391

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Security fix
  • Documentation update
  • Tests (adding or improving test coverage)
  • Refactor (no behavior change)
  • New skill (bundled or hub)

Changes Made

  • tui_gateway/methods_prompt.py: prompt.submit truncation probes has_archived_messages() and calls replace_messages(..., active_only=has_archived), mirroring acp_adapter/session.py _persist. A probe failure falls back to False, preserving the current behavior for sessions without archived rows.
  • tests/test_tui_gateway_server.py: new regression test drives prompt.submit truncation against a real SessionDB seeded with soft-archived compaction rows and asserts the archived rows survive (active=0/compacted=1) and the live set matches the truncated transcript. Existing fake DBs accept the new active_only kwarg.

Searched for competing PRs on this call site and found none open. #72694 covers the ACP /compress path only. gateway/session.py rewrite_transcript has the same bare call but different semantics (/undo vs archived rewind rows), left out of scope on purpose.

How to Test

  1. scripts/run_tests.sh tests/test_tui_gateway_server.py -k truncat -q
    • 8 passed, 0 failed (includes the new archived-rows regression test)
  2. scripts/run_tests.sh tests/test_tui_gateway_server.py -q
    • 517 passed, 1 failed. The failure is test_write_json_serializes_concurrent_writes, a thread-timing assertion on stdout chunking far from this change. It passes on an isolated rerun and does not touch the truncation path.
  3. Manual reasoning check: seed a session with archive_and_compact(), then truncate via prompt.submit. Before the fix the archived rows are DELETEed; after it they survive with active=0/compacted=1. The new test does exactly this against a real SessionDB.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run the suites above. The one full-file failure is a flaky thread-timing test that passes in isolation and is unrelated to this change
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Windows 11

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) - N/A, the fix brings the caller in line with the existing replace_messages docstring contract
  • I've updated cli-config.yaml.example if I added/changed config keys - N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows - N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide - SQLite query path only, no platform surface. Developed and tested on Windows
  • I've updated tool descriptions/schemas if I changed tool behavior - N/A

Screenshots / Logs

$ scripts/run_tests.sh tests/test_tui_gateway_server.py -k truncat -q
=== Summary: 1 files, 8 tests passed, 0 failed (100% complete) in 8.7s (32 workers) ===

…ation

In-place compaction (compression.in_place, the default since NousResearch#38763)
keeps the pre-compaction transcript on disk as soft-archived
active=0/compacted=1 rows under the live session id. The prompt.submit
truncation path (desktop/TUI edit, regenerate, rewind) called
replace_messages() with the default active_only=False, which DELETEs
every row for the session and reinserts only the truncated live tail,
so any edit after a compaction permanently wiped the archived history.
Same destructive-rewrite class as NousResearch#61145.

The handler now probes has_archived_messages() and passes
active_only=True when archives exist, so only the live rows are
replaced. This mirrors the ACP adapter's _persist and the durability
contract the gateway /compress path already documents (NousResearch#44794, NousResearch#39704).

Likely a partial explanation for NousResearch#79391: the explicit_interrupt branch
in conversation_compression.py restores memory and aborts before
archive_and_compact runs, so the interrupt alone does not delete rows,
but a later truncation wiping the archives matches the reported
missing in_place_committed history.

The regression test runs the truncation path against a real SessionDB
seeded with archived compaction rows and asserts they survive.
@alt-glitch alt-glitch added type/bug Something isn't working comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state area/sessions Session lifecycle, resume, persistence, history area/compression Context compression and continuation sessions labels Aug 6, 2026
kshitijk4poor pushed a commit that referenced this pull request Aug 7, 2026
/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
(#38763), so any /retry after a compaction permanently wiped the
archived history. #57803 named this call site as a residual gap after
its global-default approach was rejected; the TUI sibling was fixed
in #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.
@yuzilongleif-collab

Copy link
Copy Markdown
Contributor

This PR is now fully superseded by merged #80933.

On current main, the TUI prompt edit/regenerate rewrite already uses unconditional replace_messages(..., active_only=True), and #80933 also covers the ACP sibling path plus five real-SQLite/source-guard regression tests. I verified those tests pass (5 passed).

Replaying this PR's commit onto current main produces a conflict in tui_gateway/methods_prompt.py, while its test file applies cleanly, which is consistent with the production fix already being present via #80933.

I recommend closing this PR as superseded rather than rebasing it. No additional code from this branch appears necessary.

ma1138569845 pushed a commit to ma1138569845/dechnicAuditor-agent that referenced this pull request Aug 10, 2026
/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.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
/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.
@Adolanium

Copy link
Copy Markdown
Contributor Author

Agreed, this is fully covered by #80933. Closing as superseded rather than rebasing through the methods_prompt.py conflict.

@Adolanium Adolanium closed this Aug 14, 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 area/sessions Session lifecycle, resume, persistence, history comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists 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.

3 participants