diff --git a/agent/conversation_compression.py b/agent/conversation_compression.py index e11dc7c171d3..3d1890bbbcbb 100644 --- a/agent/conversation_compression.py +++ b/agent/conversation_compression.py @@ -443,6 +443,20 @@ def compress_context( except Exception as _me_err: logger.debug("memory manager on_session_switch (compression): %s", _me_err) + # Migrate goal state to the continuation session so /goal survives + # compression. Goal state is keyed by ``goal:`` in + # state_meta, so it must be copied to the new key. See #33618. + try: + _old_sid = locals().get("old_session_id") + if _old_sid: + from hermes_cli.goals import load_goal, save_goal + + _old_goal = load_goal(_old_sid) + if _old_goal is not None: + save_goal(agent.session_id, _old_goal) + except Exception as _goal_err: + logger.debug("goal migration on compression: %s", _goal_err) + # Warn on repeated compressions (quality degrades with each pass) _cc = agent.context_compressor.compression_count if _cc >= 2: diff --git a/tests/hermes_cli/test_goals.py b/tests/hermes_cli/test_goals.py index 9d8c3f48fe1d..3af555a0057f 100644 --- a/tests/hermes_cli/test_goals.py +++ b/tests/hermes_cli/test_goals.py @@ -252,6 +252,32 @@ def test_persistence_across_managers(self, hermes_home): assert mgr2.state.goal == "do the thing" assert mgr2.is_active() + def test_goal_migrates_across_session_rotation(self, hermes_home): + """Goal must survive context compression session_id rotation. + + Compression creates a continuation session with a new id and + copies the goal via load_goal/save_goal. A GoalManager bound + to the new id must see the original goal. Regression guard + for #33618. + """ + from hermes_cli.goals import GoalManager, load_goal, save_goal + + old_sid = "compress-old-sid" + new_sid = "compress-new-sid" + + mgr_old = GoalManager(session_id=old_sid) + mgr_old.set("ship the feature") + + # Simulate what compress_context now does: + old_goal = load_goal(old_sid) + assert old_goal is not None + save_goal(new_sid, old_goal) + + mgr_new = GoalManager(session_id=new_sid) + assert mgr_new.state is not None + assert mgr_new.state.goal == "ship the feature" + assert mgr_new.is_active() + def test_evaluate_after_turn_done(self, hermes_home): """Judge says done → status=done, no continuation.""" from hermes_cli import goals