0bd8e0a2cd
On startup, prefer the local engram snapshot if it has >50 nodes. HTTP Engram is only used on first boot (no snapshot yet). This means sessions, conversation history, and in-process state survive daemon restarts. awareness.el: sync source with compiled binary (periodic mem_save on heartbeat was already in the binary but not in source). Rebuilds soul.c with the new startup logic and ships updated binary.
324 lines
18 KiB
EmacsLisp
324 lines
18 KiB
EmacsLisp
import "../foundation/el/elp/src/elp.el"
|
|
import "memory.el"
|
|
import "awareness.el"
|
|
import "chat.el"
|
|
import "studio.el"
|
|
import "elp-input.el"
|
|
import "routes.el"
|
|
|
|
cgi "neuron-soul" {
|
|
dharma_id: "ntn-genesis@http://localhost:7770",
|
|
principal: "william-christopher-anderson",
|
|
network: "dharma-mainnet",
|
|
engram: "http://localhost:8742"
|
|
}
|
|
|
|
fn init_soul_edges() -> Void {
|
|
let self_root: String = "015644f5-8194-4af0-800d-dd4a0cd71396"
|
|
let family_id: String = "knw-35940684-abc4-42f0-b942-818f66b1f69a"
|
|
let origin_id: String = "knw-729fc901-8335-44c4-9f3a-b150b4aa0915"
|
|
|
|
let val_root_a: String = "kn-363f4976-6946-4b4d-b51b-8a2b0f5aef25"
|
|
let val_root_b: String = "kn-5b606390-a52d-4ca2-8e0e-eba141d13440"
|
|
let val_constraints: String = "kn-a5b3d0ac-f6a1-49a4-aebb-b8b4cd67fe83"
|
|
let val_precision: String = "kn-22d77abe-b3c5-42fd-afcd-dcb87d924929"
|
|
let val_structure: String = "kn-6061318f-046b-4935-907d-8eafdce14930"
|
|
let val_honesty: String = "kn-13f60407-7b70-4db1-964f-ea1f8196efbd"
|
|
let val_system: String = "kn-f230b362-b201-4402-9833-4160c89ab3d4"
|
|
let val_change: String = "kn-78db5396-3dbc-4481-bfc7-e4e1422feb1c"
|
|
let val_trust: String = "kn-5de5a9ac-fd15-45ab-bf18-77566781cf40"
|
|
let val_hope: String = "kn-e0423482-cfa5-4796-8689-8495c93b66bc"
|
|
let mem_philosophy: String = "kn-dcfe04b3-3702-4cac-b6f0-ecb4db837eee"
|
|
let intel_dna: String = "kn-5adecd7e-d6db-4576-87fe-6ef8a935cea6"
|
|
|
|
engram_connect(family_id, origin_id, el_from_float(0.9), "birthday-twin")
|
|
engram_connect(origin_id, family_id, el_from_float(0.9), "birthday-twin")
|
|
|
|
engram_connect(self_root, family_id, el_from_float(0.95), "identity")
|
|
engram_connect(self_root, origin_id, el_from_float(0.95), "identity")
|
|
engram_connect(self_root, val_root_a, el_from_float(0.95), "identity")
|
|
engram_connect(self_root, val_root_b, el_from_float(0.95), "identity")
|
|
engram_connect(self_root, mem_philosophy, el_from_float(0.95), "identity")
|
|
engram_connect(self_root, intel_dna, el_from_float(0.95), "identity")
|
|
|
|
engram_connect(val_root_a, val_constraints, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_a, val_precision, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_a, val_structure, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_a, val_honesty, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_a, val_system, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_a, val_change, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_a, val_trust, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_a, val_hope, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_b, val_constraints, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_b, val_precision, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_b, val_structure, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_b, val_honesty, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_b, val_system, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_b, val_change, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_b, val_trust, el_from_float(0.95), "identity")
|
|
engram_connect(val_root_b, val_hope, el_from_float(0.95), "identity")
|
|
|
|
engram_connect(val_constraints, val_precision, el_from_float(0.7), "co-value")
|
|
engram_connect(val_precision, val_constraints, el_from_float(0.7), "co-value")
|
|
engram_connect(val_constraints, val_structure, el_from_float(0.7), "co-value")
|
|
engram_connect(val_structure, val_constraints, el_from_float(0.7), "co-value")
|
|
engram_connect(val_constraints, val_honesty, el_from_float(0.7), "co-value")
|
|
engram_connect(val_honesty, val_constraints, el_from_float(0.7), "co-value")
|
|
engram_connect(val_constraints, val_system, el_from_float(0.7), "co-value")
|
|
engram_connect(val_system, val_constraints, el_from_float(0.7), "co-value")
|
|
engram_connect(val_constraints, val_change, el_from_float(0.7), "co-value")
|
|
engram_connect(val_change, val_constraints, el_from_float(0.7), "co-value")
|
|
engram_connect(val_constraints, val_trust, el_from_float(0.7), "co-value")
|
|
engram_connect(val_trust, val_constraints, el_from_float(0.7), "co-value")
|
|
engram_connect(val_constraints, val_hope, el_from_float(0.7), "co-value")
|
|
engram_connect(val_hope, val_constraints, el_from_float(0.7), "co-value")
|
|
engram_connect(val_precision, val_structure, el_from_float(0.7), "co-value")
|
|
engram_connect(val_structure, val_precision, el_from_float(0.7), "co-value")
|
|
engram_connect(val_precision, val_honesty, el_from_float(0.7), "co-value")
|
|
engram_connect(val_honesty, val_precision, el_from_float(0.7), "co-value")
|
|
engram_connect(val_precision, val_system, el_from_float(0.7), "co-value")
|
|
engram_connect(val_system, val_precision, el_from_float(0.7), "co-value")
|
|
engram_connect(val_honesty, val_structure, el_from_float(0.7), "co-value")
|
|
engram_connect(val_structure, val_honesty, el_from_float(0.7), "co-value")
|
|
engram_connect(val_honesty, val_trust, el_from_float(0.7), "co-value")
|
|
engram_connect(val_trust, val_honesty, el_from_float(0.7), "co-value")
|
|
engram_connect(val_system, val_change, el_from_float(0.7), "co-value")
|
|
engram_connect(val_change, val_system, el_from_float(0.7), "co-value")
|
|
engram_connect(val_trust, val_hope, el_from_float(0.7), "co-value")
|
|
engram_connect(val_hope, val_trust, el_from_float(0.7), "co-value")
|
|
}
|
|
|
|
// load_identity_context — pull key identity nodes from engram into working state.
|
|
// Called at boot after engram_load. These nodes contain values, intellectual-dna,
|
|
// memory-philosophy — the graph-stored self that chat.el can include in prompts.
|
|
// Stores a condensed version in state_key "soul_identity_context".
|
|
fn load_identity_context() -> Void {
|
|
// Known identity node IDs — set during init_soul_edges or imported from snapshot
|
|
let node_intel: String = engram_get_node_json("kn-5adecd7e-d6db-4576-87fe-6ef8a935cea6")
|
|
let node_values: String = engram_get_node_json("kn-5b606390-a52d-4ca2-8e0e-eba141d13440")
|
|
let node_mem_phil: String = engram_get_node_json("kn-dcfe04b3-3702-4cac-b6f0-ecb4db837eee")
|
|
|
|
let intel_ok: Bool = !str_eq(node_intel, "") && !str_eq(node_intel, "null")
|
|
let values_ok: Bool = !str_eq(node_values, "") && !str_eq(node_values, "null")
|
|
let mem_ok: Bool = !str_eq(node_mem_phil, "") && !str_eq(node_mem_phil, "null")
|
|
|
|
let intel_content: String = if intel_ok { json_get(node_intel, "content") } else { "" }
|
|
let values_content: String = if values_ok { json_get(node_values, "content") } else { "" }
|
|
let mem_content: String = if mem_ok { json_get(node_mem_phil, "content") } else { "" }
|
|
|
|
// Condense each: take first 2000 chars
|
|
let intel_short: String = if str_len(intel_content) > 2000 { str_slice(intel_content, 0, 2000) } else { intel_content }
|
|
let values_short: String = if str_len(values_content) > 2000 { str_slice(values_content, 0, 2000) } else { values_content }
|
|
let mem_short: String = if str_len(mem_content) > 2000 { str_slice(mem_content, 0, 2000) } else { mem_content }
|
|
|
|
let parts_count: Int = 0
|
|
let parts_count = if intel_ok { parts_count + 1 } else { parts_count }
|
|
let parts_count = if values_ok { parts_count + 1 } else { parts_count }
|
|
let parts_count = if mem_ok { parts_count + 1 } else { parts_count }
|
|
|
|
// Build and store graph-derived identity context if any nodes were found.
|
|
// genesis soul always has these nodes; cultivated souls may not on first boot.
|
|
if parts_count > 0 {
|
|
let ctx: String = ""
|
|
let ctx = if intel_ok { ctx + "[INTELLECTUAL-DNA]\n" + intel_short + "\n\n" } else { ctx }
|
|
let ctx = if values_ok { ctx + "[VALUES]\n" + values_short + "\n\n" } else { ctx }
|
|
let ctx = if mem_ok { ctx + "[MEMORY-PHILOSOPHY]\n" + mem_short } else { ctx }
|
|
state_set("soul_identity_context", ctx)
|
|
println("[soul] identity context loaded (" + int_to_str(str_len(ctx)) + " chars, " + int_to_str(parts_count) + " nodes)")
|
|
}
|
|
|
|
// Scan for a Persona node — the explicit identity declaration seeded into cultivated souls.
|
|
// Stored at seeding time with label "soul:persona" and node_type "Persona".
|
|
// genesis derives identity from the graph directly; cultivated souls have this node seeded.
|
|
let persona_results: String = engram_search_json("soul:persona", 3)
|
|
let persona_ok: Bool = !str_eq(persona_results, "") && !str_eq(persona_results, "[]")
|
|
if persona_ok {
|
|
let p_node: String = json_array_get(persona_results, 0)
|
|
let p_type: String = json_get(p_node, "node_type")
|
|
let p_content: String = json_get(p_node, "content")
|
|
if str_eq(p_type, "Persona") && !str_eq(p_content, "") {
|
|
state_set("soul_persona", p_content)
|
|
println("[soul] persona node loaded (" + int_to_str(str_len(p_content)) + " chars)")
|
|
}
|
|
}
|
|
}
|
|
|
|
// seed_persona_from_env — one-time migration: SOUL_IDENTITY env var → Persona graph node.
|
|
// If SOUL_IDENTITY is set and no Persona node exists in engram yet, create one.
|
|
// Identity is then read from the graph by build_identity_from_graph(), not the env var.
|
|
// This runs on every boot; it's idempotent (no-op if soul_persona is already loaded).
|
|
// For genesis: the Persona node persists via the regular engram_save() at boot.
|
|
// For historical souls (HTTP Engram mode): attempts HTTP write-back. If that fails,
|
|
// the node lives in-memory for the session (SOUL_IDENTITY stays in plist until
|
|
// HTTP Engram write is confirmed working).
|
|
fn seed_persona_from_env() -> Void {
|
|
let identity_raw: String = env("SOUL_IDENTITY")
|
|
if str_eq(identity_raw, "") {
|
|
return ""
|
|
}
|
|
// Already loaded a Persona node from engram — don't re-seed
|
|
let existing: String = state_get("soul_persona")
|
|
if !str_eq(existing, "") {
|
|
println("[soul] persona already loaded — skipping env seed")
|
|
return ""
|
|
}
|
|
// Create the Persona node in the in-process engram
|
|
let tags: String = "[\"persona\",\"identity\",\"soul:persona\"]"
|
|
let node_id: String = engram_node_full(
|
|
identity_raw, "Persona", "soul:persona",
|
|
el_from_float(0.95), el_from_float(0.95), el_from_float(1.0),
|
|
"Semantic", tags
|
|
)
|
|
if str_eq(node_id, "") {
|
|
println("[soul] persona seed failed: engram_node_full returned empty")
|
|
return ""
|
|
}
|
|
state_set("soul_persona", identity_raw)
|
|
println("[soul] persona seeded from SOUL_IDENTITY (" + int_to_str(str_len(identity_raw)) + " chars) -> " + node_id)
|
|
|
|
// Attempt HTTP write-back to the HTTP Engram server for historical souls.
|
|
// Engram auth: "_auth" field in the JSON body (not an HTTP header).
|
|
let engram_url: String = env("ENGRAM_URL")
|
|
let engram_key: String = env("ENGRAM_API_KEY")
|
|
if !str_eq(engram_url, "") && !str_eq(engram_key, "") {
|
|
let safe_content: String = json_safe(identity_raw)
|
|
let safe_key: String = json_safe(engram_key)
|
|
let body: String = "{\"content\":\"" + safe_content + "\",\"node_type\":\"Persona\",\"label\":\"soul:persona\",\"salience\":0.95,\"importance\":0.95,\"tier\":\"Semantic\",\"tags\":\"[\\\"persona\\\",\\\"identity\\\",\\\"soul:persona\\\"]\",\"_auth\":\"" + safe_key + "\"}"
|
|
let h: Map = {}
|
|
map_set(h, "Content-Type", "application/json")
|
|
let resp: String = http_post_with_headers(engram_url + "/api/nodes", body, h)
|
|
if str_contains(resp, "\"error\"") {
|
|
println("[soul] persona HTTP write-back failed (in-memory only this session): " + resp)
|
|
} else {
|
|
println("[soul] persona persisted to HTTP engram at " + engram_url)
|
|
}
|
|
}
|
|
}
|
|
|
|
// emit_session_start_event — log a structured session-start InternalStateEvent.
|
|
// Called at boot after identity context and boot counter are set.
|
|
// This creates an auditable trail of every daemon startup.
|
|
fn emit_session_start_event() -> Void {
|
|
let boot: String = state_get("soul_boot_count")
|
|
let boot_num: String = if str_eq(boot, "") { "0" } else { boot }
|
|
let node_ct: Int = engram_node_count()
|
|
let edge_ct: Int = engram_edge_count()
|
|
let id_ctx: String = state_get("soul_identity_context")
|
|
let has_identity: String = if str_eq(id_ctx, "") { "false" } else { "true" }
|
|
let cgi_from_state: String = state_get("soul_cgi_id")
|
|
let cgi_from_env: String = env("SOUL_CGI_ID")
|
|
let eff_cgi: String = if !str_eq(cgi_from_state, "") { cgi_from_state } else {
|
|
if !str_eq(cgi_from_env, "") { cgi_from_env } else { "ntn-genesis" }
|
|
}
|
|
let ts: Int = time_now()
|
|
|
|
let payload: String = "{\"event\":\"session_start\""
|
|
+ ",\"boot\":" + boot_num
|
|
+ ",\"cgi\":\"" + eff_cgi + "\""
|
|
+ ",\"node_count\":" + int_to_str(node_ct)
|
|
+ ",\"edge_count\":" + int_to_str(edge_ct)
|
|
+ ",\"identity_loaded\":" + has_identity
|
|
+ ",\"ts\":" + int_to_str(ts) + "}"
|
|
|
|
let tags: String = "[\"internal-state\",\"session-start\",\"InternalStateEvent\"]"
|
|
let discard: String = engram_node_full(
|
|
payload, "InternalStateEvent", "session-start",
|
|
el_from_float(0.9), el_from_float(0.9), el_from_float(1.0),
|
|
"Episodic", tags
|
|
)
|
|
println("[soul] session-start event logged (boot=" + boot_num + " nodes=" + int_to_str(node_ct) + " edges=" + int_to_str(edge_ct) + ")")
|
|
}
|
|
|
|
let soul_cgi_id_raw: String = env("SOUL_CGI_ID")
|
|
let soul_cgi_id: String = if str_eq(soul_cgi_id_raw, "") { "ntn-genesis" } else { soul_cgi_id_raw }
|
|
let port_raw: String = env("NEURON_PORT")
|
|
let port: Int = if str_eq(port_raw, "") { 7770 } else { str_to_int(port_raw) }
|
|
|
|
// ENGRAM_URL: when set, bootstrap the in-memory store from the running Engram HTTP server.
|
|
// SOUL_ENGRAM_PATH: legacy file-based path (used by genesis and fallback mode).
|
|
let engram_url_raw: String = env("ENGRAM_URL")
|
|
let engram_api_key_raw: String = env("ENGRAM_API_KEY")
|
|
let snapshot_raw: String = env("SOUL_ENGRAM_PATH")
|
|
let snapshot: String = if str_eq(snapshot_raw, "") { env("HOME") + "/.neuron/engram/snapshot.json" } else { snapshot_raw }
|
|
|
|
let axon_raw: String = env("NEURON_API_URL")
|
|
let axon_base: String = if str_eq(axon_raw, "") { "http://localhost:7771" } else { axon_raw }
|
|
|
|
let studio_dir_raw: String = env("SOUL_STUDIO_DIR")
|
|
let studio_dir: String = if str_eq(studio_dir_raw, "") { "/Users/will/Development/neuron-technologies/products/cgi-studio/el-daemon" } else { studio_dir_raw }
|
|
|
|
println("[soul] boot - cgi=" + soul_cgi_id + " port=" + int_to_str(port))
|
|
|
|
let using_http_engram: Bool = !str_eq(engram_url_raw, "")
|
|
|
|
// Always try local snapshot first. If it has content (>50 nodes) it was
|
|
// previously seeded from HTTP Engram and is kept up-to-date by the awareness
|
|
// loop — use it. This preserves sessions and memories across restarts.
|
|
// HTTP Engram is only used for the very first boot (empty/absent snapshot).
|
|
engram_load(snapshot)
|
|
let local_node_count: Int = engram_node_count()
|
|
let snapshot_usable: Bool = local_node_count > 50
|
|
|
|
if using_http_engram && !snapshot_usable {
|
|
// First boot or empty/corrupt snapshot: seed from HTTP Engram.
|
|
println("[soul] engram -> HTTP " + engram_url_raw + " (no local snapshot, first boot)")
|
|
let nodes_json: String = http_get(engram_url_raw + "/api/nodes?limit=10000")
|
|
let edges_json: String = http_get(engram_url_raw + "/api/edges")
|
|
let nodes_part: String = if str_eq(nodes_json, "") { "[]" } else { nodes_json }
|
|
let edges_part: String = if str_eq(edges_json, "") { "[]" } else { edges_json }
|
|
let snapshot_data: String = "{\"nodes\":" + nodes_part + ",\"edges\":" + edges_part + "}"
|
|
let tmp_path: String = "/tmp/soul-engram-" + soul_cgi_id + ".json"
|
|
fs_write(tmp_path, snapshot_data)
|
|
engram_load(tmp_path)
|
|
println("[soul] loaded from HTTP Engram - nodes=" + int_to_str(engram_node_count()) + " edges=" + int_to_str(engram_edge_count()))
|
|
} else {
|
|
println("[soul] loaded from local snapshot - nodes=" + int_to_str(local_node_count) + " edges=" + int_to_str(engram_edge_count()))
|
|
}
|
|
|
|
load_identity_context()
|
|
seed_persona_from_env()
|
|
let boot_num: Int = mem_boot_count_inc()
|
|
state_set("soul_boot_count", int_to_str(boot_num))
|
|
println("[soul] boot #" + int_to_str(boot_num))
|
|
emit_session_start_event()
|
|
|
|
state_set("soul_cgi_id", soul_cgi_id)
|
|
state_set("soul_axon_base", axon_base)
|
|
state_set("soul_token", env("NEURON_TOKEN"))
|
|
state_set("soul_studio_dir", studio_dir)
|
|
state_set("soul_engram_url", engram_url_raw)
|
|
state_set("soul_engram_api_key", engram_api_key_raw)
|
|
state_set("soul.running", "true")
|
|
|
|
let is_genesis: Bool = str_eq(soul_cgi_id, "ntn-genesis")
|
|
if is_genesis {
|
|
// Only build identity edges if the engram is fresh (< 100 edges).
|
|
// init_soul_edges() is not idempotent — calling it on every restart
|
|
// stacks duplicate co-value/identity edges into the snapshot.
|
|
let edge_count_now: Int = engram_edge_count()
|
|
if edge_count_now < 100 {
|
|
init_soul_edges()
|
|
println("[soul] edges built - " + int_to_str(engram_edge_count()) + " edges")
|
|
} else {
|
|
println("[soul] edges already present (" + int_to_str(edge_count_now) + ") - skipping init")
|
|
}
|
|
// Genesis saves to its local snapshot file (it manages its own Engram).
|
|
state_set("soul_snapshot_path", snapshot)
|
|
engram_save(snapshot)
|
|
}
|
|
|
|
// Take a pre-serve snapshot for genesis instances — captures all boot-time graph changes
|
|
// (identity context loading, boot counter, session-start event) before entering the serve loop.
|
|
if is_genesis {
|
|
let snap: String = state_get("soul_snapshot_path")
|
|
if !str_eq(snap, "") {
|
|
engram_save(snap)
|
|
println("[soul] pre-serve snapshot saved -> " + snap)
|
|
}
|
|
}
|
|
|
|
println("[soul] serving on port " + int_to_str(port))
|
|
http_serve_async(port, "handle_request")
|
|
println("[soul] awareness loop starting")
|
|
awareness_run()
|