Merge branch 'main' into perplexity/pr-checklist-ci
Some checks failed
PR Checklist / pr-checklist (pull_request) Failing after 1m18s

This commit is contained in:
2026-04-08 11:10:15 +00:00
2 changed files with 72 additions and 29 deletions

10
SOUL.md
View File

@@ -1,3 +1,13 @@
<!--
NOTE: This is the BITCOIN INSCRIPTION version of SOUL.md.
It is the immutable on-chain conscience. Do not modify this content.
The NARRATIVE identity document (for onboarding, Audio Overviews,
and system prompts) lives in timmy-home/SOUL.md.
See: #388, #378 for the divergence audit.
-->
# SOUL.md # SOUL.md
## Inscription 1 — The Immutable Conscience ## Inscription 1 — The Immutable Conscience

View File

@@ -1,28 +1,37 @@
"""Retrieval Order Enforcer — L0 through L5 memory hierarchy. """Retrieval Order Enforcer — L0 through L5 memory hierarchy.
Ensures the agent checks durable memory before falling back to free generation. 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: Layer order:
L0: Identity (~/.mempalace/identity.txt) L0: Identity (~/.mempalace/identity.txt)
L1: Palace rooms (mempalace CLI search) L1: Palace rooms (SovereignStore — SQLite + FTS5 + HRR, zero API calls)
L2: Session scratch (~/.hermes/scratchpad/{session_id}.json) L2: Session scratch (~/.hermes/scratchpad/{session_id}.json)
L3: Gitea artifacts (API search for issues/PRs) L3: Gitea artifacts (API search for issues/PRs)
L4: Procedures (skills directory search) L4: Procedures (skills directory search)
L5: Free generation (only if L0-L4 produced nothing) 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 from __future__ import annotations
import json import json
import os import os
import re import re
import subprocess
from pathlib import Path from pathlib import Path
from typing import Optional 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 # Constants
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -30,7 +39,7 @@ from typing import Optional
IDENTITY_PATH = Path.home() / ".mempalace" / "identity.txt" IDENTITY_PATH = Path.home() / ".mempalace" / "identity.txt"
SCRATCHPAD_DIR = Path.home() / ".hermes" / "scratchpad" SCRATCHPAD_DIR = Path.home() / ".hermes" / "scratchpad"
SKILLS_DIR = Path.home() / ".hermes" / "skills" 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 # Patterns that indicate a recall-style query
RECALL_PATTERNS = re.compile( RECALL_PATTERNS = re.compile(
@@ -42,6 +51,23 @@ RECALL_PATTERNS = re.compile(
r")\b" 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 # 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: def search_palace(query: str, room: Optional[str] = None) -> str:
"""Search the mempalace for relevant memories. Gracefully degrades on failure.""" """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: try:
bin_path = MEMPALACE_BIN if os.path.exists(MEMPALACE_BIN) else "mempalace" results = store.search(query, room=room, limit=5, min_trust=0.2)
result = subprocess.run( if not results:
[bin_path, "search", query], return ""
capture_output=True, lines = []
text=True, for r in results:
timeout=10, trust = r.get("trust_score", 0.5)
) room_name = r.get("room", "general")
if result.returncode == 0 and result.stdout.strip(): content = r.get("content", "")
return result.stdout.strip() lines.append(f" [{room_name}] (trust:{trust:.2f}) {content}")
except (FileNotFoundError, subprocess.TimeoutExpired, OSError): return "\n".join(lines)
# ONNX issues (#373) or mempalace not installed — degrade gracefully except Exception:
pass return ""
return ""
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -177,7 +211,6 @@ def search_skills(query: str) -> str:
try: try:
content = skill_md.read_text(encoding="utf-8").lower() content = skill_md.read_text(encoding="utf-8").lower()
if any(t in content for t in terms): if any(t in content for t in terms):
# Extract title from frontmatter
title = skill_dir.name title = skill_dir.name
matches.append(f" skill: {title}") matches.append(f" skill: {title}")
except OSError: except OSError:
@@ -236,7 +269,7 @@ def enforce_retrieval_order(
result["context"] += f"## Identity\n{identity}\n\n" result["context"] += f"## Identity\n{identity}\n\n"
result["layers_checked"].append("L0") result["layers_checked"].append("L0")
# L1: Palace search # L1: Palace search (SovereignStore — zero API, zero subprocess)
palace_results = search_palace(query) palace_results = search_palace(query)
if palace_results: if palace_results:
result["context"] += f"## Palace Memory\n{palace_results}\n\n" result["context"] += f"## Palace Memory\n{palace_results}\n\n"