# NousResearch/hermes-agent · Memory session commit missing context engine notifications

## Open now

- 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.

## TL;DR

- 6 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 4 PR(s) closed without merging.
- A fix was merged to main in PR #22764. Verify it addresses your case.
- **Recommended:** read PR #22764 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-22764`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.33`: the share of member 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.

## Closed / duplicate issues

- #22394: commit_memory_session() missing context_compressor.on_session_end() call

## Prior & competing PRs

- **#22764: fix(agent): notify context engine on commit_memory_session**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `run_agent.py`, `tests/run_agent/test_commit_memory_session_context_engine.py`
  - fix hunks:
  - `run_agent.py`

    ```diff
    @@ -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`

    ```diff
    @@ -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()
    ```
- **#22431: fix(run_agent): notify context engine on commit_memory_session (#22394)**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `e90aa7f28`, `#22764`
- **#22433: fix(agent): notify context_compressor on commit_memory_session**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#22394`, `#22764`
- **#22599: fix: commit context engine sessions on rotation**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#22764`, `#22431`, `#22433`
- **#22738: fix(memory): call context_compressor.on_session_end in commit_memory_session**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#22764`, `#22394`

## Fix status

- Merged to main in PR #22764 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-09`.
