From d115799aa7e627b702ee955f74d86993dcf1a0ee Mon Sep 17 00:00:00 2001 From: Dan Baciu Date: Fri, 11 Sep 2026 13:23:04 +0200 Subject: [PATCH] fix(sleep): skip unchanged staging proposals (LG-40917) --- skillopt_sleep/cycle.py | 20 +++++++++++-- tests/test_sleep_engine.py | 59 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/skillopt_sleep/cycle.py b/skillopt_sleep/cycle.py index f1d7bf02..4ca5dc13 100644 --- a/skillopt_sleep/cycle.py +++ b/skillopt_sleep/cycle.py @@ -988,8 +988,24 @@ def run_sleep_cycle( adopted_paths: List[str] = [] if not dry_run: _progress(cfg, "staging start") - proposed_skill = result.new_skill if (cfg.get("evolve_skill") and result.accepted) else None - proposed_memory = result.new_memory if (cfg.get("evolve_memory") and result.accepted) else None + proposed_skill = ( + result.new_skill + if ( + cfg.get("evolve_skill") + and result.accepted + and result.new_skill != skill + ) + else None + ) + proposed_memory = ( + result.new_memory + if ( + cfg.get("evolve_memory") + and result.accepted + and result.new_memory != memory + ) + else None + ) skill_proposals, skip_notes = _skill_proposals_from_groups( cfg, group_outcomes, diff --git a/tests/test_sleep_engine.py b/tests/test_sleep_engine.py index 3f975361..93fe0a2c 100644 --- a/tests/test_sleep_engine.py +++ b/tests/test_sleep_engine.py @@ -1473,6 +1473,65 @@ def test_cycle_pins_the_exact_managed_skill_and_memory_bytes_it_read(self): os.path.realpath(memory_path), ) + def test_cycle_stages_only_documents_that_changed(self): + from skillopt_sleep.consolidate import ConsolidationResult + + with tempfile.TemporaryDirectory() as proj, tempfile.TemporaryDirectory() as home: + target = os.path.join(proj, ".agents", "skills", "taste", "SKILL.md") + memory_path = os.path.join(proj, "CLAUDE.md") + os.makedirs(os.path.dirname(target), exist_ok=True) + skill = "# managed baseline\nrule\n" + memory = "# memory baseline\npreference\n" + with open(target, "w", encoding="utf-8") as handle: + handle.write(skill) + with open(memory_path, "w", encoding="utf-8") as handle: + handle.write(memory) + cfg = load_config( + invoked_project=proj, + projects="invoked", + backend="mock", + claude_home=os.path.join(home, ".claude"), + target_skill_path=target, + auto_adopt=False, + ) + result = ConsolidationResult( + accepted=True, + gate_action="accept_new_best", + baseline_score=0.1, + candidate_score=0.2, + new_skill=skill, + new_memory=memory + "learned preference\n", + applied_edits=[ + EditRecord("memory", "add", "learned preference") + ], + rejected_edits=[], + holdout_baseline=0.1, + holdout_candidate=0.2, + ) + tasks = assign_splits( + researcher_persona(), holdout_fraction=0.34, seed=42 + ) + + with mock.patch( + "skillopt_sleep.cycle.dream_consolidate", + return_value=result, + ): + outcome = run_sleep_cycle(cfg, seed_tasks=tasks) + + with open( + os.path.join(outcome.staging_dir, "manifest.json"), + encoding="utf-8", + ) as handle: + manifest = json.load(handle) + self.assertFalse(manifest["has_managed_skill"]) + self.assertTrue(manifest["has_managed_memory"]) + self.assertFalse( + os.path.exists(os.path.join(outcome.staging_dir, "proposed_SKILL.md")) + ) + self.assertTrue( + os.path.exists(os.path.join(outcome.staging_dir, "proposed_CLAUDE.md")) + ) + def test_managed_skill_change_during_consolidation_refuses_the_night(self): from skillopt_sleep.consolidate import ConsolidationResult from skillopt_sleep.staging import StagingError, latest_staging