diff --git a/src/install.el b/src/install.el index 9e48982..d613499 100644 --- a/src/install.el +++ b/src/install.el @@ -7,16 +7,14 @@ // // 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 as a launchd agent before -// installing. For reinstall: forge soul reinstall +// slug field. If the soul isn't registered yet, it auto-registers with the next +// available port (starting at 8821) and runs soul_install_one() to provision +// the launchd agent — no pre-flight required. // // Depends on: schema.el (engram_key, FORGE_VERSION) // // Environment: -// ENGRAM_API_KEY — engram auth key override (if set) +// FORCE_INSTALL — if set, reinstall even if engram_root_id already exists // ── Helpers ─────────────────────────────────────────────────────────────────── @@ -40,9 +38,9 @@ fn build_edge_body(from_id: String, to_id: String, relation: String, weight: Str // 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) +// auth_key is passed explicitly (read from registry entry, not env). +fn post_node(target_url: String, content: String, node_type: String, salience: String, auth_key: String) -> String { + let body: String = build_node_body(content, node_type, salience, auth_key) let url: String = target_url + "/nodes" let response: String = http_post_json(url, body) if str_eq(response, "") { return "" } @@ -51,9 +49,9 @@ fn post_node(target_url: String, content: String, node_type: String, salience: S // post_edge — connect two nodes with a typed, weighted edge. // 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) +// auth_key is passed explicitly (read from registry entry, not env). +fn post_edge(target_url: String, from_id: String, to_id: String, relation: String, weight: String, auth_key: String) -> String { + let body: String = build_edge_body(from_id, to_id, relation, weight, auth_key) let url: String = target_url + "/edges" let response: String = http_post_json(url, body) return response @@ -69,7 +67,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(target_url: String, root_id: String, value_json: String, index: Int) -> String { +fn install_value(target_url: String, root_id: String, value_json: String, index: Int, auth_key: String) -> 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") @@ -82,19 +80,19 @@ fn install_value(target_url: String, root_id: String, value_json: String, index: let content = content + " | grounding: " + grounding } - let node_id: String = post_node(target_url, content, "Identity", weight) + let node_id: String = post_node(target_url, content, "Identity", weight, auth_key) if str_eq(node_id, "") { println("[forge] warning: failed to create value node " + int_to_str(index)) return "" } - post_edge(target_url, root_id, node_id, "has_value", weight) + post_edge(target_url, root_id, node_id, "has_value", weight, auth_key) 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(target_url: String, root_id: String, bio_json: String, index: Int) -> String { +fn install_biography_node(target_url: String, root_id: String, bio_json: String, index: Int, auth_key: String) -> 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") @@ -102,19 +100,19 @@ fn install_biography_node(target_url: String, root_id: String, bio_json: String, if str_eq(event_text, "") { return "" } let content: String = "BIOGRAPHY: " + event_text - let node_id: String = post_node(target_url, content, "Identity", weight) + let node_id: String = post_node(target_url, content, "Identity", weight, auth_key) if str_eq(node_id, "") { println("[forge] warning: failed to create biography node " + int_to_str(index)) return "" } - post_edge(target_url, root_id, node_id, "formed_by", weight) + post_edge(target_url, root_id, node_id, "formed_by", weight, auth_key) println("[forge] biography node: " + node_id + " — " + event_text) return node_id } // install_relationship — create a Relationship node and connect to root. -fn install_relationship(target_url: String, root_id: String, rel_json: String, index: Int) -> String { +fn install_relationship(target_url: String, root_id: String, rel_json: String, index: Int, auth_key: String) -> 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") @@ -127,37 +125,118 @@ fn install_relationship(target_url: String, root_id: String, rel_json: String, i let content = content + " (" + role + ")" } - let node_id: String = post_node(target_url, content, "Identity", weight) + let node_id: String = post_node(target_url, content, "Identity", weight, auth_key) if str_eq(node_id, "") { println("[forge] warning: failed to create relationship node " + int_to_str(index)) return "" } - post_edge(target_url, root_id, node_id, "relates_to", weight) + post_edge(target_url, root_id, node_id, "relates_to", weight, auth_key) 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(target_url: String, root_id: String, pattern: String, index: Int) -> String { +fn install_reasoning_pattern(target_url: String, root_id: String, pattern: String, index: Int, auth_key: String) -> String { if str_eq(pattern, "") { return "" } let content: String = "REASONING: " + pattern - let node_id: String = post_node(target_url, content, "Identity", "0.7") + let node_id: String = post_node(target_url, content, "Identity", "0.7", auth_key) if str_eq(node_id, "") { println("[forge] warning: failed to create reasoning node " + int_to_str(index)) return "" } - post_edge(target_url, root_id, node_id, "reasons_with", "0.7") + post_edge(target_url, root_id, node_id, "reasons_with", "0.7", auth_key) println("[forge] reasoning node: " + node_id) return node_id } +// ── Registry helpers ────────────────────────────────────────────────────────── + +// find_max_port — scan registry and return the highest engram_port found. +// Returns 8820 (the last known used port) if the registry is empty or has no ports. +fn find_max_port(reg_json: String) -> Int { + let max_port: Int = 8820 + let i: Int = 0 + while i < 50 { + let entry: String = json_get(reg_json, "imprints." + int_to_str(i)) + if str_eq(entry, "") { + return max_port + } + let port: Int = json_get_int(entry, "engram_port") + if port > max_port { + let max_port = port + } + let i = i + 1 + } + return max_port +} + +// registry_find_by_slug — find a registry entry by slug field. +// Returns the LAST matching entry (most recent install wins) so that a +// fully-installed entry appended after an initial stub takes precedence. +fn registry_find_by_slug(reg_json: String, slug: String) -> String { + let last_match: String = "" + let i: Int = 0 + while i < 50 { + let entry: String = json_get(reg_json, "imprints." + int_to_str(i)) + if str_eq(entry, "") { + return last_match + } + let entry_slug: String = json_get_string(entry, "slug") + if str_eq(entry_slug, slug) { + let last_match = entry + } + let i = i + 1 + } + return last_match +} + +// registry_append — append new_entry JSON to registry.json. +// Reads the current file, inserts before the closing ] of the imprints array. +fn registry_append(new_entry: String) -> Void { + let existing_reg: String = fs_read(FORGE_DIR + "/registry.json") + if str_eq(existing_reg, "") { + let fresh_reg: String = "{\"version\":\"1.0\",\"imprints\":[" + new_entry + "]}" + fs_write(FORGE_DIR + "/registry.json", fresh_reg) + } else { + 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)) + 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(FORGE_DIR + "/registry.json", updated_reg) + } + } +} + +// registry_update_root_id — append a fully-installed entry for the slug. +// Because El has no in-place JSON mutation, we append a new entry with the +// updated root_id. Most tools (registry_find_by_slug, soul tools) scan from +// the start and break on first match — the original stub entry is effectively +// shadowed by the new fully-installed entry appended at the end. +fn registry_update_root_id(subject: String, slug: String, root_id: String, seed_file: String, target_url: String, api_key: String, port: Int) -> Void { + let new_entry: String = "{\"subject\":\"" + str_escape_json(subject) + + "\",\"slug\":\"" + slug + + "\",\"seed_file\":\"" + str_escape_json(seed_file) + + "\",\"engram_db_path\":\"imprints/" + slug + + "\",\"engram_port\":" + int_to_str(port) + + ",\"engram_url\":\"" + target_url + + "\",\"engram_root_id\":\"" + root_id + + "\",\"installed\":true,\"installed_at\":\"2026-05-03\"" + + ",\"engram_api_key\":\"" + api_key + "\"}" + registry_append(new_entry) +} + // ── Main ────────────────────────────────────────────────────────────────────── fn install_main() -> String { - // Get seed file path + // ── Step 1: Parse args ──────────────────────────────────────────────────── let argv: [String] = args() let seed_file: String = "" if len(argv) > 1 { @@ -170,7 +249,7 @@ fn install_main() -> String { println("[forge] installing from: " + seed_file) - // Read seed + // ── Step 2: Read seed ───────────────────────────────────────────────────── let seed_json: String = fs_read(seed_file) if str_eq(seed_json, "") { println("[forge] error: could not read " + seed_file) @@ -179,166 +258,237 @@ fn install_main() -> String { let subject: String = json_get_string(seed_json, "subject") if str_eq(subject, "") { let subject = "unknown" } - println("[forge] subject: " + subject) - // ── 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, "") { - let force: String = env("FORCE_INSTALL") - if str_eq(force, "") { - println("[forge] already installed — root: " + reg_root) - println("[forge] to reinstall: forge soul reinstall " + str_lower(str_replace(subject, " ", "-"))) - return reg_root - } - println("[forge] force reinstall — ignoring existing root: " + reg_root) - } - let reg_url: String = json_get_string(reg_entry, "engram_url") - if !str_eq(reg_url, "") { - let target_url = reg_url - } - let ri = ri + 30 // break after match - } else { - let ri = ri + 1 - } - } - } - } + // ── Step 3: Derive slug ─────────────────────────────────────────────────── + let slug_raw: String = str_to_lower(subject) + let slug: String = str_replace(slug_raw, " ", "-") + println("[forge] slug: " + slug) - // 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 + // ── Step 4: Resolve or create registry entry ────────────────────────────── + let reg_json: String = fs_read(FORGE_DIR + "/registry.json") + let target_url: String = "" + let api_key: String = "ntn-" + slug + "-2026" + let reg_port: Int = 0 + let entry_root_id: String = "" + + if !str_eq(reg_json, "") { + let found_entry: String = registry_find_by_slug(reg_json, slug) + + if !str_eq(found_entry, "") { + // Slug found in registry + let reg_url: String = json_get_string(found_entry, "engram_url") + let reg_key: String = json_get_string(found_entry, "engram_api_key") + let reg_root: String = json_get_string(found_entry, "engram_root_id") + let reg_p: Int = json_get_int(found_entry, "engram_port") + + if !str_eq(reg_url, "") { let target_url = reg_url } + if !str_eq(reg_key, "") { let api_key = reg_key } + if reg_p > 0 { let reg_port = reg_p } + let entry_root_id = reg_root + + // Already fully installed? + if !str_eq(reg_root, "") { + let force: String = env("FORCE_INSTALL") + if str_eq(force, "") { + println("[forge] already installed — root: " + reg_root) + println("[forge] to reinstall: forge soul reinstall " + slug) + return reg_root + } + println("[forge] force reinstall — ignoring existing root: " + reg_root) + } + println("[forge] found in registry: " + target_url) + } else { + // Slug NOT found — auto-register with next available port + let max_port: Int = find_max_port(reg_json) + let new_port: Int = max_port + 1 + let new_url: String = "http://localhost:" + int_to_str(new_port) + let new_entry: String = "{\"subject\":\"" + str_escape_json(subject) + + "\",\"slug\":\"" + slug + + "\",\"seed_file\":\"" + str_escape_json(seed_file) + + "\",\"engram_db_path\":\"imprints/" + slug + + "\",\"engram_port\":" + int_to_str(new_port) + + ",\"engram_url\":\"" + new_url + + "\",\"engram_root_id\":\"\"" + + ",\"installed\":false,\"installed_at\":\"\"" + + ",\"engram_api_key\":\"" + api_key + "\"}" + + println("[forge] new soul — assigning port " + int_to_str(new_port)) + registry_append(new_entry) + exec("mkdir -p " + FORGE_DIR + "/imprints/" + slug + " 2>/dev/null") + + let target_url = new_url + let reg_port = new_port } + } else { + // No registry at all — start fresh at port 8821 + let new_port: Int = 8821 + let new_url: String = "http://localhost:" + int_to_str(new_port) + let new_entry: String = "{\"subject\":\"" + str_escape_json(subject) + + "\",\"slug\":\"" + slug + + "\",\"seed_file\":\"" + str_escape_json(seed_file) + + "\",\"engram_db_path\":\"imprints/" + slug + + "\",\"engram_port\":" + int_to_str(new_port) + + ",\"engram_url\":\"" + new_url + + "\",\"engram_root_id\":\"\"" + + ",\"installed\":false,\"installed_at\":\"\"" + + ",\"engram_api_key\":\"" + api_key + "\"}" + + println("[forge] new registry — assigning port " + int_to_str(new_port)) + let fresh_reg: String = "{\"version\":\"1.0\",\"imprints\":[" + new_entry + "]}" + fs_write(FORGE_DIR + "/registry.json", fresh_reg) + exec("mkdir -p " + FORGE_DIR + "/imprints/" + slug + " 2>/dev/null") + + let target_url = new_url + let reg_port = new_port } // 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 forge soul reinstall <slug>") + println("[forge] error: no engram_url could be resolved for '" + slug + "'") return "" } if str_contains(target_url, ":8742") { println("[forge] error: refusing to install soul into Neuron's Engram (port 8742)") - println("[forge] start the DHARMA network first: ./forge soul start <slug>") - println("[forge] then retry. Each soul needs their own Engram instance.") return "" } + // ── Step 5: Provision the launchd agent (soul_install_one) ─────────────── + // Extract port string from target_url + let colon_pos: Int = str_last_index_of(target_url, ":") + let port_str: String = "" + if colon_pos > 0 { + let port_str = str_slice(target_url, colon_pos + 1, str_len(target_url)) + } + if str_eq(port_str, "") { + if reg_port > 0 { let port_str = int_to_str(reg_port) } + } + + if !str_eq(port_str, "") { + println("[forge] provisioning launchd agent for " + slug + "...") + soul_install_one(slug, port_str, target_url, subject) + } else { + println("[forge] warning: could not determine port for soul agent, skipping launchd setup") + } + + // ── Step 6: Wait for Engram to be healthy ───────────────────────────────── + println("[forge] waiting for engram at " + target_url + "...") + let ready: Bool = false + let attempts: Int = 0 + while !ready && attempts < 15 { + exec("sleep 1") + let stats: String = http_get(target_url + "/stats") + if !str_eq(stats, "") && !str_starts_with(stats, "{\"error\"") { + let ready = true + } + let attempts = attempts + 1 + } + if !ready { + println("[forge] warning: engram did not respond after 15s, attempting install anyway") + } else { + println("[forge] engram ready.") + } + println("[forge] engram: " + target_url) println("[forge] opening channel...") - // Create root imprint node — this is the named traversal entry point + // ── Step 7: Create root imprint node ───────────────────────────────────── let root_content: String = "IMPRINT: " + subject + " | forge/" + FORGE_VERSION - let root_id: String = post_node(target_url, root_content, "Identity", "1.0") + let root_id: String = post_node(target_url, root_content, "Identity", "1.0", api_key) if str_eq(root_id, "") { println("[forge] error: failed to create root imprint node") println("[forge] is the soul's Engram running at " + target_url + " ?") - println("[forge] start it with: ./forge soul start <slug>") return "" } println("[forge] root imprint node: " + root_id) - // ── Install values ──────────────────────────────────────────────────────── - // Use json_get() with dot-path notation for array access — json_get_raw() - // uses flat object key lookup and cannot index into JSON arrays. + // ── Step 8: Install values ──────────────────────────────────────────────── let values_raw: String = json_get_raw(seed_json, "values") if !str_eq(values_raw, "") { println("[forge] installing values...") let v0: String = json_get(seed_json, "values.0") - if !str_eq(v0, "") { install_value(target_url, root_id, v0, 0) } + if !str_eq(v0, "") { install_value(target_url, root_id, v0, 0, api_key) } let v1: String = json_get(seed_json, "values.1") - if !str_eq(v1, "") { install_value(target_url, root_id, v1, 1) } + if !str_eq(v1, "") { install_value(target_url, root_id, v1, 1, api_key) } let v2: String = json_get(seed_json, "values.2") - if !str_eq(v2, "") { install_value(target_url, root_id, v2, 2) } + if !str_eq(v2, "") { install_value(target_url, root_id, v2, 2, api_key) } let v3: String = json_get(seed_json, "values.3") - if !str_eq(v3, "") { install_value(target_url, root_id, v3, 3) } + if !str_eq(v3, "") { install_value(target_url, root_id, v3, 3, api_key) } let v4: String = json_get(seed_json, "values.4") - if !str_eq(v4, "") { install_value(target_url, root_id, v4, 4) } + if !str_eq(v4, "") { install_value(target_url, root_id, v4, 4, api_key) } let v5: String = json_get(seed_json, "values.5") - if !str_eq(v5, "") { install_value(target_url, root_id, v5, 5) } + if !str_eq(v5, "") { install_value(target_url, root_id, v5, 5, api_key) } let v6: String = json_get(seed_json, "values.6") - if !str_eq(v6, "") { install_value(target_url, root_id, v6, 6) } + if !str_eq(v6, "") { install_value(target_url, root_id, v6, 6, api_key) } let v7: String = json_get(seed_json, "values.7") - if !str_eq(v7, "") { install_value(target_url, root_id, v7, 7) } + if !str_eq(v7, "") { install_value(target_url, root_id, v7, 7, api_key) } let v8: String = json_get(seed_json, "values.8") - if !str_eq(v8, "") { install_value(target_url, root_id, v8, 8) } + if !str_eq(v8, "") { install_value(target_url, root_id, v8, 8, api_key) } let v9: String = json_get(seed_json, "values.9") - if !str_eq(v9, "") { install_value(target_url, root_id, v9, 9) } + if !str_eq(v9, "") { install_value(target_url, root_id, v9, 9, api_key) } } - // ── Install biography ───────────────────────────────────────────────────── + // ── Step 9: Install biography ───────────────────────────────────────────── let bio_raw: String = json_get_raw(seed_json, "biography") 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(target_url, root_id, b0, 0) } + if !str_eq(b0, "") { install_biography_node(target_url, root_id, b0, 0, api_key) } let b1: String = json_get(seed_json, "biography.1") - if !str_eq(b1, "") { install_biography_node(target_url, root_id, b1, 1) } + if !str_eq(b1, "") { install_biography_node(target_url, root_id, b1, 1, api_key) } let b2: String = json_get(seed_json, "biography.2") - if !str_eq(b2, "") { install_biography_node(target_url, root_id, b2, 2) } + if !str_eq(b2, "") { install_biography_node(target_url, root_id, b2, 2, api_key) } let b3: String = json_get(seed_json, "biography.3") - if !str_eq(b3, "") { install_biography_node(target_url, root_id, b3, 3) } + if !str_eq(b3, "") { install_biography_node(target_url, root_id, b3, 3, api_key) } let b4: String = json_get(seed_json, "biography.4") - if !str_eq(b4, "") { install_biography_node(target_url, root_id, b4, 4) } + if !str_eq(b4, "") { install_biography_node(target_url, root_id, b4, 4, api_key) } let b5: String = json_get(seed_json, "biography.5") - if !str_eq(b5, "") { install_biography_node(target_url, root_id, b5, 5) } + if !str_eq(b5, "") { install_biography_node(target_url, root_id, b5, 5, api_key) } let b6: String = json_get(seed_json, "biography.6") - if !str_eq(b6, "") { install_biography_node(target_url, root_id, b6, 6) } + if !str_eq(b6, "") { install_biography_node(target_url, root_id, b6, 6, api_key) } let b7: String = json_get(seed_json, "biography.7") - if !str_eq(b7, "") { install_biography_node(target_url, root_id, b7, 7) } + if !str_eq(b7, "") { install_biography_node(target_url, root_id, b7, 7, api_key) } } - // ── Install relationships ───────────────────────────────────────────────── + // ── Step 10: Install relationships ──────────────────────────────────────── let rel_raw: String = json_get_raw(seed_json, "relationships") 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(target_url, root_id, r0, 0) } + if !str_eq(r0, "") { install_relationship(target_url, root_id, r0, 0, api_key) } let r1: String = json_get(seed_json, "relationships.1") - if !str_eq(r1, "") { install_relationship(target_url, root_id, r1, 1) } + if !str_eq(r1, "") { install_relationship(target_url, root_id, r1, 1, api_key) } let r2: String = json_get(seed_json, "relationships.2") - if !str_eq(r2, "") { install_relationship(target_url, root_id, r2, 2) } + if !str_eq(r2, "") { install_relationship(target_url, root_id, r2, 2, api_key) } let r3: String = json_get(seed_json, "relationships.3") - if !str_eq(r3, "") { install_relationship(target_url, root_id, r3, 3) } + if !str_eq(r3, "") { install_relationship(target_url, root_id, r3, 3, api_key) } let r4: String = json_get(seed_json, "relationships.4") - if !str_eq(r4, "") { install_relationship(target_url, root_id, r4, 4) } + if !str_eq(r4, "") { install_relationship(target_url, root_id, r4, 4, api_key) } let r5: String = json_get(seed_json, "relationships.5") - if !str_eq(r5, "") { install_relationship(target_url, root_id, r5, 5) } + if !str_eq(r5, "") { install_relationship(target_url, root_id, r5, 5, api_key) } } - // ── Install reasoning patterns ──────────────────────────────────────────── + // ── Step 11: Install reasoning patterns ─────────────────────────────────── let reasoning_raw: String = json_get_raw(seed_json, "reasoning_patterns") 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(target_url, root_id, rp0, 0) } + if !str_eq(rp0, "") { install_reasoning_pattern(target_url, root_id, rp0, 0, api_key) } let rp1: String = json_get(seed_json, "reasoning_patterns.1") - if !str_eq(rp1, "") { install_reasoning_pattern(target_url, root_id, rp1, 1) } + if !str_eq(rp1, "") { install_reasoning_pattern(target_url, root_id, rp1, 1, api_key) } let rp2: String = json_get(seed_json, "reasoning_patterns.2") - if !str_eq(rp2, "") { install_reasoning_pattern(target_url, root_id, rp2, 2) } + if !str_eq(rp2, "") { install_reasoning_pattern(target_url, root_id, rp2, 2, api_key) } let rp3: String = json_get(seed_json, "reasoning_patterns.3") - if !str_eq(rp3, "") { install_reasoning_pattern(target_url, root_id, rp3, 3) } + if !str_eq(rp3, "") { install_reasoning_pattern(target_url, root_id, rp3, 3, api_key) } let rp4: String = json_get(seed_json, "reasoning_patterns.4") - if !str_eq(rp4, "") { install_reasoning_pattern(target_url, root_id, rp4, 4) } + if !str_eq(rp4, "") { install_reasoning_pattern(target_url, root_id, rp4, 4, api_key) } } + // ── Step 12: Write engram_root_id back to registry ──────────────────────── + registry_update_root_id(subject, slug, root_id, seed_file, target_url, api_key, reg_port) + println("[forge] registry: updated " + slug + " with root_id") + // ── Done ────────────────────────────────────────────────────────────────── println("") println("[forge] channel open.") @@ -347,57 +497,5 @@ fn install_main() -> String { 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) - } - } - - // ── Soul registration ───────────────────────────────────────────────────── - // Extract port from target_url (last segment after ":") - // e.g. "http://localhost:8806" → "8806" - let colon_pos: Int = str_last_index_of(target_url, ":") - let port_str: String = "" - if colon_pos > 0 { - let port_str = str_slice(target_url, colon_pos + 1, str_len(target_url)) - } - if !str_eq(port_str, "") { - println("[forge] registering soul as launchd agent...") - let db_full_path: String = FORGE_DIR + "/imprints/" + slug - soul_install_one(slug, port_str, target_url, subject) - } else { - println("[forge] note: could not parse port from " + target_url + ", skipping soul install") - } - return root_id }