fix(hindsight): preserve shared event loop across provider shutdowns#14109
Closed
perlowja wants to merge 1 commit into
Closed
fix(hindsight): preserve shared event loop across provider shutdowns#14109perlowja wants to merge 1 commit into
perlowja wants to merge 1 commit into
Conversation
The module-global `_loop` / `_loop_thread` pair is shared across every `HindsightMemoryProvider` instance in the process — the plugin loader creates one provider per `AIAgent`, and the gateway creates one `AIAgent` per concurrent chat session (Telegram/Discord/Slack/CLI). `HindsightMemoryProvider.shutdown()` stopped the shared loop when any one session ended. That stranded the aiohttp `ClientSession` and `TCPConnector` owned by every sibling provider on a now-dead loop — they were never reachable for close and surfaced as the `Unclosed client session` / `Unclosed connector` warnings reported in NousResearch#11923. Fix: stop stopping the shared loop in `shutdown()`. Per-provider cleanup still closes that provider's own client via `self._client.aclose()`. The loop runs on a daemon thread and is reclaimed on process exit; keeping it alive between provider shutdowns means sibling providers can drain their own sessions cleanly. Regression tests in `tests/plugins/memory/test_hindsight_provider.py` (`TestSharedEventLoopLifecycle`): - `test_shutdown_does_not_stop_shared_event_loop` — two providers share the loop; shutting down one leaves the loop live for the other. This test reproduces the NousResearch#11923 leak on `main` and passes with the fix. - `test_client_aclose_called_on_cloud_mode_shutdown` — each provider's own aiohttp session is still closed via `aclose()`. Fixes NousResearch#11923.
This was referenced Apr 24, 2026
teknium1
added a commit
that referenced
this pull request
Apr 24, 2026
nekorytaylor666
pushed a commit
to nekorytaylor666/hermes-agent
that referenced
this pull request
Apr 24, 2026
…arch#15070) Adds AUTHOR_MAP entries for perlowja, tangyuanjc, harryplusplus ahead of merging PRs NousResearch#14109, NousResearch#13153, NousResearch#13090.
justrhoto
pushed a commit
to justrhoto/hermes-agent
that referenced
this pull request
Apr 24, 2026
…arch#15070) Adds AUTHOR_MAP entries for perlowja, tangyuanjc, harryplusplus ahead of merging PRs NousResearch#14109, NousResearch#13153, NousResearch#13090.
ulasbilgen
pushed a commit
to ulasbilgen/hermes-adhd-agent
that referenced
this pull request
May 1, 2026
…arch#15070) Adds AUTHOR_MAP entries for perlowja, tangyuanjc, harryplusplus ahead of merging PRs NousResearch#14109, NousResearch#13153, NousResearch#13090.
aj-nt
pushed a commit
to aj-nt/hermes-agent
that referenced
this pull request
May 1, 2026
…arch#15070) Adds AUTHOR_MAP entries for perlowja, tangyuanjc, harryplusplus ahead of merging PRs NousResearch#14109, NousResearch#13153, NousResearch#13090.
donald131
pushed a commit
to donald131/hermes-agent
that referenced
this pull request
May 2, 2026
…arch#15070) Adds AUTHOR_MAP entries for perlowja, tangyuanjc, harryplusplus ahead of merging PRs NousResearch#14109, NousResearch#13153, NousResearch#13090.
02356abc
pushed a commit
to 02356abc/hermes-agent
that referenced
this pull request
May 14, 2026
…arch#15070) Adds AUTHOR_MAP entries for perlowja, tangyuanjc, harryplusplus ahead of merging PRs NousResearch#14109, NousResearch#13153, NousResearch#13090.
gweeteve
pushed a commit
to gweeteve/hermes-agent
that referenced
this pull request
Jun 2, 2026
…arch#15070) Adds AUTHOR_MAP entries for perlowja, tangyuanjc, harryplusplus ahead of merging PRs NousResearch#14109, NousResearch#13153, NousResearch#13090.
Egavasyug
pushed a commit
to Egavasyug/hermes-agent
that referenced
this pull request
Jun 10, 2026
…arch#15070) Adds AUTHOR_MAP entries for perlowja, tangyuanjc, harryplusplus ahead of merging PRs NousResearch#14109, NousResearch#13153, NousResearch#13090.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Fixes an aiohttp session leak in the Hindsight memory plugin that shows up as
Unclosed client session/Unclosed connectorerrors in any long-running gateway hosting more than one concurrent chat.Root cause. The module-global
_loop/_loop_threadpair inplugins/memory/hindsight/__init__.pyis shared across everyHindsightMemoryProviderinstance in the process. The plugin loader creates one provider perAIAgent(run_agent.py:1462), and the gateway creates oneAIAgentper concurrent chat session (Telegram/Discord/Slack/WhatsApp/CLI). ButHindsightMemoryProvider.shutdown()stopped the shared loop when any single session ended:This stranded the
aiohttp.ClientSession+TCPConnectorowned by every sibling provider — their sessions were created on the loop (viahindsight_client_api.rest.RESTClientObject._ensure_session, which is one-session-per-client by design), and they cannot be closed once their loop is dead. They surface minutes later as the warnings reported in #11923.Fix. Don't stop the shared loop on per-provider shutdown. Per-provider cleanup still closes that provider's own client via
self._client.aclose()beforeself._client = None. The loop runs on a daemon thread and is reclaimed on process exit; keeping it alive between provider shutdowns lets sibling providers drain their own sessions cleanly.Related Issue
Fixes #11923. Builds on (does not undo) the prior work in #4762 / #5094.
Type of Change
Changes Made
plugins/memory/hindsight/__init__.py—HindsightMemoryProvider.shutdown()no longer stops the module-global event loop. Drops the now-unusedglobal _loop, _loop_threaddeclaration. Replaces the loop-stop block with a comment explaining the sharing invariant.tests/plugins/memory/test_hindsight_provider.py— addsTestSharedEventLoopLifecyclewith two regression tests:test_shutdown_does_not_stop_shared_event_loop— primes the shared loop, creates two independent providers (two concurrent chat sessions), shuts down one, verifies the loop + thread are still the same live objects and that the second provider can still dispatch async work. Fails onmainwith the exact#11923failure mode; passes with the fix.test_client_aclose_called_on_cloud_mode_shutdown— confirms per-provider session cleanup still runs (mock_client.aclose.assert_called_once()).How to Test
pytest tests/plugins/memory/test_hindsight_provider.py -q -o addopts=— 46 passed (44 existing + 2 new regression tests).pytest tests/plugins/ -q -o addopts=— 221 passed.pytest tests/ -q -o addopts= --ignore=tests/integration --ignore=tests/e2e --ignore=tests/acp --ignore=tests/tools/test_tts_kittentts.py— 14,109 passed (71 failed + 45 errored, all confined totests/hermes_cli/test_web_server.py— an unrelated test file that also fails on pristinemain; they do not touchplugins/memory/) (the excluded paths require optional-dep modules —acp,kittentts— not installed viapip install -e ".[dev]"; they also fail to collect on pristinemain).test_shutdown_does_not_stop_shared_event_loopfails with:Checklist
Code
fix(hindsight): ...)pytest tests/ -qand all tests pass (see "How to Test" above — full suite passes; the 8 optional-dep collection errors are pre-existing and unrelated)TestSharedEventLoopLifecycle)Documentation & Housekeeping
docs/, docstrings) — N/A (no user-visible config or behavior change beyond "the warning stops happening")cli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/A