201b646d67
Follow-up to the cherry-picked #31856 fix. The contributor's guard defers idle-TTL eviction until the session store reports the session expired, so the expiry watcher can tear the agent down and fire MemoryProvider.on_session_end() with the live transcript. Two gaps remained: 1. Memory-leak regression for mode='none' sessions. _is_session_expired() returns False forever for the 'none' reset policy, so the naive guard would never idle-evict those agents — reopening the unbounded-cache leak the idle sweep (#11565) exists to relieve. Added SessionStore.is_session_finalizable() (a public predicate: will the expiry watcher EVER finalize this session?) and gate the deferral on it. mode='none' agents fall through to soft eviction as before. 2. on_session_end still dropped on the LRU-cap path. Both cache-pressure paths (_enforce_agent_cache_cap and _sweep_idle_cached_agents) soft-evict via _release_evicted_agent_soft, which by design does NOT fire on_session_end. If cache pressure evicts a finalizable-but-not-yet-expired agent before it expires, the watcher later finds no cached agent and the hook is skipped. Added _commit_memory_before_soft_evict(): at LRU eviction, if the session is finalizable and not yet expired, commit end-of-session extraction via the live agent's own (fully-scoped) memory manager using commit_memory_session() — extraction WITHOUT provider teardown, so the eviction stays soft and a resumed turn keeps working. Skipped for mode='none' (no missed boundary to compensate) and expired sessions (the watcher tears those down directly). This closes #11205 for ALL eviction paths and reset policies, not just the idle-sweep + finite-policy case, while preserving the soft-eviction resumability contract (never calls close() on a live session). Tests: 5 new cases in test_agent_cache.py (mode='none' still reaped, LRU-cap commits for finalizable / skips for none, real is_session_finalizable predicate); all mutation-checked. Contributor's original 2 tests updated to assert the finalizable path explicitly.