remove Python/bash scripts, add forge soul reinstall command
- Delete scripts/ (Python/bash superseded by native El forge commands) - install.el: fix /api/nodes → /nodes, /api/edges → /edges (Engram has no /api prefix) - install.el: add FORCE_INSTALL env var to bypass already-installed check - install.el: remove all python3/launch_dharma.sh references - soul.el: add soul_reinstall_one() and forge soul reinstall <slug|--all> Wipes engram DB, restarts launchd agent, waits for startup, runs install
This commit is contained in:
@@ -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()
|
||||
@@ -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": "<key>"}
|
||||
POST /api/edges {"from_id": "...", "to_id": "...", "relation": "...", "weight": 0.8, "_auth": "<key>"}
|
||||
GET /stats health check ({"node_count": N, "edge_count": M, "layer_count": K})
|
||||
|
||||
Auth: _auth field in the POST body.
|
||||
Per-soul keys: ntn-<slug>-2026 (matching the running DHARMA infrastructure).
|
||||
|
||||
Usage:
|
||||
python3 install_soul.py <slug>
|
||||
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/<slug>/ 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-<slug>-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()
|
||||
@@ -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/<slug>/
|
||||
# - ENGRAM_BIND = 0.0.0.0:<port>
|
||||
# - ENGRAM_API_KEY = ntn-<slug>-2026 per soul (or $DHARMA_API_KEY override)
|
||||
#
|
||||
# PIDs are written to forge/imprints/<slug>/engram.pid
|
||||
# Logs go to forge/log/dharma-<slug>.log
|
||||
#
|
||||
# Usage:
|
||||
# ./launch_dharma.sh start all soul Engrams
|
||||
# ./launch_dharma.sh <slug> 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-<slug>.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
|
||||
@@ -1,66 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# DHARMA network — stop all running soul Engrams.
|
||||
#
|
||||
# Primary: reads PIDs from forge/imprints/<slug>/engram.pid
|
||||
# Fallback: port scan kill (ports 8801-8819)
|
||||
#
|
||||
# Usage:
|
||||
# ./stop_dharma.sh stop all soul Engrams
|
||||
# ./stop_dharma.sh <slug> 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"
|
||||
@@ -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:<base>@<url>" — 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:<b_slug>@http://localhost:<b_port>"
|
||||
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-<slug>-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:<slug>@<engram_url>"
|
||||
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()
|
||||
+14
-10
@@ -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 <slug>
|
||||
//
|
||||
// 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 <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 ""
|
||||
}
|
||||
|
||||
|
||||
+149
-19
@@ -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 <slug|--all>`
|
||||
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 <slug>")
|
||||
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 <subcmd> [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 <install|start|stop|status|wire|sandbox> [slug]")
|
||||
println("")
|
||||
println(" forge soul install <slug> — write launchd plist, load soul as resident")
|
||||
println(" forge soul install --all — install all souls from registry")
|
||||
println(" forge soul start <slug> — kickstart a loaded soul")
|
||||
println(" forge soul stop <slug> — 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 <subcmd> — sandbox management (create|list|join|close|status)")
|
||||
if str_eq(subcmd, "sandbox") {
|
||||
soul_sandbox_main()
|
||||
} else {
|
||||
println("[soul] usage: forge soul <subcmd> [slug]")
|
||||
println("")
|
||||
println(" forge soul install <slug> — write launchd plist, load as resident")
|
||||
println(" forge soul install --all — install all souls from registry")
|
||||
println(" forge soul reinstall <slug> — wipe DB and reinstall from seed")
|
||||
println(" forge soul reinstall --all — wipe and reinstall all souls")
|
||||
println(" forge soul start <slug> — kickstart a loaded soul")
|
||||
println(" forge soul stop <slug> — 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 <subcmd> — sandbox management")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user