fix(compression): adopt live continuation tip at flush across multi-hop chains - #86666
Merged
Conversation
…op chains A turn writing against a session already closed by compression died with session_persistence_failed and a misleading "this is often a full disk" dialog, even though the store was healthy and a live continuation existed (#82001). Depth-1 recovery (find_live_compression_child) could not resolve lineages with >=2 compression hops (root -> mid -> tip), reproduced independently on two- and three-hop chains. - run_agent.py flush chokepoint: on CompressionSessionClosedError, resolve tip = db.get_compression_tip(old_id) (canonical bounded transitive walk), adopt only when tip != old_id AND the tip row is live, retry the flush exactly once (adoption budget); otherwise fail closed. - gateway/session.py append_to_transcript: replace the depth-1 live-child lookup with the same tip + liveness contract, so gateway transcript reroutes follow full chains. - agent/conversation_compression.py _adopt_live_compression_child: turn-start recovery preflight now resolves via get_compression_tip with the same liveness check, closing the last depth-1 consumer in this family. - classify_persistence_error: new "compression_closed" bucket; the turn-end explanation names compression rotation and tells the client to refresh the session id instead of blaming a full disk. Tests: depth-1 adoption, multi-hop chain adoption (agent + gateway), fail closed with no continuation / stale-closed (ws_orphan_reap) tip, exactly-once adoption budget, and error-wording guards (compression-closed never mentions disk; real disk failures keep disk guidance). Closes #82001 Co-authored-by: Al3xand3r1987 <125030427+Al3xand3r1987@users.noreply.github.com> Co-authored-by: yuzilongleif-collab <235949691+yuzilongleif-collab@users.noreply.github.com>
Contributor
૮ >ﻌ< ა ci reviewrunning on 3d6cedb — fix(compression): adopt live continuation tip at flush acros waiting for more jobs to start…
|
This was referenced Aug 15, 2026
This was referenced Aug 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A turn writing against a session already closed by context compression died with
session_persistence_failedand the misleading "this is often a full disk" dialog — even though the store was healthy and a live continuation existed. Fixes #82001.The depth-1 recovery API (
find_live_compression_child) could not resolve lineages with ≥2 compression hops (root(compressed) → mid(compressed) → tip(live)): the direct child is itself compression-ended, so the lookup returned nothing, adoption failed closed, and the turn died. Reproduced independently three times on this issue (two-hop production trace, minimalroot → mid → tipprobes, and a three-hop deep-research workload).Changes
run_agent.py_flush_messages_to_session_db_unlocked): onCompressionSessionClosedError, resolvetip = db.get_compression_tip(old_id)— the canonical bounded transitive walk (excludes_branched_from/_delegate_from/source='tool'children, prefers live over stale closed siblings likews_orphan_reap). Adopt only whentip != old_idand the tip row is live (ended_at IS NULL), then retry the flush exactly once (adoption budget) — a second closed-parent write fails closed, no loop.get_compression_tipreturns the input id when no continuation exists, sotip == old_idis treated as no-adopt/fail-closed.gateway/session.pyappend_to_transcript): the reroute now uses the same tip + liveness contract instead of the depth-1 lookup, so multi-hop lineages resolve on the gateway write side too.agent/conversation_compression.py_adopt_live_compression_child): the last depth-1 consumer in this family. It resolved a unique live direct child, loaded its transcript, and revalidated before mutating the agent — semantics that map cleanly onto tip resolution, so it now resolves viaget_compression_tipwith the same liveness check and post-load revalidation (confirmed tip == adopted tip). Fail-closed behavior for no-continuation / closed-tip lineages is preserved;recover_rotated_compression_session's orphan-reopen fallback is untouched.classify_persistence_errorgains acompression_closedbucket (added toPERSISTENCE_ERROR_CAUSES; matched by exception type and by the"closed by compression"phrase for RPC-wrapped strings). The turn-completion explanation for this cause names compression rotation and tells the client to refresh/pick up the new session id — it never mentions disk. Real disk failures keep the disk guidance. The per-turn_compression_adoption_faileddiagnostic flag is reset at turn start.Fail-closed semantics are strictly preserved: no live tip → no adoption, no message content or ordering is touched (this is session-identity plumbing only), and the adopted retry replays the exact same unpersisted batch.
Tests
tests/run_agent/test_compression_closed_adoption.py(new, real temp SQLite viahermes_state, no live model):root(compressed) → mid(compressed) → tip(live)adopts the tip — the regression the depth-1 API missedFalse, cause =compression_closedws_orphan_reap) → fail closed, never adopt a closed sessiondiskcause keeps "full disk" guidancetests/gateway/test_session.py: multi-hop chain reroute to the live tip, fail-closed on a stale-closed tip, and the existing backlog-migration mock moved to the canonicalget_compression_tipAPI.Relationship to existing work
root → mid → tipprobes by @yuzilongleif-collab; canonicalget_compression_tippointer and landing plan by @ayushnangia (credited viaCo-authored-by).Infographic