fix(compressor): preserve memory across repeated compression - #36099
fix(compressor): preserve memory across repeated compression#36099KeyArgo wants to merge 2 commits into
Conversation
P1-5: Calibrated token accountant — learns real/rough token ratio per
(provider, model) from update_from_response samples. Median of last 20
ratios applied by should_compress() to avoid premature compression when
tool schemas inflate rough estimates. Cold-start no-op.
P1-3: Absolute reserve trigger cap — _effective_trigger_tokens() caps
the percentage threshold at (context_length - 7120) so small windows
never plan to leave less than max_output + summarizer + margin free.
_last_compress_over_reserve flag surfaces terminal over-full condition.
P1-4: Softened SUMMARY_PREFIX — removes the "latest message WINS,
discard stale items" over-correction that broke anaphora resolution
("continue", "do the next one", "apply that to the other file").
Replaces with reference-resolution framing: use summary to understand
what the latest message refers to, don't proactively resume unless
asked. Keeps anti-hijack supersede logic, MEMORY.md authority, and end
marker. Previous hardened prefix frozen in _HISTORICAL_SUMMARY_PREFIXES
so persisted summaries re-normalize on next compaction.
P0-1 tests: 3 boundary stress cases for tool_use/result pairing at
compaction edges (machinery was already complete in the live code).
25 new tests; 119 total passing (94 pre-existing + 25 new).
P0-2 (summary-of-summary architectural rewrite) deferred to own sprint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…f-summary)
Replace the lossy iterative re-summarization path (_previous_summary fed
back as LLM input on every compaction) with an append-only segment list.
Key invariant: each turn is summarized EXACTLY ONCE. Segments are never
re-fed through the LLM; they are concatenated deterministically at render
time (_render_summary_from_segments). The LLM only ever sees NEW turns,
with PRIOR CONTEXT shown read-only to handle forward-references and avoid
duplication — not to be rewritten.
Changes:
- _summary_segments: append-only list of {start, end, text} dicts
- _turns_seen: global monotone counter for segment range-anchoring
- _render_summary_from_segments(): labels older segments, keeps newest
verbatim; pure concatenation, no LLM call
- _compact_old_segments(): merges oldest two segments when list exceeds
_SEGMENT_MERGE_THRESHOLD (5) — pure text concat, still no LLM call
- _context_for_new_turns(): builds the PRIOR CONTEXT block for delta calls
- _generate_summary() delta path: "PRIOR CONTEXT / NEW TURNS ONLY" prompt
instead of "PREVIOUS SUMMARY / UPDATE IT" prompt
- Backward compat: legacy _previous_summary from a persisted handoff
message is bootstrapped into _summary_segments on first re-compaction
- _previous_summary kept in sync = rendered view of all segments, so
fallback code paths and __new__-constructed test instances still work
- All getattr guards for __new__ instances (test_compress_focus.py pattern)
10 new tests in TestSegmentSummarization; 2 existing continuity tests
updated to match new delta-path prompt labels (intent preserved).
129 total passing (94 pre-existing + 35 huddle-fix tests).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mxnstrexgl
left a comment
There was a problem hiding this comment.
LGTM — automated review passed. No security, quality, or test coverage issues detected.
teknium1
left a comment
There was a problem hiding this comment.
Thanks for tackling a real long-session failure mode. Current main still recursively feeds PREVIOUS SUMMARY into the compressor prompt and replaces _previous_summary with the rewrite (agent/context_compressor.py:1956-1971, :2063-2071).
Problems
agent/context_compressor.py:1399-1401concatenates old segment text, while:1360-1385renders every segment. The segment-count threshold does not bound rendered tokens, so repeated compaction can recreate the context-pressure problem this change is intended to avoid.- The added state is reset in
on_session_reset(:582-583) but the PR'son_session_endonly clears_previous_summary(:648-662)._context_for_new_turns()reads_summary_segmentsfirst (:1421-1423), allowing cross-session prior context to leak on a reused compressor.
Suggested changes
- Define and test a hard rendered/prior-context token budget across many compactions; concatenating segment bodies is not a bound.
- Clear the new state at every real session boundary and add a reused-compressor regression test.
- Reconcile the reserve portion with current main's effective-input-budget logic in
agent/context_compressor.py:995-1035.
This is an automated hermes-sweeper review.
| return | ||
| a = self._summary_segments[0] | ||
| b = self._summary_segments[1] | ||
| merged_text = (a.get("text") or "").strip() + "\n\n" + (b.get("text") or "").strip() |
There was a problem hiding this comment.
This concatenates every old segment body rather than reducing it. Because _render_summary_from_segments() emits all segment text, keeping five segment objects does not bound either the handoff or the next PRIOR CONTEXT prompt. Please enforce a real token bound and cover repeated passes.
| self._context_probed = False | ||
| self._context_probe_persistable = False | ||
| self._previous_summary = None | ||
| self._summary_segments = [] |
There was a problem hiding this comment.
Please clear _summary_segments and _turns_seen in on_session_end too. That method is a separate lifecycle path and currently only clears _previous_summary; _context_for_new_turns() will prefer surviving segments and leak them into the next reused session.
|
Thanks @KeyArgo — the quantified demonstration of recursive-summary degradation here (53.4% retention vs 100% ledger over 5+ passes on a real transcript) is the best evidence anyone has produced for the Where each half stands on current main:
Closing this vehicle (stale base, mixed scope), but the segments half is explicitly invited back as a focused rebase: segments-only, hard rendered-token budget, session-boundary clearing. Credit for the retention analysis — it will be the benchmark for whatever lands. |
Summary
This PR implements the compression fixes from the May 30 context-compressor huddle:
context_length - 7120Why
Repeated prose re-summarization can degrade operational memory over long sessions. The append-only segment design keeps prior compressed ranges stable while still giving the summarizer read-only prior context for continuity.
Validation
venv/bin/python -m pytest tests/agent/test_context_compressor.py tests/agent/test_context_compressor_huddle_fixes.py tests/agent/test_context_compressor_summary_continuity.pyNotes
The fork branch is currently behind upstream
main; this is opened as a draft so maintainers can review the compression design before merge-readiness cleanup.