This repository has been archived on 2026-08-20. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
forge-retired/reinstall_imprints.py
T
Will Anderson 0a15e2fa42 DHARMA network: per-soul Engram instances (8801–8819)
Each soul now gets their own isolated Engram process with a dedicated data
directory (imprints/<slug>/), port, and API key — the DHARMA network.
Neuron's Engram at 8742 is never touched.

- registry.json: add engram_db_path, engram_port, engram_url to all 19 entries
  (Bobby Anderson 8801 → Helen Keller 8819)
- launch_dharma.sh: start all 19 soul Engrams in background; supports
  single-slug filter and skip-if-running detection
- stop_dharma.sh: graceful shutdown via PID file, falls back to port scan
- reinstall_imprints.py: bulk reinstall — starts each Engram temporarily,
  installs seed, records new root_id, stops; supports --slug and --dry-run
- src/install.el: resolves soul's engram_url from registry (never 8742);
  guards against accidental writes to Neuron's shared Engram
- src/summon.el: reads engram_url per-soul from registry; passes it to soul
  server in POST body so soul connects to the right Engram; supports
  both single-soul (engram_url) and multi-soul (engram_urls array) payloads
2026-05-03 02:52:01 -05:00

375 lines
13 KiB
Python
Executable File

#!/usr/bin/env python3
"""
reinstall_imprints.py — Reinstall all soul imprints into their own Engram instances.
Each soul gets a dedicated Engram process with:
- Data dir: forge/imprints/<slug>/
- Port: from registry.json (engram_port)
- API key: ntn-<slug>-2026
This script:
1. Reads registry.json for soul metadata
2. For each soul:
a. Starts their Engram instance temporarily
b. Waits for it to be ready (polls GET /stats)
c. Reads their seed JSON file
d. POSTs all nodes/edges to their Engram
e. Records the new root node ID
f. Stops the Engram instance
3. Updates registry.json with confirmed engram_root_id
Do NOT touch Neuron's Engram at localhost:8742.
Usage:
python3 reinstall_imprints.py
python3 reinstall_imprints.py --slug richard-feynman # single soul
python3 reinstall_imprints.py --dry-run # validate only, no writes
"""
import argparse
import json
import os
import signal
import subprocess
import sys
import time
import urllib.error
import urllib.request
from datetime import date
from pathlib import Path
# ── Paths ──────────────────────────────────────────────────────────────────────
FORGE_DIR = Path("/Users/will/Development/neuron-technologies/forge")
ENGRAM_BIN = Path("/Users/will/Development/neuron-technologies/foundation/engram/dist/engram")
REGISTRY_FILE = FORGE_DIR / "registry.json"
# Startup: how long to wait for Engram to become ready
STARTUP_TIMEOUT_S = 30
STARTUP_POLL_INTERVAL_S = 0.5
# HTTP timeout for node/edge creation calls
HTTP_TIMEOUT_S = 300
# ── HTTP helpers ───────────────────────────────────────────────────────────────
def engram_get(base_url: str, path: str) -> dict:
url = f"{base_url}{path}"
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=HTTP_TIMEOUT_S) as resp:
return json.loads(resp.read())
def engram_post(base_url: str, path: str, body: dict, api_key: str) -> dict:
url = f"{base_url}{path}"
data = json.dumps(body).encode()
req = urllib.request.Request(
url,
data=data,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
},
method="POST",
)
with urllib.request.urlopen(req, timeout=HTTP_TIMEOUT_S) as resp:
return json.loads(resp.read())
# ── Engram lifecycle ───────────────────────────────────────────────────────────
def start_engram(slug: str, db_path: Path, port: int) -> subprocess.Popen:
"""Start an Engram instance for a soul. Returns the process handle."""
api_key = f"ntn-{slug}-2026"
log_path = FORGE_DIR / "log" / f"dharma-{slug}.log"
log_path.parent.mkdir(parents=True, exist_ok=True)
db_path.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["ENGRAM_DB_PATH"] = str(db_path)
env["ENGRAM_BIND"] = f"0.0.0.0:{port}"
env["ENGRAM_API_KEY"] = api_key
env["ENGRAM_PEER_NAME"] = f"dharma-{slug}"
log_fh = open(log_path, "a")
proc = subprocess.Popen(
[str(ENGRAM_BIN)],
env=env,
stdout=log_fh,
stderr=log_fh,
)
return proc
def wait_for_engram(base_url: str, timeout_s: float = STARTUP_TIMEOUT_S) -> bool:
"""Poll GET /stats until Engram responds or timeout."""
deadline = time.time() + timeout_s
while time.time() < deadline:
try:
engram_get(base_url, "/stats")
return True
except Exception:
time.sleep(STARTUP_POLL_INTERVAL_S)
return False
def stop_engram(proc: subprocess.Popen, slug: str) -> None:
"""Gracefully stop an Engram process."""
if proc.poll() is None:
proc.terminate()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait()
print(f" [{slug}] Engram stopped")
# ── Imprint install ────────────────────────────────────────────────────────────
def install_imprint(base_url: str, api_key: str, subject: str, seed: dict) -> str:
"""
POST all seed nodes and edges to the soul's Engram.
Returns the root node ID.
"""
# Root node
root = engram_post(base_url, "/nodes", {
"content": f"IMPRINT: {subject} | forge/0.1.0",
"node_type": "Identity",
"salience": 1.0,
}, api_key)
root_id = root["id"]
print(f" root node: {root_id}")
# Value nodes
for v in seed.get("values", []):
content = f"VALUE: {v['value']}"
if v.get("grounding"):
content += f" | grounding: {v['grounding']}"
node = engram_post(base_url, "/nodes", {
"content": content,
"node_type": "Identity",
"salience": v.get("weight", 0.8),
}, api_key)
engram_post(base_url, "/edges", {
"from_id": root_id,
"to_id": node["id"],
"relation": "has_value",
"weight": v.get("weight", 0.8),
}, api_key)
print(f" values: {len(seed.get('values', []))}")
# Biography nodes
for b in seed.get("biography", []):
node = engram_post(base_url, "/nodes", {
"content": f"BIOGRAPHY: {b['event']}",
"node_type": "Identity",
"salience": b.get("weight", 0.7),
}, api_key)
engram_post(base_url, "/edges", {
"from_id": root_id,
"to_id": node["id"],
"relation": "formed_by",
"weight": b.get("weight", 0.7),
}, api_key)
print(f" biography: {len(seed.get('biography', []))}")
# Relationship nodes
for r in seed.get("relationships", []):
content = f"RELATIONSHIP: {r['name']}"
if r.get("role"):
content += f" ({r['role']})"
node = engram_post(base_url, "/nodes", {
"content": content,
"node_type": "Identity",
"salience": r.get("weight", 0.6),
}, api_key)
engram_post(base_url, "/edges", {
"from_id": root_id,
"to_id": node["id"],
"relation": "relates_to",
"weight": r.get("weight", 0.6),
}, api_key)
print(f" relationships: {len(seed.get('relationships', []))}")
# Reasoning pattern nodes
for pattern in seed.get("reasoning_patterns", []):
node = engram_post(base_url, "/nodes", {
"content": f"REASONING: {pattern}",
"node_type": "Identity",
"salience": 0.7,
}, api_key)
engram_post(base_url, "/edges", {
"from_id": root_id,
"to_id": node["id"],
"relation": "reasons_with",
"weight": 0.7,
}, api_key)
print(f" reasoning patterns: {len(seed.get('reasoning_patterns', []))}")
# Voice profile node (if present)
voice = seed.get("voice_profile", {})
if voice:
voice_parts = []
for k, v in voice.items():
voice_parts.append(f"{k}: {v}")
voice_content = "VOICE: " + " | ".join(voice_parts)
node = engram_post(base_url, "/nodes", {
"content": voice_content[:2000], # guard against very long profiles
"node_type": "Identity",
"salience": 0.9,
}, api_key)
engram_post(base_url, "/edges", {
"from_id": root_id,
"to_id": node["id"],
"relation": "speaks_as",
"weight": 0.9,
}, api_key)
print(f" voice profile: installed")
return root_id
# ── Registry helpers ───────────────────────────────────────────────────────────
def load_registry() -> dict:
return json.loads(REGISTRY_FILE.read_text())
def save_registry(registry: dict) -> None:
REGISTRY_FILE.write_text(json.dumps(registry, indent=2, ensure_ascii=False))
def resolve_seed_path(entry: dict) -> Path:
"""Return absolute path to a seed file, resolving relative to FORGE_DIR."""
seed_file = entry["seed_file"]
path = Path(seed_file)
if not path.is_absolute():
path = FORGE_DIR / path
return path
# ── Main ───────────────────────────────────────────────────────────────────────
def main() -> None:
parser = argparse.ArgumentParser(description="Reinstall soul imprints into per-soul Engrams")
parser.add_argument("--slug", help="Only reinstall this soul (by slug)")
parser.add_argument("--dry-run", action="store_true", help="Validate config without writing")
args = parser.parse_args()
if not ENGRAM_BIN.exists():
print(f"ERROR: Engram binary not found at {ENGRAM_BIN}")
print("Build it: cd /Users/will/Development/neuron-technologies/foundation/engram && cargo build --release")
sys.exit(1)
registry = load_registry()
imprints = registry["imprints"]
if args.slug:
imprints = [e for e in imprints if e["slug"] == args.slug]
if not imprints:
print(f"ERROR: slug '{args.slug}' not found in registry.json")
sys.exit(1)
print(f"[reinstall] {'DRY RUN — ' if args.dry_run else ''}reinstalling {len(imprints)} soul(s)")
print(f"[reinstall] engram binary: {ENGRAM_BIN}")
print()
results = []
for entry in imprints:
subject = entry["subject"]
slug = entry["slug"]
port = entry["engram_port"]
base_url = entry["engram_url"]
api_key = f"ntn-{slug}-2026"
db_path = FORGE_DIR / entry["engram_db_path"]
seed_path = resolve_seed_path(entry)
print(f"{'=' * 60}")
print(f"[{slug}] {subject} port={port} db={entry['engram_db_path']}")
# Validate seed file exists
if not seed_path.exists():
print(f" ERROR: seed file not found: {seed_path}")
results.append({"slug": slug, "subject": subject, "ok": False, "error": "seed not found"})
continue
if args.dry_run:
print(f" seed: {seed_path} [ok]")
print(f" port: {port} db: {db_path} [ok]")
results.append({"slug": slug, "subject": subject, "ok": True, "dry_run": True})
continue
seed = json.loads(seed_path.read_text())
proc = None
try:
# Start Engram
print(f" starting Engram on :{port}...")
proc = start_engram(slug, db_path, port)
# Wait for ready
if not wait_for_engram(base_url):
raise RuntimeError(f"Engram did not start within {STARTUP_TIMEOUT_S}s")
print(f" Engram ready at {base_url}")
# Install imprint
root_id = install_imprint(base_url, api_key, subject, seed)
print(f" root_id: {root_id}")
# Update registry entry
entry["engram_root_id"] = root_id
entry["engram_db_path"] = f"imprints/{slug}"
entry["engram_port"] = port
entry["engram_url"] = base_url
entry["installed"] = True
entry["installed_at"] = str(date.today())
save_registry(registry)
print(f" registry updated")
results.append({"slug": slug, "subject": subject, "ok": True, "root_id": root_id})
except Exception as exc:
import traceback
print(f" ERROR: {exc}")
traceback.print_exc()
results.append({"slug": slug, "subject": subject, "ok": False, "error": str(exc)})
finally:
if proc is not None:
stop_engram(proc, slug)
# Brief pause so ports release cleanly
time.sleep(1)
print()
# Summary
print("=" * 60)
print("SUMMARY")
print("=" * 60)
ok_count = sum(1 for r in results if r["ok"])
fail_count = len(results) - ok_count
for r in results:
status = "OK" if r["ok"] else "FAIL"
detail = r.get("root_id", r.get("error", "dry-run"))
print(f" [{status}] {r['subject']:30s} {detail}")
print()
print(f" total={len(results)} ok={ok_count} failed={fail_count}")
if fail_count > 0:
sys.exit(1)
if __name__ == "__main__":
main()