feat(compression): add keep_history context engine — request-only compaction that preserves the visible chat history - #85611
Conversation
…paction that preserves the visible chat history The built-in compressor rewrites the live transcript on compaction (archive_and_compact soft-archives every active row, then inserts head + summary + tail). UIs that render the session store (desktop app, dashboard) show only active rows, so a compaction visibly collapses the entire conversation into a summary card plus the recent tail — the user can no longer scroll up to review the original messages. This adds a bundled keep_history context engine that compacts background context (tool logs, terminal dumps, file arrays) ONLY in the per-request message list via the select_context() hook, which is request-only by contract. The persisted transcript — and therefore the visible chat history — is never rewritten: - should_compress() always returns False, so the destructive LLM-summary + archive_and_compact path never auto-fires. - select_context() deterministically prunes old tool results when the request crosses the token trigger (dedup, one-line summaries, arg truncation), with a rearm watermark so prompt-cache breaks stay episodic, plus a hard-ceiling trim for pathological sessions. - compress() (manual /compress, gateway hygiene) performs the same deterministic prune — every user/assistant message survives verbatim and no summary card is produced. Also exposes the engine in the desktop settings dropdown and documents it in the developer guide. Refs: NousResearch#45117 (pre-compression history discoverable), NousResearch#82462, NousResearch#78484
feat(compression): add keep_history context engine — request-only compaction that preserves the visible chat history
|
|
Thanks for the thorough review!
|
- safety valve: when neither the model context length nor budget_tokens is known, select_context now falls back to a conservative 128k window instead of returning None, so request compaction can never silently disable itself - docs: distinguish request-level drops (the hard-ceiling trim may drop mid-chat messages from the request the model sees) from persisted drops (the transcript keeps every turn); drop the false claim that threshold feeds the request-prune trigger — name the 0.55 fallback ratio and document threshold as not consulted - config: resolve the compression block through the standard hermes_cli.config.load_config (env overrides, managed scope, profile switches, mtime cache) instead of a hand-rolled YAML read with a silent except; log failures at debug level - tests: cover the missing-budget fallback path; apply ruff format
|
Follow-up pushed (47c32b8) addressing points 1–3:
Full engine suite: 8 passed. ruff check/format clean. |
Problem
When auto-compression triggers, the entire visible chat history disappears from the UI. Everything from the beginning of the conversation is wiped out and replaced with just a summary card — the user can no longer scroll up to review the original past messages.
Root cause
The built-in
ContextCompressor.compress()rewrites the live transcript on every compaction:archive_and_compact()soft-archives every active row (active=0, compacted=1) and re-inserts head + LLM summary + tail as fresh rows. UIs that render the session store (desktop app, dashboard, web) show onlyactive=1rows, so after a compaction the visible timeline collapses to a summary card plus the recent tail.Real-world evidence from a desktop session (
20260813_065425_b72b43): 1503 stored messages, only 197 active — 1044 rows soft-archived by repeated compressions. The archived rows are still in the DB (searchable viasession_search), but the UI cannot show them.Related: #45117 (make pre-compression history discoverable/resumable), #82462, #78484.
Fix
Adds a bundled
keep_historycontext engine that takes the Codex-Desktop approach: compact background context (tool logs, terminal dumps, file arrays) only in the per-request message list via the existingselect_context()hook, which is request-only by contract — the persisted transcript is never rewritten, so the visible chat history stays fully intact and scrollable at all times while the model still receives a bounded, compacted context.should_compress()always returnsFalse— the destructive LLM-summary +archive_and_compactpath never auto-fires.select_context()deterministically prunes old tool results (dedup, one-line summaries for large outputs, tool_call-arg truncation) when the request crosses the token trigger, with a rearm watermark so prompt-cache breaks stay episodic, plus a hard-ceiling trim so the request still fits the window in pathological sessions.compress()(manual/compress, gateway hygiene) performs the same deterministic prune — every user/assistant message survives verbatim and no summary card is produced.compression.*config block (threshold,protect_last_n,min_tail_user_messages,proactive_prune_tokens,proactive_prune_min_result_chars,proactive_prune_min_reclaim_tokens).Opt-in, additive: the default
compressorengine is untouched. Select via:Tests
tests/agent/test_keep_history_engine.py— 8 tests covering discovery, never-triggering destructive compression, loop-prune no-op, request-only pruning (transcript untouched), small-request no-op, rearm gating, andcompress()preserving every chat turn.