fix(memory): port upstream's hindsight contextvars thread wrap (#92608)
Per-provider background threads (writer, daemon-start, prefetch) start with an EMPTY contextvars Context. Under multiplex_profiles the spawning thread carries the profile's secret scope + HERMES_HOME override (gateway/run.py wraps the agent turn in copy_context().run), and get_secret fails closed without it (#92608). Snapshot the spawner's context into each thread — the exact fix upstream main carries, ported on top of this branch's pm-migrated hindsight so the upstream merge drops those hunks cleanly. The shared hindsight-loop thread stays unwrapped (coroutines scheduled via run_coroutine_threadsafe inherit the submitter's context per call). Verified: tests/plugins/memory shows the same 7 pre-existing Windows- host failures as the unmodified-tree baseline (symlink privilege, hindsight_client_api thread fakes, CRLF env bytes) — no new failures from this port. uv.lock unchanged (extras-platforms is metadata-only; uv lock --check passes).
This commit is contained in:
@@ -32,6 +32,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import atexit
|
||||
import contextvars
|
||||
import importlib
|
||||
import json
|
||||
import logging
|
||||
@@ -1332,8 +1333,17 @@ class HindsightMemoryProvider(MemoryProvider):
|
||||
# If the previous writer exited (e.g. after a prior shutdown), reset
|
||||
# the flag so this fresh writer is allowed to drain new jobs.
|
||||
self._shutting_down.clear()
|
||||
# Per-provider background threads start with an EMPTY contextvars
|
||||
# Context. Under multiplex_profiles the spawning thread carries the
|
||||
# profile's secret scope + HERMES_HOME override (gateway/run.py wraps
|
||||
# the agent turn in copy_context().run), and get_secret fails closed
|
||||
# without it (#92608). Snapshot the spawner's context into the thread.
|
||||
# (The shared ``hindsight-loop`` thread needs no wrap: coroutines
|
||||
# scheduled via run_coroutine_threadsafe inherit the submitter's
|
||||
# context per call, so one loop can serve every profile.)
|
||||
thread = threading.Thread(
|
||||
target=self._writer_loop,
|
||||
target=contextvars.copy_context().run,
|
||||
args=(self._writer_loop,),
|
||||
daemon=True,
|
||||
name="hindsight-writer",
|
||||
)
|
||||
@@ -1849,7 +1859,12 @@ class HindsightMemoryProvider(MemoryProvider):
|
||||
f.write(f"\n=== Daemon startup failed: {e} ===\n")
|
||||
traceback.print_exc(file=f)
|
||||
|
||||
t = threading.Thread(target=_start_daemon, daemon=True, name="hindsight-daemon-start")
|
||||
t = threading.Thread(
|
||||
target=contextvars.copy_context().run,
|
||||
args=(_start_daemon,),
|
||||
daemon=True,
|
||||
name="hindsight-daemon-start",
|
||||
)
|
||||
t.start()
|
||||
|
||||
def system_prompt_block(self) -> str:
|
||||
@@ -2006,7 +2021,12 @@ class HindsightMemoryProvider(MemoryProvider):
|
||||
self._prefetch_result = recalled.text
|
||||
self._prefetch_count = recalled.count
|
||||
|
||||
self._prefetch_thread = threading.Thread(target=_run, daemon=True, name="hindsight-prefetch")
|
||||
self._prefetch_thread = threading.Thread(
|
||||
target=contextvars.copy_context().run,
|
||||
args=(_run,),
|
||||
daemon=True,
|
||||
name="hindsight-prefetch",
|
||||
)
|
||||
self._prefetch_thread.start()
|
||||
|
||||
def _build_turn_messages(self, user_content: str, assistant_content: str) -> List[Dict[str, str]]:
|
||||
|
||||
Reference in New Issue
Block a user