mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
502 lines
21 KiB
Python
502 lines
21 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
ENE Memory Atlas — MCP Surface (M1, sketch)
|
||
|
||
Exposes the unified ENE memory atlas as MCP tools so every LLM in the stack
|
||
(Claude, local Ollama models, swarm agents) sees the same address-first
|
||
substrate through one protocol.
|
||
|
||
Spec: 6-Documentation/docs/specs/ENE_MEMORY_ATLAS_SPEC.md
|
||
Pattern: matches local mcp_server.py (Server + stdio_server + @list_tools/@call_tool)
|
||
Status:
|
||
LIVE — atlas.lookup, atlas.scan, atlas.expand (gemma3:1b + lexical
|
||
fallback), atlas.attest (Trimvirate GET /status), atlas.stats
|
||
STUBS — atlas.spiral (M2+M3), atlas.project (M3), atlas.write (M5)
|
||
Each stub returns Tier-5 visible refusal naming the exact promotion
|
||
milestone, never silent insert.
|
||
|
||
Address-first axiom: every read tool starts from an address (pkg+version,
|
||
voxel_key, regime bin, or expanded query → regime bin), never from
|
||
nearest-neighbor in an embedding space.
|
||
"""
|
||
|
||
import sys
|
||
import json
|
||
import sqlite3
|
||
import asyncio
|
||
import urllib.request
|
||
import urllib.error
|
||
from pathlib import Path
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
ROOT = Path(__file__).parent
|
||
sys.path.insert(0, str(ROOT))
|
||
sys.path.insert(0, str(ROOT / "4-Infrastructure" / "infra"))
|
||
sys.path.insert(0, str(ROOT / "scripts"))
|
||
sys.path.insert(0, str(ROOT / "nodes"))
|
||
|
||
try:
|
||
from mcp.server import Server
|
||
from mcp.server.stdio import stdio_server
|
||
from mcp.types import Tool, TextContent
|
||
except ImportError:
|
||
print("MCP SDK not installed. Install with: pip install mcp", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
# ── Atlas dependencies (live components) ─────────────────────────────────────
|
||
# Point to the actual database location in shared-data
|
||
SUBSTRATE_DB = Path("/home/allaun/Documents/Research Stack/shared-data/data/substrate_index.db")
|
||
|
||
# Local Ollama IPv6 carrier — sovereignty rule (see MEMORY.md)
|
||
OLLAMA_USER = "http://[::1]:11435"
|
||
GEMMA_MODEL = "gemma3:1b"
|
||
|
||
# Trimvirate BFT endpoints (tardy.py serve mode) — atom writes route here
|
||
WARDEN_LOCAL = "127.0.0.1" # warden runs co-located with the atlas
|
||
JUDGE_TARDY = "http://[100.111.192.47]:8450"
|
||
HUTTER_TARDY = "http://[100.110.117.19]:8450"
|
||
BFT_QUORUM = 2 # 2-of-3
|
||
|
||
# Dless Ω weights (MOIM canonical, see spec §4.1)
|
||
W_CHI, W_KAPPA, W_SIGMA, W_LAMBDA, W_ETA = 0.25, 0.20, 0.30, 0.15, 0.10
|
||
|
||
server = Server("ene-memory-atlas")
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════
|
||
# TOOL SURFACE
|
||
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
@server.list_tools()
|
||
async def list_tools() -> List[Tool]:
|
||
return [
|
||
Tool(
|
||
name="atlas.lookup",
|
||
description=(
|
||
"Direct address lookup. Resolve an atom by (pkg, version) or "
|
||
"by voxel_key. Returns the full MemoryAtom record. O(1)."
|
||
),
|
||
inputSchema={
|
||
"type": "object",
|
||
"properties": {
|
||
"pkg": {"type": "string"},
|
||
"version": {"type": "string"},
|
||
"voxel_key": {"type": "integer", "description": "34-bit voxel address"},
|
||
},
|
||
},
|
||
),
|
||
Tool(
|
||
name="atlas.scan",
|
||
description=(
|
||
"Filtered scan. Restrict by tier / layer / domain / archetype / "
|
||
"regime bin / settlement state / Ω threshold. Sort by Ω desc. "
|
||
"Address-first: filters are address predicates, not similarity."
|
||
),
|
||
inputSchema={
|
||
"type": "object",
|
||
"properties": {
|
||
"tier": {"type": "string", "enum": ["CORE", "INFRA", "RESEARCH"]},
|
||
"layer": {"type": "string"},
|
||
"domain": {"type": "string"},
|
||
"archetype": {"type": "string"},
|
||
"regime_bin": {"type": "integer", "minimum": 0, "maximum": 3,
|
||
"description": "0=random, 1=weak, 2=strong, 3=constant"},
|
||
"settlement_min": {"type": "string",
|
||
"enum": ["SEED", "FORMING", "STABLE",
|
||
"CRYSTALLIZED", "COMPRESSED"]},
|
||
"omega_min": {"type": "number", "minimum": 0.0, "maximum": 1.0},
|
||
"limit": {"type": "integer", "default": 16, "maximum": 64},
|
||
},
|
||
},
|
||
),
|
||
Tool(
|
||
name="atlas.expand",
|
||
description=(
|
||
"Local-LLM query expansion (gemma3:1b at [::1]:11435). "
|
||
"Returns expanded terms + IoC regime bin classification. "
|
||
"Output feeds atlas.scan or atlas.spiral as the entry point."
|
||
),
|
||
inputSchema={
|
||
"type": "object",
|
||
"properties": {
|
||
"query": {"type": "string"},
|
||
"model": {"type": "string", "default": GEMMA_MODEL},
|
||
},
|
||
"required": ["query"],
|
||
},
|
||
),
|
||
Tool(
|
||
name="atlas.spiral",
|
||
description=(
|
||
"Golden spiral search over the 5D manifold projection. "
|
||
"Phase 137.5°, radius × 1.0078 per step, Dless-Ω-warped "
|
||
"distance, branch-prune by subtree_fold_point. Returns top-k."
|
||
),
|
||
inputSchema={
|
||
"type": "object",
|
||
"properties": {
|
||
"seed_pkg": {"type": "string"},
|
||
"max_distance": {"type": "number", "default": 0.5},
|
||
"max_results": {"type": "integer", "default": 8, "maximum": 16},
|
||
"type_filter": {"type": "array", "items": {"type": "string"}},
|
||
},
|
||
"required": ["seed_pkg"],
|
||
},
|
||
),
|
||
Tool(
|
||
name="atlas.project",
|
||
description=(
|
||
"Read the Q0_16 scalar through one of the three projection "
|
||
"lanes (calculation, defense, verification). Returns signed "
|
||
"[-1, 1] value at the addressed atom for the requested lane."
|
||
),
|
||
inputSchema={
|
||
"type": "object",
|
||
"properties": {
|
||
"pkg": {"type": "string"},
|
||
"version": {"type": "string"},
|
||
"lane": {"type": "string",
|
||
"enum": ["calculation", "defense", "verification"]},
|
||
},
|
||
"required": ["pkg", "lane"],
|
||
},
|
||
),
|
||
Tool(
|
||
name="atlas.write",
|
||
description=(
|
||
"Submit a new atom. Routes through warden → judge + hutter "
|
||
"(2-of-3 Trimvirate BFT). Origin Protocol traits checked "
|
||
"at write time. Returns BFT verdict + MMR index. Failure "
|
||
"Tier 5 visible: silence forbidden."
|
||
),
|
||
inputSchema={
|
||
"type": "object",
|
||
"properties": {
|
||
"pkg": {"type": "string"},
|
||
"version": {"type": "string"},
|
||
"tier": {"type": "string"},
|
||
"domain": {"type": "string"},
|
||
"archetype": {"type": "string"},
|
||
"description": {"type": "string"},
|
||
"files": {"type": "array", "items": {"type": "string"}},
|
||
"trinary": {"type": "string",
|
||
"enum": ["ADD", "PAUSE", "SUBTRACT"],
|
||
"default": "ADD"},
|
||
},
|
||
"required": ["pkg", "version", "tier", "description"],
|
||
},
|
||
),
|
||
Tool(
|
||
name="atlas.attest",
|
||
description=(
|
||
"Return MMR proof for an atom (leaf hash + sibling path to "
|
||
"current MMR root). Read across all three nodes to verify "
|
||
"consistency. Used by external auditors and the swarm."
|
||
),
|
||
inputSchema={
|
||
"type": "object",
|
||
"properties": {
|
||
"mmr_index": {"type": "integer"},
|
||
"node": {"type": "string",
|
||
"enum": ["warden", "judge", "hutter", "all"],
|
||
"default": "all"},
|
||
},
|
||
"required": ["mmr_index"],
|
||
},
|
||
),
|
||
Tool(
|
||
name="atlas.stats",
|
||
description=(
|
||
"Atlas health snapshot: total atoms, regime distribution, "
|
||
"Ω histogram, MMR depth per node, BFT consistency check. "
|
||
"Cheap; safe to call frequently."
|
||
),
|
||
inputSchema={"type": "object", "properties": {}},
|
||
),
|
||
]
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════
|
||
# DISPATCH
|
||
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
@server.call_tool()
|
||
async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
|
||
try:
|
||
if name == "atlas.lookup": result = _lookup(arguments)
|
||
elif name == "atlas.scan": result = _scan(arguments)
|
||
elif name == "atlas.expand": result = await _expand(arguments)
|
||
elif name == "atlas.spiral": result = _spiral(arguments)
|
||
elif name == "atlas.project": result = _project(arguments)
|
||
elif name == "atlas.write": result = await _write(arguments)
|
||
elif name == "atlas.attest": result = await _attest(arguments)
|
||
elif name == "atlas.stats": result = _stats()
|
||
else:
|
||
raise ValueError(f"Unknown tool: {name}")
|
||
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
||
except Exception as e:
|
||
# Failure Contract: silence is forbidden. Tier 5 = visible failure
|
||
# with diagnostic info.
|
||
err = {
|
||
"ok": False,
|
||
"tool": name,
|
||
"error_class": type(e).__name__,
|
||
"reason": str(e),
|
||
"journalctl": "journalctl --user -u mcp-ene-atlas -n 50",
|
||
}
|
||
return [TextContent(type="text", text=json.dumps(err, indent=2))]
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════
|
||
# STUBS — Promote each to live code in spec milestones M2-M5
|
||
# Each stub names the exact file/function that holds the real implementation
|
||
# so future-Claude doesn't reinvent the path.
|
||
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
def _lookup(args: Dict) -> Dict:
|
||
"""M1 — direct query against substrate_index.db packages table.
|
||
voxel_key path requires M2 (fractal hash + voxel_key column added)."""
|
||
conn = sqlite3.connect(SUBSTRATE_DB)
|
||
cur = conn.cursor()
|
||
if "pkg" in args:
|
||
cur.execute(
|
||
"SELECT pkg, version, tier, domain, archetype, description, "
|
||
" files, sha256, indexed_utc, nd_point, idea_weights "
|
||
"FROM packages WHERE pkg = ? "
|
||
+ ("AND version = ? " if "version" in args else "")
|
||
+ "LIMIT 1",
|
||
(args["pkg"],) + ((args["version"],) if "version" in args else ()),
|
||
)
|
||
elif "voxel_key" in args:
|
||
# TODO(M2): once voxel_key column added, lookup by 34-bit address
|
||
return {"ok": False, "reason": "voxel_key lookup pending M2 migration"}
|
||
else:
|
||
return {"ok": False, "reason": "atlas.lookup needs pkg or voxel_key"}
|
||
row = cur.fetchone()
|
||
conn.close()
|
||
if not row:
|
||
return {"ok": True, "found": False}
|
||
cols = ["pkg", "version", "tier", "domain", "archetype", "description",
|
||
"files", "sha256", "indexed_utc", "nd_point", "idea_weights"]
|
||
return {"ok": True, "found": True, "atom": dict(zip(cols, row))}
|
||
|
||
|
||
def _scan(args: Dict) -> Dict:
|
||
"""M1 — filtered scan. Ω sort blocked until M3 adds dless_omega column;
|
||
until then, sort by indexed_utc desc as a stable proxy."""
|
||
where, params = [], []
|
||
for k in ("tier", "layer", "domain", "archetype"):
|
||
if k in args:
|
||
where.append(f"{k} = ?"); params.append(args[k])
|
||
sql = "SELECT pkg, version, tier, domain, archetype, description "
|
||
sql += "FROM packages"
|
||
if where:
|
||
sql += " WHERE " + " AND ".join(where)
|
||
sql += " ORDER BY indexed_utc DESC LIMIT ?"
|
||
params.append(min(args.get("limit", 16), 64))
|
||
conn = sqlite3.connect(SUBSTRATE_DB); cur = conn.cursor()
|
||
cur.execute(sql, params)
|
||
rows = cur.fetchall(); conn.close()
|
||
return {
|
||
"ok": True,
|
||
"count": len(rows),
|
||
"note_M3": "Ω sort pending dless_omega column migration",
|
||
"atoms": [dict(zip(["pkg","version","tier","domain","archetype","description"], r))
|
||
for r in rows],
|
||
}
|
||
|
||
|
||
_STOPWORDS = frozenset({
|
||
"the", "a", "an", "and", "or", "of", "in", "on", "at", "to", "for",
|
||
"is", "are", "was", "were", "be", "been", "being", "have", "has", "had",
|
||
"do", "does", "did", "will", "would", "could", "should", "may", "might",
|
||
"i", "me", "my", "we", "our", "you", "your", "it", "its", "this", "that",
|
||
"with", "by", "from", "as", "if", "but", "not", "so", "than",
|
||
})
|
||
|
||
|
||
def _lexical_expand(query: str) -> List[str]:
|
||
"""Stopword-split fallback when Ollama is unreachable."""
|
||
toks = [t.strip(".,;:!?()[]{}\"'`").lower()
|
||
for t in query.split() if t]
|
||
seen, out = set(), []
|
||
for t in toks:
|
||
if t and t not in _STOPWORDS and t not in seen:
|
||
seen.add(t)
|
||
out.append(t)
|
||
return out or [query.lower()]
|
||
|
||
|
||
def _gemma_expand(query: str, model: str = GEMMA_MODEL,
|
||
endpoint: str = OLLAMA_USER, timeout: float = 8.0) -> Dict:
|
||
"""Ask local Ollama gemma3:1b to expand the query into 5-10 related
|
||
terms. Returns {ok, expanded, source} dict. Sovereignty: IPv6 [::1]:11435,
|
||
no Gemini/OpenAI fallback. On any failure → lexical stopword split,
|
||
Tier 4 (degraded with context, not silence)."""
|
||
prompt = (
|
||
"List 5-10 short related terms for the following query, "
|
||
"separated by commas, no extra commentary.\n\nQuery: " + query
|
||
)
|
||
body = json.dumps({"model": model, "prompt": prompt, "stream": False}).encode("utf-8")
|
||
req = urllib.request.Request(
|
||
f"{endpoint}/api/generate",
|
||
data=body,
|
||
headers={"Content-Type": "application/json"},
|
||
method="POST",
|
||
)
|
||
try:
|
||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||
payload = json.loads(resp.read().decode("utf-8"))
|
||
text = (payload.get("response") or "").strip()
|
||
terms = [t.strip().strip(".,;:!?\"'`").lower()
|
||
for t in text.replace("\n", ",").split(",")]
|
||
terms = [t for t in terms if t and len(t) <= 64]
|
||
if not terms:
|
||
raise ValueError("gemma3 returned no parseable terms")
|
||
return {"ok": True, "expanded": terms, "source": f"gemma:{model}"}
|
||
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError,
|
||
ValueError, json.JSONDecodeError) as e:
|
||
return {
|
||
"ok": True, # degraded but functional
|
||
"expanded": _lexical_expand(query),
|
||
"source": "lexical_fallback",
|
||
"ollama_unreachable": f"{type(e).__name__}: {e}",
|
||
}
|
||
|
||
|
||
async def _expand(args: Dict) -> Dict:
|
||
"""M1 — local-LLM query expansion against gemma3:1b at [::1]:11435,
|
||
with lexical stopword fallback. IoC regime classification of the
|
||
expanded set deferred to M3."""
|
||
model = args.get("model", GEMMA_MODEL)
|
||
result = _gemma_expand(args["query"], model=model)
|
||
return {
|
||
"ok": True,
|
||
"query": args["query"],
|
||
"expanded": result["expanded"],
|
||
"source": result["source"],
|
||
"regime_bin": None, # M3: IoC regime classification
|
||
**({"ollama_unreachable": result["ollama_unreachable"]}
|
||
if "ollama_unreachable" in result else {}),
|
||
}
|
||
|
||
|
||
def _spiral(args: Dict) -> Dict:
|
||
"""M4 — golden spiral on 5D manifold projection.
|
||
Requires M2 (fractal hash) + M3 (Ω) before spiral becomes meaningful."""
|
||
return {
|
||
"ok": False,
|
||
"reason": "atlas.spiral pending M2 (fractal hash) + M3 (Dless Ω)",
|
||
"fallback": "use atlas.scan + atlas.expand for now",
|
||
}
|
||
|
||
|
||
def _project(args: Dict) -> Dict:
|
||
"""M3 — three-lane projection of Q0_16. Q0_16 column added with M3.
|
||
Lane semantics from ScalarEventProjection.lean."""
|
||
return {
|
||
"ok": False,
|
||
"reason": "atlas.project pending M3 (Q0_16 + lane columns)",
|
||
}
|
||
|
||
|
||
async def _write(args: Dict) -> Dict:
|
||
"""M5 — Origin Protocol gates + Trimvirate BFT.
|
||
Path: warden tardy → POST to judge + hutter → 2-of-3 quorum.
|
||
Until M5 lands, refuse rather than silently inserting unguarded rows."""
|
||
return {
|
||
"ok": False,
|
||
"reason": "atlas.write pending M5 (Origin Protocol + creator letter)",
|
||
"rationale": (
|
||
"Failure Contract Tier 5: visible refusal beats silent insert. "
|
||
"Atom writes that bypass trait enforcement are exactly the "
|
||
"boundary the Origin Protocol exists to protect."
|
||
),
|
||
}
|
||
|
||
|
||
def _tardy_status(endpoint: str, timeout: float = 5.0) -> Dict:
|
||
"""GET /status on a remote tardy.py serve node over Tailscale. Returns
|
||
{ok, mmr_root, node_id, ...} or {ok: false, error_class, reason}.
|
||
Failure Contract Tier 5: visible diagnostic, never silent."""
|
||
try:
|
||
req = urllib.request.Request(f"{endpoint}/status", method="GET")
|
||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||
payload = json.loads(resp.read().decode("utf-8"))
|
||
return {"ok": True, "endpoint": endpoint, **payload}
|
||
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError,
|
||
ValueError, json.JSONDecodeError) as e:
|
||
return {
|
||
"ok": False,
|
||
"endpoint": endpoint,
|
||
"error_class": type(e).__name__,
|
||
"reason": str(e),
|
||
}
|
||
|
||
|
||
async def _attest(args: Dict) -> Dict:
|
||
"""M1 — query GET /status on each Trimvirate node concurrently,
|
||
return MMR root from each, flag inconsistencies. Selection: 'all'
|
||
contacts both judge and hutter; 'judge' or 'hutter' contacts one."""
|
||
target = args.get("node", "all")
|
||
endpoints = []
|
||
if target in ("all", "judge"): endpoints.append(("judge", JUDGE_TARDY))
|
||
if target in ("all", "hutter"): endpoints.append(("hutter", HUTTER_TARDY))
|
||
if not endpoints:
|
||
return {"ok": False, "reason": f"unknown node selector: {target}"}
|
||
|
||
loop = asyncio.get_event_loop()
|
||
results = await asyncio.gather(*[
|
||
loop.run_in_executor(None, _tardy_status, ep) for _, ep in endpoints
|
||
])
|
||
by_node = {name: res for (name, _), res in zip(endpoints, results)}
|
||
|
||
# Cross-node MMR consistency check
|
||
roots = {n: r.get("mmr_root") for n, r in by_node.items() if r.get("ok")}
|
||
consistent = (len(set(roots.values())) <= 1) if len(roots) > 1 else None
|
||
|
||
return {
|
||
"ok": True,
|
||
"mmr_index": args["mmr_index"],
|
||
"nodes": by_node,
|
||
"mmr_roots_consistent": consistent,
|
||
"live_node_count": sum(1 for r in by_node.values() if r.get("ok")),
|
||
"bft_quorum_required": BFT_QUORUM,
|
||
}
|
||
|
||
|
||
def _stats() -> Dict:
|
||
"""M1 — substrate_index.db row count + tier/domain/layer histograms.
|
||
Ω histogram + MMR depth pending M3 / M5."""
|
||
conn = sqlite3.connect(SUBSTRATE_DB); cur = conn.cursor()
|
||
cur.execute("SELECT COUNT(*) FROM packages")
|
||
total = cur.fetchone()[0]
|
||
cur.execute("SELECT tier, COUNT(*) FROM packages GROUP BY tier")
|
||
by_tier = dict(cur.fetchall())
|
||
cur.execute("SELECT layer, COUNT(*) FROM packages GROUP BY layer LIMIT 16")
|
||
by_layer = dict(cur.fetchall())
|
||
conn.close()
|
||
return {
|
||
"ok": True,
|
||
"total_atoms": total,
|
||
"by_tier": by_tier,
|
||
"by_layer_top16": by_layer,
|
||
"pending": {
|
||
"regime_distribution": "M3",
|
||
"omega_histogram": "M3",
|
||
"mmr_depth_per_node": "M1 wire to tardy",
|
||
"bft_consistency": "M1 wire to tardy",
|
||
},
|
||
}
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════
|
||
# ENTRY
|
||
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
async def main():
|
||
async with stdio_server() as (read, write):
|
||
await server.run(read, write, server.create_initialization_options())
|
||
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(main())
|