Skip to content

fix(compressor): preserve memory across repeated compression - #36099

Closed
KeyArgo wants to merge 2 commits into
NousResearch:mainfrom
KeyArgo:fix/context-compressor-huddle-fixes
Closed

fix(compressor): preserve memory across repeated compression#36099
KeyArgo wants to merge 2 commits into
NousResearch:mainfrom
KeyArgo:fix/context-compressor-huddle-fixes

Conversation

@KeyArgo

@KeyArgo KeyArgo commented May 31, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR implements the compression fixes from the May 30 context-compressor huddle:

  • add calibrated rough-token accounting so compression decisions can learn from real token usage
  • reserve a fixed output/summarizer buffer by capping the effective compression trigger at context_length - 7120
  • soften the summary prefix so prior context remains usable for anaphora without reviving stale active-task hijacks
  • add boundary tests for tool-call/tool-result tail splitting behavior
  • replace repeated summary-of-summary rewriting with append-only summary segments, so each compressed turn range is summarized once and older facts are rendered forward rather than rewritten by the LLM

Why

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.py
    • 129 passed, 1 warning
  • Deterministic session-compression eval against a real 22 MB Codex JSONL transcript:
    • typed ledger retention after 5 passes: 100.0%
    • typed ledger retention after 10 passes: 100.0%
    • typed ledger retention after 25 passes: 100.0%
    • typed ledger retention after 50 passes: 100.0%
    • recursive summary baseline after 5 passes: 53.4%

Notes

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.

ArgoBox Security and others added 2 commits May 31, 2026 13:15
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>
@alt-glitch alt-glitch added type/feature New feature or request comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists labels May 31, 2026

@mxnstrexgl mxnstrexgl left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — automated review passed. No security, quality, or test coverage issues detected.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-1401 concatenates old segment text, while :1360-1385 renders 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's on_session_end only clears _previous_summary (:648-662). _context_for_new_turns() reads _summary_segments first (: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()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 = []

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit area/memory Memory subsystem: store, providers, sync, background reviews area/compression Context compression and continuation sessions labels Jul 13, 2026
@teknium1

Copy link
Copy Markdown
Contributor

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 _previous_summary compounding problem, and it remains a documented known weakness.

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.

@teknium1 teknium1 closed this Jul 23, 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/memory Memory subsystem: store, providers, sync, background reviews comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants