Revert "fix(agent): persist _last_summary across restarts with used sentinel"

This reverts commit e5a1416a37b423de95b0fa279e9473110a678112.
This commit is contained in:
Xubin Ren 2026-05-09 15:00:29 +08:00
parent e5a1416a37
commit 9252f4d826
4 changed files with 13 additions and 27 deletions

View File

@ -89,7 +89,6 @@ class AutoCompact:
if summary and summary != "(nothing)": if summary and summary != "(nothing)":
self._summaries[key] = (summary, last_active) self._summaries[key] = (summary, last_active)
session.metadata["_last_summary"] = {"text": summary, "last_active": last_active.isoformat()} session.metadata["_last_summary"] = {"text": summary, "last_active": last_active.isoformat()}
session.metadata.pop("_last_summary_used", None)
session.messages = kept_msgs session.messages = kept_msgs
session.last_consolidated = 0 session.last_consolidated = 0
session.updated_at = datetime.now() session.updated_at = datetime.now()
@ -112,15 +111,13 @@ class AutoCompact:
logger.info("Auto-compact: reloading session {} (archiving={})", key, key in self._archiving) logger.info("Auto-compact: reloading session {} (archiving={})", key, key in self._archiving)
session = self.sessions.get_or_create(key) session = self.sessions.get_or_create(key)
# Hot path: summary from in-memory dict (process hasn't restarted). # Hot path: summary from in-memory dict (process hasn't restarted).
# Also clean metadata copy so stale _last_summary never leaks to disk.
entry = self._summaries.pop(key, None) entry = self._summaries.pop(key, None)
if entry: if entry:
session.metadata["_last_summary_used"] = True session.metadata.pop("_last_summary", None)
return session, self._format_summary(entry[0], entry[1]) return session, self._format_summary(entry[0], entry[1])
# Cold path: summary persisted in session metadata (process restarted). if "_last_summary" in session.metadata:
# Keep it in metadata so it survives restarts; only inject once per meta = session.metadata.pop("_last_summary")
# turn via the _last_summary_used sentinel. self.sessions.save(session)
meta = session.metadata.get("_last_summary")
if meta and not session.metadata.get("_last_summary_used"):
session.metadata["_last_summary_used"] = True
return session, self._format_summary(meta["text"], datetime.fromisoformat(meta["last_active"])) return session, self._format_summary(meta["text"], datetime.fromisoformat(meta["last_active"]))
return session, None return session, None

View File

@ -585,7 +585,6 @@ class Consolidator:
"text": summary, "text": summary,
"last_active": session.updated_at.isoformat(), "last_active": session.updated_at.isoformat(),
} }
session.metadata.pop("_last_summary_used", None)
self.sessions.save(session) self.sessions.save(session)
def estimate_session_prompt_tokens( def estimate_session_prompt_tokens(

View File

@ -1021,15 +1021,13 @@ class TestSummaryPersistence:
assert summary is not None assert summary is not None
assert "User said hello." in summary assert "User said hello." in summary
assert "Inactive for" in summary assert "Inactive for" in summary
# Metadata persists so the summary survives restarts; _last_summary_used # Metadata should be cleaned up after consumption
# sentinel prevents duplicate injection within the same turn. assert "_last_summary" not in reloaded.metadata
assert "_last_summary" in reloaded.metadata
assert reloaded.metadata.get("_last_summary_used") is True
await loop.close_mcp() await loop.close_mcp()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_metadata_cleanup_no_leak(self, tmp_path): async def test_metadata_cleanup_no_leak(self, tmp_path):
"""_last_summary persists in metadata for restart survival; _last_summary_used sentinel prevents duplicate injection.""" """_last_summary should be removed from metadata after being consumed."""
loop = _make_loop(tmp_path, session_ttl_minutes=15) loop = _make_loop(tmp_path, session_ttl_minutes=15)
session = loop.sessions.get_or_create("cli:test") session = loop.sessions.get_or_create("cli:test")
_add_turns(session, 6, prefix="hello") _add_turns(session, 6, prefix="hello")
@ -1052,13 +1050,10 @@ class TestSummaryPersistence:
_, summary = loop.auto_compact.prepare_session(reloaded, "cli:test") _, summary = loop.auto_compact.prepare_session(reloaded, "cli:test")
assert summary is not None assert summary is not None
# Second call: no summary (already consumed this turn) # Second call: no summary (already consumed)
_, summary2 = loop.auto_compact.prepare_session(reloaded, "cli:test") _, summary2 = loop.auto_compact.prepare_session(reloaded, "cli:test")
assert summary2 is None assert summary2 is None
# _last_summary stays in metadata for restart survival; assert "_last_summary" not in reloaded.metadata
# _last_summary_used sentinel prevents duplicate injection.
assert "_last_summary" in reloaded.metadata
assert reloaded.metadata.get("_last_summary_used") is True
await loop.close_mcp() await loop.close_mcp()
@pytest.mark.asyncio @pytest.mark.asyncio
@ -1086,8 +1081,6 @@ class TestSummaryPersistence:
# In-memory path is taken (no restart) # In-memory path is taken (no restart)
_, summary = loop.auto_compact.prepare_session(reloaded, "cli:test") _, summary = loop.auto_compact.prepare_session(reloaded, "cli:test")
assert summary is not None assert summary is not None
# _last_summary stays in metadata for restart survival; # Metadata should also be cleaned up
# _last_summary_used sentinel prevents duplicate injection. assert "_last_summary" not in reloaded.metadata
assert "_last_summary" in reloaded.metadata
assert reloaded.metadata.get("_last_summary_used") is True
await loop.close_mcp() await loop.close_mcp()

View File

@ -190,10 +190,7 @@ async def test_consolidation_persists_summary_for_next_prepare_session(tmp_path,
reloaded, pending = loop.auto_compact.prepare_session(reloaded, "cli:test") reloaded, pending = loop.auto_compact.prepare_session(reloaded, "cli:test")
assert pending is not None assert pending is not None
assert "User discussed project status." in pending assert "User discussed project status." in pending
# _last_summary persists for restart survival; _last_summary_used prevents assert "_last_summary" not in reloaded.metadata
# duplicate injection within the same turn.
assert "_last_summary" in reloaded.metadata
assert reloaded.metadata.get("_last_summary_used") is True
@pytest.mark.asyncio @pytest.mark.asyncio