hermes - ✅(Solved) Fix Hindsight provider can submit retain work during interpreter shutdown [2 pull requests, 3 comments, 4 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#15497Fetched 2026-04-26 05:27:03
View on GitHub
Comments
3
Participants
4
Timeline
11
Reactions
0
Author
Timeline (top)
commented ×3labeled ×3referenced ×3cross-referenced ×2

Error Message

During process/session shutdown, Hindsight retain sync can run late enough that Python is already tearing down executor/future machinery. The provider currently waits for existing background threads in shutdown(), but normal sync_turn() can still accept new work during or after shutdown begins.

Root Cause

Root cause hypothesis

Fix Action

Fixed

PR fix notes

PR #15507: fix(hindsight): prevent late retain sync during interpreter shutdown

Description (problem / solution / changelog)

Summary

The Hindsight memory provider can submit async retain work after shutdown begins, causing RuntimeError: cannot schedule new futures after interpreter shutdown during Python teardown.

Root Cause

shutdown() joins existing background threads and closes the client, but there's no lifecycle gate preventing sync_turn() from submitting new asyncio.run_coroutine_threadsafe() calls after shutdown starts. A late sync_turn() call races against the interpreter's cleanup of the event loop machinery.

Fix

1. Lifecycle state gating

  • Added _shutting_down, _closed flags and _state_lock
  • sync_turn() returns early once _shutting_down is set
  • _run_sync() accepts allow_shutdown=True parameter — normal callers are blocked after shutdown starts, but the shutdown path itself can still flush/close

2. Extracted _submit_retain() helper

  • Retain submission logic extracted from sync_turn() so both normal sync and shutdown flush use the same code path
  • Tracks _last_retained_turn to avoid duplicating a full-session retain during shutdown flush

3. Shutdown final flush

  • Before closing the client, shutdown() checks for un-retained pending turns and flushes them via _submit_retain(allow_shutdown=True)
  • This ensures recent conversation turns aren't lost on shutdown

Changes

  • plugins/memory/hindsight/__init__.py — lifecycle state, _submit_retain(), guarded sync_turn(), enhanced shutdown()

Fixes #15497

Changed files

  • plugins/memory/hindsight/__init__.py (modified, +85/-35)

PR #15512: fix: prevent Hindsight retain work during interpreter shutdown

Description (problem / solution / changelog)

Fixes #15497

Problem

The HindsightMemoryProvider can still submit async retain work after provider shutdown has started. During interpreter teardown, asyncio.run_coroutine_threadsafe() fails with RuntimeError: cannot schedule new futures after interpreter shutdown.

Root Cause

shutdown() joins existing threads but has no guard flag to prevent sync_turn() from accepting new work.

Fix

  1. Added self._shutting_down = False in __init__() — initializes the guard flag
  2. Added early-return guard in sync_turn() — checks self._shutting_down before doing any work
  3. Set self._shutting_down = True at top of shutdown() — marks provider as shutting down before joining threads
  4. Defense-in-depth in _run_sync() — checks loop.is_closed() before calling asyncio.run_coroutine_threadsafe()

Files Changed

  • plugins/memory/hindsight/__init__.py — 7 lines added, 0 removed

Changed files

  • plugins/memory/hindsight/__init__.py (modified, +7/-0)

Code Example

RuntimeError: cannot schedule new futures after interpreter shutdown

---

asyncio.run_coroutine_threadsafe(coro, loop)

---

python -m pytest tests/plugins/memory/test_hindsight_provider.py -q -o 'addopts='
python -m py_compile plugins/memory/hindsight/__init__.py tests/plugins/memory/test_hindsight_provider.py
git diff --check -- plugins/memory/hindsight/__init__.py tests/plugins/memory/test_hindsight_provider.py

---

77 passed in 0.37s

---

plugins/memory/hindsight/__init__.py
tests/plugins/memory/test_hindsight_provider.py

---

cdd9953c fix(hindsight): prevent late retain sync during shutdown
RAW_BUFFERClick to expand / collapse

Hindsight provider can submit retain work during interpreter shutdown

Bug description

The native Hermes Hindsight memory provider can still submit async retain work after provider shutdown has started. In the shutdown window this can call asyncio.run_coroutine_threadsafe(), which may fail during Python interpreter teardown with:

RuntimeError: cannot schedule new futures after interpreter shutdown

This also risks secondary teardown noise such as unclosed aiohttp client/session warnings if cleanup is interrupted.

Affected area

  • Repository: NousResearch/hermes-agent
  • Provider: plugins/memory/hindsight/__init__.py
  • Tests: tests/plugins/memory/test_hindsight_provider.py

Observed behavior

During process/session shutdown, Hindsight retain sync can run late enough that Python is already tearing down executor/future machinery. The provider currently waits for existing background threads in shutdown(), but normal sync_turn() can still accept new work during or after shutdown begins.

Expected behavior

Once provider shutdown begins:

  1. Normal sync_turn() calls should not submit new async work.
  2. Existing in-flight background work should be joined/drained.
  3. Any pending partial auto-retain batch should be flushed before client close, so recent conversation turns are not lost.
  4. Client cleanup (aclose() / close path) should still be allowed through a shutdown-owned path.

Root cause hypothesis

The provider shutdown path joins existing _prefetch_thread / _sync_thread and then closes the Hindsight client, but there is no lifecycle gate that freezes normal retain submission once shutdown starts.

A late sync_turn() can therefore still reach the async scheduling path:

asyncio.run_coroutine_threadsafe(coro, loop)

while the Python interpreter is shutting down.

Proposed fix

Add provider lifecycle state and a shutdown-safe final flush path:

  • Add guarded provider state such as _shutting_down, _closed, and _state_lock.
  • Have sync_turn() return early once shutdown starts.
  • Guard normal _run_sync() calls after shutdown begins.
  • Allow only explicit shutdown-owned cleanup/final-flush paths via an allow_shutdown=True style escape hatch.
  • Extract retain submission into a helper that can be used both by normal sync and shutdown final flush.
  • Track the last retained turn counter to avoid duplicating an already retained full-session document during shutdown.
  • Add regression tests for:
    • sync_turn() after shutdown does not call asyncio.run_coroutine_threadsafe().
    • shutdown flushes a pending partial batch before closing the client.

Local validation from a candidate fix

A local candidate fix was tested with:

python -m pytest tests/plugins/memory/test_hindsight_provider.py -q -o 'addopts='
python -m py_compile plugins/memory/hindsight/__init__.py tests/plugins/memory/test_hindsight_provider.py
git diff --check -- plugins/memory/hindsight/__init__.py tests/plugins/memory/test_hindsight_provider.py

Result:

77 passed in 0.37s

The local candidate commit only touches:

plugins/memory/hindsight/__init__.py
tests/plugins/memory/test_hindsight_provider.py

Local commit reference, if useful for comparison:

cdd9953c fix(hindsight): prevent late retain sync during shutdown

Additional test isolation note

Some Hindsight provider tests can be polluted by a real host-level ~/.hindsight/config.json. Tests that rely on env fallback or no legacy config should isolate HOME / Path.home() to a temporary directory.

Suggested labels

  • bug
  • memory
  • hindsight

extent analysis

TL;DR

Add a lifecycle gate to the Hindsight provider to prevent new async work submission during shutdown.

Guidance

  • Introduce a _shutting_down flag to track the provider's shutdown state and guard sync_turn() calls.
  • Extract retain submission into a helper function for use in both normal sync and shutdown final flush paths.
  • Implement an allow_shutdown=True style escape hatch for shutdown-owned cleanup and final-flush paths.
  • Add regression tests to verify that sync_turn() does not call asyncio.run_coroutine_threadsafe() after shutdown and that shutdown flushes pending partial batches.

Example

def sync_turn(self):
    with self._state_lock:
        if self._shutting_down:
            return
    # existing sync_turn logic

Notes

The proposed fix requires careful consideration of the provider's lifecycle state and async work submission. The introduction of a _shutting_down flag and extraction of retain submission into a helper function should help prevent late retain sync during shutdown.

Recommendation

Apply the proposed fix by adding a lifecycle gate to the Hindsight provider, as it addresses the root cause of the issue and provides a clear solution to prevent new async work submission during shutdown.

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…

FAQ

Expected behavior

Once provider shutdown begins:

  1. Normal sync_turn() calls should not submit new async work.
  2. Existing in-flight background work should be joined/drained.
  3. Any pending partial auto-retain batch should be flushed before client close, so recent conversation turns are not lost.
  4. Client cleanup (aclose() / close path) should still be allowed through a shutdown-owned path.

Still need to ship something?

×6

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

Back to top recommendations

TRENDING