From d32350fcc509380cb64ebea76f71f98a68bd9cd3 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:36:15 -0700 Subject: [PATCH] docs(memory-provider): threading contract names spawn_context_thread and the sidecar/config primitives The developer guide showed the exact bare threading.Thread pattern the previous commits removed from six in-tree providers; an external plugin copying it loses the profile ContextVar under multiplex. Point it at agent.memory_provider.spawn_context_thread and the canonical JSON/config writers. --- website/docs/developer-guide/memory-provider-plugin.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/website/docs/developer-guide/memory-provider-plugin.md b/website/docs/developer-guide/memory-provider-plugin.md index bda90a9dec..00f8fc948e 100644 --- a/website/docs/developer-guide/memory-provider-plugin.md +++ b/website/docs/developer-guide/memory-provider-plugin.md @@ -297,9 +297,11 @@ hooks: ## Threading Contract -**`sync_turn()` MUST be non-blocking.** If your backend has latency (API calls, LLM processing), run the work in a daemon thread: +**`sync_turn()` MUST be non-blocking.** If your backend has latency (API calls, LLM processing), run the work in a daemon thread — spawned with `agent.memory_provider.spawn_context_thread`, never a bare `threading.Thread`. Profile isolation (the active `HERMES_HOME`, the per-turn secret scope) lives in `contextvars`, and a plain thread starts with an empty context: under multiplexed profiles it would silently write into the *default* profile's store, and `get_secret()` fails closed there. ```python +from agent.memory_provider import spawn_context_thread + def sync_turn(self, user_content, assistant_content, *, session_id="", messages=None): def _sync(): try: @@ -309,10 +311,12 @@ def sync_turn(self, user_content, assistant_content, *, session_id="", messages= if self._sync_thread and self._sync_thread.is_alive(): self._sync_thread.join(timeout=5.0) - self._sync_thread = threading.Thread(target=_sync, daemon=True) + self._sync_thread = spawn_context_thread(_sync, name="myprovider-sync") self._sync_thread.start() ``` +The same applies to prefetch and writer threads. Small JSON config sidecars (`$HERMES_HOME/.json`) are read with `utils.read_json_or_empty` and written with `utils.atomic_json_write`; anything under `config.yaml` goes through `hermes_cli.config.save_config(..., merge_existing=True)`. + `messages` is optional OpenAI-style conversation context as of the completed turn. When present, it includes user/assistant messages, assistant tool calls, and tool result messages. Providers that do not need raw turn context can omit