Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions plugins/memory/hindsight/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1018,9 +1018,19 @@ def shutdown(self) -> None:
if self._client is not None:
try:
if self._mode == "local_embedded":
# Use the public close() API. The RuntimeError from
# aiohttp's "attached to a different loop" is expected
# and harmless — the daemon keeps running independently.
# HindsightEmbedded.close() delegates to its sync client.close().
# When Hermes created/used that client on the shared async loop,
# closing it from this thread can raise "attached to a different
# loop" before aiohttp releases the session. Close the embedded
# inner async client on the shared loop first, then let the
# wrapper clean up daemon/UI bookkeeping.
inner_client = getattr(self._client, "_client", None)
if inner_client is not None and hasattr(inner_client, "aclose"):
_run_sync(inner_client.aclose())
try:
self._client._client = None
except Exception:
pass
try:
self._client.close()
except RuntimeError:
Expand Down
18 changes: 18 additions & 0 deletions tests/plugins/memory/test_hindsight_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,3 +612,21 @@ def test_available_in_local_mode(self, tmp_path, monkeypatch):
monkeypatch.setenv("HINDSIGHT_MODE", "local")
p = HindsightMemoryProvider()
assert p.is_available()

class TestShutdown:
def test_local_embedded_shutdown_closes_inner_async_client_on_shared_loop(self, provider):
inner_client = _make_mock_client()
embedded = MagicMock()
embedded._client = inner_client
embedded.close = MagicMock()

provider._mode = "local_embedded"
provider._client = embedded

provider.shutdown()

inner_client.aclose.assert_awaited_once()
embedded.close.assert_called_once()
assert embedded._client is None
assert provider._client is None