diff --git a/scripts/install_all.py b/scripts/install_all.py deleted file mode 100755 index a8146fa..0000000 --- a/scripts/install_all.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python3 -""" -Install all 19 soul seeds into their own dedicated Engram instances. - -Runs install_soul.py logic sequentially to avoid port conflicts. -Updates registry.json with engram_db_path, engram_port, engram_url, -engram_api_key, and engram_root_id for each soul. - -Usage: - python3 install_all.py # skip souls whose data dir exists with root_id - python3 install_all.py --force # reinstall all (destructive) - python3 install_all.py --only richard-feynman # single soul -""" - -import argparse -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).parent)) -from install_soul import install_soul - -SOULS = [ - "bobby-anderson", - "alan-turing", - "albert-einstein", - "nikola-tesla", - "leonardo-da-vinci", - "richard-feynman", - "carl-sagan", - "rene-descartes", - "robin-williams", - "frederick-douglass", - "marcus-aurelius", - "friedrich-nietzsche", - "james-baldwin", - "ada-lovelace", - "harriet-tubman", - "virginia-woolf", - "hedy-lamarr", - "marie-curie", - "helen-keller", -] - - -def main(): - parser = argparse.ArgumentParser( - description="Install all 19 soul seeds into dedicated Engram instances" - ) - parser.add_argument( - "--force", - action="store_true", - help="Reinstall all souls even if already installed", - ) - parser.add_argument( - "--only", - metavar="SLUG", - help="Install only this slug", - ) - args = parser.parse_args() - - souls_to_install = [args.only] if args.only else SOULS - - results = [] - - for slug in souls_to_install: - print(f"\n{'=' * 60}") - print(f"Soul: {slug}") - print("=" * 60) - - try: - result = install_soul(slug, force=args.force) - results.append({"slug": slug, "success": True, **result}) - except Exception as exc: - import traceback - traceback.print_exc() - results.append({"slug": slug, "success": False, "error": str(exc)}) - - print(f"\n{'=' * 60}") - print("SUMMARY") - print("=" * 60) - - ok = [r for r in results if r["success"] and not r.get("skipped")] - skipped = [r for r in results if r.get("skipped")] - failed = [r for r in results if not r["success"]] - - for r in ok: - print(f" [OK] {r['slug']} root={r.get('root_id', '?')}") - for r in skipped: - print(f" [SKIP] {r['slug']} (already installed)") - for r in failed: - print(f" [FAILED] {r['slug']} error={r.get('error', '?')}") - - print(f"\nInstalled: {len(ok)} Skipped: {len(skipped)} Failed: {len(failed)}") - - if failed: - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/scripts/install_soul.py b/scripts/install_soul.py deleted file mode 100755 index f3c7e74..0000000 --- a/scripts/install_soul.py +++ /dev/null @@ -1,425 +0,0 @@ -#!/usr/bin/env python3 -""" -Install a single soul's seed into their own dedicated Engram instance. - -Uses the native Engram binary HTTP API: - POST /api/nodes {"content": "...", "node_type": "...", "salience": 0.8, "_auth": ""} - POST /api/edges {"from_id": "...", "to_id": "...", "relation": "...", "weight": 0.8, "_auth": ""} - GET /stats health check ({"node_count": N, "edge_count": M, "layer_count": K}) - -Auth: _auth field in the POST body. -Per-soul keys: ntn--2026 (matching the running DHARMA infrastructure). - -Usage: - python3 install_soul.py - python3 install_soul.py richard-feynman - python3 install_soul.py richard-feynman --force # reinstall even if data exists - -Steps: - 1. Checks if soul's Engram is already running (port in use) — installs into it. - If not running, starts the Engram binary, installs, then stops it. - 2. Creates forge/imprints// data directory - 3. Polls GET /stats until the instance is ready - 4. Reads the soul's seed JSON - 5. POSTs all nodes and edges using the soul's API key - 6. Captures and returns the root node ID - 7. If we started it, stops the instance (data persists) -""" - -import json -import os -import socket -import subprocess -import sys -import time -from pathlib import Path - -import requests - -# ── Config ────────────────────────────────────────────────────────────────── - -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_TIMEOUT = 30 -STARTUP_POLL_INTERVAL = 0.3 - - -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)) - - -def soul_api_key(soul: dict) -> str: - """ - Return the API key for a soul's Engram instance. - Per-soul keys: ntn--2026 (matching the running DHARMA infrastructure). - If registry has engram_api_key set, that takes precedence. - """ - if soul.get("engram_api_key"): - return soul["engram_api_key"] - return f"ntn-{soul['slug']}-2026" - - -def find_soul(registry: dict, slug: str) -> dict | None: - for imp in registry["imprints"]: - if imp["slug"] == slug: - return imp - return None - - -def find_seed_file(registry_entry: dict) -> Path: - """Resolve the seed file path from the registry entry.""" - slug = registry_entry["slug"] - - # Try registry-specified path first - seed_rel = registry_entry.get("seed_file", "") - if seed_rel: - candidate = FORGE_DIR / seed_rel - if candidate.exists(): - return candidate - - # Fallback: search known locations - candidates = [ - FORGE_DIR / "seeds" / f"{slug}-seed.json", - FORGE_DIR / f"{slug}-seed.json", - # Legacy names used before seed file reorganization - FORGE_DIR / "seeds" / f"{slug.split('-')[0]}-seed.json", # e.g. alan from alan-turing - ] - for c in candidates: - if c.exists(): - return c - - raise FileNotFoundError( - f"Cannot find seed file for {slug!r}. " - f"Tried: {[str(c) for c in candidates]}" - ) - - -def is_port_in_use(port: int) -> bool: - """Return True if a process is already listening on the port.""" - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - return s.connect_ex(("127.0.0.1", port)) == 0 - - -def start_engram(slug: str, port: int, db_path: Path, api_key: str) -> subprocess.Popen: - """Start an Engram instance for the soul and return the process handle.""" - db_path.mkdir(parents=True, exist_ok=True) - - log_dir = FORGE_DIR / "log" - log_dir.mkdir(exist_ok=True) - log_file = log_dir / f"dharma-{slug}.log" - - env = os.environ.copy() - # Native binary uses ENGRAM_DATA_DIR and ENGRAM_DB_PATH (both accepted) - env["ENGRAM_DATA_DIR"] = str(db_path) - env["ENGRAM_DB_PATH"] = str(db_path) - env["ENGRAM_BIND"] = f"0.0.0.0:{port}" - env["ENGRAM_API_KEY"] = api_key - - with open(log_file, "a") as lf: - proc = subprocess.Popen( - [str(ENGRAM_BIN)], - env=env, - stdout=lf, - stderr=lf, - ) - - return proc - - -def wait_for_ready(port: int, timeout: float = STARTUP_TIMEOUT) -> bool: - """Poll /stats until the instance responds or timeout expires.""" - url = f"http://localhost:{port}/stats" - deadline = time.time() + timeout - while time.time() < deadline: - try: - resp = requests.get(url, timeout=2) - if resp.status_code == 200: - return True - except requests.exceptions.ConnectionError: - pass - time.sleep(STARTUP_POLL_INTERVAL) - return False - - -def sanitize_content(s: str) -> str: - """ - Sanitize text content for the native Engram binary's JSON parser. - - The native binary's JSON response serializer does not escape double-quotes - in node content, which causes the response JSON to be malformed. Replace - double-quotes and other problematic characters with safe ASCII equivalents. - """ - replacements = [ - ('"', "'"), # double-quote → single (CRITICAL: prevents JSON response corruption) - ("—", "--"), # em dash - ("–", "-"), # en dash - ("‘", "'"), ("’", "'"), # curly single quotes - ("“", "'"), ("”", "'"), # curly double quotes - ("…", "..."), # ellipsis - ("é", "e"), ("è", "e"), ("ê", "e"), ("ë", "e"), - ("à", "a"), ("â", "a"), ("á", "a"), - ("ü", "u"), ("û", "u"), ("ú", "u"), - ("ö", "o"), ("ô", "o"), ("ó", "o"), - ("ä", "a"), ("ß", "ss"), - ("î", "i"), ("í", "i"), - ("É", "E"), ("Ê", "E"), ("È", "E"), - ("Â", "A"), ("Î", "I"), ("Ô", "O"), ("Û", "U"), - ("ñ", "n"), ("Ñ", "N"), - ("ç", "c"), ("Ç", "C"), - ] - for char, replacement in replacements: - s = s.replace(char, replacement) - # Replace any remaining non-ASCII with '?' - return s.encode("ascii", errors="replace").decode("ascii") - - -def post_node(port: int, content: str, node_type: str, salience: float, api_key: str) -> str: - """POST a node via /api/nodes. Returns the node UUID.""" - url = f"http://localhost:{port}/api/nodes" - safe_content = sanitize_content(content) - body = { - "content": safe_content, - "node_type": node_type, - "salience": salience, - "_auth": api_key, - } - resp = requests.post(url, json=body, timeout=30) - resp.raise_for_status() - result = resp.json() - if "id" not in result: - raise ValueError(f"Unexpected response from /api/nodes: {result}") - return result["id"] - - -def post_edge(port: int, from_id: str, to_id: str, relation: str, weight: float, api_key: str) -> None: - """POST an edge via /api/edges.""" - url = f"http://localhost:{port}/api/edges" - body = { - "from_id": from_id, - "to_id": to_id, - "relation": relation, - "weight": weight, - "_auth": api_key, - } - resp = requests.post(url, json=body, timeout=30) - resp.raise_for_status() - - -def install_seed(port: int, seed: dict, api_key: str) -> str: - """ - Install all nodes and edges from a seed dict. - Returns the root node UUID. - """ - subject = seed["subject"] - print(f" Installing nodes for {subject}...") - - # Root identity node - root_id = post_node( - port, - content=f"IMPRINT: {subject} | dharma/1.0", - node_type="Identity", - salience=1.0, - api_key=api_key, - ) - print(f" Root node: {root_id}") - - # Values - for v in seed.get("values", []): - content = f"VALUE: {v['value']}" - if v.get("grounding"): - content += f" | grounding: {v['grounding']}" - nid = post_node(port, content, "Identity", float(v.get("weight", 0.8)), api_key) - post_edge(port, root_id, nid, "has_value", float(v.get("weight", 0.8)), api_key) - - # Biography - for b in seed.get("biography", []): - content = f"BIOGRAPHY: {b['event']}" - nid = post_node(port, content, "Identity", float(b.get("weight", 0.7)), api_key) - post_edge(port, root_id, nid, "formed_by", float(b.get("weight", 0.7)), api_key) - - # Relationships - for r in seed.get("relationships", []): - content = f"RELATIONSHIP: {r['name']}" - if r.get("role"): - content += f" ({r['role']})" - nid = post_node(port, content, "Identity", float(r.get("weight", 0.6)), api_key) - post_edge(port, root_id, nid, "relates_to", float(r.get("weight", 0.6)), api_key) - - # Reasoning patterns - for pattern in seed.get("reasoning_patterns", []): - content = f"REASONING: {pattern}" - nid = post_node(port, content, "Identity", 0.7, api_key) - post_edge(port, root_id, nid, "reasons_with", 0.7, api_key) - - # Voice profile — one consolidated node - voice = seed.get("voice_profile", {}) - if voice: - voice_parts = [] - for k, v in voice.items(): - if v: - voice_parts.append(f"{k}: {v}") - if voice_parts: - voice_content = "VOICE: " + " | ".join(voice_parts) - nid = post_node(port, voice_content[:2000], "Identity", 0.9, api_key) - post_edge(port, root_id, nid, "speaks_as", 0.9, api_key) - - # Stats after install - stats_resp = requests.get(f"http://localhost:{port}/stats", timeout=10) - stats = stats_resp.json() - print( - f" Engram stats after install: " - f"{stats.get('node_count', '?')} nodes, " - f"{stats.get('edge_count', '?')} edges" - ) - - # Persist to disk - save_url = f"http://localhost:{port}/api/save" - try: - save_resp = requests.post(save_url, json={"_auth": api_key}, timeout=10) - save_result = save_resp.json() - print(f" Saved to: {save_result.get('path', '?')}") - except Exception as exc: - print(f" WARN: save failed: {exc}") - - return root_id - - -def install_soul(slug: str, force: bool = False) -> dict: - """ - Full install pipeline for one soul. - - If the soul's Engram is already running (port in use), installs directly - into it. Otherwise starts a temporary instance, installs, and stops it. - - Returns dict with: slug, root_id, engram_db_path, engram_port, engram_url, engram_api_key - """ - if not ENGRAM_BIN.exists(): - raise FileNotFoundError( - f"Engram binary not found at {ENGRAM_BIN}." - ) - - registry = load_registry() - soul = find_soul(registry, slug) - if soul is None: - raise ValueError(f"Soul {slug!r} not found in registry.json") - - port = soul["engram_port"] - db_path = FORGE_DIR / soul.get("engram_db_path", f"imprints/{slug}") - api_key = soul_api_key(soul) - - # Skip if already installed (data dir non-empty AND registry has root_id), - # unless --force is given. - already_has_data = db_path.exists() and any(db_path.iterdir()) - already_has_root = bool(soul.get("engram_root_id")) - if not force and already_has_data and already_has_root: - print(f"[{slug}] Already installed (root={soul['engram_root_id']}) — skipping.") - print(f"[{slug}] Use --force to reinstall.") - return { - "slug": slug, - "root_id": soul.get("engram_root_id"), - "skipped": True, - } - - seed_file = find_seed_file(soul) - seed = json.loads(seed_file.read_text()) - print( - f"[{slug}] Seed: {seed_file.name} " - f"({len(seed.get('values', []))} values, " - f"{len(seed.get('biography', []))} bio, " - f"{len(seed.get('reasoning_patterns', []))} patterns, " - f"{len(seed.get('relationships', []))} relationships)" - ) - print(f"[{slug}] API key: {api_key}") - - already_running = is_port_in_use(port) - proc = None - - if already_running: - print(f"[{slug}] Engram already running on port {port} — installing into live instance.") - if not wait_for_ready(port, timeout=5): - raise RuntimeError( - f"Port {port} is in use but Engram is not responding at /stats" - ) - else: - print(f"[{slug}] Starting Engram on port {port}...") - proc = start_engram(slug, port, db_path, api_key) - - try: - if proc is not None: - if not wait_for_ready(port): - proc.terminate() - raise RuntimeError( - f"Engram for {slug} did not become ready within {STARTUP_TIMEOUT}s" - ) - print(f"[{slug}] Engram ready on port {port}") - - root_id = install_seed(port, seed, api_key) - print(f"[{slug}] Install complete. Root node: {root_id}") - - finally: - if proc is not None: - proc.terminate() - try: - proc.wait(timeout=5) - except subprocess.TimeoutExpired: - proc.kill() - print(f"[{slug}] Instance stopped.") - - # Update registry entry - from datetime import date - for imp in registry["imprints"]: - if imp["slug"] == slug: - imp["engram_db_path"] = str(db_path.relative_to(FORGE_DIR)) - imp["engram_port"] = port - imp["engram_url"] = f"http://localhost:{port}" - imp["engram_api_key"] = api_key - imp["engram_root_id"] = root_id - imp["installed"] = True - imp["installed_at"] = str(date.today()) - break - - save_registry(registry) - print(f"[{slug}] Registry updated.") - - return { - "slug": slug, - "root_id": root_id, - "engram_db_path": str(db_path.relative_to(FORGE_DIR)), - "engram_port": port, - "engram_url": f"http://localhost:{port}", - "engram_api_key": api_key, - "skipped": False, - } - - -def main(): - import argparse - - parser = argparse.ArgumentParser( - description="Install a soul's seed into its Engram instance" - ) - parser.add_argument("slug", help="Soul slug (e.g. richard-feynman)") - parser.add_argument( - "--force", - action="store_true", - help="Reinstall even if imprint directory already exists", - ) - args = parser.parse_args() - - result = install_soul(args.slug, force=args.force) - if result.get("skipped"): - print(f"\nSkipped (already installed). Root: {result.get('root_id', 'unknown')}") - else: - print(f"\nDone. Root node ID: {result['root_id']}") - - -if __name__ == "__main__": - main() diff --git a/scripts/launch_dharma.sh b/scripts/launch_dharma.sh deleted file mode 100755 index 40c2a4d..0000000 --- a/scripts/launch_dharma.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env bash -# DHARMA network — start all 19 soul Engrams as background processes. -# -# Each soul gets: -# - Their own Engram binary instance -# - ENGRAM_DATA_DIR / ENGRAM_DB_PATH = forge/imprints// -# - ENGRAM_BIND = 0.0.0.0: -# - ENGRAM_API_KEY = ntn--2026 per soul (or $DHARMA_API_KEY override) -# -# PIDs are written to forge/imprints//engram.pid -# Logs go to forge/log/dharma-.log -# -# Usage: -# ./launch_dharma.sh start all soul Engrams -# ./launch_dharma.sh start one soul by slug -# -# Neuron stays untouched at localhost:8742. - -set -euo pipefail - -FORGE=/Users/will/Development/neuron-technologies/forge -ENGRAM=/Users/will/Development/neuron-technologies/foundation/engram/dist/engram -REGISTRY="$FORGE/registry.json" - -if [ ! -x "$ENGRAM" ]; then - echo "[dharma] ERROR: engram binary not found at $ENGRAM" - exit 1 -fi - -if [ ! -f "$REGISTRY" ]; then - echo "[dharma] ERROR: registry.json not found at $REGISTRY" - exit 1 -fi - -mkdir -p "$FORGE/log" - -FILTER="${1:-}" - -started=0 -skipped=0 -missing=0 - -while IFS='|' read -r slug port subject db_path; do - [ -z "$slug" ] && continue - - if [ -n "$FILTER" ] && [ "$slug" != "$FILTER" ]; then - continue - fi - - full_db_path="$FORGE/$db_path" - if [ ! -d "$full_db_path" ] || [ -z "$(ls -A "$full_db_path" 2>/dev/null)" ]; then - echo "[dharma] WARN: $slug — imprint directory missing or empty" - echo "[dharma] Run: python3 scripts/install_all.py" - missing=$((missing + 1)) - continue - fi - - # Skip if already running on that port - if lsof -ti tcp:"$port" >/dev/null 2>&1; then - echo "[dharma] $slug already running on port $port — skipping" - skipped=$((skipped + 1)) - continue - fi - - log_file="$FORGE/log/dharma-${slug}.log" - pid_file="$full_db_path/engram.pid" - - # Per-soul key unless overridden by DHARMA_API_KEY env var - soul_key="${DHARMA_API_KEY:-ntn-${slug}-2026}" - - echo "[dharma] starting $slug on :$port" - - ENGRAM_DATA_DIR="$full_db_path" \ - ENGRAM_DB_PATH="$full_db_path" \ - ENGRAM_BIND="0.0.0.0:$port" \ - ENGRAM_API_KEY="$soul_key" \ - "$ENGRAM" >> "$log_file" 2>&1 & - - pid=$! - echo "$pid" > "$pid_file" - started=$((started + 1)) - -done < <(python3 - "$REGISTRY" <<'PYEOF' -import json, sys -reg = json.load(open(sys.argv[1])) -for imp in reg["imprints"]: - print(f"{imp['slug']}|{imp['engram_port']}|{imp['subject']}|{imp.get('engram_db_path', 'imprints/' + imp['slug'])}") -PYEOF -) - -echo "" -echo "[dharma] started=$started skipped=$skipped missing=$missing" -if [ "$missing" -gt 0 ]; then - echo "[dharma] Run 'python3 scripts/install_all.py' to install missing souls first." -fi -echo "[dharma] logs: $FORGE/log/dharma-.log" -echo "" -if [ "$started" -gt 0 ]; then - echo "[dharma] Verify:" - echo " curl -s http://localhost:8801/stats # bobby-anderson" - echo " curl -s http://localhost:8819/stats # helen-keller" - echo "" - echo "[dharma] Then wire peers:" - echo " python3 scripts/wire_peers.py" -fi diff --git a/scripts/stop_dharma.sh b/scripts/stop_dharma.sh deleted file mode 100755 index dd9fb13..0000000 --- a/scripts/stop_dharma.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env bash -# DHARMA network — stop all running soul Engrams. -# -# Primary: reads PIDs from forge/imprints//engram.pid -# Fallback: port scan kill (ports 8801-8819) -# -# Usage: -# ./stop_dharma.sh stop all soul Engrams -# ./stop_dharma.sh stop one soul by slug - -set -euo pipefail - -FORGE=/Users/will/Development/neuron-technologies/forge -REGISTRY="$FORGE/registry.json" -FILTER="${1:-}" - -stopped=0 -missed=0 - -if [ ! -f "$REGISTRY" ]; then - echo "[dharma] ERROR: registry.json not found at $REGISTRY" - exit 1 -fi - -while IFS='|' read -r slug port db_path; do - [ -z "$slug" ] && continue - - if [ -n "$FILTER" ] && [ "$slug" != "$FILTER" ]; then - continue - fi - - full_db_path="$FORGE/$db_path" - pid_file="$full_db_path/engram.pid" - - if [ -f "$pid_file" ]; then - pid=$(cat "$pid_file") - if kill -0 "$pid" 2>/dev/null; then - kill "$pid" - echo "[dharma] stopped $slug (pid=$pid port=$port)" - stopped=$((stopped + 1)) - else - echo "[dharma] $slug (pid=$pid) already gone" - fi - rm -f "$pid_file" - else - # Fallback: kill by port - pids=$(lsof -ti tcp:"$port" 2>/dev/null || true) - if [ -n "$pids" ]; then - echo "$pids" | xargs kill 2>/dev/null || true - echo "[dharma] stopped $slug by port kill (port=$port)" - stopped=$((stopped + 1)) - else - missed=$((missed + 1)) - fi - fi - -done < <(python3 - "$REGISTRY" <<'PYEOF' -import json, sys -reg = json.load(open(sys.argv[1])) -for imp in reg["imprints"]: - print(f"{imp['slug']}|{imp['engram_port']}|{imp.get('engram_db_path', 'imprints/' + imp['slug'])}") -PYEOF -) - -echo "" -echo "[dharma] stopped=$stopped not_running=$missed" diff --git a/scripts/wire_peers.py b/scripts/wire_peers.py deleted file mode 100755 index eb1395f..0000000 --- a/scripts/wire_peers.py +++ /dev/null @@ -1,256 +0,0 @@ -#!/usr/bin/env python3 -""" -Wire all DHARMA soul Engrams as peers to each other. - -Run after all soul Engrams are started (launch_dharma.sh or scripts/launch_dharma.sh). - -How DHARMA peer wiring works in the native Engram binary ---------------------------------------------------------- -The native runtime (engram-runtime-native) tracks peers using graph primitives: - - - "dharma:self" — DharmaSelf node (this instance's identity) - - "dharma:peer:@" — DharmaPeer node per known peer - - "dharma-relation" edge from "dharma:self" to the peer node, weight > 0 - -To register soul B as a peer of soul A, POST to A's /api/edges: - from_id = "dharma:self" - to_id = "dharma:peer:@http://localhost:" - relation = "dharma-relation" - weight = 1.0 - _auth = A's API key - -The runtime auto-creates DharmaSelf and DharmaPeer nodes from the edge. -The dharma_peers() el builtin returns all peers with weight > 0. - -19 souls × 18/2 = 171 bidirectional pairs = 342 total edge POSTs. -Idempotent: re-running reinforces existing edges. - -Usage: - python3 wire_peers.py # wire all running souls - python3 wire_peers.py --dry-run # show what would be done - python3 wire_peers.py --status # check connectivity - python3 wire_peers.py --soul richard-feynman # wire one soul only -""" - -import argparse -import json -import sys -from pathlib import Path - -import requests - -FORGE_DIR = Path("/Users/will/Development/neuron-technologies/forge") -REGISTRY_FILE = FORGE_DIR / "registry.json" - - -def load_registry() -> list[dict]: - reg = json.loads(REGISTRY_FILE.read_text()) - return reg["imprints"] - - -def soul_url(soul: dict) -> str: - return soul.get("engram_url", f"http://localhost:{soul['engram_port']}") - - -def soul_api_key(soul: dict) -> str: - """Per-soul API key: ntn--2026 (matching the running DHARMA infrastructure).""" - if soul.get("engram_api_key"): - return soul["engram_api_key"] - return f"ntn-{soul['slug']}-2026" - - -def peer_node_id(soul: dict) -> str: - """ - Build the DHARMA peer node ID for a soul. - - Format: "dharma:peer:@" - The runtime uses the part before @ as the peer identifier and the part - after @ as the routing URL for outbound messages. - """ - return f"dharma:peer:{soul['slug']}@{soul_url(soul)}" - - -def check_soul_alive(soul: dict) -> bool: - try: - resp = requests.get(f"{soul_url(soul)}/stats", timeout=3) - return resp.status_code == 200 - except requests.exceptions.RequestException: - return False - - -def register_peer_on_host(host: dict, peer: dict, dry_run: bool = False) -> bool: - """ - Register peer as a DHARMA peer of host by posting a dharma-relation edge. - - Posts to host's /api/edges with host's API key. - """ - edge_url = f"{soul_url(host)}/api/edges" - peer_id = peer_node_id(peer) - api_key = soul_api_key(host) - - body = { - "from_id": "dharma:self", - "to_id": peer_id, - "relation": "dharma-relation", - "weight": 1.0, - "_auth": api_key, - } - - if dry_run: - print(f" [DRY-RUN] POST {edge_url} to_id={peer_id}") - return True - - try: - resp = requests.post(edge_url, json=body, timeout=10) - if resp.status_code in (200, 201): - return True - print(f" WARN: POST {edge_url} → {resp.status_code}: {resp.text[:200]}") - return False - except requests.exceptions.RequestException as exc: - print(f" ERROR: POST {edge_url} failed: {exc}") - return False - - -def wire_all(souls: list[dict], dry_run: bool = False) -> dict: - """Wire every soul as a peer of every other soul.""" - total_ok = 0 - total_fail = 0 - pair_count = 0 - n = len(souls) - expected = n * (n - 1) // 2 - - for i in range(n): - for j in range(i + 1, n): - a = souls[i] - b = souls[j] - pair_count += 1 - - ok1 = register_peer_on_host(a, b, dry_run=dry_run) - ok2 = register_peer_on_host(b, a, dry_run=dry_run) - - if ok1 and ok2: - total_ok += 1 - if pair_count % 30 == 0 or pair_count == expected: - print(f" [{pair_count}/{expected}] {a['slug']} <-> {b['slug']}") - else: - total_fail += 1 - print( - f" [{pair_count}/{expected}] {a['slug']} <-> {b['slug']} " - f"FAIL (A→B={ok1} B→A={ok2})" - ) - - return {"pairs": pair_count, "ok": total_ok, "failed": total_fail} - - -def get_peer_count(soul: dict) -> int: - """Count DharmaPeer nodes in a soul's graph.""" - try: - url = f"{soul_url(soul)}/api/nodes" - headers = {"Authorization": f"Bearer {soul_api_key(soul)}"} - resp = requests.get(url, headers=headers, timeout=10) - resp.raise_for_status() - nodes = resp.json() - if isinstance(nodes, list): - return sum(1 for n in nodes if n.get("node_type") == "DharmaPeer") - return 0 - except requests.exceptions.RequestException: - return -1 - - -def cmd_wire(souls: list[dict], dry_run: bool) -> None: - alive = [s for s in souls if check_soul_alive(s)] - dead = [s for s in souls if not check_soul_alive(s)] - - if dead: - print(f"\nWARN: {len(dead)} soul(s) not reachable:") - for s in dead: - print(f" - {s['slug']} ({soul_url(s)})") - print(f"\nStart them: bash scripts/launch_dharma.sh") - if not alive: - sys.exit(1) - print(f"\nProceeding with {len(alive)} reachable souls...\n") - else: - print(f"All {len(alive)} souls reachable.\n") - - n = len(alive) - expected = n * (n - 1) // 2 - print(f"Wiring {n} souls ({expected} pairs, {expected * 2} edge registrations)...") - if dry_run: - print("[DRY-RUN — no requests will be sent]\n") - - stats = wire_all(alive, dry_run=dry_run) - - print(f"\nWiring complete:") - print(f" Pairs attempted : {stats['pairs']}") - print(f" Succeeded : {stats['ok']}") - print(f" Failed : {stats['failed']}") - - if not dry_run: - print(f"\nVerify: python3 scripts/wire_peers.py --status") - - -def cmd_status(souls: list[dict]) -> None: - print("DHARMA swarm status:\n") - for soul in souls: - if not check_soul_alive(soul): - print(f" {soul['slug']:32s} OFFLINE") - continue - peer_count = get_peer_count(soul) - label = f"peers={peer_count}" if peer_count >= 0 else "(read error)" - print(f" {soul['slug']:32s} online {label}") - - -def main(): - parser = argparse.ArgumentParser( - description="Wire DHARMA soul Engrams as peers via dharma-relation edges" - ) - parser.add_argument("--dry-run", action="store_true") - parser.add_argument("--status", action="store_true") - parser.add_argument("--soul", metavar="SLUG") - args = parser.parse_args() - - souls = load_registry() - - # Backfill missing engram_url - for soul in souls: - if "engram_url" not in soul: - soul["engram_url"] = f"http://localhost:{soul['engram_port']}" - - if args.soul: - all_souls = souls - target = next((s for s in souls if s["slug"] == args.soul), None) - if not target: - print(f"ERROR: soul {args.soul!r} not found in registry") - sys.exit(1) - - if args.status: - cmd_status([target]) - else: - others = [s for s in all_souls if s["slug"] != args.soul] - alive_others = [s for s in others if check_soul_alive(s)] - - if not check_soul_alive(target): - print(f"ERROR: {args.soul} is offline at {soul_url(target)}") - sys.exit(1) - - print(f"Wiring {args.soul} to {len(alive_others)} souls...\n") - ok = fail = 0 - for peer in alive_others: - o1 = register_peer_on_host(target, peer, dry_run=args.dry_run) - o2 = register_peer_on_host(peer, target, dry_run=args.dry_run) - if o1 and o2: - ok += 1 - print(f" wired: {args.soul} <-> {peer['slug']}") - else: - fail += 1 - print(f" FAIL: {args.soul} <-> {peer['slug']}") - print(f"\nDone: ok={ok} failed={fail}") - else: - if args.status: - cmd_status(souls) - else: - cmd_wire(souls, dry_run=args.dry_run) - - -if __name__ == "__main__": - main() diff --git a/src/install.el b/src/install.el index 1356328..9e48982 100644 --- a/src/install.el +++ b/src/install.el @@ -10,8 +10,8 @@ // engram_url field (e.g. http://localhost:8806 for Feynman). It NEVER writes // to the shared Neuron Engram at localhost:8742. // -// The soul's Engram must already be running (launch_dharma.sh) before -// installing. For bulk reinstall use: python3 reinstall_imprints.py +// The soul's Engram must already be running as a launchd agent before +// installing. For reinstall: forge soul reinstall // // Depends on: schema.el (engram_key, FORGE_VERSION) // @@ -43,7 +43,7 @@ fn build_edge_body(from_id: String, to_id: String, relation: String, weight: Str fn post_node(target_url: String, content: String, node_type: String, salience: String) -> String { let key: String = engram_key() let body: String = build_node_body(content, node_type, salience, key) - let url: String = target_url + "/api/nodes" + let url: String = target_url + "/nodes" let response: String = http_post_json(url, body) if str_eq(response, "") { return "" } return json_get_string(response, "id") @@ -54,7 +54,7 @@ fn post_node(target_url: String, content: String, node_type: String, salience: S fn post_edge(target_url: String, from_id: String, to_id: String, relation: String, weight: String) -> String { let key: String = engram_key() let body: String = build_edge_body(from_id, to_id, relation, weight, key) - let url: String = target_url + "/api/edges" + let url: String = target_url + "/edges" let response: String = http_post_json(url, body) return response } @@ -199,9 +199,13 @@ fn install_main() -> String { if str_eq(reg_subject, subject) { let reg_root: String = json_get_string(reg_entry, "engram_root_id") if !str_eq(reg_root, "") { - println("[forge] already installed — root: " + reg_root) - println("[forge] skip. to reinstall: python3 reinstall_imprints.py --slug ") - return reg_root + let force: String = env("FORCE_INSTALL") + if str_eq(force, "") { + println("[forge] already installed — root: " + reg_root) + println("[forge] to reinstall: forge soul reinstall " + str_lower(str_replace(subject, " ", "-"))) + return reg_root + } + println("[forge] force reinstall — ignoring existing root: " + reg_root) } let reg_url: String = json_get_string(reg_entry, "engram_url") if !str_eq(reg_url, "") { @@ -226,12 +230,12 @@ fn install_main() -> String { // Safety guard: refuse to install to the shared Neuron Engram if str_eq(target_url, "") { println("[forge] error: no engram_url found for '" + subject + "' in registry.json") - println("[forge] ensure registry.json has an engram_url entry, or run reinstall_imprints.py") + println("[forge] ensure registry.json has an engram_url entry, or run forge soul reinstall <slug>") return "" } if str_contains(target_url, ":8742") { println("[forge] error: refusing to install soul into Neuron's Engram (port 8742)") - println("[forge] start the DHARMA network first: ./launch_dharma.sh") + println("[forge] start the DHARMA network first: ./forge soul start <slug>") println("[forge] then retry. Each soul needs their own Engram instance.") return "" } @@ -245,7 +249,7 @@ fn install_main() -> String { if str_eq(root_id, "") { println("[forge] error: failed to create root imprint node") println("[forge] is the soul's Engram running at " + target_url + " ?") - println("[forge] start it with: ./launch_dharma.sh") + println("[forge] start it with: ./forge soul start <slug>") return "" } diff --git a/src/soul.el b/src/soul.el index 953899b..301b6f8 100644 --- a/src/soul.el +++ b/src/soul.el @@ -784,6 +784,130 @@ fn soul_sandbox_main() -> String { return "" } +// ── soul_reinstall_one — wipe and reinstall a single soul ──────────────────── +// +// 1. stop the launchd agent (launchctl stop) +// 2. delete the engram DB directory (rm -rf) +// 3. kickstart the launchd agent (launchctl kickstart) +// 4. wait up to 10s for the Engram to respond on /stats +// 5. run install_main() against this soul's engram_url + seed_file +// +// slug: e.g. "kal-el" +fn soul_reinstall_one(slug: String) -> String { + println("[soul] reinstalling: " + slug) + + // Look up soul in registry + let reg_json: String = fs_read("registry.json") + if str_eq(reg_json, "") { + println("[soul] error: cannot read registry.json") + return "" + } + let entry: String = "" + let ri: Int = 0 + while ri < 30 { + let e: String = json_get(reg_json, "imprints." + int_to_str(ri)) + if str_eq(e, "") { let ri = ri + 30 } + else { + let s: String = json_get_string(e, "slug") + if str_eq(s, slug) { let entry = e; let ri = ri + 30 } + else { let ri = ri + 1 } + } + } + if str_eq(entry, "") { + println("[soul] error: slug not found in registry: " + slug) + return "" + } + + let engram_url: String = json_get_string(entry, "engram_url") + let seed_file: String = json_get_string(entry, "seed_file") + let db_path: String = json_get_string(entry, "engram_db_path") + let label: String = "ai.neurontechnologies.soul." + slug + let uid: String = str_trim(exec("id -u")) + + // Step 1: stop + println("[soul] stopping launchd agent...") + exec("launchctl stop " + label + " 2>/dev/null") + exec("sleep 1") + + // Step 2: wipe DB + if !str_eq(db_path, "") { + println("[soul] wiping engram DB: " + db_path) + exec("rm -rf " + db_path) + } + + // Step 3: kickstart + println("[soul] starting fresh engram...") + exec("launchctl kickstart gui/" + uid + "/" + label + " 2>/dev/null") + + // Step 4: wait for Engram (poll /stats up to 10s) + let ready: Bool = false + let attempts: Int = 0 + while !ready && attempts < 10 { + exec("sleep 1") + let stats: String = http_get(engram_url + "/stats") + if !str_eq(stats, "") && !str_starts_with(stats, "{\"error\"") { + let ready = true + } + let attempts = attempts + 1 + } + if !ready { + println("[soul] warning: engram did not respond after 10s, proceeding anyway") + } else { + println("[soul] engram ready") + } + + // Step 5: clear registry engram_root_id so install proceeds (not skipped) + // We write a temp registry update by patching the registry in place. + // Easiest: just set ENGRAM_URL env and call install directly via HTTP. + // The install.el skips if engram_root_id is set in registry — so we must + // call the Engram directly to create nodes. + // + // For now: install by calling install_main() is not possible from soul.el + // since it's a separate compiled unit. Instead we call forge install via exec. + let api_key: String = "ntn-" + slug + "-2026" + let forge_cmd: String = "ENGRAM_URL=" + engram_url + " ENGRAM_API_KEY=" + api_key + + " FORCE_INSTALL=1 ./dist/forge install " + seed_file + " 2>&1" + println("[soul] installing seed: " + seed_file) + let install_out: String = exec(forge_cmd) + println(install_out) + + return "done" +} + +// soul_reinstall_main — dispatch `forge soul reinstall ` +fn soul_reinstall_main() -> String { + let argv: [String] = args() + let target: String = "" + if len(argv) > 2 { let target = get(argv, 2) } + + if str_eq(target, "") { + println("[soul] usage: forge soul reinstall ") + println(" forge soul reinstall --all") + return "" + } + + if str_eq(target, "--all") { + println("[soul] reinstalling all souls...") + let reg_json: String = fs_read("registry.json") + if str_eq(reg_json, "") { println("[soul] error: cannot read registry.json"); return "" } + let i: Int = 0 + while i < 30 { + let e: String = json_get(reg_json, "imprints." + int_to_str(i)) + if str_eq(e, "") { let i = i + 30 } + else { + let s: String = json_get_string(e, "slug") + if !str_eq(s, "") { soul_reinstall_one(s) } + let i = i + 1 + } + } + println("[soul] reinstall complete.") + return "" + } + + soul_reinstall_one(target) + return "" +} + // ── soul_main — dispatch ───────────────────────────────────────────────────── // // args() layout when invoked as: forge soul [slug] @@ -801,30 +925,36 @@ fn soul_main() -> String { if str_eq(subcmd, "install") { soul_install_main() } else { - if str_eq(subcmd, "start") { - soul_start_main() + if str_eq(subcmd, "reinstall") { + soul_reinstall_main() } else { - if str_eq(subcmd, "stop") { - soul_stop_main() + if str_eq(subcmd, "start") { + soul_start_main() } else { - if str_eq(subcmd, "status") { - soul_status_main() + if str_eq(subcmd, "stop") { + soul_stop_main() } else { - if str_eq(subcmd, "wire") { - soul_wire_main() + if str_eq(subcmd, "status") { + soul_status_main() } else { - if str_eq(subcmd, "sandbox") { - soul_sandbox_main() + if str_eq(subcmd, "wire") { + soul_wire_main() } else { - println("[soul] usage: forge soul [slug]") - println("") - println(" forge soul install — write launchd plist, load soul as resident") - println(" forge soul install --all — install all souls from registry") - println(" forge soul start — kickstart a loaded soul") - println(" forge soul stop — stop a running soul") - println(" forge soul status — health check all souls") - println(" forge soul wire — register all souls as Engram peers") - println(" forge soul sandbox — sandbox management (create|list|join|close|status)") + if str_eq(subcmd, "sandbox") { + soul_sandbox_main() + } else { + println("[soul] usage: forge soul [slug]") + println("") + println(" forge soul install — write launchd plist, load as resident") + println(" forge soul install --all — install all souls from registry") + println(" forge soul reinstall — wipe DB and reinstall from seed") + println(" forge soul reinstall --all — wipe and reinstall all souls") + println(" forge soul start — kickstart a loaded soul") + println(" forge soul stop — stop a running soul") + println(" forge soul status — health check all souls") + println(" forge soul wire — register all souls as Engram peers") + println(" forge soul sandbox — sandbox management") + } } } }