diff --git a/plugins/memory/openviking/README.md b/plugins/memory/openviking/README.md index 3627bcd730..cf9af8a198 100644 --- a/plugins/memory/openviking/README.md +++ b/plugins/memory/openviking/README.md @@ -50,7 +50,6 @@ OPENVIKING_ENDPOINT=http://127.0.0.1:1933 # OPENVIKING_API_KEY=... # OPENVIKING_ACCOUNT=default # OPENVIKING_USER=default -# OPENVIKING_AGENT=hermes ``` ## Config @@ -73,7 +72,7 @@ profile's `.env`: | `OPENVIKING_API_KEY` | (none) | User/admin API key for authenticated servers | | `OPENVIKING_ACCOUNT` | `default` | Tenant account for local/trusted mode | | `OPENVIKING_USER` | `default` | Tenant user for local/trusted mode | -| `OPENVIKING_AGENT` | `hermes` | Hermes peer ID in OpenViking, used for peer-scoped memories | +| `OPENVIKING_AGENT` | (none) | Optional peer ID for separate assistant context | When `OPENVIKING_API_KEY` is set, Hermes lets OpenViking derive account/user identity from the key. In local or trusted deployments without an API key, @@ -82,6 +81,33 @@ Hermes also sends `User-Agent: openviking-memory-hermes/` on OpenViking requests. This standard harness identifier contains the Hermes version, but no per-user identifier, and does not add a separate request. +### Optional peer identity + +New connections use the OpenViking user's memory directory by default. Setup +does not ask for a peer ID. Without a configured peer, Hermes sends neither +`X-OpenViking-Actor-Peer` nor assistant-message `peer_id`. + +For separate assistant context, set the existing `agent` field in the active +profile's `config.yaml`: + +```yaml +memory: + openviking: + agent: work-assistant +``` + +Existing non-empty `OPENVIKING_AGENT`, YAML `agent`, and linked OpenViking +`actor_peer_id` or legacy `agent_id` values retain their behavior. Resolution +order remains environment, linked OpenViking config, then Hermes YAML. To use +no peer, remove the peer value from each configured source and start a new +Hermes session. + +Upgrades do not move or delete existing memories. Installations that relied +on the old implicit `hermes` peer now use user memory for new writes. Set +`agent: hermes` to retain that previous behavior. Without a peer ID, default +OpenViking search can still find old peer memories, including other peers +under the same OpenViking user. Keep a peer ID if you need the narrower view. + ## Tools | Tool | Description | @@ -96,8 +122,9 @@ version, but no per-user identifier, and does not add a separate request. ## Memory Writes And Deletes `viking_remember` writes directly to OpenViking with `POST /api/v1/content/write` -and `mode=create`. It creates peer-scoped memory files under explicit-uid -`viking://user//peers/${OPENVIKING_AGENT}/memories/...` URIs, where +and `mode=create`. By default it creates files under explicit-uid +`viking://user//memories/...` URIs. When a peer ID is configured, it keeps +the existing `viking://user//peers//memories/...` path. In both cases, `` is resolved client-side from `/api/v1/system/status` (server-asserted current user). Hermes caches a confirmed user only for the active connection. If the probe fails, Hermes uses the configured user, or `default`, for that @@ -111,7 +138,7 @@ local memory operation succeeds: | Hermes action | OpenViking operation | |---------------|----------------------| -| `add` | `content/write` with `mode=create` under the configured peer memory namespace | +| `add` | `content/write` with `mode=create` under user memory, or the configured peer memory directory | Built-in `replace` and `remove` operations are not mirrored because Hermes native memory entries do not yet carry stable OpenViking file URIs. Use diff --git a/plugins/memory/openviking/__init__.py b/plugins/memory/openviking/__init__.py index de29838187..96eff0bc59 100644 --- a/plugins/memory/openviking/__init__.py +++ b/plugins/memory/openviking/__init__.py @@ -13,7 +13,7 @@ or a linked OpenViking CLI config: OPENVIKING_API_KEY — API key (required for authenticated servers) OPENVIKING_ACCOUNT — Tenant account for local/trusted mode (default: default) OPENVIKING_USER — Tenant user for local/trusted mode (default: default) - OPENVIKING_AGENT — Hermes peer ID in OpenViking (default: hermes) + OPENVIKING_AGENT — Optional peer ID (default: no peer) Capabilities: - Automatic memory extraction on session commit (6 categories) @@ -65,9 +65,8 @@ logger = logging.getLogger(__name__) _DEFAULT_ENDPOINT = "http://127.0.0.1:1933" _OPENVIKING_SERVICE_ENDPOINT = "https://api.vikingdb.cn-beijing.volces.com/openviking" -_DEFAULT_AGENT = "hermes" +_DEFAULT_AGENT = "" _OPENVIKING_USER_AGENT = f"openviking-memory-hermes/{_HERMES_VERSION}" -_AGENT_PROMPT_LABEL = "Hermes peer ID in OpenViking" _OVCLI_CONFIG_ENV = "OPENVIKING_CLI_CONFIG_FILE" _OVCLI_DEFAULT_RELATIVE_PATH = ".openviking/ovcli.conf" _OVCLI_SAVED_PREFIX = "ovcli.conf." @@ -1835,7 +1834,6 @@ def _prompt_manual_connection_values(prompt, select, cancelled, *, service: bool is_local = _is_local_openviking_url(endpoint) api_key_type = "user" if service else "" prefilled_api_key = "" - prefilled_agent = "" while True: values = { "endpoint": endpoint, @@ -1859,9 +1857,6 @@ def _prompt_manual_connection_values(prompt, select, cancelled, *, service: bool if credential_choice == cancelled: return _SETUP_CANCELLED if credential_choice == 2: - values["agent"] = _clean_config_value( - prompt(_AGENT_PROMPT_LABEL, default=_DEFAULT_AGENT) - ) or _DEFAULT_AGENT _print_validation_progress("Validating OpenViking local dev access...") valid, message, _role = _validate_openviking_setup_values(values) if valid: @@ -1975,13 +1970,6 @@ def _prompt_manual_connection_values(prompt, select, cancelled, *, service: bool prefilled_api_key = values["api_key"] continue - if prefilled_agent: - values["agent"] = prefilled_agent - prefilled_agent = "" - else: - values["agent"] = _clean_config_value( - prompt(_AGENT_PROMPT_LABEL, default=_DEFAULT_AGENT) - ) or _DEFAULT_AGENT _print_validation_progress("Validating OpenViking API access...") valid, message, role = _validate_openviking_setup_values( values, @@ -2003,7 +1991,6 @@ def _prompt_manual_connection_values(prompt, select, cancelled, *, service: bool ) if route_choice == 0: prefilled_api_key = values["api_key"] - prefilled_agent = values["agent"] api_key_type = "root" continue if route_choice == 1: @@ -2063,6 +2050,9 @@ def _save_hermes_only_config( ) -> None: provider_config["use_ovcli_config"] = False provider_config.pop("ovcli_config_path", None) + # A newly selected connection must not inherit the previous YAML peer. + # Non-empty peer values, if supplied, are saved with the connection below. + provider_config.pop("agent", None) _set_openviking_provider(config, provider_config) _write_env_vars( env_path, @@ -2386,10 +2376,10 @@ class OpenVikingMemoryProvider(MemoryProvider): { "key": "agent", "description": ( - "Hermes peer ID in OpenViking, sent as the actor peer and " - "used for peer-scoped memories" + "Optional peer ID for separate assistant context. " + "Uses user memory when no peer is configured." ), - "default": "hermes", + "default": _DEFAULT_AGENT, "env_var": "OPENVIKING_AGENT", }, { @@ -4881,20 +4871,21 @@ class OpenVikingMemoryProvider(MemoryProvider): ) def _build_memory_uri(self, subdir: str, *, client=None, timeout: Optional[float] = None) -> str: - """Build a viking:// memory URI under the configured peer namespace.""" + """Build a user memory URI, optionally under the configured peer.""" slug = uuid.uuid4().hex[:12] # Explicit-uid URIs are canonical under every auth mode; the uid-less # `viking://user/peers/...` shorthand was removed upstream (#4196) and # `viking://~/...` only expands for USER/ADMIN roles, not dev/ROOT. active_client = client if client is not None else getattr(self, "_client", None) + # An empty peer on a captured client is intentional. Do not borrow a + # later peer from the provider after a configuration reload. agent = str( - getattr(active_client, "_agent", "") - or getattr(self, "_agent", "") - or _DEFAULT_AGENT + getattr(active_client, "_agent", getattr(self, "_agent", "")) or "" ).strip() + peer_prefix = f"peers/{agent}/" if agent else "" return _user_scoped_uri( self._user_space(active_client, timeout=timeout), - f"peers/{agent}/memories/{subdir}/mem_{slug}.md", + f"{peer_prefix}memories/{subdir}/mem_{slug}.md", ) def on_memory_write( diff --git a/tests/openviking_plugin/test_openviking.py b/tests/openviking_plugin/test_openviking.py index 30fdfcf16f..d795777768 100644 --- a/tests/openviking_plugin/test_openviking.py +++ b/tests/openviking_plugin/test_openviking.py @@ -8,6 +8,8 @@ from http.server import BaseHTTPRequestHandler, HTTPServer from typing import Any, cast from urllib.parse import parse_qs, urlparse +import pytest + import plugins.memory.openviking as openviking_plugin from hermes_cli import __version__ as _HERMES_VERSION from plugins.memory.openviking import OpenVikingMemoryProvider @@ -635,7 +637,8 @@ class TestOpenVikingRead: class TestOpenVikingAutoRecallPrefetch: - def test_prefetch_e2e_sends_limit_and_reads_l2_content(self, monkeypatch): + @pytest.mark.parametrize("peer", ["", "hermes"]) + def test_prefetch_e2e_sends_limit_and_reads_l2_content(self, monkeypatch, peer): records = {"searches": [], "reads": [], "listings": [], "headers": []} class Handler(BaseHTTPRequestHandler): @@ -655,6 +658,7 @@ class TestOpenVikingAutoRecallPrefetch: if parsed.path == "/health": self._send_json({"status": "ok", "healthy": True, "version": "test"}) return + records["headers"].append(dict(self.headers)) if parsed.path == "/api/v1/system/status": self._send_json({"status": "ok", "result": {"user": "user"}}) return @@ -709,7 +713,7 @@ class TestOpenVikingAutoRecallPrefetch: "result": { "memories": [ { - "uri": "viking://user/peers/hermes/memories/e2e-full.md", + "uri": "viking://user/user/peers/hermes/memories/e2e-full.md", "score": 0.9, "level": 2, "category": "events", @@ -742,7 +746,7 @@ class TestOpenVikingAutoRecallPrefetch: monkeypatch.setenv("OPENVIKING_ENDPOINT", endpoint) monkeypatch.setenv("OPENVIKING_ACCOUNT", "acct") monkeypatch.setenv("OPENVIKING_USER", "user") - monkeypatch.setenv("OPENVIKING_AGENT", "hermes") + monkeypatch.setenv("OPENVIKING_AGENT", peer) provider = OpenVikingMemoryProvider() try: @@ -760,7 +764,7 @@ class TestOpenVikingAutoRecallPrefetch: assert "people/ada.md — Ada is the project owner." in block assert "E2E full L2 memory content." in block assert "E2E abstract should not be injected." not in block - assert records["reads"] == ["viking://user/peers/hermes/memories/e2e-full.md"] + assert records["reads"] == ["viking://user/user/peers/hermes/memories/e2e-full.md"] assert [listing["uri"] for listing in records["listings"]] == [ "viking://user/user/memories/preferences", "viking://user/user/memories/entities", @@ -781,7 +785,7 @@ class TestOpenVikingAutoRecallPrefetch: {key.lower(): value for key, value in headers.items()} for headers in records["headers"] ] - assert all(headers.get("x-openviking-actor-peer") == "hermes" for headers in normalized_headers) + assert all(headers.get("x-openviking-actor-peer", "") == peer for headers in normalized_headers) assert all( headers.get("user-agent") == f"openviking-memory-hermes/{_HERMES_VERSION}" for headers in normalized_headers @@ -907,6 +911,7 @@ class TestEnsureClientReloadsEnv: monkeypatch.setenv("OPENVIKING_ENDPOINT", "http://srv:31933") monkeypatch.setenv("OPENVIKING_API_KEY", "") monkeypatch.setenv("OPENVIKING_USER", "alice") + monkeypatch.setenv("OPENVIKING_AGENT", "hermes") provider = OpenVikingMemoryProvider() provider._env_refresh_enabled = True @@ -946,6 +951,7 @@ class TestEnsureClientReloadsEnv: monkeypatch.setattr("plugins.memory.openviking._VikingClient", _StubClient) monkeypatch.setenv("OPENVIKING_ENDPOINT", "https://openviking.example") monkeypatch.setenv("OPENVIKING_API_KEY", "sk-test") + monkeypatch.setenv("OPENVIKING_AGENT", "hermes") provider = OpenVikingMemoryProvider() provider.initialize("session-1") diff --git a/tests/plugins/memory/test_openviking_optional_peer.py b/tests/plugins/memory/test_openviking_optional_peer.py new file mode 100644 index 0000000000..3c14122407 --- /dev/null +++ b/tests/plugins/memory/test_openviking_optional_peer.py @@ -0,0 +1,254 @@ +"""Optional peer identity must agree across setup, requests and memory writes.""" + +import json +import threading +from http.server import BaseHTTPRequestHandler, HTTPServer +from pathlib import Path + +import pytest +import yaml + +import plugins.memory.openviking as ov + + +@pytest.fixture(autouse=True) +def isolated_config(tmp_path, monkeypatch): + monkeypatch.setattr(Path, "home", lambda: tmp_path) + monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes")) + for key in (*ov._OPENVIKING_ENV_KEYS, "OPENVIKING_CLI_CONFIG_FILE"): + monkeypatch.delenv(key, raising=False) + + +@pytest.mark.parametrize("source", ["env", "yaml", "actor_peer_id", "agent_id"]) +@pytest.mark.parametrize("peer", ["", "hermes", "work-assistant"]) +def test_configured_peer_routing_is_preserved(tmp_path, monkeypatch, source, peer): + config = {} + if source == "env": + monkeypatch.setenv("OPENVIKING_AGENT", peer) + elif source == "yaml": + config["agent"] = peer + else: + path = tmp_path / "ovcli.conf" + path.write_text( + json.dumps({"url": "http://localhost:1933", source: peer}), encoding="utf-8" + ) + config = {"use_ovcli_config": True, "ovcli_config_path": str(path)} + + settings = ov._resolve_connection_settings(config) + client = ov._VikingClient("http://localhost:1933", agent=settings["agent"]) + monkeypatch.setattr(client, "get", lambda *a, **kw: {"result": {"user": "alice"}}) + provider = ov.OpenVikingMemoryProvider() + uri = provider._build_memory_uri("preferences", client=client) + + assert settings["agent"] == peer + assert client._headers().get("X-OpenViking-Actor-Peer", "") == peer + prefix = f"peers/{peer}/" if peer else "" + assert uri.startswith(f"viking://user/alice/{prefix}memories/preferences/mem_") + + +def test_unconfigured_client_and_schema_do_not_supply_a_peer(): + settings = ov._resolve_connection_settings({}) + client = ov._VikingClient("http://localhost:1933") + schema = { + field["key"]: field + for field in ov.OpenVikingMemoryProvider().get_config_schema() + } + + assert settings["agent"] == "" + assert schema["agent"]["default"] == "" + assert "X-OpenViking-Actor-Peer" not in client._headers() + assert "X-OpenViking-Actor-Peer" not in client._multipart_headers() + + +@pytest.mark.parametrize("peer", ["", "hermes"]) +def test_memory_uri_uses_captured_peer_even_when_empty(monkeypatch, peer): + client = ov._VikingClient("http://localhost:1933", agent=peer) + monkeypatch.setattr(client, "get", lambda *a, **kw: {"result": {"user": "alice"}}) + provider = ov.OpenVikingMemoryProvider() + provider._agent = "later-peer" + + uri = provider._build_memory_uri("preferences", client=client) + + prefix = f"peers/{peer}/" if peer else "" + assert uri.startswith(f"viking://user/alice/{prefix}memories/preferences/mem_") + assert "later-peer" not in uri + + +@pytest.mark.parametrize("save_to_store", [False, True]) +@pytest.mark.parametrize("credential", ["dev", "user", "root", "service"]) +def test_new_setup_does_not_ask_for_or_save_peer( + tmp_path, + monkeypatch, + save_to_store, + credential, +): + from hermes_cli import memory_setup + + home = tmp_path / "hermes" + home.mkdir() + (home / ".env").write_text( + "OPENVIKING_AGENT=old-peer\nOTHER_KEY=keep\n", encoding="utf-8" + ) + config = {"memory": {"openviking": {"agent": "old-peer", "recall_limit": 9}}} + validations = [] + + def validate(values, **kwargs): + validations.append(dict(values)) + role = ( + "root" + if credential == "root" + else "user" + if values.get("api_key") + else None + ) + return True, "", role + + def prompt(label, default=None, secret=False): + values = { + "OpenViking server URL": "http://localhost:1933", + "OpenViking user API key": "test-user-key", + "OpenViking root API key": "test-root-key", + "OpenViking API key": "test-service-key", + "OpenViking account": "account", + "OpenViking user": "alice", + "OpenViking profile name": "personal", + } + assert label in values, f"Unexpected setup question: {label}" + return values[label] + + def select(title, options, **kwargs): + choices = { + " OpenViking connection": 0 if credential == "service" else 1, + " OpenViking credential": {"dev": 2, "user": 0, "root": 1}.get( + credential, 0 + ), + " Save OpenViking config": int(save_to_store), + } + assert title in choices, f"Unexpected setup menu: {title}" + return choices[title] + + monkeypatch.setattr(memory_setup, "_prompt", prompt) + monkeypatch.setattr(memory_setup, "_curses_select", select) + monkeypatch.setattr(ov, "_validate_openviking_reachability", lambda *a: (True, "")) + monkeypatch.setattr(ov, "_validate_openviking_setup_values", validate) + + ov.OpenVikingMemoryProvider().post_setup(str(home), config) + + assert validations + assert all(values["agent"] == "" for values in validations) + assert "OPENVIKING_AGENT" not in (home / ".env").read_text(encoding="utf-8") + assert "OTHER_KEY=keep" in (home / ".env").read_text(encoding="utf-8") + saved_config = ov._load_hermes_openviking_config() + assert saved_config["recall_limit"] == 9 + assert ov._resolve_connection_settings(saved_config)["agent"] == "" + if save_to_store: + saved = json.loads( + Path(saved_config["ovcli_config_path"]).read_text(encoding="utf-8") + ) + assert "actor_peer_id" not in saved + assert "agent_id" not in saved + + +@pytest.mark.parametrize("peer", ["", "hermes"]) +def test_wire_requests_keep_writes_and_session_messages_in_the_selected_scope( + tmp_path, + monkeypatch, + peer, +): + records = [] + + class Handler(BaseHTTPRequestHandler): + def log_message(self, *args): + pass + + def respond(self, payload): + body = json.dumps(payload).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def do_GET(self): + if self.path == "/health": + self.respond({"status": "ok", "healthy": True, "version": "test"}) + elif self.path == "/api/v1/system/status": + self.respond({"result": {"user": "alice"}}) + else: + self.send_error(404) + + def do_POST(self): + payload = json.loads(self.rfile.read(int(self.headers["Content-Length"]))) + records.append((self.path, dict(self.headers), payload)) + self.respond({"status": "ok", "result": {"written_bytes": 10}}) + + server = HTTPServer(("127.0.0.1", 0), Handler) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + home = tmp_path / "hermes" + home.mkdir() + provider_config = {"endpoint": f"http://127.0.0.1:{server.server_port}"} + if peer: + provider_config["agent"] = peer + (home / "config.yaml").write_text( + yaml.safe_dump({ + "memory": {"provider": "openviking", "openviking": provider_config} + }), + encoding="utf-8", + ) + provider = ov.OpenVikingMemoryProvider() + try: + provider.initialize("peer-test", hermes_home=str(home)) + assert provider._client is not None + result = json.loads( + provider.handle_tool_call("viking_remember", {"content": "I like tea"}) + ) + assert result["status"] == "stored" + provider.on_memory_write("add", "user", "I like coffee") + provider.sync_turn("hello", "hi", session_id="peer-test") + assert provider._drain_writers("peer-test", timeout=5.0) + provider.sync_turn( + "next", + "reply", + session_id="peer-test", + messages=[ + {"role": "user", "content": "next"}, + {"role": "assistant", "content": "reply"}, + ], + ) + assert provider._drain_writers("peer-test", timeout=5.0) + provider.on_session_end([]) + finally: + provider.shutdown() + server.shutdown() + server.server_close() + thread.join(timeout=3.0) + + assert records + for _path, headers, _payload in records: + if peer: + assert headers["X-OpenViking-Actor-Peer"] == peer + else: + assert "X-OpenViking-Actor-Peer" not in headers + writes = [ + payload for path, _, payload in records if path == "/api/v1/content/write" + ] + prefix = f"peers/{peer}/" if peer else "" + assert {write["content"] for write in writes} == {"I like tea", "I like coffee"} + assert all( + write["uri"].startswith(f"viking://user/alice/{prefix}memories/") + for write in writes + ) + batches = [ + payload["messages"] + for path, _, payload in records + if path.endswith("/messages/batch") + ] + assert len(batches) == 2 + for batch in batches: + assert "peer_id" not in batch[0] + if peer: + assert batch[1]["peer_id"] == peer + else: + assert "peer_id" not in batch[1] + assert any(path.endswith("/commit") for path, _, _ in records) diff --git a/tests/plugins/memory/test_openviking_provider.py b/tests/plugins/memory/test_openviking_provider.py index 29acdc00ee..315511d156 100644 --- a/tests/plugins/memory/test_openviking_provider.py +++ b/tests/plugins/memory/test_openviking_provider.py @@ -274,7 +274,10 @@ def test_link_ovcli_profile_removes_stale_inline_config(tmp_path): assert "OTHER_KEY=keep" in env_path.read_text(encoding="utf-8") -def test_post_setup_existing_profile_picker_validates_and_links_saved_profile(tmp_path, monkeypatch): +@pytest.mark.parametrize("peer_key", [None, "actor_peer_id", "agent_id"]) +def test_post_setup_existing_profile_picker_validates_and_links_saved_profile( + tmp_path, monkeypatch, peer_key, +): _clear_openviking_env(monkeypatch) hermes_home = tmp_path / "hermes" hermes_home.mkdir() @@ -285,10 +288,10 @@ def test_post_setup_existing_profile_picker_validates_and_links_saved_profile(tm active_path = openviking_home / "ovcli.conf" saved_path = openviking_home / "ovcli.conf.VPS" active_path.write_text(json.dumps({"url": "http://active.test"}), encoding="utf-8") - saved_path.write_text( - json.dumps({"url": "https://vps.example", "api_key": "user-key"}), - encoding="utf-8", - ) + saved_values = {"url": "https://vps.example", "api_key": "user-key"} + if peer_key: + saved_values[peer_key] = "existing-peer" + saved_path.write_text(json.dumps(saved_values), encoding="utf-8") monkeypatch.setenv("HERMES_HOME", str(hermes_home)) monkeypatch.setattr(openviking_module.Path, "home", staticmethod(lambda: tmp_path)) @@ -318,7 +321,7 @@ def test_post_setup_existing_profile_picker_validates_and_links_saved_profile(tm "root_api_key": "", "account": "", "user": "", - "agent": "", + "agent": "existing-peer" if peer_key else "", }] assert config["memory"]["provider"] == "openviking" assert config["memory"]["openviking"] == { @@ -328,6 +331,9 @@ def test_post_setup_existing_profile_picker_validates_and_links_saved_profile(tm env_text = env_path.read_text(encoding="utf-8") assert "OPENVIKING_" not in env_text assert "OTHER_KEY=keep" in env_text + settings = openviking_module._resolve_connection_settings(config["memory"]["openviking"]) + assert settings["agent"] == ("existing-peer" if peer_key else "") + assert json.loads(saved_path.read_text(encoding="utf-8")) == saved_values def test_local_setup_recommends_user_api_key_before_unauthenticated_mode(monkeypatch): @@ -355,8 +361,6 @@ def test_local_setup_recommends_user_api_key_before_unauthenticated_mode(monkeyp if label == "OpenViking user API key": assert secret is True return "user-key" - if label == openviking_module._AGENT_PROMPT_LABEL: - return default raise AssertionError(f"Unexpected prompt: {label}") values = openviking_module._prompt_manual_connection_values( diff --git a/website/docs/user-guide/features/memory-providers.md b/website/docs/user-guide/features/memory-providers.md index 61b873a9f2..66c374f17c 100644 --- a/website/docs/user-guide/features/memory-providers.md +++ b/website/docs/user-guide/features/memory-providers.md @@ -315,7 +315,6 @@ OPENVIKING_ENDPOINT=http://127.0.0.1:1933 # OPENVIKING_API_KEY=... # OPENVIKING_ACCOUNT=default # OPENVIKING_USER=default -# OPENVIKING_AGENT=hermes ``` OpenViking server settings live in `ov.conf` (`--config`, @@ -329,7 +328,18 @@ live in `ovcli.conf` (`OPENVIKING_CLI_CONFIG_FILE` or - `viking://` URI scheme for hierarchical knowledge browsing `OPENVIKING_ACCOUNT` and `OPENVIKING_USER` are used for local/trusted mode. -`OPENVIKING_AGENT` is Hermes' peer ID in OpenViking for peer-scoped memories. +Peer identity is optional. By default, Hermes sends no peer ID and writes +explicit memories to `viking://user//memories/...`. Setup does not ask +for a peer ID. For separate assistant context, set +`memory.openviking.agent: work-assistant` in `config.yaml`. + +Existing non-empty peer settings keep their peer-scoped writes and recall. +This includes `OPENVIKING_AGENT` and `actor_peer_id` or legacy `agent_id` in a +linked OpenViking config. Existing memories are not moved or deleted. +Installations that relied on the old implicit `hermes` peer can retain it by +setting `memory.openviking.agent: hermes`. With no peer ID, default search can +include all existing peer memories under the same OpenViking user. + Hermes sends `User-Agent: openviking-memory-hermes/` on OpenViking requests. This standard harness identifier contains no per-user identifier and does not add a separate request.