fix(agent): keep cooldown state on the active session (#54465)

This commit is contained in:
Rod Boev 2026-06-28 20:23:47 -04:00 committed by kshitij
parent cafe9d9261
commit 6fd701acbe
2 changed files with 57 additions and 1 deletions

View file

@ -692,6 +692,24 @@ class AIAgent:
reset_engine=True,
)
# Reset-only session switches (/new, /resume, /branch) update
# agent.session_id before calling reset_session_state(). The built-in
# compressor keeps durable cooldown state keyed by its bound session,
# so rebind it when the active session changed but no full start hook ran.
engine = getattr(self, "context_compressor", None)
target_session_id = getattr(self, "session_id", "") or ""
bound_session_id = getattr(engine, "_session_id", "") if engine is not None else ""
if (
engine is not None
and hasattr(engine, "bind_session_state")
and target_session_id
and target_session_id != bound_session_id
):
try:
engine.bind_session_state(getattr(self, "_session_db", None), target_session_id)
except Exception as exc:
logger.debug("context engine bind_session_state during reset: %s", exc)
def _ensure_lmstudio_runtime_loaded(self, config_context_length: Optional[int] = None) -> None:
"""
Preload the LM Studio model with at least Hermes' minimum context.

View file

@ -28,7 +28,8 @@ from __future__ import annotations
from unittest.mock import MagicMock
from agent.context_compressor import ContextCompressor
from hermes_state import SessionDB
from run_agent import AIAgent
@ -158,6 +159,43 @@ def test_reset_session_state_default_call_only_resets():
assert not engine.on_session_start.called
def test_reset_session_state_rebinds_builtin_compressor_after_session_switch(tmp_path, monkeypatch):
"""Reset-only session switches must rebind durable cooldown state to the new session."""
db = SessionDB(db_path=tmp_path / "state.db")
db.create_session("old-sid", source="cli")
db.create_session("new-sid", source="cli")
db.record_compression_failure_cooldown("old-sid", 4_000_000_000.0, "old-timeout")
monkeypatch.setattr(
"agent.context_compressor.get_model_context_length",
lambda *_a, **_k: 100_000,
)
compressor = ContextCompressor(
model="fake-model",
threshold_percent=0.85,
protect_first_n=2,
protect_last_n=2,
quiet_mode=True,
)
compressor.bind_session_state(db, "old-sid")
agent = _bare_agent()
agent._session_db = db
agent.context_compressor = compressor
agent.session_id = "new-sid"
agent.reset_session_state()
assert compressor._session_id == "new-sid"
assert compressor.get_active_compression_failure_cooldown() is None
assert db.get_compression_failure_cooldown("old-sid") is not None
compressor._record_compression_failure_cooldown(30.0, "new-timeout")
assert db.get_compression_failure_cooldown("new-sid") is not None
assert db.get_compression_failure_cooldown("old-sid")["error"] == "old-timeout"
def test_update_from_response_forwards_canonical_cache_buckets():
"""conversation_loop passes cache_read/write/reasoning tokens to engine."""
# Test the contract directly: a usage_dict built from CanonicalUsage must