"""Dream-cycle tests: backlog sensing + a full forced pass, with LLM/embeddings stubbed so nothing hits a real backend.""" from __future__ import annotations import importlib import pytest @pytest.fixture def lyra(tmp_path, monkeypatch): """A fresh Lyra wired to a temp DB with stubbed embeddings + LLM.""" monkeypatch.setenv("LYRA_DB_PATH", str(tmp_path / "test.db")) monkeypatch.setenv("SUMMARY_BACKEND", "local") from lyra import llm # Deterministic 3-d embeddings; content-insensitive is fine for storage tests. monkeypatch.setattr(llm, "embed", lambda texts: [[0.1, 0.2, 0.3] for _ in texts]) # reflect() expects JSON back; everything else just stores the text. monkeypatch.setattr( llm, "complete", lambda messages, backend=None, model=None: '{"mood":"focused","valence":0.7,"new_reflections":["I got some thinking done."]}', ) import lyra.memory as memory importlib.reload(memory) # drop any cached connection from another test/db return memory def _seed(memory, session_id, n, summarized_up_to=None): ids = [memory.remember(session_id, "user", f"msg {i}") for i in range(n)] if summarized_up_to is not None: memory.store_summary(session_id, "gist", ids[summarized_up_to]) return ids def test_backlog_stats(lyra): memory = lyra _seed(memory, "s-fresh", 5) # never summarized -> ripe _seed(memory, "s-ripe", 25, summarized_up_to=0) # 24 new turns -> ripe _seed(memory, "s-clean", 3, summarized_up_to=2) # caught up -> not dirty stats = memory.backlog_stats(ripe_threshold=20) assert stats["sessions"] == 3 assert stats["dirty"] == 2 assert stats["ripe"] == 2 assert stats["max_exchange_id"] == 33 def test_dream_cycle_consolidates_and_persists(lyra): memory = lyra from lyra import dream # A big backlog: enough never-summarized sessions that continuity saturates # and the resulting fresh gists push coherence past threshold too. for k in range(7): _seed(memory, f"s{k}", 4) state = dream.dream_cycle(force=False) # continuity built up and fired -> sessions got summarized assert len(memory.list_summaries()) == 7 acts = state["dream"]["last_actions"] assert any("consolidated" in a for a in acts) # 7 fresh gists -> coherence crossed threshold -> profile got integrated assert any("integrated" in a for a in acts) assert memory.get_profile() is not None # drives + bookkeeping persisted and reload-able assert set(state["drives"]) == {"continuity", "coherence", "curiosity", "stability"} assert state["dream"]["cycle_count"] == 1 assert memory.get_self_state()["dream"]["last_exchange_id"] == 28 # a second pass with no new activity should rest (continuity relieved) state2 = dream.dream_cycle(force=False) assert state2["dream"]["cycle_count"] == 2 assert state2["drives"]["continuity"] == 0.0