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:
+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
|
||||
}
|
||||
Reference in New Issue
Block a user