forge: add soul daemon install/start/stop/status commands

Adds `forge soul daemon <install|start|stop|status>` 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`.
This commit is contained in:
Will Anderson
2026-05-03 18:34:42 -05:00
parent 52a40e7edc
commit cd77ceb68b
+508 -11
View File
@@ -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, "&", "&amp;")
let s2: String = str_replace(s1, "<", "&lt;")
let s3: String = str_replace(s2, ">", "&gt;")
str_replace(s3, "\"", "&quot;")
}
// 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 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" +
"<plist version=\"1.0\">\n" +
"<dict>\n" +
" <key>Label</key><string>" + label + "</string>\n" +
" <key>ProgramArguments</key>\n" +
" <array>\n" +
" <string>" + NEURON_BIN + "</string>\n" +
" </array>\n" +
" <key>EnvironmentVariables</key>\n" +
" <dict>\n" +
" <key>SOUL_CGI_ID</key><string>" + slug + "</string>\n" +
" <key>NEURON_PORT</key><string>" + soul_port + "</string>\n" +
" <key>SOUL_IDENTITY</key><string>" + identity + "</string>\n" +
" <key>SOUL_ENGRAM_PATH</key><string>" + engram_path + "</string>\n" +
" <key>ANTHROPIC_API_KEY</key><string>" + api_key + "</string>\n" +
" </dict>\n" +
" <key>RunAtLoad</key><true/>\n" +
" <key>KeepAlive</key><true/>\n" +
" <key>StandardOutPath</key><string>" + log_file + "</string>\n" +
" <key>StandardErrorPath</key><string>" + log_file + "</string>\n" +
"</dict>\n" +
"</plist>\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 <slug|--all>
fn soul_daemon_install_main() -> String {
let argv: [String] = args()
// args layout: [0]="soul" [1]="daemon" [2]="install" [3]=<slug|--all>
let target: String = ""
if len(argv) > 3 {
let target = get(argv, 3)
}
if str_eq(target, "") {
println("[soul-daemon] usage: forge soul daemon install <slug>")
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 <slug|--all>
fn soul_daemon_start_main() -> String {
let argv: [String] = args()
// args: [0]="soul" [1]="daemon" [2]="start" [3]=<slug|--all>
let target: String = ""
if len(argv) > 3 { let target = get(argv, 3) }
if str_eq(target, "") {
println("[soul-daemon] usage: forge soul daemon start <slug|--all>")
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 <slug|--all>
fn soul_daemon_stop_main() -> String {
let argv: [String] = args()
// args: [0]="soul" [1]="daemon" [2]="stop" [3]=<slug|--all>
let target: String = ""
if len(argv) > 3 { let target = get(argv, 3) }
if str_eq(target, "") {
println("[soul-daemon] usage: forge soul daemon stop <slug|--all>")
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 <subcmd> [args]
fn soul_daemon_main() -> String {
let argv: [String] = args()
// args: [0]="soul" [1]="daemon" [2]=<subcmd>
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 <install|start|stop|status> [slug|--all]")
println("")
println(" forge soul daemon install <slug> — write plist and load soul daemon")
println(" forge soul daemon install --all — install all 26 soul daemons")
println(" forge soul daemon start <slug> — kickstart a soul daemon")
println(" forge soul daemon start --all — start all soul daemons")
println(" forge soul daemon stop <slug> — 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 <subcmd> [slug]
@@ -943,17 +1435,22 @@ fn soul_main() -> String {
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")
if str_eq(subcmd, "daemon") {
soul_daemon_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")
println(" forge soul daemon <subcmd> — soul daemon (neuron binary) management")
}
}
}
}