fix(compression): stop cross-session _previous_summary contamination#41717
Conversation
… contamination When a cron or background session compacts, it sets _previous_summary for iterative updates. If that session ends without /new or /reset (which calls on_session_reset()), the stale summary survives on the ContextCompressor instance. A subsequent live messaging session's compaction then injects it as 'PREVIOUS SUMMARY:' into the summarizer prompt — contaminating the live session with unrelated content from the prior session. Add an else guard in compress(): when no handoff summary is found in the current messages but _previous_summary is non-empty, discard it so _generate_summary() starts fresh instead of iteratively updating a stale cross-session summary. Fixes #38788
…depth) ContextCompressor inherited a no-op on_session_end() from ContextEngine, so per-session iterative-summary state (_previous_summary) survived a real session boundary on a reused compressor instance. Override it to clear the summary the moment the owning session ends, complementing the point-of-use guard in compress(). Closes the cross-session contamination path in #38788. Co-authored-by: dusterbloom <32869278+dusterbloom@users.noreply.github.com>
|
Positive verification — clean security review Reviewed the cross-session
No issues found. |
🔎 Lint report:
|
| Rule | Count |
|---|---|
no-matching-overload |
1 |
First entries
tests/agent/test_context_compressor_cross_session_guard.py:26: [no-matching-overload] no-matching-overload: No overload of bound method `MutableMapping.setdefault` matches arguments
✅ Fixed issues: none
Unchanged: 5201 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
Summary
Compaction summaries no longer leak across session boundaries — a cron/background session's summary can no longer contaminate the next live conversation (#38788).
Root cause:
ContextCompressor._previous_summaryis per-session iterative-summary state. It was cleared byon_session_reset()(only/newand/reset), butContextCompressornever overrodeon_session_end()— it inherited a no-op fromContextEngine. On a reused compressor instance, a cron session's_previous_summarysurvived a real session boundary and got injected as the "PREVIOUS SUMMARY:" block into the next live session's summarizer prompt, producing off-topic / fabricated output.Two complementary layers, covering genuinely different leak paths (verified below):
Changes
agent/context_compressor.py:compress(), @basilalshukaili): when no handoff summary is found in the current messages but_previous_summaryis non-empty, discard it before_generate_summary(). Catches the leak regardless of how the stale state arrived.on_session_end()override, @dusterbloom): clear_previous_summarythe moment the owning session ends. Closes the remaining hole the point-of-use guard alone leaves open — when the live session has its own prior summary in messages (handoff present), theelifdoesn't fire, so a stale cron summary would otherwise survive on a reused instance.scripts/release.py: addbasilalshukailitoAUTHOR_MAP.tests/agent/test_context_compressor_cross_session_guard.py: 3 tests for the point-of-use guard (@basilalshukaili).Validation
_previous_summarysurvivesContextCompressor, real method calls): confirmed both guards fire on their respective paths, and that legitimate intra-session iterative summarization is preserved (real summary prefix is still recognized and kept).Credit
on_session_endlifecycle clear: @dusterbloom's approach (fix(agent): clear _previous_summary on session end to prevent cross-contamination (#38788) #39725), rewritten cleanly (the original diff was malformed and bundled formal-spec files).Closes #38788. Supersedes #38832 and #39725.
Infographic