From 85485696db864609d9452f269c8e2603e10e87fd Mon Sep 17 00:00:00 2001 From: Markus Wesaw Date: Sat, 20 Jun 2026 17:19:30 -0600 Subject: [PATCH] fix: shut down Honcho memory threads in oneshot mode --- hermes_cli/oneshot.py | 10 +++++++++- plugins/memory/honcho/__init__.py | 8 ++++++-- plugins/memory/honcho/session.py | 14 +++++++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/hermes_cli/oneshot.py b/hermes_cli/oneshot.py index f66d71c62e6d..a695c2198365 100644 --- a/hermes_cli/oneshot.py +++ b/hermes_cli/oneshot.py @@ -364,7 +364,15 @@ def _run_agent( agent.stream_delta_callback = None agent.tool_gen_callback = None - return agent.chat(prompt) or "" + try: + return agent.chat(prompt) or "" + finally: + try: + messages = getattr(agent, "_session_messages", None) + if hasattr(agent, "shutdown_memory_provider"): + agent.shutdown_memory_provider(messages if isinstance(messages, list) else None) + except Exception: + pass def _oneshot_clarify_callback(question: str, choices=None) -> str: diff --git a/plugins/memory/honcho/__init__.py b/plugins/memory/honcho/__init__.py index c9ddc41bc898..d9755032ed62 100644 --- a/plugins/memory/honcho/__init__.py +++ b/plugins/memory/honcho/__init__.py @@ -1415,10 +1415,14 @@ def shutdown(self) -> None: for t in (self._prefetch_thread, self._sync_thread): if t and t.is_alive(): t.join(timeout=5.0) - # Flush any remaining messages + # Flush remaining messages and shut down manager-owned background threads. if self._manager and not (self._init_thread and self._init_thread.is_alive() and not self._session_initialized): try: - self._manager.flush_all() + shutdown = getattr(self._manager, "shutdown", None) + if callable(shutdown): + shutdown() + else: + self._manager.flush_all() except Exception: pass diff --git a/plugins/memory/honcho/session.py b/plugins/memory/honcho/session.py index cff81916a7e3..2d1c93e33ed7 100644 --- a/plugins/memory/honcho/session.py +++ b/plugins/memory/honcho/session.py @@ -116,6 +116,8 @@ def __init__( # one source of truth; see __init__.py _do_session_init for the prewarm. self._context_cache: dict[str, dict] = {} self._prefetch_cache_lock = threading.Lock() + self._context_prefetch_threads: list[threading.Thread] = [] + self._context_prefetch_threads_lock = threading.Lock() self._dialectic_reasoning_level: str = ( config.dialectic_reasoning_level if config else "low" ) @@ -546,7 +548,14 @@ def flush_all(self) -> None: break def shutdown(self) -> None: - """Gracefully shut down the async writer thread.""" + """Gracefully shut down the async writer and context prefetch threads.""" + with self._context_prefetch_threads_lock: + threads = list(self._context_prefetch_threads) + self._context_prefetch_threads = [] + for t in threads: + if t.is_alive(): + t.join(timeout=10) + if self._async_queue is not None and self._async_thread is not None: self.flush_all() self._async_queue.put(_ASYNC_SHUTDOWN) @@ -676,6 +685,9 @@ def _run(): self.set_context_result(session_key, result) t = threading.Thread(target=_run, name="honcho-context-prefetch", daemon=True) + with self._context_prefetch_threads_lock: + self._context_prefetch_threads = [x for x in self._context_prefetch_threads if x.is_alive()] + self._context_prefetch_threads.append(t) t.start() def set_context_result(self, session_key: str, result: dict[str, str]) -> None: