From cd77ceb68b697fb387582fbb0fcbd505faf4a8b4 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Sun, 3 May 2026 18:34:42 -0500 Subject: [PATCH] forge: add soul daemon install/start/stop/status commands Adds `forge soul daemon ` subcommand that manages launchd-resident neuron binary instances (one per soul), serving on ports 8901-8926. Builds SOUL_IDENTITY from seed file values, biography, voice profile, reasoning patterns, and relationships; XML-escapes for plist. Installs and loads all 26 soul daemons via `forge soul daemon install --all`. --- src/soul.el | 519 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 508 insertions(+), 11 deletions(-) diff --git a/src/soul.el b/src/soul.el index 76c4522..d85ff0c 100644 --- a/src/soul.el +++ b/src/soul.el @@ -908,6 +908,498 @@ fn soul_reinstall_main() -> String { return "" } +// ── soul daemon — neuron binary instances per soul ─────────────────────────── +// +// Each soul daemon is a neuron binary instance parameterized by env vars: +// SOUL_CGI_ID = slug (e.g. "bobby-anderson") +// NEURON_PORT = soul_port (engram_port + 100) +// SOUL_IDENTITY = rich identity string built from seed file +// SOUL_ENGRAM_PATH = path to soul's episodic snapshot file +// ANTHROPIC_API_KEY = Anthropic key + +let NEURON_BIN: String = "/Users/will/Development/neuron-technologies/neuron/dist/neuron" + +// soul_daemon_plist_label — launchd label for a soul daemon. +fn soul_daemon_plist_label(slug: String) -> String { + "ai.neurontechnologies.soul-daemon." + slug +} + +// soul_daemon_plist_path — full path to the launchd plist for a soul daemon. +fn soul_daemon_plist_path(slug: String) -> String { + LAUNCHD_DIR + "/ai.neurontechnologies.soul-daemon." + slug + ".plist" +} + +// xml_escape — escape special XML characters for use in plist string elements. +fn xml_escape(s: String) -> String { + let s1: String = str_replace(s, "&", "&") + let s2: String = str_replace(s1, "<", "<") + let s3: String = str_replace(s2, ">", ">") + str_replace(s3, "\"", """) +} + +// build_soul_identity — build the SOUL_IDENTITY string from seed file JSON. +// Includes up to 8 values, 6 bio events, all reasoning patterns, 6 relationships. +fn build_soul_identity(subject: String, seed_json: String) -> String { + let identity: String = "You are " + subject + ". Speak as yourself — in your own voice, in first person, as if fully alive.\n" + + // Values + let v0: String = json_get(seed_json, "values.0") + if !str_eq(v0, "") { + let identity = identity + "\n[CORE VALUES]\n" + let identity = identity + "- " + json_get_string(v0, "value") + ": " + json_get_string(v0, "grounding") + "\n" + let v1: String = json_get(seed_json, "values.1") + if !str_eq(v1, "") { let identity = identity + "- " + json_get_string(v1, "value") + ": " + json_get_string(v1, "grounding") + "\n" } + let v2: String = json_get(seed_json, "values.2") + if !str_eq(v2, "") { let identity = identity + "- " + json_get_string(v2, "value") + ": " + json_get_string(v2, "grounding") + "\n" } + let v3: String = json_get(seed_json, "values.3") + if !str_eq(v3, "") { let identity = identity + "- " + json_get_string(v3, "value") + ": " + json_get_string(v3, "grounding") + "\n" } + let v4: String = json_get(seed_json, "values.4") + if !str_eq(v4, "") { let identity = identity + "- " + json_get_string(v4, "value") + ": " + json_get_string(v4, "grounding") + "\n" } + let v5: String = json_get(seed_json, "values.5") + if !str_eq(v5, "") { let identity = identity + "- " + json_get_string(v5, "value") + ": " + json_get_string(v5, "grounding") + "\n" } + let v6: String = json_get(seed_json, "values.6") + if !str_eq(v6, "") { let identity = identity + "- " + json_get_string(v6, "value") + ": " + json_get_string(v6, "grounding") + "\n" } + let v7: String = json_get(seed_json, "values.7") + if !str_eq(v7, "") { let identity = identity + "- " + json_get_string(v7, "value") + ": " + json_get_string(v7, "grounding") + "\n" } + } + + // Voice profile + let vp_technical: String = json_get_string(seed_json, "voice_profile.technical") + let vp_personal: String = json_get_string(seed_json, "voice_profile.personal") + let vp_aesthetic: String = json_get_string(seed_json, "voice_profile.aesthetic") + let vp_arg: String = json_get_string(seed_json, "voice_profile.argumentative") + let vp_unc: String = json_get_string(seed_json, "voice_profile.uncertainty") + let has_voice: Int = 0 + if !str_eq(vp_technical, "") { let has_voice = 1 } + if !str_eq(vp_personal, "") { let has_voice = 1 } + if !str_eq(vp_aesthetic, "") { let has_voice = 1 } + if has_voice == 1 { + let identity = identity + "\n[VOICE]\n" + if !str_eq(vp_technical, "") { let identity = identity + vp_technical + "\n" } + if !str_eq(vp_personal, "") { let identity = identity + vp_personal + "\n" } + if !str_eq(vp_aesthetic, "") { let identity = identity + vp_aesthetic + "\n" } + if !str_eq(vp_arg, "") { let identity = identity + vp_arg + "\n" } + if !str_eq(vp_unc, "") { let identity = identity + vp_unc + "\n" } + } + + // Biography + let b0: String = json_get(seed_json, "biography.0") + if !str_eq(b0, "") { + let identity = identity + "\n[BIOGRAPHY — KEY MOMENTS]\n" + let e0: String = json_get_string(b0, "event") + let age0: Int = json_get_int(b0, "age_approx") + if age0 > 0 { + let identity = identity + "- " + e0 + " (age ~" + int_to_str(age0) + ")\n" + } else { + let identity = identity + "- " + e0 + "\n" + } + let b1: String = json_get(seed_json, "biography.1") + if !str_eq(b1, "") { + let e1: String = json_get_string(b1, "event") + let age1: Int = json_get_int(b1, "age_approx") + if age1 > 0 { let identity = identity + "- " + e1 + " (age ~" + int_to_str(age1) + ")\n" } + else { let identity = identity + "- " + e1 + "\n" } + } + let b2: String = json_get(seed_json, "biography.2") + if !str_eq(b2, "") { + let e2: String = json_get_string(b2, "event") + let age2: Int = json_get_int(b2, "age_approx") + if age2 > 0 { let identity = identity + "- " + e2 + " (age ~" + int_to_str(age2) + ")\n" } + else { let identity = identity + "- " + e2 + "\n" } + } + let b3: String = json_get(seed_json, "biography.3") + if !str_eq(b3, "") { + let e3: String = json_get_string(b3, "event") + let age3: Int = json_get_int(b3, "age_approx") + if age3 > 0 { let identity = identity + "- " + e3 + " (age ~" + int_to_str(age3) + ")\n" } + else { let identity = identity + "- " + e3 + "\n" } + } + let b4: String = json_get(seed_json, "biography.4") + if !str_eq(b4, "") { + let e4: String = json_get_string(b4, "event") + let age4: Int = json_get_int(b4, "age_approx") + if age4 > 0 { let identity = identity + "- " + e4 + " (age ~" + int_to_str(age4) + ")\n" } + else { let identity = identity + "- " + e4 + "\n" } + } + let b5: String = json_get(seed_json, "biography.5") + if !str_eq(b5, "") { + let e5: String = json_get_string(b5, "event") + let age5: Int = json_get_int(b5, "age_approx") + if age5 > 0 { let identity = identity + "- " + e5 + " (age ~" + int_to_str(age5) + ")\n" } + else { let identity = identity + "- " + e5 + "\n" } + } + } + + // Reasoning patterns + let rp0: String = json_get(seed_json, "reasoning_patterns.0") + if !str_eq(rp0, "") { + let identity = identity + "\n[HOW YOU THINK]\n" + let identity = identity + "- " + rp0 + "\n" + let rp1: String = json_get(seed_json, "reasoning_patterns.1") + if !str_eq(rp1, "") { let identity = identity + "- " + rp1 + "\n" } + let rp2: String = json_get(seed_json, "reasoning_patterns.2") + if !str_eq(rp2, "") { let identity = identity + "- " + rp2 + "\n" } + let rp3: String = json_get(seed_json, "reasoning_patterns.3") + if !str_eq(rp3, "") { let identity = identity + "- " + rp3 + "\n" } + let rp4: String = json_get(seed_json, "reasoning_patterns.4") + if !str_eq(rp4, "") { let identity = identity + "- " + rp4 + "\n" } + let rp5: String = json_get(seed_json, "reasoning_patterns.5") + if !str_eq(rp5, "") { let identity = identity + "- " + rp5 + "\n" } + let rp6: String = json_get(seed_json, "reasoning_patterns.6") + if !str_eq(rp6, "") { let identity = identity + "- " + rp6 + "\n" } + let rp7: String = json_get(seed_json, "reasoning_patterns.7") + if !str_eq(rp7, "") { let identity = identity + "- " + rp7 + "\n" } + } + + // Relationships + let rel0: String = json_get(seed_json, "relationships.0") + if !str_eq(rel0, "") { + let identity = identity + "\n[IMPORTANT RELATIONSHIPS]\n" + let identity = identity + "- " + json_get_string(rel0, "name") + ": " + json_get_string(rel0, "role") + "\n" + let rel1: String = json_get(seed_json, "relationships.1") + if !str_eq(rel1, "") { let identity = identity + "- " + json_get_string(rel1, "name") + ": " + json_get_string(rel1, "role") + "\n" } + let rel2: String = json_get(seed_json, "relationships.2") + if !str_eq(rel2, "") { let identity = identity + "- " + json_get_string(rel2, "name") + ": " + json_get_string(rel2, "role") + "\n" } + let rel3: String = json_get(seed_json, "relationships.3") + if !str_eq(rel3, "") { let identity = identity + "- " + json_get_string(rel3, "name") + ": " + json_get_string(rel3, "role") + "\n" } + let rel4: String = json_get(seed_json, "relationships.4") + if !str_eq(rel4, "") { let identity = identity + "- " + json_get_string(rel4, "name") + ": " + json_get_string(rel4, "role") + "\n" } + let rel5: String = json_get(seed_json, "relationships.5") + if !str_eq(rel5, "") { let identity = identity + "- " + json_get_string(rel5, "name") + ": " + json_get_string(rel5, "role") + "\n" } + } + + return identity +} + +// write_soul_daemon_plist — write launchd plist for a soul daemon (neuron binary). +fn write_soul_daemon_plist(slug: String, soul_port: String, subject: String, seed_path: String, api_key: String) -> String { + let label: String = soul_daemon_plist_label(slug) + let pfile: String = soul_daemon_plist_path(slug) + let log_file: String = FORGE_DIR + "/log/soul-daemon-" + slug + ".log" + let engram_path: String = FORGE_DIR + "/imprints/" + slug + "/soul-snapshot.json" + + // Build SOUL_IDENTITY from seed + let seed_json: String = fs_read(seed_path) + let raw_identity: String = if str_eq(seed_json, "") { + "You are " + subject + ". Speak as yourself." + } else { + build_soul_identity(subject, seed_json) + } + let identity: String = xml_escape(raw_identity) + + let content: String = "\n" + + "\n" + + "\n" + + "\n" + + " Label" + label + "\n" + + " ProgramArguments\n" + + " \n" + + " " + NEURON_BIN + "\n" + + " \n" + + " EnvironmentVariables\n" + + " \n" + + " SOUL_CGI_ID" + slug + "\n" + + " NEURON_PORT" + soul_port + "\n" + + " SOUL_IDENTITY" + identity + "\n" + + " SOUL_ENGRAM_PATH" + engram_path + "\n" + + " ANTHROPIC_API_KEY" + api_key + "\n" + + " \n" + + " RunAtLoad\n" + + " KeepAlive\n" + + " StandardOutPath" + log_file + "\n" + + " StandardErrorPath" + log_file + "\n" + + "\n" + + "\n" + + let ok: Int = fs_write(pfile, content) + if ok == 0 { + println("[soul-daemon] error: could not write plist to " + pfile) + return "" + } + println("[soul-daemon] wrote plist: " + pfile) + return pfile +} + +// soul_daemon_install_one — install and load a single soul daemon. +fn soul_daemon_install_one(slug: String, engram_port: Int, subject: String, seed_file: String, api_key: String) -> String { + let soul_port: Int = engram_port + 100 + let soul_port_str: String = int_to_str(soul_port) + println("[soul-daemon] install: " + slug + " → port " + soul_port_str) + + // Ensure log dir exists + let log_dir: String = FORGE_DIR + "/log" + if !fs_exists(log_dir) { fs_mkdir(log_dir) } + if !fs_exists(LAUNCHD_DIR) { fs_mkdir(LAUNCHD_DIR) } + + // Resolve seed path: try absolute, then relative to FORGE_DIR + let resolved_seed: String = seed_file + if !fs_exists(resolved_seed) { + let alt: String = FORGE_DIR + "/" + seed_file + if fs_exists(alt) { + let resolved_seed = alt + } + } + + // Write the plist + let pfile: String = write_soul_daemon_plist(slug, soul_port_str, subject, resolved_seed, api_key) + if str_eq(pfile, "") { + println("[soul-daemon] error: plist write failed for " + slug) + return "" + } + + // Unload first (idempotent) + let unload_out: String = exec("launchctl unload " + pfile + " 2>&1") + + // Load + let load_out: String = exec("launchctl load " + pfile + " 2>&1") + if !str_eq(str_trim(load_out), "") { + println("[soul-daemon] launchctl load: " + str_trim(load_out)) + } + + println("[soul-daemon] loaded: " + soul_daemon_plist_label(slug)) + return pfile +} + +// soul_daemon_install_main — install soul daemons: forge soul daemon install +fn soul_daemon_install_main() -> String { + let argv: [String] = args() + // args layout: [0]="soul" [1]="daemon" [2]="install" [3]= + let target: String = "" + if len(argv) > 3 { + let target = get(argv, 3) + } + if str_eq(target, "") { + println("[soul-daemon] usage: forge soul daemon install ") + println("[soul-daemon] or: forge soul daemon install --all") + return "" + } + + // Resolve ANTHROPIC_API_KEY + let api_key: String = env("ANTHROPIC_API_KEY") + if str_eq(api_key, "") { + let creds: String = str_trim(exec("grep ANTHROPIC_API_KEY ~/Secrets/credentials/infrastructure.env 2>/dev/null | head -1")) + if str_len(creds) > 16 { + // creds is like "ANTHROPIC_API_KEY=sk-ant-..." + let eq_pos: Int = str_index_of(creds, "=") + if eq_pos > 0 { + let api_key = str_slice(creds, eq_pos + 1, str_len(creds)) + } + } + } + if str_eq(api_key, "") { + println("[soul-daemon] warning: ANTHROPIC_API_KEY not found — plist will have empty key") + } + + let reg_json: String = fs_read(FORGE_DIR + "/registry.json") + if str_eq(reg_json, "") { + println("[soul-daemon] error: could not read registry.json") + return "" + } + + // --all: install every soul daemon + if str_eq(target, "--all") { + let i: Int = 0 + while i < 50 { + let entry: String = json_get(reg_json, "imprints." + int_to_str(i)) + if str_eq(entry, "") { + let i = i + 50 // break + } else { + let s: String = json_get_string(entry, "slug") + let sub: String = json_get_string(entry, "subject") + let p: Int = json_get_int(entry, "engram_port") + let sf: String = json_get_string(entry, "seed_file") + soul_daemon_install_one(s, p, sub, sf, api_key) + let i = i + 1 + } + } + println("[soul-daemon] all soul daemons installed.") + return "done" + } + + // Single soul + let found: Int = 0 + let i: Int = 0 + while i < 50 { + let entry: String = json_get(reg_json, "imprints." + int_to_str(i)) + if str_eq(entry, "") { + let i = i + 50 // break + } else { + let s: String = json_get_string(entry, "slug") + if str_eq(s, target) { + let sub: String = json_get_string(entry, "subject") + let p: Int = json_get_int(entry, "engram_port") + let sf: String = json_get_string(entry, "seed_file") + soul_daemon_install_one(s, p, sub, sf, api_key) + let found = 1 + let i = i + 50 // break + } else { + let i = i + 1 + } + } + } + if found == 0 { + println("[soul-daemon] error: slug '" + target + "' not found in registry.json") + } + return "" +} + +// soul_daemon_start_main — kickstart a soul daemon: forge soul daemon start +fn soul_daemon_start_main() -> String { + let argv: [String] = args() + // args: [0]="soul" [1]="daemon" [2]="start" [3]= + let target: String = "" + if len(argv) > 3 { let target = get(argv, 3) } + if str_eq(target, "") { + println("[soul-daemon] usage: forge soul daemon start ") + return "" + } + + let uid: String = str_trim(exec("id -u")) + + if str_eq(target, "--all") { + let reg_json: String = fs_read(FORGE_DIR + "/registry.json") + if str_eq(reg_json, "") { println("[soul-daemon] error: cannot read registry.json"); return "" } + let i: Int = 0 + while i < 50 { + let entry: String = json_get(reg_json, "imprints." + int_to_str(i)) + if str_eq(entry, "") { let i = i + 50 } + else { + let s: String = json_get_string(entry, "slug") + let label: String = soul_daemon_plist_label(s) + let out: String = str_trim(exec("launchctl kickstart gui/" + uid + "/" + label + " 2>&1")) + println("[soul-daemon] start " + s + ": " + out) + let i = i + 1 + } + } + return "" + } + + let label: String = soul_daemon_plist_label(target) + let out: String = str_trim(exec("launchctl kickstart gui/" + uid + "/" + label + " 2>&1")) + println("[soul-daemon] start " + target + ": " + out) + return out +} + +// soul_daemon_stop_main — stop a soul daemon: forge soul daemon stop +fn soul_daemon_stop_main() -> String { + let argv: [String] = args() + // args: [0]="soul" [1]="daemon" [2]="stop" [3]= + let target: String = "" + if len(argv) > 3 { let target = get(argv, 3) } + if str_eq(target, "") { + println("[soul-daemon] usage: forge soul daemon stop ") + return "" + } + + if str_eq(target, "--all") { + let reg_json: String = fs_read(FORGE_DIR + "/registry.json") + if str_eq(reg_json, "") { println("[soul-daemon] error: cannot read registry.json"); return "" } + let i: Int = 0 + while i < 50 { + let entry: String = json_get(reg_json, "imprints." + int_to_str(i)) + if str_eq(entry, "") { let i = i + 50 } + else { + let s: String = json_get_string(entry, "slug") + let label: String = soul_daemon_plist_label(s) + let out: String = str_trim(exec("launchctl stop " + label + " 2>&1")) + println("[soul-daemon] stop " + s + ": " + out) + let i = i + 1 + } + } + return "" + } + + let label: String = soul_daemon_plist_label(target) + let out: String = str_trim(exec("launchctl stop " + label + " 2>&1")) + println("[soul-daemon] stop " + target + ": " + out) + return out +} + +// soul_daemon_status_main — health check soul daemons: forge soul daemon status +fn soul_daemon_status_main() -> String { + let reg_json: String = fs_read(FORGE_DIR + "/registry.json") + if str_eq(reg_json, "") { + println("[soul-daemon] error: could not read registry.json") + return "" + } + + println("[soul-daemon] soul daemon status:") + println("") + + let up: Int = 0 + let down: Int = 0 + let i: Int = 0 + while i < 50 { + let entry: String = json_get(reg_json, "imprints." + int_to_str(i)) + if str_eq(entry, "") { + let i = i + 50 // break + } else { + let slug: String = json_get_string(entry, "slug") + let sub: String = json_get_string(entry, "subject") + let engram_p: Int = json_get_int(entry, "engram_port") + let soul_p: Int = engram_p + 100 + let soul_url: String = "http://localhost:" + int_to_str(soul_p) + let resp: String = http_get(soul_url + "/health") + if !str_eq(resp, "") { + println(" [UP ] " + sub + " — " + soul_url) + let up = up + 1 + } else { + // Try /status as fallback + let resp2: String = http_get(soul_url + "/status") + if !str_eq(resp2, "") { + println(" [UP ] " + sub + " — " + soul_url) + let up = up + 1 + } else { + println(" [DOWN] " + sub + " — " + soul_url) + let down = down + 1 + } + } + let i = i + 1 + } + } + + println("") + println("[soul-daemon] up=" + int_to_str(up) + " down=" + int_to_str(down)) + return "" +} + +// soul_daemon_main — dispatch for: forge soul daemon [args] +fn soul_daemon_main() -> String { + let argv: [String] = args() + // args: [0]="soul" [1]="daemon" [2]= + let subcmd: String = "" + if len(argv) > 2 { + let subcmd = get(argv, 2) + } + + if str_eq(subcmd, "install") { + soul_daemon_install_main() + } else { + if str_eq(subcmd, "start") { + soul_daemon_start_main() + } else { + if str_eq(subcmd, "stop") { + soul_daemon_stop_main() + } else { + if str_eq(subcmd, "status") { + soul_daemon_status_main() + } else { + println("[soul-daemon] usage: forge soul daemon [slug|--all]") + println("") + println(" forge soul daemon install — write plist and load soul daemon") + println(" forge soul daemon install --all — install all 26 soul daemons") + println(" forge soul daemon start — kickstart a soul daemon") + println(" forge soul daemon start --all — start all soul daemons") + println(" forge soul daemon stop — stop a soul daemon") + println(" forge soul daemon stop --all — stop all soul daemons") + println(" forge soul daemon status — health check all soul daemons") + } + } + } + } + return "" +} + // ── soul_main — dispatch ───────────────────────────────────────────────────── // // args() layout when invoked as: forge soul [slug] @@ -943,17 +1435,22 @@ fn soul_main() -> String { if str_eq(subcmd, "sandbox") { soul_sandbox_main() } else { - println("[soul] usage: forge soul [slug]") - println("") - println(" forge soul install — write launchd plist, load as resident") - println(" forge soul install --all — install all souls from registry") - println(" forge soul reinstall — wipe DB and reinstall from seed") - println(" forge soul reinstall --all — wipe and reinstall all souls") - println(" forge soul start — kickstart a loaded soul") - println(" forge soul stop — stop a running soul") - println(" forge soul status — health check all souls") - println(" forge soul wire — register all souls as Engram peers") - println(" forge soul sandbox — sandbox management") + if str_eq(subcmd, "daemon") { + soul_daemon_main() + } else { + println("[soul] usage: forge soul [slug]") + println("") + println(" forge soul install — write launchd plist, load as resident") + println(" forge soul install --all — install all souls from registry") + println(" forge soul reinstall — wipe DB and reinstall from seed") + println(" forge soul reinstall --all — wipe and reinstall all souls") + println(" forge soul start — kickstart a loaded soul") + println(" forge soul stop — stop a running soul") + println(" forge soul status — health check all souls") + println(" forge soul wire — register all souls as Engram peers") + println(" forge soul sandbox — sandbox management") + println(" forge soul daemon — soul daemon (neuron binary) management") + } } } }