"""Profile derivation: fold only new gists into the existing profile (incremental). The old pass re-digested all ~851 gists every consolidation; this checks the cheap delta path fires in steady state and the full rebuild fires only when it should. """ from __future__ import annotations import importlib import pytest from lyra.memory import Summary @pytest.fixture def prof(monkeypatch): import lyra.profile as profile importlib.reload(profile) return profile def _wire(profile, monkeypatch, gists, covered, existing): """Stub memory + the LLM passes; record which path ran.""" state = {"stored_content": existing, "stored_covered": covered, "calls": []} monkeypatch.setattr(profile.memory, "list_summaries", lambda: [Summary(f"s{i}", g, i, "t") for i, g in enumerate(gists)]) monkeypatch.setattr(profile.memory, "get_profile", lambda: state["stored_content"]) monkeypatch.setattr(profile.memory, "profile_sessions_covered", lambda: state["stored_covered"]) def set_profile(content, sessions_covered, profile_id="self"): state["stored_content"], state["stored_covered"] = content, sessions_covered monkeypatch.setattr(profile.memory, "set_profile", set_profile) monkeypatch.setattr(profile, "_map_reduce", lambda gists, backend: state["calls"].append(("map_reduce", len(gists))) or "facts") monkeypatch.setattr(profile, "_call", lambda prompt, body, backend: state["calls"].append(("fold",)) or "folded profile") return state def test_no_profile_yet_does_full_rebuild(prof, monkeypatch): state = _wire(prof, monkeypatch, gists=["a", "b", "c"], covered=0, existing=None) out = prof.rebuild_profile(backend="local") assert state["calls"] == [("map_reduce", 3)] # mapped all three gists assert out == "facts" and state["stored_covered"] == 3 def test_unchanged_skips_entirely(prof, monkeypatch): state = _wire(prof, monkeypatch, gists=["a", "b"], covered=2, existing="old profile") out = prof.rebuild_profile(backend="local") assert state["calls"] == [] # no LLM work at all assert out == "old profile" def test_small_delta_folds_only_new(prof, monkeypatch): state = _wire(prof, monkeypatch, gists=["a", "b", "c", "d"], covered=2, existing="old profile") out = prof.rebuild_profile(backend="local") assert state["calls"] == [("map_reduce", 2), ("fold",)] # mapped just the 2 new, then folded assert out == "folded profile" and state["stored_covered"] == 4 def test_force_does_full_rebuild(prof, monkeypatch): state = _wire(prof, monkeypatch, gists=["a", "b", "c"], covered=3, existing="old profile") out = prof.rebuild_profile(backend="local", force=True) assert state["calls"] == [("map_reduce", 3)] # ignored the up-to-date profile assert out == "facts" def test_big_gap_falls_back_to_full_rebuild(prof, monkeypatch): gists = [str(i) for i in range(40)] # 30 new > FOLD_LIMIT state = _wire(prof, monkeypatch, gists=gists, covered=10, existing="old profile") out = prof.rebuild_profile(backend="local") assert state["calls"] == [("map_reduce", 40)] # full rebuild, not a giant fold assert out == "facts" def test_crossing_cadence_forces_full_rebuild(prof, monkeypatch): # covered=98, total=102 is a tiny delta, but it crosses the 100-session boundary. gists = [str(i) for i in range(102)] state = _wire(prof, monkeypatch, gists=gists, covered=98, existing="old profile") out = prof.rebuild_profile(backend="local") assert state["calls"] == [("map_reduce", 102)] # anti-drift full rebuild assert out == "facts"