Skip to content

fix(goals): forward standing /goal state on auto-compression session rotation#23530

Merged
teknium1 merged 1 commit into
mainfrom
fix/goal-survives-compression
May 11, 2026
Merged

fix(goals): forward standing /goal state on auto-compression session rotation#23530
teknium1 merged 1 commit into
mainfrom
fix/goal-survives-compression

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Auto-compression silently kills any standing /goal. run_agent._compress_context rotates self.session_id to a brand new ID when context fills up, and the goal state lives in state_meta keyed on the old session_id. After rotation load_goal(new_sid) returns None, mgr.is_active() flips to False, and the continuation loop dies with no message to the user.

Fix forwards the goal:<old> row to goal:<new> in the same transaction that creates the continuation session.

Root cause

In run_agent.py::_compress_context:

  1. SessionDB.end_session(self.session_id, "compression") ends the parent
  2. self.session_id = <new uuid> rotates
  3. SessionDB.create_session(parent_session_id=old_session_id, ...) creates the child
  4. CLI / gateway / TUI gateway all pick up the new session_id for downstream calls
  5. _get_goal_manager() rebinds with the new ID → load_goal(new_sid)None → goal gone

This is a regression risk for any long-running goal that compresses at least once. The harsher decompose Phase A makes this worse because the checklist is now substantial state worth preserving.

Changes

run_agent.py — inside _compress_context, immediately after create_session, copy state_meta[goal:<old>] to state_meta[goal:<new>] when present. No-op when no goal is set. Logged at INFO so a stuck loop is debuggable.

tests/hermes_cli/test_goals.py — 2 new tests:

  • Round-trip: set a goal under parent_sid, mirror the production forwarding block, verify a fresh GoalManager(new_sid) loads identical state.
  • No-op: forwarding doesn't accidentally write a stub row when the parent has no goal.

Validation

Before After
tests/hermes_cli/test_goals.py 61 passing 63 passing (2 new)

The full _compress_context method has ~60 dependencies (sessions, memory provider, system prompt rebuild, title carryover, etc.) so the test mirrors the forwarding block directly rather than running the whole method. The block under test is pure SessionDB get_meta / set_meta so the mirror is faithful.

Surfaces affected

CLI, gateway, TUI gateway — all three call into _compress_context via run_conversation. Single rotation site, single fix.

…rotation

When run_agent's _compress_context fires mid-turn it ends the parent
session in SessionDB and creates a new continuation session with a
fresh session_id. The /goal state is keyed on session_id in
state_meta ("goal:<sid>"), so without forwarding the goal silently
disappears: _get_goal_manager() rebinds for the new session_id,
load_goal() returns None, mgr.is_active() is False, and the
continuation loop dies with no user-visible signal.

Fix: in the same SessionDB transaction block that creates the
continuation session, copy state_meta[goal:<old>] →
state_meta[goal:<new>] when present. No-op when the user has no
active goal. Logged at INFO so a stuck loop is debuggable.

Tests cover the round-trip via SessionDB and the no-op path.

Affects all three run-conversation surfaces (CLI, gateway, TUI
gateway) because _compress_context is the single rotation site.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: fix/goal-survives-compression vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 8129 on HEAD, 8124 on base (🆕 +5)

🆕 New issues (1):

Rule Count
unresolved-attribute 1
First entries
tests/hermes_cli/test_goals.py:1111: [unresolved-attribute] unresolved-attribute: Attribute `goal` is not defined on `None` in union `GoalState | None`

✅ Fixed issues: none

Unchanged: 4269 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch

Copy link
Copy Markdown
Collaborator

Competing PRs for same goal-loss-on-compression bug: #18427, #18749. Related issue: #18467.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent loop, run_agent.py, prompt builder labels May 11, 2026
@teknium1 teknium1 merged commit 4a080b1 into main May 11, 2026
15 of 18 checks passed
@teknium1 teknium1 deleted the fix/goal-survives-compression branch May 11, 2026 03:41
teknium1 added a commit that referenced this pull request May 11, 2026
* Revert "fix(goals): force judge to use tool calls instead of JSON-text replies (#23547)"

This reverts commit a63a2b7.

* Revert "fix(goals): forward standing /goal state on auto-compression session rotation (#23530)"

This reverts commit 4a080b1.

* Revert "feat(goals): /goal checklist + /subgoal user controls (#23456)"

This reverts commit 404640a.
rmulligan pushed a commit to rmulligan/hermes-agent that referenced this pull request May 11, 2026
…rotation (NousResearch#23530)

When run_agent's _compress_context fires mid-turn it ends the parent
session in SessionDB and creates a new continuation session with a
fresh session_id. The /goal state is keyed on session_id in
state_meta ("goal:<sid>"), so without forwarding the goal silently
disappears: _get_goal_manager() rebinds for the new session_id,
load_goal() returns None, mgr.is_active() is False, and the
continuation loop dies with no user-visible signal.

Fix: in the same SessionDB transaction block that creates the
continuation session, copy state_meta[goal:<old>] →
state_meta[goal:<new>] when present. No-op when the user has no
active goal. Logged at INFO so a stuck loop is debuggable.

Tests cover the round-trip via SessionDB and the no-op path.

Affects all three run-conversation surfaces (CLI, gateway, TUI
gateway) because _compress_context is the single rotation site.
rmulligan pushed a commit to rmulligan/hermes-agent that referenced this pull request May 11, 2026
…rch#23813)

* Revert "fix(goals): force judge to use tool calls instead of JSON-text replies (NousResearch#23547)"

This reverts commit a63a2b7.

* Revert "fix(goals): forward standing /goal state on auto-compression session rotation (NousResearch#23530)"

This reverts commit 4a080b1.

* Revert "feat(goals): /goal checklist + /subgoal user controls (NousResearch#23456)"

This reverts commit 404640a.
JinyuID pushed a commit to JinyuID/hermes-agent that referenced this pull request May 11, 2026
…rotation (NousResearch#23530)

When run_agent's _compress_context fires mid-turn it ends the parent
session in SessionDB and creates a new continuation session with a
fresh session_id. The /goal state is keyed on session_id in
state_meta ("goal:<sid>"), so without forwarding the goal silently
disappears: _get_goal_manager() rebinds for the new session_id,
load_goal() returns None, mgr.is_active() is False, and the
continuation loop dies with no user-visible signal.

Fix: in the same SessionDB transaction block that creates the
continuation session, copy state_meta[goal:<old>] →
state_meta[goal:<new>] when present. No-op when the user has no
active goal. Logged at INFO so a stuck loop is debuggable.

Tests cover the round-trip via SessionDB and the no-op path.

Affects all three run-conversation surfaces (CLI, gateway, TUI
gateway) because _compress_context is the single rotation site.
JinyuID pushed a commit to JinyuID/hermes-agent that referenced this pull request May 11, 2026
…rch#23813)

* Revert "fix(goals): force judge to use tool calls instead of JSON-text replies (NousResearch#23547)"

This reverts commit d7d4d91.

* Revert "fix(goals): forward standing /goal state on auto-compression session rotation (NousResearch#23530)"

This reverts commit 0398de7.

* Revert "feat(goals): /goal checklist + /subgoal user controls (NousResearch#23456)"

This reverts commit b968856.
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
…rotation (NousResearch#23530)

When run_agent's _compress_context fires mid-turn it ends the parent
session in SessionDB and creates a new continuation session with a
fresh session_id. The /goal state is keyed on session_id in
state_meta ("goal:<sid>"), so without forwarding the goal silently
disappears: _get_goal_manager() rebinds for the new session_id,
load_goal() returns None, mgr.is_active() is False, and the
continuation loop dies with no user-visible signal.

Fix: in the same SessionDB transaction block that creates the
continuation session, copy state_meta[goal:<old>] →
state_meta[goal:<new>] when present. No-op when the user has no
active goal. Logged at INFO so a stuck loop is debuggable.

Tests cover the round-trip via SessionDB and the no-op path.

Affects all three run-conversation surfaces (CLI, gateway, TUI
gateway) because _compress_context is the single rotation site.
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
…rch#23813)

* Revert "fix(goals): force judge to use tool calls instead of JSON-text replies (NousResearch#23547)"

This reverts commit a63a2b7.

* Revert "fix(goals): forward standing /goal state on auto-compression session rotation (NousResearch#23530)"

This reverts commit 4a080b1.

* Revert "feat(goals): /goal checklist + /subgoal user controls (NousResearch#23456)"

This reverts commit 404640a.
jsboige pushed a commit to jsboige/hermes-agent that referenced this pull request May 14, 2026
…rotation (NousResearch#23530)

When run_agent's _compress_context fires mid-turn it ends the parent
session in SessionDB and creates a new continuation session with a
fresh session_id. The /goal state is keyed on session_id in
state_meta ("goal:<sid>"), so without forwarding the goal silently
disappears: _get_goal_manager() rebinds for the new session_id,
load_goal() returns None, mgr.is_active() is False, and the
continuation loop dies with no user-visible signal.

Fix: in the same SessionDB transaction block that creates the
continuation session, copy state_meta[goal:<old>] →
state_meta[goal:<new>] when present. No-op when the user has no
active goal. Logged at INFO so a stuck loop is debuggable.

Tests cover the round-trip via SessionDB and the no-op path.

Affects all three run-conversation surfaces (CLI, gateway, TUI
gateway) because _compress_context is the single rotation site.
jsboige pushed a commit to jsboige/hermes-agent that referenced this pull request May 14, 2026
…rch#23813)

* Revert "fix(goals): force judge to use tool calls instead of JSON-text replies (NousResearch#23547)"

This reverts commit 4e224c0.

* Revert "fix(goals): forward standing /goal state on auto-compression session rotation (NousResearch#23530)"

This reverts commit f7865f8.

* Revert "feat(goals): /goal checklist + /subgoal user controls (NousResearch#23456)"

This reverts commit be5fc05.
AlexFoxD pushed a commit to AlexFoxD/hermes-agent that referenced this pull request May 21, 2026
…rotation (NousResearch#23530)

When run_agent's _compress_context fires mid-turn it ends the parent
session in SessionDB and creates a new continuation session with a
fresh session_id. The /goal state is keyed on session_id in
state_meta ("goal:<sid>"), so without forwarding the goal silently
disappears: _get_goal_manager() rebinds for the new session_id,
load_goal() returns None, mgr.is_active() is False, and the
continuation loop dies with no user-visible signal.

Fix: in the same SessionDB transaction block that creates the
continuation session, copy state_meta[goal:<old>] →
state_meta[goal:<new>] when present. No-op when the user has no
active goal. Logged at INFO so a stuck loop is debuggable.

Tests cover the round-trip via SessionDB and the no-op path.

Affects all three run-conversation surfaces (CLI, gateway, TUI
gateway) because _compress_context is the single rotation site.
AlexFoxD pushed a commit to AlexFoxD/hermes-agent that referenced this pull request May 21, 2026
…rch#23813)

* Revert "fix(goals): force judge to use tool calls instead of JSON-text replies (NousResearch#23547)"

This reverts commit a63a2b7.

* Revert "fix(goals): forward standing /goal state on auto-compression session rotation (NousResearch#23530)"

This reverts commit 4a080b1.

* Revert "feat(goals): /goal checklist + /subgoal user controls (NousResearch#23456)"

This reverts commit 404640a.
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…rotation (NousResearch#23530)

When run_agent's _compress_context fires mid-turn it ends the parent
session in SessionDB and creates a new continuation session with a
fresh session_id. The /goal state is keyed on session_id in
state_meta ("goal:<sid>"), so without forwarding the goal silently
disappears: _get_goal_manager() rebinds for the new session_id,
load_goal() returns None, mgr.is_active() is False, and the
continuation loop dies with no user-visible signal.

Fix: in the same SessionDB transaction block that creates the
continuation session, copy state_meta[goal:<old>] →
state_meta[goal:<new>] when present. No-op when the user has no
active goal. Logged at INFO so a stuck loop is debuggable.

Tests cover the round-trip via SessionDB and the no-op path.

Affects all three run-conversation surfaces (CLI, gateway, TUI
gateway) because _compress_context is the single rotation site.
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…rch#23813)

* Revert "fix(goals): force judge to use tool calls instead of JSON-text replies (NousResearch#23547)"

This reverts commit a63a2b7.

* Revert "fix(goals): forward standing /goal state on auto-compression session rotation (NousResearch#23530)"

This reverts commit 4a080b1.

* Revert "feat(goals): /goal checklist + /subgoal user controls (NousResearch#23456)"

This reverts commit 404640a.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants