soul loop-2: session events, conversation persistence, richer health + inbox

- soul.el: emit_session_start_event() logs structured InternalStateEvent on every boot; pre-serve snapshot saves boot-time graph mutations before any requests arrive
- routes.el: /health now returns boot count, node/edge counts, pulse; /api/sessions endpoint surfaces session-start history
- awareness.el: perceive() tries soul-inbox-pending then soul-inbox fallback, catches both tagging conventions
- chat.el: conv_history_persist/load provide cross-restart conversation continuity via engram
This commit is contained in:
2026-05-06 22:04:06 -05:00
parent 4fc0294a49
commit f2599919be
4 changed files with 111 additions and 3 deletions
+13 -1
View File
@@ -23,7 +23,19 @@ fn make_action(kind: String, payload: String) -> String {
}
fn perceive() -> String {
return engram_activate_json("soul-inbox-pending", 2)
// Try the primary inbox first
let from_pending: String = engram_activate_json("soul-inbox-pending", 2)
let pending_ok: Bool = !str_eq(from_pending, "") && !str_eq(from_pending, "[]")
if pending_ok {
return from_pending
}
// Fallback: broader inbox scan
let from_inbox: String = engram_activate_json("soul-inbox", 2)
let inbox_ok: Bool = !str_eq(from_inbox, "") && !str_eq(from_inbox, "[]")
if inbox_ok {
return from_inbox
}
return "[]"
}
fn attend(node_json: String) -> String {
+31 -1
View File
@@ -123,6 +123,33 @@ fn clean_llm_response(s: String) -> String {
return s3
}
// conv_history_persist save conversation history to engram for cross-restart continuity.
// Stores as a Conversation node. Overwrites by using consistent label "conv:history".
fn conv_history_persist(hist: String) -> Void {
if str_eq(hist, "") { return "" }
if str_eq(hist, "[]") { return "" }
let ts: Int = time_now()
let tags: String = "[\"conv-history\",\"persistent\"]"
let discard: String = engram_node_full(
hist, "Conversation", "conv:history",
el_from_float(0.7), el_from_float(0.8), el_from_float(0.9),
"Episodic", tags
)
}
// conv_history_load restore conversation history from engram on first access.
// Returns the most recent "conv:history" node content, or "" if none found.
fn conv_history_load() -> String {
let results: String = engram_search_json("conv:history", 3)
if str_eq(results, "") { return "" }
if str_eq(results, "[]") { return "" }
let node: String = json_array_get(results, 0)
let content: String = json_get(node, "content")
// Validate it looks like a JSON array
if !str_starts_with(content, "[") { return "" }
return content
}
fn handle_chat(body: String) -> String {
let message: String = json_get(body, "message")
if str_eq(message, "") {
@@ -132,7 +159,9 @@ fn handle_chat(body: String) -> String {
let ctx: String = engram_compile(message)
let system: String = build_system_prompt(ctx)
let stored_hist: String = state_get("conv_history")
// Load from state; if empty, try to recover from engram (cross-restart continuity)
let state_hist: String = state_get("conv_history")
let stored_hist: String = if str_eq(state_hist, "") { conv_history_load() } else { state_hist }
let hist_len: Int = if str_eq(stored_hist, "") { 0 } else { json_array_len(stored_hist) }
let full_system: String = if hist_len > 0 {
system + "\n\n[RECENT CONVERSATION — last " + int_to_str(hist_len) + " turns]\n" + stored_hist
@@ -163,6 +192,7 @@ fn handle_chat(body: String) -> String {
updated_hist2
}
state_set("conv_history", final_hist)
conv_history_persist(final_hist)
let activation_nodes: String = engram_activate_json(message, 2)
let act_ok: Bool = !str_eq(activation_nodes, "") && !str_eq(activation_nodes, "[]")
+22 -1
View File
@@ -22,7 +22,18 @@ fn err_405(method: String, path: String) -> String {
fn route_health() -> String {
let cgi_id: String = state_get("soul_cgi_id")
return "{\"status\":\"alive\",\"cgi_id\":\"" + cgi_id + "\"}"
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 pulse: String = state_get("soul.pulse")
let pulse_num: String = if str_eq(pulse, "") { "0" } else { pulse }
return "{\"status\":\"alive\""
+ ",\"cgi_id\":\"" + cgi_id + "\""
+ ",\"boot\":" + boot_num
+ ",\"node_count\":" + int_to_str(node_ct)
+ ",\"edge_count\":" + int_to_str(edge_ct)
+ ",\"pulse\":" + pulse_num + "}"
}
fn route_lineage() -> String {
@@ -184,6 +195,13 @@ fn handle_dharma_recv(body: String) -> String {
return "{\"error\":\"unknown event_type\",\"event_type\":\"" + eff_event + "\"}"
}
fn route_sessions() -> String {
let results: String = engram_search_json("session-start", 20)
if str_eq(results, "") { return "[]" }
if str_eq(results, "[]") { return "[]" }
return results
}
fn handle_request(method: String, path: String, body: String) -> String {
let clean: String = strip_query(path)
@@ -195,6 +213,9 @@ fn handle_request(method: String, path: String, body: String) -> String {
if str_eq(clean, "/health") {
return route_health()
}
if str_eq(clean, "/api/sessions") {
return route_sessions()
}
if str_eq(clean, "/lineage") {
return route_lineage()
}
+45
View File
@@ -129,6 +129,40 @@ fn load_identity_context() -> Void {
println("[soul] identity context loaded (" + int_to_str(str_len(ctx)) + " chars, " + int_to_str(parts_count) + " nodes)")
}
// 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")
@@ -175,6 +209,7 @@ load_identity_context()
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()
let identity_raw: String = env("SOUL_IDENTITY")
let soul_identity: String = if str_eq(identity_raw, "") { "You are " + soul_cgi_id + ", a CGI." } else { identity_raw }
@@ -205,5 +240,15 @@ if is_genesis {
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(port, "handle_request")