{"repo":"NousResearch/hermes-agent","public_id":"8705","display_label":"OpenViking memory plugin session creation and handling","summary":"All three members concern the OpenViking memory plugin's handling of sessions, including using the Hermes session_id without first creating an OpenViking session, and one also mentions an account default and committing sessions on /new and compress. Titles alone do not show the outcome of any change.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":10463,"merged_at":"2026-04-15T18:28:45+00:00"},"resolution":{"state":"resolved","by":"pr-10463"},"freshness":{"state":"stale","last_verified_at":"2026-04-15T18:28:45+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":8705,"pr_number":10463,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-04-15T18:28:45+00:00"},{"issue_number":8742,"pr_number":10463,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-04-15T18:28:45+00:00"}],"members":[{"kind":"issue","number":8705,"title":"openviking memory plugin: initialize() uses Hermes session_id without creating an OpenViking session first","url":null,"state":"closed"},{"kind":"pr","number":9104,"title":"fix(memory): create OpenViking session server-side instead of reusing Hermes session_id","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#10463","number":10463},{"kind":"pr","value":"#10396","number":10396}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":10463,"title":"fix(openviking): correct account default + commit session on /new and compress","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["cli.py","plugins/memory/openviking/__init__.py","run_agent.py","scripts/release.py","tests/agent/test_memory_provider.py"],"fix_hunks":[{"file":"cli.py","hunks":["@@ -4100,6 +4100,8 @@ def new_session(self, silent=False):\n                 self.agent.flush_memories(self.conversation_history)\n             except (Exception, KeyboardInterrupt):\n                 pass\n+            # Trigger memory extraction on the old session before session_id rotates.\n+            self.agent.commit_memory_session(self.conversation_history)\n             self._notify_session_boundary(\"on_session_finalize\")\n         elif self.agent:\n             # First session or empty history — still finalize the old session"]},{"file":"plugins/memory/openviking/__init__.py","hunks":["@@ -10,8 +10,9 @@\n Config via environment variables (profile-scoped via each profile's .env):\n   OPENVIKING_ENDPOINT  — Server URL (default: http://127.0.0.1:1933)\n   OPENVIKING_API_KEY   — API key (required for authenticated servers)\n-  OPENVIKING_ACCOUNT   — Tenant account (default: root)\n+  OPENVIKING_ACCOUNT   — Tenant account (default: default)\n   OPENVIKING_USER      — Tenant user (default: default)\n+  OPENVIKING_AGENT   — Tenant agent (default: hermes)\n \n Capabilities:\n   - Automatic memory extraction on session commit (6 categories)","@@ -80,11 +81,12 @@ class _VikingClient:\n     \"\"\"Thin HTTP client for the OpenViking REST API.\"\"\"\n \n     def __init__(self, endpoint: str, api_key: str = \"\",\n-                 account: str = \"\", user: str = \"\"):\n+                 account: str = \"\", user: str = \"\", agent: str = \"\"):\n         self._endpoint = endpoint.rstrip(\"/\")\n         self._api_key = [redacted:secret-assignment]\n-        self._account = account or os.environ.get(\"OPENVIKING_ACCOUNT\", \"root\")\n+        self._account = account or os.environ.get(\"OPENVIKING_ACCOUNT\", \"default\")\n         self._user = user or os.environ.get(\"OPENVIKING_USER\", \"default\")\n+        self._agent = agent or os.environ.get(\"OPENVIKING_AGENT\", \"hermes\")\n         self._httpx = _get_httpx()\n         if self._httpx is None:\n             raise ImportError(\"httpx is required for OpenViking: pip install httpx\")","@@ -94,6 +96,7 @@ def _headers(self) -> dict:\n             \"Content-Type\": \"application/json\",\n             \"X-OpenViking-Account\": self._account,\n             \"X-OpenViking-User\": self._user,\n+            \"X-OpenViking-Agent\": self._agent,\n         }\n         if self._api_key:\n             h[\"X-API-Key\"] = self._api_key","@@ -282,20 +285,44 @@ def get_config_schema(self):\n             },\n             {\n                 \"key\": \"api_key\",\n-                \"description\": \"OpenViking API key\",\n+                \"description\": \"OpenViking API key (leave blank for local dev mode)\",\n                 \"secret\": True,\n                 \"env_var\": \"OPENVIKING_API_KEY\",\n             },\n+            {\n+                \"key\": \"account\",\n+                \"description\": \"OpenViking tenant account ID ([default], used when local mode, OPENVIKING_API_KEY is empty)\",\n+                \"default\": \"default\",\n+                \"env_var\": \"OPENVIKING_ACCOUNT\",\n+            },\n+            {\n+                \"key\": \"user\",\n+                \"description\": \"OpenViking user ID within the account ([default], used when local mode, OPENVIKING_API_KEY is empty)\",\n+                \"default\": \"default\",\n+                \"env_var\": \"OPENVIKING_USER\",\n+            },\n+            {\n+                \"key\": \"agent\",\n+                \"description\": \"OpenViking agent ID within the account ([hermes], useful in multi-agent mode)\",\n+                \"default\": \"hermes\",\n+                \"env_var\": \"OPENVIKING_AGENT\",\n+            },\n         ]\n \n     def initialize(self, session_id: str, **kwargs) -> None:\n         self._endpoint = os.environ.get(\"OPENVIKING_ENDPOINT\", _DEFAULT_ENDPOINT)\n         self._api_key = os.environ.get(\"OPENVIKING_API_KEY\", \"\")\n+        self._account = os.environ.get(\"OPENVIKING_ACCOUNT\", \"default\")\n+        self._user = os.environ.get(\"OPENVIKING_USER\", \"default\")\n+        self._agent = os.environ.get(\"OPENVIKING_AGENT\", \"hermes\")\n         self._session_id = session_id\n         self._turn_count = 0\n \n         try:\n-            self._client = _VikingClient(self._endpoint, self._api_key)\n+            self._client = _VikingClient(\n+                self._endpoint, self._api_key,\n+                account=self._account, user=self._user, agent=self._agent,\n+            )\n             if not self._client.health():\n                 logger.warning(\"OpenViking server at %s is not reachable\", self._endpoint)\n                 self._client = None","@@ -325,7 +352,8 @@ def system_prompt_block(self) -> str:\n                 \"(abstract/overview/full), viking_browse to explore.\\n\"\n                 \"Use viking_remember to store facts, viking_add_resource to index URLs/docs.\"\n             )\n-        except Exception:\n+        except Exception as e:\n+            logger.warning(\"OpenViking system_prompt_block failed: %s\", e)\n             return (\n                 \"# OpenViking Knowledge Base\\n\"\n                 f\"Active. Endpoint: {self._endpoint}\\n\"","@@ -351,7 +379,10 @@ def queue_prefetch(self, query: str, *, session_id: str = \"\") -> None:\n \n         def _run():\n             try:\n-                client = _VikingClient(self._endpoint, self._api_key)\n+                client = _VikingClient(\n+                    self._endpoint, self._api_key,\n+                    account=self._account, user=self._user, agent=self._agent,\n+                )\n                 resp = client.post(\"/api/v1/search/find\", {\n                     \"query\": query,\n                     \"top_k\": 5,","@@ -386,7 +417,10 @@ def sync_turn(self, user_content: str, assistant_content: str, *, session_id: st\n \n         def _sync():\n             try:\n-                client = _VikingClient(self._endpoint, self._api_key)\n+                client = _VikingClient(\n+                    self._endpoint, self._api_key,\n+                    account=self._account, user=self._user, agent=self._agent,\n+                )\n                 sid = self._session_id\n \n                 # Add user message","@@ -442,7 +476,10 @@ def on_memory_write(self, action: str, target: str, content: str) -> None:\n \n         def _write():\n             try:\n-                client = _VikingClient(self._endpoint, self._api_key)\n+                client = _VikingClient(\n+                    self._endpoint, self._api_key,\n+                    account=self._account, user=self._user, agent=self._agent,\n+                )\n                 # Add as a user message with memory context so the commit\n                 # picks it up as an explicit memory during extraction\n                 client.post(f\"/api/v1/sessions/{self._session_id}/messages\", {"]},{"file":"run_agent.py","hunks":["@@ -3040,6 +3040,18 @@ def shutdown_memory_provider(self, messages: list = None) -> None:\n             except Exception:\n                 pass\n     \n+    def commit_memory_session(self, messages: list = None) -> None:\n+        \"\"\"Trigger end-of-session extraction without tearing providers down.\n+        Called when session_id rotates (e.g. /new, context compression);\n+        providers keep their state and continue running under the old\n+        session_id — they just flush pending extraction now.\"\"\"\n+        if not self._memory_manager:\n+            return\n+        try:\n+            self._memory_manager.on_session_end(messages or [])\n+        except Exception:\n+            pass\n+\n     def close(self) -> None:\n         \"\"\"Release all resources held by this agent instance.\n ","@@ -6826,6 +6838,8 @@ def _compress_context(self, messages: list, system_message: str, *, approx_token\n             try:\n                 # Propagate title to the new session with auto-numbering\n                 old_title = self._session_db.get_session_title(self.session_id)\n+                # Trigger memory extraction on the old session before it rotates.\n+                self.commit_memory_session(messages)\n                 self._session_db.end_session(self.session_id, \"compression\")\n                 old_session_id = self.session_id\n                 self.session_id = f\"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:6]}\""]},{"file":"scripts/release.py","hunks":["@@ -197,6 +197,8 @@\n     \"[redacted:email]\": \"zhouboli\",\n     \"[redacted:email]\": \"tomqiaozc\",\n     \"[redacted:email]\": \"xinbenlv\",\n+    \"[redacted:email]\": \"ZaynJarvis\",\n+    \"[redacted:email]\": \"ZaynJarvis\",\n }\n \n "]},{"file":"tests/agent/test_memory_provider.py","hunks":["@@ -695,3 +695,44 @@ def test_fenced_block_separates_user_from_recall(self):\n         fence_end = combined.index(\"</memory-context>\")\n         assert \"Alice\" in combined[fence_start:fence_end]\n         assert combined.index(\"weather\") < fence_start\n+\n+\n+# ---------------------------------------------------------------------------\n+# AIAgent.commit_memory_session — routes to MemoryManager.on_session_end\n+# ---------------------------------------------------------------------------\n+\n+\n+class _CommitRecorder(FakeMemoryProvider):\n+    \"\"\"Provider that records on_session_end calls for assertions.\"\"\"\n+\n+    def __init__(self, name=\"recorder\"):\n+        super().__init__(name)\n+        self.end_calls = []\n+\n+    def on_session_end(self, messages):\n+        self.end_calls.append(list(messages or []))\n+\n+\n+class TestCommitMemorySessionRouting:\n+    def test_on_session_end_fans_out(self):\n+        mgr = MemoryManager()\n+        builtin = _CommitRecorder(\"builtin\")\n+        external = _CommitRecorder(\"openviking\")\n+        mgr.add_provider(builtin)\n+        mgr.add_provider(external)\n+\n+        msgs = [{\"role\": \"user\", \"content\": \"hi\"}]\n+        mgr.on_session_end(msgs)\n+\n+        assert builtin.end_calls == [msgs]\n+        assert external.end_calls == [msgs]\n+\n+    def test_on_session_end_tolerates_failure(self):\n+        mgr = MemoryManager()\n+        builtin = FakeMemoryProvider(\"builtin\")\n+        bad = _CommitRecorder(\"bad-provider\")\n+        bad.on_session_end = lambda m: (_ for _ in ()).throw(RuntimeError(\"boom\"))\n+        mgr.add_provider(builtin)\n+        mgr.add_provider(bad)\n+\n+        mgr.on_session_end([])  # must not raise\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}