9ba5850e95
Six of eight memory providers spawned plain threading.Thread for prefetch/sync/
writer work. A plain thread starts with an EMPTY contextvars.Context, so under
multiplex profiles the worker resolved the DEFAULT profile's HERMES_HOME (and
fails closed on scoped secrets). honcho and hindsight had each noticed and
written their own copy_context() wrapper; core had a third in memory_manager.
One canonical pair now lives on the ABC module every provider already imports:
agent/memory_provider.py::ctx_bound / spawn_context_thread. memory_manager,
honcho, hindsight, mem0, retaindb, byterover, supermemory and openviking all use
it; the honcho and hindsight wrappers and memory_manager._ctx_bound are deleted.
Five "json.loads(path.read_text()) or {}" readers (mem0._read_mem0_json,
honcho client/oauth/cli _read_config, hindsight save_config/_load_config) fold
into utils.read_json_or_empty, the read half of every read-merge-atomic_json_write
sidecar store.
holographic.save_config was the only config.yaml writer in the tree that
bypassed hermes_cli.config.save_config: raw open("w") + yaml.dump with no config
lock, no managed-mode refusal, no atomic replace, and a swallowed exception. It
now calls save_config(..., merge_existing=True). Behavior change: a managed
install refuses the write (previously silently rewrote config.yaml); other
sections are deep-merged instead of round-tripped through a raw dump.
openviking._hermes_home_path guarded an impossible ImportError of
hermes_constants (the module already imports agent.*) with a ~/.hermes fallback
that is wrong on Windows and under profile overrides; it is replaced by
get_hermes_home() directly.
Tests: tests/plugins/memory/test_provider_threads_inherit_profile.py drives each
provider's real spawn path with a fake backend and asserts the thread sees the
spawner's HERMES_HOME override (sabotage: retaindb back on threading.Thread ->
red). tests/plugins/memory/test_holographic_save_config.py pins merge-with-
existing-sections and managed-mode refusal (sabotage: raw yaml.dump -> red).
40 lines
1.6 KiB
Python
40 lines
1.6 KiB
Python
"""``save_config`` of the holographic provider writes config.yaml through the canonical writer.
|
|
|
|
The provider used to ``yaml.dump`` straight over config.yaml, bypassing the config lock, the
|
|
managed-mode refusal and the atomic replace. Two contracts pin the canonical path: unrelated
|
|
sections survive a provider save, and a managed install refuses the write.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import yaml
|
|
|
|
from plugins.memory.holographic import HolographicMemoryProvider
|
|
|
|
|
|
def _provider():
|
|
return HolographicMemoryProvider(config={})
|
|
|
|
|
|
def test_save_config_merges_into_existing_yaml(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
|
(tmp_path / "config.yaml").write_text("model:\n default: keep-me\nmemory:\n provider: holographic\n")
|
|
|
|
_provider().save_config({"db_path": "custom.db", "hrr_dim": "512"}, str(tmp_path))
|
|
|
|
raw = yaml.safe_load((tmp_path / "config.yaml").read_text())
|
|
assert raw["plugins"]["hermes-memory-store"] == {"db_path": "custom.db", "hrr_dim": "512"}
|
|
assert raw["model"]["default"] == "keep-me"
|
|
assert raw["memory"]["provider"] == "holographic"
|
|
|
|
|
|
def test_save_config_respects_managed_mode(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
|
before = "model:\n default: managed\n"
|
|
(tmp_path / "config.yaml").write_text(before)
|
|
monkeypatch.setattr("hermes_cli.config.is_managed", lambda: True)
|
|
monkeypatch.setattr("hermes_cli.config.managed_error", lambda *_a, **_k: None)
|
|
|
|
_provider().save_config({"db_path": "custom.db"}, str(tmp_path))
|
|
|
|
assert (tmp_path / "config.yaml").read_text() == before
|