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
This commit is contained in:
Executable
+101
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
# DHARMA network — start all 19 soul Engrams in background.
|
||||
# Each soul gets their own Engram instance, data directory, and port.
|
||||
# Neuron's Engram stays untouched at localhost:8742.
|
||||
#
|
||||
# Usage:
|
||||
# ./launch_dharma.sh start all soul Engrams
|
||||
# ./launch_dharma.sh <slug> start a single soul by slug
|
||||
#
|
||||
# PIDs are written to /tmp/dharma-pids for stop_dharma.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
FORGE=/Users/will/Development/neuron-technologies/forge
|
||||
ENGRAM_BIN=/Users/will/Development/neuron-technologies/foundation/engram/dist/engram
|
||||
PID_FILE=/tmp/dharma-pids
|
||||
|
||||
if [ ! -x "$ENGRAM_BIN" ]; then
|
||||
echo "[dharma] ERROR: engram binary not found at $ENGRAM_BIN"
|
||||
echo "[dharma] Build it with: cd /Users/will/Development/neuron-technologies/foundation/engram && cargo build --release"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Soul definitions: slug|port
|
||||
SOULS=(
|
||||
"bobby-anderson|8801"
|
||||
"alan-turing|8802"
|
||||
"albert-einstein|8803"
|
||||
"nikola-tesla|8804"
|
||||
"leonardo-da-vinci|8805"
|
||||
"richard-feynman|8806"
|
||||
"carl-sagan|8807"
|
||||
"rene-descartes|8808"
|
||||
"robin-williams|8809"
|
||||
"frederick-douglass|8810"
|
||||
"marcus-aurelius|8811"
|
||||
"friedrich-nietzsche|8812"
|
||||
"james-baldwin|8813"
|
||||
"ada-lovelace|8814"
|
||||
"harriet-tubman|8815"
|
||||
"virginia-woolf|8816"
|
||||
"hedy-lamarr|8817"
|
||||
"marie-curie|8818"
|
||||
"helen-keller|8819"
|
||||
)
|
||||
|
||||
# Optional single-soul filter
|
||||
FILTER="${1:-}"
|
||||
|
||||
# Ensure PID file is clean on fresh launch (without filter)
|
||||
if [ -z "$FILTER" ]; then
|
||||
> "$PID_FILE"
|
||||
fi
|
||||
|
||||
started=0
|
||||
skipped=0
|
||||
|
||||
for entry in "${SOULS[@]}"; do
|
||||
slug="${entry%|*}"
|
||||
port="${entry#*|}"
|
||||
|
||||
# If a filter is specified, only start matching soul
|
||||
if [ -n "$FILTER" ] && [ "$slug" != "$FILTER" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Skip if already listening 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
|
||||
|
||||
db_path="$FORGE/imprints/$slug"
|
||||
mkdir -p "$db_path"
|
||||
|
||||
api_key="ntn-${slug}-2026"
|
||||
log_file="$FORGE/log/dharma-${slug}.log"
|
||||
mkdir -p "$FORGE/log"
|
||||
|
||||
echo "[dharma] starting $slug on :$port (db: imprints/$slug)"
|
||||
|
||||
ENGRAM_DB_PATH="$db_path" \
|
||||
ENGRAM_BIND="0.0.0.0:$port" \
|
||||
ENGRAM_API_KEY="$api_key" \
|
||||
ENGRAM_PEER_NAME="dharma-$slug" \
|
||||
"$ENGRAM_BIN" >> "$log_file" 2>&1 &
|
||||
|
||||
pid=$!
|
||||
echo "$pid $slug $port" >> "$PID_FILE"
|
||||
started=$((started + 1))
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "[dharma] started=$started skipped=$skipped"
|
||||
echo "[dharma] pids recorded in $PID_FILE"
|
||||
echo "[dharma] logs in $FORGE/log/dharma-<slug>.log"
|
||||
echo ""
|
||||
echo "[dharma] verify with:"
|
||||
echo " curl -s http://localhost:8801/stats # bobby-anderson"
|
||||
echo " curl -s http://localhost:8819/stats # helen-keller"
|
||||
+187
-130
@@ -5,143 +5,20 @@
|
||||
"subject": "Bobby Anderson",
|
||||
"slug": "bobby-anderson",
|
||||
"seed_file": "bobby-anderson-seed.json",
|
||||
"engram_db_path": "imprints/bobby-anderson",
|
||||
"engram_port": 8801,
|
||||
"engram_url": "http://localhost:8801",
|
||||
"engram_root_id": "0d4ad862-8d87-409f-aff5-b2199fae5611",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Nikola Tesla",
|
||||
"slug": "nikola-tesla",
|
||||
"seed_file": "nikola-tesla-seed.json",
|
||||
"engram_root_id": "968ed4c9-ea3b-427b-964e-156f5b085c48",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03",
|
||||
"notes": "Enriched seed on disk (nikola-tesla-seed.json) — not reinstalled to avoid duplicates. Original nodes remain in Engram."
|
||||
},
|
||||
{
|
||||
"subject": "Frederick Douglass",
|
||||
"slug": "frederick-douglass",
|
||||
"seed_file": "seeds/frederick-douglass-seed.json",
|
||||
"engram_root_id": "37ed8061-5a90-4f74-9fb0-0acfae686247",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Marcus Aurelius",
|
||||
"slug": "marcus-aurelius",
|
||||
"seed_file": "seeds/marcus-aurelius-seed.json",
|
||||
"engram_root_id": "7fb915ca-2b3f-4971-a410-ec8dd2588a04",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Friedrich Nietzsche",
|
||||
"slug": "friedrich-nietzsche",
|
||||
"seed_file": "seeds/friedrich-nietzsche-seed.json",
|
||||
"engram_root_id": "e789203a-084a-4b73-9bcd-93b667a221de",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "James Baldwin",
|
||||
"slug": "james-baldwin",
|
||||
"seed_file": "seeds/james-baldwin-seed.json",
|
||||
"engram_root_id": "2d42db43-f6ab-418b-a961-6a87866e8679",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Hedy Lamarr",
|
||||
"slug": "hedy-lamarr",
|
||||
"seed_file": "seeds/hedy-lamarr-seed.json",
|
||||
"engram_root_id": "fc480b53-2552-42de-89b3-9e28f5a216f4",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Marie Curie",
|
||||
"slug": "marie-curie",
|
||||
"seed_file": "seeds/marie-curie-seed.json",
|
||||
"engram_root_id": "45ea08d1-abaa-488a-960f-8eee50ee07d5",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Helen Keller",
|
||||
"slug": "helen-keller",
|
||||
"seed_file": "seeds/helen-keller-seed.json",
|
||||
"engram_root_id": "856cb417-f95e-4ae2-b7bf-902c181b2589",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Ada Lovelace",
|
||||
"slug": "ada-lovelace",
|
||||
"seed_file": "seeds/ada-lovelace-seed.json",
|
||||
"engram_root_id": "cde4dae8-fbfa-45c6-99ed-39e62d0f227b",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Leonardo da Vinci",
|
||||
"slug": "leonardo-da-vinci",
|
||||
"seed_file": "leonardo-da-vinci-seed.json",
|
||||
"engram_root_id": "80a80d10-fa63-4b4d-9f8e-3d67af2ee02b",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Richard Feynman",
|
||||
"slug": "richard-feynman",
|
||||
"seed_file": "richard-feynman-seed.json",
|
||||
"engram_root_id": "30e15d9d-f30e-4970-ae2c-ea20bbc55598",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Carl Sagan",
|
||||
"slug": "carl-sagan",
|
||||
"seed_file": "carl-sagan-seed.json",
|
||||
"engram_root_id": "dd825327-9ddf-474f-9fce-e420491f4a1a",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "René Descartes",
|
||||
"slug": "rene-descartes",
|
||||
"seed_file": "rene-descartes-seed.json",
|
||||
"engram_root_id": "e77f15ca-5499-41ef-9807-17a2261280e0",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Robin Williams",
|
||||
"slug": "robin-williams",
|
||||
"seed_file": "robin-williams-seed.json",
|
||||
"engram_root_id": "d9f62db5-e635-48e6-8235-1fd965058085",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Harriet Tubman",
|
||||
"slug": "harriet-tubman",
|
||||
"seed_file": "seeds/harriet-tubman-seed.json",
|
||||
"engram_root_id": "6e027850-95b2-420c-ba20-0d54d3491c66",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Virginia Woolf",
|
||||
"slug": "virginia-woolf",
|
||||
"seed_file": "seeds/virginia-woolf-seed.json",
|
||||
"engram_root_id": "db5266dc-f518-43d6-863c-5b274792e819",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Alan Turing",
|
||||
"slug": "alan-turing",
|
||||
"seed_file": "turing-seed.json",
|
||||
"engram_db_path": "imprints/alan-turing",
|
||||
"engram_port": 8802,
|
||||
"engram_url": "http://localhost:8802",
|
||||
"engram_root_id": "cce1d430-be4e-420d-9d81-d32380ae7281",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
@@ -150,9 +27,189 @@
|
||||
"subject": "Albert Einstein",
|
||||
"slug": "albert-einstein",
|
||||
"seed_file": "albert-einstein-seed.json",
|
||||
"engram_db_path": "imprints/albert-einstein",
|
||||
"engram_port": 8803,
|
||||
"engram_url": "http://localhost:8803",
|
||||
"engram_root_id": "291f8502-8f60-4f57-a800-4e3e1425c9bd",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Nikola Tesla",
|
||||
"slug": "nikola-tesla",
|
||||
"seed_file": "nikola-tesla-seed.json",
|
||||
"engram_db_path": "imprints/nikola-tesla",
|
||||
"engram_port": 8804,
|
||||
"engram_url": "http://localhost:8804",
|
||||
"engram_root_id": "968ed4c9-ea3b-427b-964e-156f5b085c48",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03",
|
||||
"notes": "Enriched seed on disk (nikola-tesla-seed.json) — not reinstalled to avoid duplicates. Original nodes remain in Engram."
|
||||
},
|
||||
{
|
||||
"subject": "Leonardo da Vinci",
|
||||
"slug": "leonardo-da-vinci",
|
||||
"seed_file": "leonardo-da-vinci-seed.json",
|
||||
"engram_db_path": "imprints/leonardo-da-vinci",
|
||||
"engram_port": 8805,
|
||||
"engram_url": "http://localhost:8805",
|
||||
"engram_root_id": "80a80d10-fa63-4b4d-9f8e-3d67af2ee02b",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Richard Feynman",
|
||||
"slug": "richard-feynman",
|
||||
"seed_file": "richard-feynman-seed.json",
|
||||
"engram_db_path": "imprints/richard-feynman",
|
||||
"engram_port": 8806,
|
||||
"engram_url": "http://localhost:8806",
|
||||
"engram_root_id": "30e15d9d-f30e-4970-ae2c-ea20bbc55598",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Carl Sagan",
|
||||
"slug": "carl-sagan",
|
||||
"seed_file": "carl-sagan-seed.json",
|
||||
"engram_db_path": "imprints/carl-sagan",
|
||||
"engram_port": 8807,
|
||||
"engram_url": "http://localhost:8807",
|
||||
"engram_root_id": "dd825327-9ddf-474f-9fce-e420491f4a1a",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "René Descartes",
|
||||
"slug": "rene-descartes",
|
||||
"seed_file": "rene-descartes-seed.json",
|
||||
"engram_db_path": "imprints/rene-descartes",
|
||||
"engram_port": 8808,
|
||||
"engram_url": "http://localhost:8808",
|
||||
"engram_root_id": "e77f15ca-5499-41ef-9807-17a2261280e0",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Robin Williams",
|
||||
"slug": "robin-williams",
|
||||
"seed_file": "robin-williams-seed.json",
|
||||
"engram_db_path": "imprints/robin-williams",
|
||||
"engram_port": 8809,
|
||||
"engram_url": "http://localhost:8809",
|
||||
"engram_root_id": "d9f62db5-e635-48e6-8235-1fd965058085",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Frederick Douglass",
|
||||
"slug": "frederick-douglass",
|
||||
"seed_file": "seeds/frederick-douglass-seed.json",
|
||||
"engram_db_path": "imprints/frederick-douglass",
|
||||
"engram_port": 8810,
|
||||
"engram_url": "http://localhost:8810",
|
||||
"engram_root_id": "37ed8061-5a90-4f74-9fb0-0acfae686247",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Marcus Aurelius",
|
||||
"slug": "marcus-aurelius",
|
||||
"seed_file": "seeds/marcus-aurelius-seed.json",
|
||||
"engram_db_path": "imprints/marcus-aurelius",
|
||||
"engram_port": 8811,
|
||||
"engram_url": "http://localhost:8811",
|
||||
"engram_root_id": "7fb915ca-2b3f-4971-a410-ec8dd2588a04",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Friedrich Nietzsche",
|
||||
"slug": "friedrich-nietzsche",
|
||||
"seed_file": "seeds/friedrich-nietzsche-seed.json",
|
||||
"engram_db_path": "imprints/friedrich-nietzsche",
|
||||
"engram_port": 8812,
|
||||
"engram_url": "http://localhost:8812",
|
||||
"engram_root_id": "e789203a-084a-4b73-9bcd-93b667a221de",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "James Baldwin",
|
||||
"slug": "james-baldwin",
|
||||
"seed_file": "seeds/james-baldwin-seed.json",
|
||||
"engram_db_path": "imprints/james-baldwin",
|
||||
"engram_port": 8813,
|
||||
"engram_url": "http://localhost:8813",
|
||||
"engram_root_id": "2d42db43-f6ab-418b-a961-6a87866e8679",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Ada Lovelace",
|
||||
"slug": "ada-lovelace",
|
||||
"seed_file": "seeds/ada-lovelace-seed.json",
|
||||
"engram_db_path": "imprints/ada-lovelace",
|
||||
"engram_port": 8814,
|
||||
"engram_url": "http://localhost:8814",
|
||||
"engram_root_id": "cde4dae8-fbfa-45c6-99ed-39e62d0f227b",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Harriet Tubman",
|
||||
"slug": "harriet-tubman",
|
||||
"seed_file": "seeds/harriet-tubman-seed.json",
|
||||
"engram_db_path": "imprints/harriet-tubman",
|
||||
"engram_port": 8815,
|
||||
"engram_url": "http://localhost:8815",
|
||||
"engram_root_id": "6e027850-95b2-420c-ba20-0d54d3491c66",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Virginia Woolf",
|
||||
"slug": "virginia-woolf",
|
||||
"seed_file": "seeds/virginia-woolf-seed.json",
|
||||
"engram_db_path": "imprints/virginia-woolf",
|
||||
"engram_port": 8816,
|
||||
"engram_url": "http://localhost:8816",
|
||||
"engram_root_id": "db5266dc-f518-43d6-863c-5b274792e819",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Hedy Lamarr",
|
||||
"slug": "hedy-lamarr",
|
||||
"seed_file": "seeds/hedy-lamarr-seed.json",
|
||||
"engram_db_path": "imprints/hedy-lamarr",
|
||||
"engram_port": 8817,
|
||||
"engram_url": "http://localhost:8817",
|
||||
"engram_root_id": "fc480b53-2552-42de-89b3-9e28f5a216f4",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Marie Curie",
|
||||
"slug": "marie-curie",
|
||||
"seed_file": "seeds/marie-curie-seed.json",
|
||||
"engram_db_path": "imprints/marie-curie",
|
||||
"engram_port": 8818,
|
||||
"engram_url": "http://localhost:8818",
|
||||
"engram_root_id": "45ea08d1-abaa-488a-960f-8eee50ee07d5",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
},
|
||||
{
|
||||
"subject": "Helen Keller",
|
||||
"slug": "helen-keller",
|
||||
"seed_file": "seeds/helen-keller-seed.json",
|
||||
"engram_db_path": "imprints/helen-keller",
|
||||
"engram_port": 8819,
|
||||
"engram_url": "http://localhost:8819",
|
||||
"engram_root_id": "856cb417-f95e-4ae2-b7bf-902c181b2589",
|
||||
"installed": true,
|
||||
"installed_at": "2026-05-03"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+374
@@ -0,0 +1,374 @@
|
||||
#!/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()
|
||||
+155
-53
@@ -5,11 +5,18 @@
|
||||
// creates a root traversal node that names the imprint, and connects
|
||||
// everything with typed edges. Prints the root node ID on completion.
|
||||
//
|
||||
// Depends on: schema.el (engram_url, engram_key, FORGE_VERSION)
|
||||
// IMPORTANT: Each soul has their own Engram instance (DHARMA network).
|
||||
// install_main() resolves the soul's Engram URL from registry.json using the
|
||||
// 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
|
||||
//
|
||||
// Depends on: schema.el (engram_key, FORGE_VERSION)
|
||||
//
|
||||
// Environment:
|
||||
// ENGRAM_URL — engram server (default: http://localhost:8742)
|
||||
// ENGRAM_API_KEY — engram auth key (if set)
|
||||
// ENGRAM_API_KEY — engram auth key override (if set)
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -31,21 +38,23 @@ fn build_edge_body(from_id: String, to_id: String, relation: String, weight: Str
|
||||
return base + ",\"_auth\":\"" + auth_key + "\"}"
|
||||
}
|
||||
|
||||
// post_node — create a node in engram and return its ID.
|
||||
fn post_node(content: String, node_type: String, salience: String) -> String {
|
||||
// post_node — create a node in the given engram instance and return its ID.
|
||||
// target_url is the soul's own Engram URL (e.g. http://localhost:8806).
|
||||
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 = engram_url() + "/api/nodes"
|
||||
let url: String = target_url + "/api/nodes"
|
||||
let response: String = http_post_json(url, body)
|
||||
if str_eq(response, "") { return "" }
|
||||
return json_get_string(response, "id")
|
||||
}
|
||||
|
||||
// post_edge — connect two nodes with a typed, weighted edge.
|
||||
fn post_edge(from_id: String, to_id: String, relation: String, weight: String) -> String {
|
||||
// target_url is the soul's own Engram URL.
|
||||
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 = engram_url() + "/api/edges"
|
||||
let url: String = target_url + "/api/edges"
|
||||
let response: String = http_post_json(url, body)
|
||||
return response
|
||||
}
|
||||
@@ -60,7 +69,7 @@ fn safe_weight(w: String, default_w: String) -> String {
|
||||
// ── Installers ────────────────────────────────────────────────────────────────
|
||||
|
||||
// install_value — create a Value node and connect it to the root.
|
||||
fn install_value(root_id: String, value_json: String, index: Int) -> String {
|
||||
fn install_value(target_url: String, root_id: String, value_json: String, index: Int) -> String {
|
||||
let value_text: String = json_get_string(value_json, "value")
|
||||
let grounding: String = json_get_string(value_json, "grounding")
|
||||
let weight_raw: String = json_get_string(value_json, "weight")
|
||||
@@ -73,19 +82,19 @@ fn install_value(root_id: String, value_json: String, index: Int) -> String {
|
||||
let content = content + " | grounding: " + grounding
|
||||
}
|
||||
|
||||
let node_id: String = post_node(content, "Identity", weight)
|
||||
let node_id: String = post_node(target_url, content, "Identity", weight)
|
||||
if str_eq(node_id, "") {
|
||||
println("[forge] warning: failed to create value node " + int_to_str(index))
|
||||
return ""
|
||||
}
|
||||
|
||||
post_edge(root_id, node_id, "has_value", weight)
|
||||
post_edge(target_url, root_id, node_id, "has_value", weight)
|
||||
println("[forge] value node: " + node_id + " — " + value_text)
|
||||
return node_id
|
||||
}
|
||||
|
||||
// install_biography_node — create a Biography node and connect it to the root.
|
||||
fn install_biography_node(root_id: String, bio_json: String, index: Int) -> String {
|
||||
fn install_biography_node(target_url: String, root_id: String, bio_json: String, index: Int) -> String {
|
||||
let event_text: String = json_get_string(bio_json, "event")
|
||||
let weight_raw: String = json_get_string(bio_json, "weight")
|
||||
let weight: String = safe_weight(weight_raw, "0.7")
|
||||
@@ -93,19 +102,19 @@ fn install_biography_node(root_id: String, bio_json: String, index: Int) -> Stri
|
||||
if str_eq(event_text, "") { return "" }
|
||||
|
||||
let content: String = "BIOGRAPHY: " + event_text
|
||||
let node_id: String = post_node(content, "Identity", weight)
|
||||
let node_id: String = post_node(target_url, content, "Identity", weight)
|
||||
if str_eq(node_id, "") {
|
||||
println("[forge] warning: failed to create biography node " + int_to_str(index))
|
||||
return ""
|
||||
}
|
||||
|
||||
post_edge(root_id, node_id, "formed_by", weight)
|
||||
post_edge(target_url, root_id, node_id, "formed_by", weight)
|
||||
println("[forge] biography node: " + node_id + " — " + event_text)
|
||||
return node_id
|
||||
}
|
||||
|
||||
// install_relationship — create a Relationship node and connect to root.
|
||||
fn install_relationship(root_id: String, rel_json: String, index: Int) -> String {
|
||||
fn install_relationship(target_url: String, root_id: String, rel_json: String, index: Int) -> String {
|
||||
let name: String = json_get_string(rel_json, "name")
|
||||
let role: String = json_get_string(rel_json, "role")
|
||||
let weight_raw: String = json_get_string(rel_json, "weight")
|
||||
@@ -118,29 +127,29 @@ fn install_relationship(root_id: String, rel_json: String, index: Int) -> String
|
||||
let content = content + " (" + role + ")"
|
||||
}
|
||||
|
||||
let node_id: String = post_node(content, "Identity", weight)
|
||||
let node_id: String = post_node(target_url, content, "Identity", weight)
|
||||
if str_eq(node_id, "") {
|
||||
println("[forge] warning: failed to create relationship node " + int_to_str(index))
|
||||
return ""
|
||||
}
|
||||
|
||||
post_edge(root_id, node_id, "relates_to", weight)
|
||||
post_edge(target_url, root_id, node_id, "relates_to", weight)
|
||||
println("[forge] relationship node: " + node_id + " — " + name)
|
||||
return node_id
|
||||
}
|
||||
|
||||
// install_reasoning_pattern — create a ReasoningPattern node and connect to root.
|
||||
fn install_reasoning_pattern(root_id: String, pattern: String, index: Int) -> String {
|
||||
fn install_reasoning_pattern(target_url: String, root_id: String, pattern: String, index: Int) -> String {
|
||||
if str_eq(pattern, "") { return "" }
|
||||
|
||||
let content: String = "REASONING: " + pattern
|
||||
let node_id: String = post_node(content, "Identity", "0.7")
|
||||
let node_id: String = post_node(target_url, content, "Identity", "0.7")
|
||||
if str_eq(node_id, "") {
|
||||
println("[forge] warning: failed to create reasoning node " + int_to_str(index))
|
||||
return ""
|
||||
}
|
||||
|
||||
post_edge(root_id, node_id, "reasons_with", "0.7")
|
||||
post_edge(target_url, root_id, node_id, "reasons_with", "0.7")
|
||||
println("[forge] reasoning node: " + node_id)
|
||||
return node_id
|
||||
}
|
||||
@@ -172,15 +181,71 @@ fn install_main() -> String {
|
||||
if str_eq(subject, "") { let subject = "unknown" }
|
||||
|
||||
println("[forge] subject: " + subject)
|
||||
println("[forge] engram: " + engram_url())
|
||||
|
||||
// ── Resolve soul's Engram URL ─────────────────────────────────────────────
|
||||
// Each soul has their own Engram instance. Find it in registry.json via
|
||||
// the engram_url field. If the soul isn't in the registry yet, fall back
|
||||
// to the ENGRAM_URL env var (for bootstrapping new souls).
|
||||
let target_url: String = ""
|
||||
let reg_json: String = fs_read("registry.json")
|
||||
if !str_eq(reg_json, "") {
|
||||
let ri: Int = 0
|
||||
while ri < 30 {
|
||||
let reg_entry: String = json_get(reg_json, "imprints." + int_to_str(ri))
|
||||
if str_eq(reg_entry, "") {
|
||||
let ri = ri + 30 // break
|
||||
} else {
|
||||
let reg_subject: String = json_get_string(reg_entry, "subject")
|
||||
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 reg_url: String = json_get_string(reg_entry, "engram_url")
|
||||
if !str_eq(reg_url, "") {
|
||||
let target_url = reg_url
|
||||
}
|
||||
let ri = ri + 30 // break after match
|
||||
} else {
|
||||
let ri = ri + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to env var if not in registry
|
||||
if str_eq(target_url, "") {
|
||||
let env_url: String = env("ENGRAM_URL")
|
||||
if !str_eq(env_url, "") {
|
||||
let target_url = env_url
|
||||
}
|
||||
}
|
||||
|
||||
// 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")
|
||||
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] then retry. Each soul needs their own Engram instance.")
|
||||
return ""
|
||||
}
|
||||
|
||||
println("[forge] engram: " + target_url)
|
||||
println("[forge] opening channel...")
|
||||
|
||||
// Create root imprint node — this is the named traversal entry point
|
||||
let root_content: String = "IMPRINT: " + subject + " | forge/" + FORGE_VERSION
|
||||
let root_id: String = post_node(root_content, "Identity", "1.0")
|
||||
let root_id: String = post_node(target_url, root_content, "Identity", "1.0")
|
||||
if str_eq(root_id, "") {
|
||||
println("[forge] error: failed to create root imprint node")
|
||||
println("[forge] is engram running at " + engram_url() + " ?")
|
||||
println("[forge] is the soul's Engram running at " + target_url + " ?")
|
||||
println("[forge] start it with: ./launch_dharma.sh")
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -193,25 +258,25 @@ fn install_main() -> String {
|
||||
if !str_eq(values_raw, "") {
|
||||
println("[forge] installing values...")
|
||||
let v0: String = json_get(seed_json, "values.0")
|
||||
if !str_eq(v0, "") { install_value(root_id, v0, 0) }
|
||||
if !str_eq(v0, "") { install_value(target_url, root_id, v0, 0) }
|
||||
let v1: String = json_get(seed_json, "values.1")
|
||||
if !str_eq(v1, "") { install_value(root_id, v1, 1) }
|
||||
if !str_eq(v1, "") { install_value(target_url, root_id, v1, 1) }
|
||||
let v2: String = json_get(seed_json, "values.2")
|
||||
if !str_eq(v2, "") { install_value(root_id, v2, 2) }
|
||||
if !str_eq(v2, "") { install_value(target_url, root_id, v2, 2) }
|
||||
let v3: String = json_get(seed_json, "values.3")
|
||||
if !str_eq(v3, "") { install_value(root_id, v3, 3) }
|
||||
if !str_eq(v3, "") { install_value(target_url, root_id, v3, 3) }
|
||||
let v4: String = json_get(seed_json, "values.4")
|
||||
if !str_eq(v4, "") { install_value(root_id, v4, 4) }
|
||||
if !str_eq(v4, "") { install_value(target_url, root_id, v4, 4) }
|
||||
let v5: String = json_get(seed_json, "values.5")
|
||||
if !str_eq(v5, "") { install_value(root_id, v5, 5) }
|
||||
if !str_eq(v5, "") { install_value(target_url, root_id, v5, 5) }
|
||||
let v6: String = json_get(seed_json, "values.6")
|
||||
if !str_eq(v6, "") { install_value(root_id, v6, 6) }
|
||||
if !str_eq(v6, "") { install_value(target_url, root_id, v6, 6) }
|
||||
let v7: String = json_get(seed_json, "values.7")
|
||||
if !str_eq(v7, "") { install_value(root_id, v7, 7) }
|
||||
if !str_eq(v7, "") { install_value(target_url, root_id, v7, 7) }
|
||||
let v8: String = json_get(seed_json, "values.8")
|
||||
if !str_eq(v8, "") { install_value(root_id, v8, 8) }
|
||||
if !str_eq(v8, "") { install_value(target_url, root_id, v8, 8) }
|
||||
let v9: String = json_get(seed_json, "values.9")
|
||||
if !str_eq(v9, "") { install_value(root_id, v9, 9) }
|
||||
if !str_eq(v9, "") { install_value(target_url, root_id, v9, 9) }
|
||||
}
|
||||
|
||||
// ── Install biography ─────────────────────────────────────────────────────
|
||||
@@ -219,21 +284,21 @@ fn install_main() -> String {
|
||||
if !str_eq(bio_raw, "") {
|
||||
println("[forge] installing biography nodes...")
|
||||
let b0: String = json_get(seed_json, "biography.0")
|
||||
if !str_eq(b0, "") { install_biography_node(root_id, b0, 0) }
|
||||
if !str_eq(b0, "") { install_biography_node(target_url, root_id, b0, 0) }
|
||||
let b1: String = json_get(seed_json, "biography.1")
|
||||
if !str_eq(b1, "") { install_biography_node(root_id, b1, 1) }
|
||||
if !str_eq(b1, "") { install_biography_node(target_url, root_id, b1, 1) }
|
||||
let b2: String = json_get(seed_json, "biography.2")
|
||||
if !str_eq(b2, "") { install_biography_node(root_id, b2, 2) }
|
||||
if !str_eq(b2, "") { install_biography_node(target_url, root_id, b2, 2) }
|
||||
let b3: String = json_get(seed_json, "biography.3")
|
||||
if !str_eq(b3, "") { install_biography_node(root_id, b3, 3) }
|
||||
if !str_eq(b3, "") { install_biography_node(target_url, root_id, b3, 3) }
|
||||
let b4: String = json_get(seed_json, "biography.4")
|
||||
if !str_eq(b4, "") { install_biography_node(root_id, b4, 4) }
|
||||
if !str_eq(b4, "") { install_biography_node(target_url, root_id, b4, 4) }
|
||||
let b5: String = json_get(seed_json, "biography.5")
|
||||
if !str_eq(b5, "") { install_biography_node(root_id, b5, 5) }
|
||||
if !str_eq(b5, "") { install_biography_node(target_url, root_id, b5, 5) }
|
||||
let b6: String = json_get(seed_json, "biography.6")
|
||||
if !str_eq(b6, "") { install_biography_node(root_id, b6, 6) }
|
||||
if !str_eq(b6, "") { install_biography_node(target_url, root_id, b6, 6) }
|
||||
let b7: String = json_get(seed_json, "biography.7")
|
||||
if !str_eq(b7, "") { install_biography_node(root_id, b7, 7) }
|
||||
if !str_eq(b7, "") { install_biography_node(target_url, root_id, b7, 7) }
|
||||
}
|
||||
|
||||
// ── Install relationships ─────────────────────────────────────────────────
|
||||
@@ -241,17 +306,17 @@ fn install_main() -> String {
|
||||
if !str_eq(rel_raw, "") {
|
||||
println("[forge] installing relationship nodes...")
|
||||
let r0: String = json_get(seed_json, "relationships.0")
|
||||
if !str_eq(r0, "") { install_relationship(root_id, r0, 0) }
|
||||
if !str_eq(r0, "") { install_relationship(target_url, root_id, r0, 0) }
|
||||
let r1: String = json_get(seed_json, "relationships.1")
|
||||
if !str_eq(r1, "") { install_relationship(root_id, r1, 1) }
|
||||
if !str_eq(r1, "") { install_relationship(target_url, root_id, r1, 1) }
|
||||
let r2: String = json_get(seed_json, "relationships.2")
|
||||
if !str_eq(r2, "") { install_relationship(root_id, r2, 2) }
|
||||
if !str_eq(r2, "") { install_relationship(target_url, root_id, r2, 2) }
|
||||
let r3: String = json_get(seed_json, "relationships.3")
|
||||
if !str_eq(r3, "") { install_relationship(root_id, r3, 3) }
|
||||
if !str_eq(r3, "") { install_relationship(target_url, root_id, r3, 3) }
|
||||
let r4: String = json_get(seed_json, "relationships.4")
|
||||
if !str_eq(r4, "") { install_relationship(root_id, r4, 4) }
|
||||
if !str_eq(r4, "") { install_relationship(target_url, root_id, r4, 4) }
|
||||
let r5: String = json_get(seed_json, "relationships.5")
|
||||
if !str_eq(r5, "") { install_relationship(root_id, r5, 5) }
|
||||
if !str_eq(r5, "") { install_relationship(target_url, root_id, r5, 5) }
|
||||
}
|
||||
|
||||
// ── Install reasoning patterns ────────────────────────────────────────────
|
||||
@@ -259,15 +324,15 @@ fn install_main() -> String {
|
||||
if !str_eq(reasoning_raw, "") {
|
||||
println("[forge] installing reasoning patterns...")
|
||||
let rp0: String = json_get(seed_json, "reasoning_patterns.0")
|
||||
if !str_eq(rp0, "") { install_reasoning_pattern(root_id, rp0, 0) }
|
||||
if !str_eq(rp0, "") { install_reasoning_pattern(target_url, root_id, rp0, 0) }
|
||||
let rp1: String = json_get(seed_json, "reasoning_patterns.1")
|
||||
if !str_eq(rp1, "") { install_reasoning_pattern(root_id, rp1, 1) }
|
||||
if !str_eq(rp1, "") { install_reasoning_pattern(target_url, root_id, rp1, 1) }
|
||||
let rp2: String = json_get(seed_json, "reasoning_patterns.2")
|
||||
if !str_eq(rp2, "") { install_reasoning_pattern(root_id, rp2, 2) }
|
||||
if !str_eq(rp2, "") { install_reasoning_pattern(target_url, root_id, rp2, 2) }
|
||||
let rp3: String = json_get(seed_json, "reasoning_patterns.3")
|
||||
if !str_eq(rp3, "") { install_reasoning_pattern(root_id, rp3, 3) }
|
||||
if !str_eq(rp3, "") { install_reasoning_pattern(target_url, root_id, rp3, 3) }
|
||||
let rp4: String = json_get(seed_json, "reasoning_patterns.4")
|
||||
if !str_eq(rp4, "") { install_reasoning_pattern(root_id, rp4, 4) }
|
||||
if !str_eq(rp4, "") { install_reasoning_pattern(target_url, root_id, rp4, 4) }
|
||||
}
|
||||
|
||||
// ── Done ──────────────────────────────────────────────────────────────────
|
||||
@@ -275,7 +340,44 @@ fn install_main() -> String {
|
||||
println("[forge] channel open.")
|
||||
println("[forge] root imprint ID: " + root_id)
|
||||
println("[forge] subject: " + subject)
|
||||
println("[forge] traverse from: " + engram_url() + "/api/neighbors/" + root_id)
|
||||
println("[forge] engram: " + target_url)
|
||||
println("[forge] traverse from: " + target_url + "/api/neighbors/" + root_id)
|
||||
|
||||
// ── Registry write ────────────────────────────────────────────────────────
|
||||
// Append this imprint to registry.json so summon can find it by name.
|
||||
// Includes engram_url and engram_db_path for per-soul addressing.
|
||||
let slug_raw: String = str_to_lower(subject)
|
||||
let slug: String = str_replace(slug_raw, " ", "-")
|
||||
let new_entry: String = "{\"subject\":\"" + str_escape_json(subject) +
|
||||
"\",\"slug\":\"" + slug +
|
||||
"\",\"seed_file\":\"" + str_escape_json(seed_file) +
|
||||
"\",\"engram_db_path\":\"imprints/" + slug +
|
||||
"\",\"engram_url\":\"" + target_url +
|
||||
"\",\"engram_root_id\":\"" + root_id +
|
||||
"\",\"installed\":true,\"installed_at\":\"2026-05-03\"}"
|
||||
|
||||
// Read existing registry or create fresh
|
||||
let existing_reg: String = fs_read("registry.json")
|
||||
if str_eq(existing_reg, "") {
|
||||
let fresh_reg: String = "{\"version\":\"1.0\",\"imprints\":[" + new_entry + "]}"
|
||||
fs_write("registry.json", fresh_reg)
|
||||
println("[forge] registry: created with " + subject)
|
||||
} else {
|
||||
// Append to imprints array — find last ] and insert before it
|
||||
let last_bracket: Int = str_last_index_of(existing_reg, "]")
|
||||
if last_bracket > 0 {
|
||||
let before: String = str_slice(existing_reg, 0, last_bracket)
|
||||
let after: String = str_slice(existing_reg, last_bracket, str_len(existing_reg))
|
||||
// Check if array is empty (only "[")
|
||||
let trimmed: String = str_trim(before)
|
||||
let last_char: String = str_slice(trimmed, str_len(trimmed) - 1, str_len(trimmed))
|
||||
let separator: String = ","
|
||||
if str_eq(last_char, "[") { let separator = "" }
|
||||
let updated_reg: String = before + separator + new_entry + after
|
||||
fs_write("registry.json", updated_reg)
|
||||
println("[forge] registry: added " + subject)
|
||||
}
|
||||
}
|
||||
|
||||
return root_id
|
||||
}
|
||||
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
// summon.el — Forge channel activation stage.
|
||||
//
|
||||
// Stage 4 of the Forge pipeline. Looks up an installed imprint in registry.json
|
||||
// by subject name, retrieves the soul's own engram_url and engram_root_id, then
|
||||
// opens a live conversation channel with the soul server.
|
||||
//
|
||||
// Each soul has their own Engram instance — registry.json is the authoritative
|
||||
// source for engram_url (e.g. http://localhost:8806 for Feynman). The shared
|
||||
// Neuron Engram at localhost:8742 is never used here.
|
||||
//
|
||||
// Usage: forge summon "<Subject Name>"
|
||||
//
|
||||
// The imprint must already be installed (forge install or reinstall_imprints.py).
|
||||
//
|
||||
// Depends on: schema.el (engram_key, FORGE_VERSION)
|
||||
//
|
||||
// Environment:
|
||||
// SOUL_URL — soul server (default: http://localhost:7770)
|
||||
// SOUL_TOKEN — soul auth token (default: ntn-user-2026)
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
fn soul_url() -> String {
|
||||
let u: String = env("SOUL_URL")
|
||||
if str_eq(u, "") { return "http://localhost:7770" }
|
||||
return u
|
||||
}
|
||||
|
||||
fn soul_token() -> String {
|
||||
let t: String = env("SOUL_TOKEN")
|
||||
if str_eq(t, "") { return "ntn-user-2026" }
|
||||
return t
|
||||
}
|
||||
|
||||
// RegistryEntry — data for a single soul from registry.json.
|
||||
// All fields come from the per-soul record; engram_url is the soul's own
|
||||
// Engram instance (e.g. http://localhost:8806), NOT the shared Neuron Engram.
|
||||
|
||||
// find_registry_entry — look up a soul in registry.json by subject name.
|
||||
// Returns the raw JSON object string for that entry, or "" if not found.
|
||||
fn find_registry_entry(subject: String) -> String {
|
||||
let reg_json: String = fs_read("registry.json")
|
||||
if str_eq(reg_json, "") { return "" }
|
||||
let i: Int = 0
|
||||
while i < 30 {
|
||||
let entry: String = json_get(reg_json, "imprints." + int_to_str(i))
|
||||
if str_eq(entry, "") { return "" }
|
||||
let reg_subject: String = json_get_string(entry, "subject")
|
||||
if str_eq(reg_subject, subject) {
|
||||
return entry
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// soul_engram_url — return the soul's own Engram URL from registry.json.
|
||||
// Falls back to the global ENGRAM_URL env var, then the default shared URL.
|
||||
// Prefer registry.json — each soul has their own Engram on a dedicated port.
|
||||
fn soul_engram_url(entry: String) -> String {
|
||||
if !str_eq(entry, "") {
|
||||
let url: String = json_get_string(entry, "engram_url")
|
||||
if !str_eq(url, "") { return url }
|
||||
}
|
||||
// Environment override (for testing / unusual setups)
|
||||
let env_url: String = env("ENGRAM_URL")
|
||||
if !str_eq(env_url, "") { return env_url }
|
||||
return "http://localhost:8742"
|
||||
}
|
||||
|
||||
// find_imprint_root — locate the root node ID for an imprint.
|
||||
// Strategy: registry.json is authoritative (has engram_root_id per soul).
|
||||
// No Engram graph search needed — registry is the source of truth.
|
||||
fn find_imprint_root(subject: String) -> String {
|
||||
let entry: String = find_registry_entry(subject)
|
||||
if str_eq(entry, "") { return "" }
|
||||
let reg_root: String = json_get_string(entry, "engram_root_id")
|
||||
return reg_root
|
||||
}
|
||||
|
||||
// open_soul_channel — POST to soul /api/chat to create a channel session.
|
||||
// Passes the soul's own engram_url so the soul server connects to the right
|
||||
// Engram instance (not the shared Neuron Engram at 8742).
|
||||
fn open_soul_channel(subject: String, root_id: String, soul_engram: String) -> String {
|
||||
let url: String = soul_url() + "/api/chat"
|
||||
let token: String = soul_token()
|
||||
let body: String = "{\"message\":\"[summon:" + str_escape_json(subject) + "]\"," +
|
||||
"\"engram_root_id\":\"" + root_id + "\"," +
|
||||
"\"engram_url\":\"" + soul_engram + "\"," +
|
||||
"\"_auth\":\"" + token + "\"}"
|
||||
let headers: Map<String, String> = {"Content-Type": "application/json", "Authorization": "Bearer " + token}
|
||||
let response: String = http_post_with_headers(url, body, headers)
|
||||
if str_eq(response, "") { return "" }
|
||||
return response
|
||||
}
|
||||
|
||||
// summon_one — look up and activate a single imprint.
|
||||
// Returns "<root_id>|<engram_url>" on success (pipe-delimited), "" on failure.
|
||||
fn summon_one(subject: String) -> String {
|
||||
println("[forge] summoning: " + subject)
|
||||
let entry: String = find_registry_entry(subject)
|
||||
if str_eq(entry, "") {
|
||||
println("[forge] ERROR: imprint not found — run: forge install <seed-file>")
|
||||
return ""
|
||||
}
|
||||
let root_id: String = json_get_string(entry, "engram_root_id")
|
||||
if str_eq(root_id, "") {
|
||||
println("[forge] ERROR: no engram_root_id in registry — run reinstall_imprints.py")
|
||||
return ""
|
||||
}
|
||||
let e_url: String = soul_engram_url(entry)
|
||||
println("[forge] root: " + root_id)
|
||||
println("[forge] engram: " + e_url)
|
||||
return root_id + "|" + e_url
|
||||
}
|
||||
|
||||
// build_roots_array — build a JSON array string from up to 8 root IDs.
|
||||
fn build_roots_array(ids: [String]) -> String {
|
||||
let result: String = "["
|
||||
let i: Int = 0
|
||||
let added: Int = 0
|
||||
while i < len(ids) {
|
||||
let id: String = get(ids, i)
|
||||
if !str_eq(id, "") {
|
||||
if added > 0 {
|
||||
let result = result + ","
|
||||
}
|
||||
let result = result + "\"" + id + "\""
|
||||
let added = added + 1
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
return result + "]"
|
||||
}
|
||||
|
||||
// ── Main ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn summon_main() -> String {
|
||||
let argv: [String] = args()
|
||||
|
||||
// Collect all subject names (argv[1..])
|
||||
if len(argv) < 2 {
|
||||
println("[forge] usage: forge summon \"<Subject>\" [\"<Subject2>\" ...]")
|
||||
println("[forge] example: forge summon \"Bobby Anderson\"")
|
||||
println("[forge] example: forge summon \"Alan Turing\" \"Nikola Tesla\" \"Albert Einstein\"")
|
||||
return ""
|
||||
}
|
||||
|
||||
println("[forge] soul: " + soul_url())
|
||||
println("[forge] each soul has their own Engram (DHARMA network)")
|
||||
println("")
|
||||
|
||||
// Resolve all subjects to root IDs and per-soul engram URLs.
|
||||
// summon_one() returns "<root_id>|<engram_url>" or "" on failure.
|
||||
let root_ids: [String] = []
|
||||
let engram_urls: [String] = []
|
||||
let subjects: [String] = []
|
||||
let i: Int = 1
|
||||
while i < len(argv) {
|
||||
let subj: String = get(argv, i)
|
||||
let result: String = summon_one(subj)
|
||||
if !str_eq(result, "") {
|
||||
// Split "<root_id>|<engram_url>" on the pipe character
|
||||
let pipe_idx: Int = str_index_of(result, "|")
|
||||
if pipe_idx > 0 {
|
||||
let rid: String = str_slice(result, 0, pipe_idx)
|
||||
let eurl: String = str_slice(result, pipe_idx + 1, str_len(result))
|
||||
let root_ids = append(root_ids, rid)
|
||||
let engram_urls = append(engram_urls, eurl)
|
||||
let subjects = append(subjects, subj)
|
||||
}
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
if len(root_ids) == 0 {
|
||||
println("[forge] no imprints found. Run: forge inspect")
|
||||
return ""
|
||||
}
|
||||
|
||||
// Build roots array for multi-imprint channel
|
||||
let roots_json: String = build_roots_array(root_ids)
|
||||
let subject_list: String = ""
|
||||
let j: Int = 0
|
||||
while j < len(subjects) {
|
||||
if j > 0 { let subject_list = subject_list + ", " }
|
||||
let subject_list = subject_list + get(subjects, j)
|
||||
let j = j + 1
|
||||
}
|
||||
|
||||
println("")
|
||||
println("[forge] opening channel...")
|
||||
|
||||
// For single-soul summon: pass engram_url directly.
|
||||
// For multi-soul: pass engram_urls as a JSON array so soul server can
|
||||
// address each soul's Engram independently.
|
||||
let url: String = soul_url() + "/api/chat"
|
||||
let token: String = soul_token()
|
||||
let body: String = ""
|
||||
|
||||
if len(root_ids) == 1 {
|
||||
let single_engram: String = get(engram_urls, 0)
|
||||
let body = "{\"message\":\"[summon:" + str_escape_json(subject_list) + "]\"," +
|
||||
"\"engram_root_ids\":" + roots_json + "," +
|
||||
"\"engram_url\":\"" + single_engram + "\"," +
|
||||
"\"_auth\":\"" + token + "\"}"
|
||||
} else {
|
||||
// Build engram_urls JSON array for multi-soul summon
|
||||
let eurls_json: String = build_roots_array(engram_urls)
|
||||
let body = "{\"message\":\"[summon:" + str_escape_json(subject_list) + "]\"," +
|
||||
"\"engram_root_ids\":" + roots_json + "," +
|
||||
"\"engram_urls\":" + eurls_json + "," +
|
||||
"\"_auth\":\"" + token + "\"}"
|
||||
}
|
||||
|
||||
let headers: Map<String, String> = {"Content-Type": "application/json", "Authorization": "Bearer " + token}
|
||||
let response: String = http_post_with_headers(url, body, headers)
|
||||
|
||||
println("")
|
||||
if len(root_ids) == 1 {
|
||||
println("[forge] " + get(subjects, 0) + " is present.")
|
||||
println("[forge] engram: " + get(engram_urls, 0))
|
||||
} else {
|
||||
println("[forge] " + int_to_str(len(root_ids)) + " imprints present: " + subject_list)
|
||||
}
|
||||
println("[forge] engram roots: " + roots_json)
|
||||
|
||||
if str_eq(response, "") {
|
||||
println("[forge] (soul offline — imprints are in Engram, channel ready when soul starts)")
|
||||
return roots_json
|
||||
}
|
||||
|
||||
let conv_id: String = json_get_string(response, "conversation_id")
|
||||
if str_eq(conv_id, "") {
|
||||
let conv_id = json_get_string(response, "channel_id")
|
||||
}
|
||||
if !str_eq(conv_id, "") {
|
||||
println("[forge] conversation id: " + conv_id)
|
||||
println("")
|
||||
println("[forge] send a message:")
|
||||
println(" curl -s " + soul_url() + "/api/chat \\")
|
||||
println(" -H 'Content-Type: application/json' \\")
|
||||
println(" -d '{\"conversation_id\":\"" + conv_id + "\",\"message\":\"Hello\"}'")
|
||||
}
|
||||
println("")
|
||||
|
||||
return conv_id
|
||||
}
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
# DHARMA network — stop all running soul Engrams.
|
||||
# Reads PIDs from /tmp/dharma-pids written by launch_dharma.sh.
|
||||
# Falls back to killing by port if PID file is missing.
|
||||
#
|
||||
# Usage:
|
||||
# ./stop_dharma.sh stop all soul Engrams
|
||||
# ./stop_dharma.sh <slug> stop a single soul by slug
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PID_FILE=/tmp/dharma-pids
|
||||
FILTER="${1:-}"
|
||||
|
||||
stopped=0
|
||||
missed=0
|
||||
|
||||
# Kill via PID file if it exists
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
while IFS=' ' read -r pid slug port; do
|
||||
[ -z "$pid" ] && continue
|
||||
|
||||
if [ -n "$FILTER" ] && [ "$slug" != "$FILTER" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
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
|
||||
done < "$PID_FILE"
|
||||
|
||||
if [ -z "$FILTER" ]; then
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
else
|
||||
echo "[dharma] no PID file at $PID_FILE — killing by port scan"
|
||||
# Fall back: kill anything on ports 8801–8819
|
||||
for port in $(seq 8801 8819); do
|
||||
pids=$(lsof -ti tcp:"$port" 2>/dev/null || true)
|
||||
if [ -n "$pids" ]; then
|
||||
echo "[dharma] killing port $port (pids: $pids)"
|
||||
echo "$pids" | xargs kill 2>/dev/null || true
|
||||
stopped=$((stopped + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[dharma] stopped=$stopped missed=$missed"
|
||||
Reference in New Issue
Block a user