fix(openviking): add missing /agent/{agent}/ segment to memory URI — fixes #36969
_build_memory_uri produced URIs of the form:
viking://user/{user}/memories/{subdir}/mem_{slug}.md
The /agent/{agent}/ segment was missing, causing every agent under
the same user to write into the same flat namespace. In multi-agent
deployments agents silently overwrite each other's memories and
vector retrieval cross-pollinates results.
self._agent was already populated correctly (from OPENVIKING_AGENT
env var, default 'hermes') and sent via X-OpenViking-Agent header —
it was simply not interpolated into the URI.
Fix: add the missing segment so URIs follow the documented shape:
viking://user/{user}/agent/{agent}/memories/{subdir}/mem_{slug}.md
Tests: 4 new regression tests in TestOpenVikingMemoryUriBuilder,
13/13 passed (9 existing + 4 new).
This commit is contained in:
@ -627,9 +627,9 @@ class OpenVikingMemoryProvider(MemoryProvider):
|
||||
logger.warning("OpenViking session commit failed: %s", e)
|
||||
|
||||
def _build_memory_uri(self, subdir: str) -> str:
|
||||
"""Build a viking:// memory URI under the configured user/subdir."""
|
||||
"""Build a viking:// memory URI under the configured user/agent/subdir."""
|
||||
slug = uuid.uuid4().hex[:12]
|
||||
return f"viking://user/{self._user}/memories/{subdir}/mem_{slug}.md"
|
||||
return f"viking://user/{self._user}/agent/{self._agent}/memories/{subdir}/mem_{slug}.md"
|
||||
|
||||
def on_memory_write(
|
||||
self,
|
||||
|
||||
@ -231,3 +231,53 @@ class TestOpenVikingBrowse:
|
||||
"/api/v1/fs/ls",
|
||||
{"uri": "viking://user/hermes"},
|
||||
)]
|
||||
|
||||
|
||||
class TestOpenVikingMemoryUriBuilder:
|
||||
"""Regression tests for _build_memory_uri — fixes #36969.
|
||||
|
||||
Before the fix the URI omitted /agent/{agent}/, causing all agents
|
||||
under the same user to share the same memory namespace.
|
||||
"""
|
||||
|
||||
def _make_provider(self, user="alice", agent="coder"):
|
||||
p = OpenVikingMemoryProvider.__new__(OpenVikingMemoryProvider)
|
||||
p._user = user
|
||||
p._agent = agent
|
||||
return p
|
||||
|
||||
def test_uri_layout_includes_agent_segment(self):
|
||||
"""URI must contain /agent/{agent}/ between user and memories."""
|
||||
p = self._make_provider(user="alice", agent="coder")
|
||||
uri = p._build_memory_uri("preferences")
|
||||
assert uri.startswith("viking://user/alice/agent/coder/memories/preferences/mem_")
|
||||
assert uri.endswith(".md")
|
||||
|
||||
def test_uri_uses_configured_agent_not_default(self):
|
||||
"""_agent value must be interpolated — not hardcoded to 'hermes'."""
|
||||
p = self._make_provider(user="alice", agent="research-bot")
|
||||
uri = p._build_memory_uri("entities")
|
||||
assert "/agent/research-bot/" in uri
|
||||
assert "/agent/hermes/" not in uri
|
||||
|
||||
def test_uri_slug_is_twelve_hex_chars_and_unique(self):
|
||||
"""Slug must be 12 hex chars and differ between calls."""
|
||||
import re
|
||||
p = self._make_provider()
|
||||
uri1 = p._build_memory_uri("preferences")
|
||||
uri2 = p._build_memory_uri("preferences")
|
||||
slug1 = uri1.split("/mem_")[1].replace(".md", "")
|
||||
slug2 = uri2.split("/mem_")[1].replace(".md", "")
|
||||
assert re.fullmatch(r"[0-9a-f]{12}", slug1)
|
||||
assert re.fullmatch(r"[0-9a-f]{12}", slug2)
|
||||
assert slug1 != slug2
|
||||
|
||||
def test_uri_subdir_placed_correctly_for_all_categories(self):
|
||||
"""All five category subdirs must appear between memories/ and slug."""
|
||||
p = self._make_provider(user="u", agent="a")
|
||||
subdirs = ["preferences", "entities", "events", "cases", "patterns"]
|
||||
for subdir in subdirs:
|
||||
uri = p._build_memory_uri(subdir)
|
||||
assert f"/memories/{subdir}/mem_" in uri, (
|
||||
f"subdir '{subdir}' not placed correctly in URI: {uri}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user