hermes - ✅(Solved) Fix [Bug]: Gateway restart drops session memory — shutdown_memory_provider receives empty messages [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#15165Fetched 2026-04-25 06:24:05
View on GitHub
Comments
0
Participants
1
Timeline
7
Reactions
0
Author
Participants
Timeline (top)
labeled ×6cross-referenced ×1

Root Cause

Two compounding problems:

Fix Action

Fix / Workaround

  • #7759 (closed) — /new and /reset don't trigger memory commit — fixed by e964cfc4
  • #14981 (open) — on_session_finalize hook not fired on idle timeout
  • #15073 (open) — Hindsight sync races interpreter shutdown

PR fix notes

PR #15481: fix(gateway): pass session messages to shutdown_memory_provider (#15165)

Description (problem / solution / changelog)

Summary

  • Fixes #15165 Part A_cleanup_agent_resources previously invoked agent.shutdown_memory_provider() with no arguments, so every memory provider's on_session_end hook received an empty list. Providers with an early-return guard on empty input (Holographic, Hindsight, etc.) never extracted facts from the conversation.
  • Forward agent._session_messages — the transcript AIAgent maintains and refreshes every turn via _persist_session — so providers see the actual conversation instead of [].
  • Falls back to the legacy no-arg call whenever _session_messages is absent or not a list (test stubs built via object.__new__ or MagicMock) to keep every existing suite green.

The bug

Per #15165:

...any provider that tries to extract facts from the session's conversation gets an empty list. Providers like Holographic (on_session_end([])) have an early return guard... Every gateway restart wipes the session's conversational memory...

Users saw "抱歉,找不到相關的對話記錄" (roughly: "sorry, cannot find the corresponding conversation record") on the first turn after any gateway restart / idle expiry / session reset because no facts had ever been persisted.

The fix

AIAgent.shutdown_memory_provider already accepts messages: list = None (run_agent.py:4126), so this is a pure caller-side change in gateway/run.py:

session_messages = getattr(agent, "_session_messages", None)
if isinstance(session_messages, list):
    agent.shutdown_memory_provider(session_messages)
else:
    agent.shutdown_memory_provider()
  • _session_messages is set on AIAgent.__init__ (run_agent.py:1518) and refreshed at the end of every run_conversation turn (_persist_session, line 3264). By the time _cleanup_agent_resources fires, it holds the real transcript.
  • isinstance(..., list) discrimination is deliberate — it protects against MagicMock agents (whose attribute access auto-synthesises a child mock, not None) falling through and passing a bogus object to the provider's List[Dict] hook.
  • The try/except Exception: pass wrap is inherited; providers that raise never prevent close() from running.
  • Paths using skip_memory=True temporary agents (pre-reset memory flush, session hygiene auto-compress, /compress) are no-ops inside shutdown_memory_provider because self._memory_manager is None — so this change has no behaviour effect on them.

Scope

Part A only. Part B of #15165 (adding on_session_end to the Hindsight plugin) is a separate concern that benefits from this fix landing first — without Part A, a Hindsight on_session_end hook would still receive [].

Test plan

  • New regression suite: tests/gateway/test_shutdown_memory_provider_messages.py — 7 cases:
    • Populated _session_messages list is forwarded byte-for-byte
    • Empty list is still explicitly forwarded (matches pre-fix observable behaviour)
    • Agent without _session_messages falls back to no-arg call (stub compatibility)
    • MagicMock agent (non-list attribute) falls back to no-arg call
    • Provider exception is swallowed — close() still runs afterward
    • None agent is a no-op (idle-sweep race tolerance)
    • Agent without shutdown_memory_provider method still gets close()
  • Regression guard verified: reverted the fix → test_populated_messages_forwarded fails with Expected: mock([...]) / Actual: mock(); restored the fix → all 7 pass.
  • Full tests/gateway/ suite: 3703 passed, 9 pre-existing failures (dingtalk, matrix with missing mautrix, one approve-deny test — all fail identically on main, unrelated to this change).
  • Related suites pass clean (100/100): test_agent_cache.py, test_compress_command.py, test_background_command.py, test_flush_memory_stale_guard.py, test_session_boundary_hooks.py, test_compress_plugin_engine.py, test_clean_shutdown_marker.py.

Related

  • Fixes #15165 (Part A)
  • #7759 (closed) — /new / /reset memory commit, different code path
  • #14981 — on_session_finalize not fired on idle timeout (orthogonal)
  • #15073 — Hindsight sync races interpreter shutdown (orthogonal)

🤖 Generated with Claude Code

Changed files

  • gateway/run.py (modified, +15/-1)
  • tests/gateway/test_shutdown_memory_provider_messages.py (added, +148/-0)

Code Example

if hasattr(agent, "shutdown_memory_provider"):
    agent.shutdown_memory_provider()   # ← no messages passed

---

def shutdown_memory_provider(self, messages: list = None) -> None:

---

if not messages:
    return

---

agent.shutdown_memory_provider()

---

agent.shutdown_memory_provider(conversation_messages)
RAW_BUFFERClick to expand / collapse

Bug Description

When a gateway session ends (e.g. gateway process restart, Discord bot reconnection, idle timeout), the conversation history is not preserved in the Hindsight memory system. After the session restarts, the agent responds with "抱歉,找不到相關的對話記錄" even though the previous conversation was active.

This is not the same as the /new or /reset bug fixed in #7759. That issue was about /new and /reset not triggering memory provider shutdown. This issue is about the gateway restart path specifically — which does call shutdown_memory_provider(), but passes an empty list.

Root Cause Analysis

Two compounding problems:

1. shutdown_memory_provider() is called without session messages

In gateway/run.py (main, line 1767–1768):

if hasattr(agent, "shutdown_memory_provider"):
    agent.shutdown_memory_provider()   # ← no messages passed

The method signature in run_agent.py (line 3959):

def shutdown_memory_provider(self, messages: list = None) -> None:

Because no messages are passed, on_session_end([]) is called on all memory providers, and any provider that tries to extract facts from the session's conversation gets an empty list. Providers like Holographic (on_session_end([])) have an early return guard:

if not messages:
    return

2. Hindsight has no on_session_end or on_pre_compress hook

The Hindsight plugin (plugins/memory/hindsight/) only implements:

  • arecall — injects memories when a session starts
  • No on_session_end hook
  • No on_pre_compress hook

This means even if messages were passed correctly, Hindsight would still not save anything at session end. The only way Hindsight retains content is via the retain tool — which requires the agent to explicitly call it.

Impact

  • Every gateway restart wipes the session's conversational memory from Hindsight's perspective
  • Users with long-running sessions that span multiple days lose context after any gateway restart
  • The compression summary (generated by on_pre_compress) is session-local and is not written to Hindsight
  • The on_session_finalize hook (mentioned in docs) is also not fired on idle timeout (#14981)

Expected Behavior

When a gateway session ends, the memory provider should:

  1. Receive the full session message list via on_session_end(messages)
  2. Extract key facts and save them to Hindsight
  3. On next session start, arecall should surface relevant memories

Suggested Fix (two parts)

Part A — Pass messages to shutdown_memory_provider

In gateway/run.py, change:

agent.shutdown_memory_provider()

to:

agent.shutdown_memory_provider(conversation_messages)

This requires tracking conversation_messages in the gateway session context and passing it at shutdown time.

Part B — Implement on_session_end in Hindsight plugin

The Hindsight plugin should implement on_session_end(messages) (and optionally on_pre_compress) to extract and retain key facts from the session before it is discarded.

Related Issues

  • #7759 (closed) — /new and /reset don't trigger memory commit — fixed by e964cfc4
  • #14981 (open) — on_session_finalize hook not fired on idle timeout
  • #15073 (open) — Hindsight sync races interpreter shutdown

extent analysis

TL;DR

Passing conversation messages to shutdown_memory_provider and implementing on_session_end in the Hindsight plugin should fix the issue of lost conversation history after a gateway session ends.

Guidance

  • Modify gateway/run.py to pass conversation_messages to shutdown_memory_provider when a gateway session ends.
  • Implement the on_session_end method in the Hindsight plugin to extract and retain key facts from the session messages.
  • Consider implementing on_pre_compress in the Hindsight plugin for additional memory retention.
  • Review related issues (#7759, #14981, #15073) for potential connections to this problem.

Example

# In gateway/run.py
agent.shutdown_memory_provider(conversation_messages)

# In Hindsight plugin (plugins/memory/hindsight/)
def on_session_end(self, messages):
    # Extract key facts from session messages and retain them
    # ...
    self.retain(key_facts)

Notes

The provided fix assumes that conversation_messages is tracked and available in the gateway session context. Implementing on_session_end in the Hindsight plugin requires understanding the specific requirements for retaining key facts from session messages.

Recommendation

Apply the suggested fix (Part A and Part B) to ensure conversation history is preserved after a gateway session ends. This approach addresses the root cause of the issue and provides a clear path to resolving the problem.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix [Bug]: Gateway restart drops session memory — shutdown_memory_provider receives empty messages [1 pull requests, 1 participants]