Memory session commit missing context engine notifications

NousResearch/hermes-agent · cluster 22394

Data as of

Nothing open on GitHub. Fix merged in #22764. Check it covers your case.

The short answer

0open issues
0open PRs
6items grouped
Known?
Yes. 6 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #22764. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #22764. That is separate from the fix event.
Still open
Nothing open on GitHub.

These issues and PRs address a bug where committing a memory session fails to notify the context engine or context compressor at session end. Multiple fixes target the same missing callback across related components.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #22764 fix(agent): notify context engine on commit_memory_session

    merged to main

    files: run_agent.py, tests/run_agent/test_commit_memory_session_context_engine.py

    Show the diff
    • run_agent.py
      @@ -5067,12 +5067,25 @@ def commit_memory_session(self, messages: list = None) -> None:
               Called when session_id rotates (e.g. /new, context compression);
               providers keep their state and continue running under the old
               session_id — they just flush pending extraction now."""
      -        if not self._memory_manager:
      -            return
      -        try:
      -            self._memory_manager.on_session_end(messages or [])
      -        except Exception:
      -            pass
      +        if self._memory_manager:
      +            try:
      +                self._memory_manager.on_session_end(messages or [])
      +            except Exception:
      +                pass
      +        # Notify context engine of session end too — same lifecycle moment as
      +        # the memory manager's on_session_end. Without this, engines that
      +        # accumulate per-session state (DAGs, summaries) leak that state from
      +        # the rotated-out session into whatever comes next under the same
      +        # compressor instance. Mirrors the call in shutdown_memory_provider().
      +        # See issue #22394.
      +        if hasattr(self, "context_compressor") and self.context_compressor:
      +            try:
      +                self.context_compressor.on_session_end(
      +                    self.session_id or "",
      +                    messages or [],
      +                )
      +            except Exception:
      +                pass
       
           def _sync_external_memory_for_turn(
               self,
    • tests/run_agent/test_commit_memory_session_context_engine.py
      @@ -0,0 +1,102 @@
      +"""Regression tests for AIAgent.commit_memory_session.
      +
      +Issue #22394: commit_memory_session was calling MemoryManager.on_session_end
      +but never ContextEngine.on_session_end. Context engines that accumulate
      +per-session state (LCM-style DAGs, summary stores) leaked that state from a
      +rotated-out session into whatever continued under the same compressor
      +instance.
      +"""
      +
      +from __future__ import annotations
      +
      +from types import SimpleNamespace
      +from unittest.mock import MagicMock
      +
      +
      +def _make_minimal_agent(memory_manager, context_compressor, session_id="abc"):
      +    """Build an object with just enough surface for commit_memory_session to run.
      +
      +    AIAgent.__init__ is too heavy for a focused unit test — bind the method
      +    to a SimpleNamespace-style object that has the attributes the method
      +    actually touches.
      +    """
      +    from run_agent import AIAgent
      +
      +    obj = SimpleNamespace(
      +        _memory_manager=memory_manager,
      +        context_compressor=context_compressor,
      +        session_id=session_id,
      +    )
      +    obj.commit_memory_session = AIAgent.commit_memory_session.__get__(obj)
      +    return obj
      +
      +
      +def test_commit_memory_session_notifies_context_engine():
      +    """Both the memory manager AND the context engine receive on_session_end."""
      +    mm = MagicMock()
      +    ctx = MagicMock()
      +    agent = _make_minimal_agent(mm, ctx, session_id="sess-42")
      +
      +    msgs = [{"role": "user", "content": "hi"}, {"role": "assistant", "content": "yo"}]
      +    agent.commit_memory_session(msgs)
      +
      +    mm.on_session_end.assert_called_once_with(msgs)
      +    ctx.on_session_end.assert_called_once_with("sess-42", msgs)
      +
      +
      +def test_commit_memory_session_with_no_messages_passes_empty_list():
      +    """Empty/None messages must still fire both hooks with an empty list."""
      +    mm = MagicMock()
      +    ctx = MagicMock()
      +    agent = _make_minimal_agent(mm, ctx, session_id="sess-7")
      +
      +    agent.commit_memory_session(None)
      +
      +    mm.on_session_end.assert_called_once_with([])
      +    ctx.on_session_end.assert_called_once_with("sess-7", [])
      +
      +
      +def test_commit_memory_session_no_memory_manager_still_notifies_context_engine():
      +    """If only the context engine is configured, it still gets the hook."""
      +    ctx = MagicMock()
      +    agent = _make_minimal_agent(None, ctx, session_id="sess-9")
      +
      +    agent.commit_memory_session([{"role": "user", "content": "x"}])
      +
      +    ctx.on_session_end.assert_called_once_with("sess-9", [{"role": "user", "content": "x"}])
      +
      +
      +def test_commit_memory_session_no_context_engine_still_notifies_memory_manager():
      +    """If only the memory manager is configured, it still gets the hook."""
      +    mm = MagicMock()
      +    agent = _make_minimal_agent(mm, None, session_id="sess-3")
      +
      +    agent.commit_memory_session([{"role": "user", "content": "x"}])
      +
      +    mm.on_session_end.assert_called_once_with([{"role": "user", "content": "x"}])
      +
      +
      +def test_commit_memory_session_tolerates_memory_manager_failure():
      +    """A raising memory manager must not block the context engine notification."""
      +    mm = MagicMock()
      +    mm.on_session_end.side_effect = RuntimeError("boom")
      +    ctx = MagicMock()
      +    agent = _make_minimal_agent(mm, ctx, session_id="sess-X")
      +
      +    # Must not raise
      +    agent.commit_memory_session([{"role": "user", "content": "x"}])
      +
      +    ctx.on_session_end.assert_called_once_with("sess-X", [{"role": "user", "content": "x"}])
      +
      +
      +def test_commit_memory_session_tolerates_context_engine_failure():
      +    """A raising context engine must not surface the exception."""
      +    mm = MagicMock()
      +    ctx = MagicMock()
      +    ctx.on_session_end.side_effect = RuntimeError("boom")
      +    agent = _make_minimal_agent(mm, ctx, session_id="sess-Y")
      +
      +    # Must not raise
      +    agent.commit_memory_session([{"role": "user", "content": "x"}])
      +
      +    mm.on_session_end.assert_called_once()

4 PRs closed without merging

From the close comments: superseded by another PR 3 · duplicate 1

  • #22431 fix(run_agent): notify context engine on commit_memory_session (#22394) · superseded by another PR → e90aa7f, #22764
  • #22433 fix(agent): notify context_compressor on commit_memory_session · superseded by another PR → #22394, #22764
  • #22599 fix: commit context engine sessions on rotation · duplicate → #22764, #22431, #22433
  • #22738 fix(memory): call context_compressor.on_session_end in commit_memory_session · superseded by another PR → #22764, #22394
Closed issues · 1
  • Closed#22394commit_memory_session() missing context_compressor.on_session_end() call
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.33: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Merged PRs
#22764
Closed issues
1
Attempts
4 PRs closed without merging
Fix link checked
9 May 2026

For your agent

The same facts, typed.