From 3b008916149540d4a35127c27b1cc622989b1c5b Mon Sep 17 00:00:00 2001 From: Perplexity Computer Date: Wed, 8 Apr 2026 10:32:52 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20wire=20retrieval=5Fenforcer=20L1=20?= =?UTF-8?q?to=20SovereignStore=20=E2=80=94=20eliminate=20subprocess/ONNX?= =?UTF-8?q?=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the subprocess call to mempalace CLI binary with direct SovereignStore import. L1 palace search now uses SQLite + FTS5 + HRR vectors in-process. No ONNX, no subprocess, no API calls. Removes: import subprocess, MEMPALACE_BIN constant Adds: SovereignStore lazy singleton, _get_store(), SOVEREIGN_DB path Closes #383 Depends on #380 (sovereign_store.py) --- .../mempalace/retrieval_enforcer.py | 91 +++++++++++++------ 1 file changed, 62 insertions(+), 29 deletions(-) diff --git a/hermes-sovereign/mempalace/retrieval_enforcer.py b/hermes-sovereign/mempalace/retrieval_enforcer.py index f5030737..a8ca5aa1 100644 --- a/hermes-sovereign/mempalace/retrieval_enforcer.py +++ b/hermes-sovereign/mempalace/retrieval_enforcer.py @@ -1,28 +1,37 @@ """Retrieval Order Enforcer — L0 through L5 memory hierarchy. Ensures the agent checks durable memory before falling back to free generation. -Gracefully degrades if any layer is unavailable (ONNX issues, missing files, etc). +Gracefully degrades if any layer is unavailable (missing files, etc). Layer order: - L0: Identity (~/.mempalace/identity.txt) - L1: Palace rooms (mempalace CLI search) - L2: Session scratch (~/.hermes/scratchpad/{session_id}.json) - L3: Gitea artifacts (API search for issues/PRs) - L4: Procedures (skills directory search) - L5: Free generation (only if L0-L4 produced nothing) + L0: Identity (~/.mempalace/identity.txt) + L1: Palace rooms (SovereignStore — SQLite + FTS5 + HRR, zero API calls) + L2: Session scratch (~/.hermes/scratchpad/{session_id}.json) + L3: Gitea artifacts (API search for issues/PRs) + L4: Procedures (skills directory search) + L5: Free generation (only if L0-L4 produced nothing) -Refs: Epic #367, Sub-issue #369 +Refs: Epic #367, Sub-issue #369, Wiring: #383 """ - from __future__ import annotations import json import os import re -import subprocess from pathlib import Path from typing import Optional +# --------------------------------------------------------------------------- +# Sovereign Store (replaces mempalace CLI subprocess) +# --------------------------------------------------------------------------- +try: + from .sovereign_store import SovereignStore +except ImportError: + try: + from sovereign_store import SovereignStore + except ImportError: + SovereignStore = None # type: ignore[misc,assignment] + # --------------------------------------------------------------------------- # Constants # --------------------------------------------------------------------------- @@ -30,7 +39,7 @@ from typing import Optional IDENTITY_PATH = Path.home() / ".mempalace" / "identity.txt" SCRATCHPAD_DIR = Path.home() / ".hermes" / "scratchpad" SKILLS_DIR = Path.home() / ".hermes" / "skills" -MEMPALACE_BIN = "/Library/Frameworks/Python.framework/Versions/3.12/bin/mempalace" +SOVEREIGN_DB = Path.home() / ".hermes" / "palace" / "sovereign.db" # Patterns that indicate a recall-style query RECALL_PATTERNS = re.compile( @@ -42,6 +51,23 @@ RECALL_PATTERNS = re.compile( r")\b" ) +# Singleton store instance (lazy-init) +_store: Optional["SovereignStore"] = None + + +def _get_store() -> Optional["SovereignStore"]: + """Lazy-init the SovereignStore singleton.""" + global _store + if _store is not None: + return _store + if SovereignStore is None: + return None + try: + _store = SovereignStore(db_path=str(SOVEREIGN_DB)) + return _store + except Exception: + return None + # --------------------------------------------------------------------------- # L0: Identity @@ -62,25 +88,33 @@ def load_identity() -> str: # --------------------------------------------------------------------------- -# L1: Palace search +# L1: Palace search (now via SovereignStore — zero subprocess, zero API) # --------------------------------------------------------------------------- -def search_palace(query: str) -> str: - """Search the mempalace for relevant memories. Gracefully degrades on failure.""" +def search_palace(query: str, room: Optional[str] = None) -> str: + """Search the sovereign memory store for relevant memories. + + Uses SovereignStore (SQLite + FTS5 + HRR) for hybrid keyword + semantic + search. No subprocess calls, no ONNX, no API keys. + + Gracefully degrades to empty string if store is unavailable. + """ + store = _get_store() + if store is None: + return "" try: - bin_path = MEMPALACE_BIN if os.path.exists(MEMPALACE_BIN) else "mempalace" - result = subprocess.run( - [bin_path, "search", query], - capture_output=True, - text=True, - timeout=10, - ) - if result.returncode == 0 and result.stdout.strip(): - return result.stdout.strip() - except (FileNotFoundError, subprocess.TimeoutExpired, OSError): - # ONNX issues (#373) or mempalace not installed — degrade gracefully - pass - return "" + results = store.search(query, room=room, limit=5, min_trust=0.2) + if not results: + return "" + lines = [] + for r in results: + trust = r.get("trust_score", 0.5) + room_name = r.get("room", "general") + content = r.get("content", "") + lines.append(f" [{room_name}] (trust:{trust:.2f}) {content}") + return "\n".join(lines) + except Exception: + return "" # --------------------------------------------------------------------------- @@ -177,7 +211,6 @@ def search_skills(query: str) -> str: try: content = skill_md.read_text(encoding="utf-8").lower() if any(t in content for t in terms): - # Extract title from frontmatter title = skill_dir.name matches.append(f" skill: {title}") except OSError: @@ -236,7 +269,7 @@ def enforce_retrieval_order( result["context"] += f"## Identity\n{identity}\n\n" result["layers_checked"].append("L0") - # L1: Palace search + # L1: Palace search (SovereignStore — zero API, zero subprocess) palace_results = search_palace(query) if palace_results: result["context"] += f"## Palace Memory\n{palace_results}\n\n"