From 30aaf8da1e97261d1f3d5a445bee413f8d694b25 Mon Sep 17 00:00:00 2001 From: sharziki Date: Thu, 28 May 2026 14:15:17 -0400 Subject: [PATCH] fix(agent): migrate goal state across compression session rotation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Goal state is stored in state_meta under `goal:`. When context compression rotates the session_id to create a continuation session, GoalManager for the new id finds no goal — the user's /goal silently disappears. Copy the active goal to the continuation session's key during compression, following the same pattern used for session titles, system prompts, context engine hooks, and memory manager hooks. Closes #33618 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- agent/conversation_compression.py | 14 ++++++++++++++ tests/hermes_cli/test_goals.py | 26 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) 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