Skip to content

fix(compression): decay head protection after restart when a persisted summary exists - #69342

Merged
teknium1 merged 3 commits into
mainfrom
salvage/57835-protect-head-decay
Jul 22, 2026
Merged

fix(compression): decay head protection after restart when a persisted summary exists#69342
teknium1 merged 3 commits into
mainfrom
salvage/57835-protect-head-decay

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

After a gateway restart, the first compaction no longer re-protects the persisted handoff summary and pre-restart head turns, so the head stops growing one fossil layer per restart+compaction cycle. Root cause: protect_first_n decay keyed off in-memory state only (compression_count / _previous_summary), and _previous_summary rehydration from the persisted handoff ran AFTER compress_start = _protect_head_size(...) was computed.

Changes

  • agent/context_compressor.py: _effective_protect_first_n(messages) now probes a bounded resumed-head window (protect_first_n + 4 messages past the system prompt) for a persisted handoff summary (metadata key or content prefix) and decays protection when found — BEFORE boundary computation, so a restarted process decays exactly like a live one. Applies to in_place mode too.
  • agent/context_compressor.py: first post-restart compaction self-heals — stacked summary fossils (incl. beyond-probe and tail-protected ones) fold into the next summary prompt instead of being copied forward verbatim; _previous_summary rehydration rolls back on compaction abort; deterministic fallback carries a bounded redacted previous-summary snapshot; forced user-leading merged summaries keep the live tail request after the summary end marker so they stay rehydratable.
  • tests/agent/test_context_compressor_summary_continuity.py: 12 new regressions from the source PR (restart fossil folding, stacked handoffs, probe boundary in/out, zero-protect, fallback preservation, abort-then-retry, merged-tail rehydration) plus a restart-simulation test: a fresh ContextCompressor over a transcript containing a persisted handoff computes the same decayed protected-head boundary (compress_start base) as a live already-compacted process.
  • tests/agent/test_context_compressor.py: no-system resumed-handoff role-ordering regression.

Validation

Before After
Fresh compressor + persisted handoff in head head re-protected; old summary + fossils preserved verbatim, head grows per restart protection decays to system-prompt-only; fossils folded into the new summary
bash scripts/run_tests.sh tests/agent/ -k 'protect or head or decay' 162 passed, 0 failed
test_context_compressor.py + test_context_compressor_summary_continuity.py 201 passed, 0 failed

Sibling guards green: test_compression_rotation_state.py, test_resume_stale_active_task.py, test_summary_prefix_tool_use.py (26 passed). Ruff + windows-footguns clean.

Credit

Salvaged from #57835 by @harjothkhara (12-commit series squash-reapplied onto current main with authorship preserved; branch was 1210 commits behind — single add/add conflict with the task-snapshot grounding helpers resolved by keeping both). Follow-up commit adapts three exact-string test pins to main's deterministic task-snapshot grounding (761a0b1) and adds the restart-simulation test.

Fixes #57814.

Infographic

protect-head-restart-decay

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

running on 17e6617

CI timings

CI timings · View job

Wall time 7m17s vs 9m59s (-27.0%). 12 job(s) slower, 6 faster, 2 unchanged.

  • Build&Test Docker image / build (amd64, ubuntu-latest, linux/amd64, type=gha,scope=docker-amd64, type=gha,mode=max,scope=do...: -147.0s
  • Python tests / Run tests slice 7/8: -35.0s
  • Build&Test Docker image / build (arm64, ubuntu-24.04-arm, linux/arm64, type=gha,scope=docker-arm64, type=gha,mode=max,scope...: -35.0s
  • Python tests / Run tests slice 1/8: +26.0s
  • Python tests / Run tests slice 6/8: +17.0s

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/compression Context compression and continuation sessions P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 22, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #57835 and #57814: this current patch retains the restart-decay repair while adding summary-continuity and abort/fallback coverage; maintainers should consolidate on the current-head implementation.

@teknium1
teknium1 force-pushed the salvage/57835-protect-head-decay branch from 81c6bf2 to af9a21d Compare July 22, 2026 14:44
harjothkhara and others added 3 commits July 22, 2026 08:42
protect_first_n decay state (compression_count / _previous_summary) is
in-memory only, so a gateway restart re-protected the persisted handoff
summary and head fossils, growing the head unboundedly across
restart+compaction cycles (#57814).

_effective_protect_first_n now probes a bounded resumed-head window for
a persisted handoff summary (by metadata or content prefix) and decays
protection when one is found, before compress_start is computed. The
first post-restart compaction self-heals: stacked summary fossils are
folded into the next summary prompt instead of preserved verbatim,
rehydration is rolled back on abort, deterministic fallback carries a
bounded redacted previous-summary snapshot, and forced user-leading
merged summaries keep the live tail request after the summary end
marker so they stay rehydratable.

Squashed reapply of the 12-commit series from PR #57835 onto current
main (branch was 1210 commits behind; single add/add conflict with the
task-snapshot grounding helpers resolved by keeping both).

Fixes #57814.
…dd restart-simulation test

- Three '_previous_summary == "fresh summary"' exact pins and one
  transcript-wide fossil-absence pin predated main's deterministic
  task-snapshot grounding (761a0b1), which prepends a
  '## Historical Task Snapshot' section to stored summaries and may
  quote a folded head turn inside the handoff. Re-pin the contracts
  (fresh body present, fossil absent from non-summary messages)
  instead of exact strings.
- Add test_restart_simulation_fresh_compressor_does_not_reprotect_head:
  a fresh ContextCompressor over a transcript containing a persisted
  handoff summary computes the same decayed protected-head boundary
  (compress_start base) as a live already-compacted process, and the
  first post-restart compaction does not preserve pre-restart head
  fossils (#57814).
…he decay scan

Composing #57835's multi-fossil summary scan with #47274's merged-handoff
unwrap: when the restart-decay path pulls a merged handoff into the
compression window, its genuine prior-tail user content must enter the
summarizer input (folded into the fresh summary) rather than being
dropped with the summary row. Standalone handoffs still drop. The
continuity test now pins the composed contract: recovered verbatim OR
via summarizer input, never silently deleted, never duplicated.
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/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint 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.

[Bug]: protect_first_n decay state is in-memory — a gateway restart re-protects old compaction summaries, head grows unboundedly

3 participants