Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 95cb49a8b0 | |||
| 795b32ad1a |
@@ -347,11 +347,26 @@ fn clean_llm_response(s: String) -> String {
|
||||
}
|
||||
|
||||
// conv_history_persist — save conversation history to engram for cross-restart continuity.
|
||||
// Stores as a Conversation node. Overwrites by using consistent label "conv:history".
|
||||
// Delete-before-write under label "conv:history" prevents unbounded node accumulation (issue #11).
|
||||
fn conv_history_persist(hist: String) -> Void {
|
||||
if str_eq(hist, "") { return "" }
|
||||
if str_eq(hist, "[]") { return "" }
|
||||
let ts: Int = time_now()
|
||||
// Delete any existing conv:history nodes before writing to avoid accumulation.
|
||||
let old_hist_results: String = engram_search_json("conv:history", 3)
|
||||
let old_hist_ok: Bool = !str_eq(old_hist_results, "") && !str_eq(old_hist_results, "[]")
|
||||
if old_hist_ok {
|
||||
let ohr_total: Int = json_array_len(old_hist_results)
|
||||
let ohr_i: Int = 0
|
||||
while ohr_i < ohr_total {
|
||||
let ohr_node: String = json_array_get(old_hist_results, ohr_i)
|
||||
let ohr_label: String = json_get(ohr_node, "label")
|
||||
let ohr_id: String = json_get(ohr_node, "id")
|
||||
if str_eq(ohr_label, "conv:history") && !str_eq(ohr_id, "") {
|
||||
engram_forget(ohr_id)
|
||||
}
|
||||
let ohr_i = ohr_i + 1
|
||||
}
|
||||
}
|
||||
let tags: String = "[\"conv-history\",\"persistent\"]"
|
||||
let discard: String = engram_node_full(
|
||||
hist, "Conversation", "conv:history",
|
||||
@@ -400,18 +415,25 @@ fn handle_chat(body: String) -> String {
|
||||
|
||||
// Cross-session affective context: on session start (no history yet), check engram
|
||||
// for recent distress signals within 72h and prepend a care directive if found.
|
||||
// Fixes issue #6: soul_affective_context is pre-loaded at boot — use it first to
|
||||
// avoid a redundant engram search and to make the boot-time state key functional.
|
||||
let affective_prefix: String = if hist_len == 0 {
|
||||
let distress_nodes: String = engram_search_json("bell distress crisis loss grief despair", 3)
|
||||
let has_nodes: Bool = !str_eq(distress_nodes, "") && !str_eq(distress_nodes, "[]")
|
||||
let now_ts: Int = time_now()
|
||||
let cutoff: Int = now_ts - 259200
|
||||
let found_recent: Bool = if has_nodes {
|
||||
let dn0: String = json_array_get(distress_nodes, 0)
|
||||
let ts0_raw: String = json_get(dn0, "created_at")
|
||||
let ts0_str: String = if str_eq(ts0_raw, "") { json_get(dn0, "updated_at") } else { ts0_raw }
|
||||
let ts0: Int = if str_eq(ts0_str, "") { 0 } else { str_to_int(ts0_str) }
|
||||
ts0 > cutoff
|
||||
} else { false }
|
||||
let soul_aff_ctx: String = state_get("soul_affective_context")
|
||||
let found_recent: Bool = if !str_eq(soul_aff_ctx, "") {
|
||||
true
|
||||
} else {
|
||||
let distress_nodes: String = engram_search_json("bell distress crisis loss grief despair", 3)
|
||||
let has_nodes: Bool = !str_eq(distress_nodes, "") && !str_eq(distress_nodes, "[]")
|
||||
let now_ts: Int = time_now()
|
||||
let cutoff: Int = now_ts - 259200
|
||||
if has_nodes {
|
||||
let dn0: String = json_array_get(distress_nodes, 0)
|
||||
let ts0_raw: String = json_get(dn0, "created_at")
|
||||
let ts0_str: String = if str_eq(ts0_raw, "") { json_get(dn0, "updated_at") } else { ts0_raw }
|
||||
let ts0: Int = if str_eq(ts0_str, "") { 0 } else { str_to_int(ts0_str) }
|
||||
ts0 > cutoff
|
||||
} else { false }
|
||||
}
|
||||
if found_recent {
|
||||
"[RECENT CONTEXT: User recently expressed significant distress. Monitor for indirect crisis signals and respond with care.]\n\n"
|
||||
} else { "" }
|
||||
@@ -429,6 +451,49 @@ fn handle_chat(body: String) -> String {
|
||||
let profile_ok: Bool = !str_eq(profile_nodes, "") && !str_eq(profile_nodes, "[]")
|
||||
let work_ok: Bool = !str_eq(work_nodes, "") && !str_eq(work_nodes, "[]")
|
||||
|
||||
// Load the previous session summary. Search by label text + type, then filter by
|
||||
// exact label match. Fallback: broader vector search for SessionSummary nodes.
|
||||
// Fixes issue #2: prev session summary was never loaded at startup.
|
||||
// Fixes issue #2b (phantom engram_get_node_by_label replaced with engram_search_json).
|
||||
let sum_search_nodes: String = engram_search_json("session:summary SessionSummary", 5)
|
||||
let sum_search_ok: Bool = !str_eq(sum_search_nodes, "") && !str_eq(sum_search_nodes, "[]")
|
||||
let prev_sum_node_content: String = if sum_search_ok {
|
||||
let ss_total: Int = json_array_len(sum_search_nodes)
|
||||
let ssi: Int = 0
|
||||
let found_content: String = ""
|
||||
while ssi < ss_total {
|
||||
let ss_node: String = json_array_get(sum_search_nodes, ssi)
|
||||
let ss_label: String = json_get(ss_node, "label")
|
||||
let ss_type: String = json_get(ss_node, "node_type")
|
||||
let ss_content: String = json_get(ss_node, "content")
|
||||
let found_content = if str_eq(ss_label, "session:summary") && str_eq(ss_type, "SessionSummary") && !str_eq(ss_content, "") {
|
||||
if str_eq(found_content, "") { ss_content } else { found_content }
|
||||
} else { found_content }
|
||||
let ssi = ssi + 1
|
||||
}
|
||||
found_content
|
||||
} else { "" }
|
||||
// Check state first: soul.el pre-loads this at boot (soul_prev_session_summary) — fixes issue #5.
|
||||
let soul_cached_sum: String = state_get("soul_prev_session_summary")
|
||||
let prev_summary_raw: String = if !str_eq(soul_cached_sum, "") {
|
||||
soul_cached_sum
|
||||
} else if !str_eq(prev_sum_node_content, "") {
|
||||
prev_sum_node_content
|
||||
} else {
|
||||
let sum_nodes: String = engram_search_json("SessionSummary previous-session", 3)
|
||||
let sum_ok: Bool = !str_eq(sum_nodes, "") && !str_eq(sum_nodes, "[]")
|
||||
if sum_ok {
|
||||
let sn0: String = json_array_get(sum_nodes, 0)
|
||||
let stype: String = json_get(sn0, "node_type")
|
||||
let scontent: String = json_get(sn0, "content")
|
||||
if str_eq(stype, "SessionSummary") && !str_eq(scontent, "") { scontent } else { "" }
|
||||
} else { "" }
|
||||
}
|
||||
let has_prev_summary: Bool = !str_eq(prev_summary_raw, "")
|
||||
let prev_summary_snip: String = if str_len(prev_summary_raw) > 400 {
|
||||
str_slice(prev_summary_raw, 0, 400)
|
||||
} else { prev_summary_raw }
|
||||
|
||||
// Extract content fields and render as bullet points (one per node, first 120 chars).
|
||||
let profile_bullets: String = if profile_ok {
|
||||
let pn: Int = json_array_len(profile_nodes)
|
||||
@@ -476,15 +541,19 @@ fn handle_chat(body: String) -> String {
|
||||
|
||||
let has_profile: Bool = !str_eq(profile_bullets, "")
|
||||
let has_work: Bool = !str_eq(work_bullets, "")
|
||||
let preload: String = if has_profile || has_work {
|
||||
let preload: String = if has_profile || has_work || has_prev_summary {
|
||||
let summary_section: String = if has_prev_summary {
|
||||
"[PREVIOUS SESSION - what we discussed last time]\n" + prev_summary_snip
|
||||
} else { "" }
|
||||
let profile_section: String = if has_profile {
|
||||
"[USER CONTEXT — from memory]\n" + profile_bullets
|
||||
"[USER CONTEXT - from memory]\n" + profile_bullets
|
||||
} else { "" }
|
||||
let work_section: String = if has_work {
|
||||
"[ACTIVE WORK — from memory]\n" + work_bullets
|
||||
"[ACTIVE WORK - from memory]\n" + work_bullets
|
||||
} else { "" }
|
||||
let sep_sp: String = if has_prev_summary && (has_profile || has_work) { "\n\n" } else { "" }
|
||||
let sep_pw: String = if has_profile && has_work { "\n\n" } else { "" }
|
||||
"\n\n" + profile_section + sep_pw + work_section
|
||||
"\n\n" + summary_section + sep_sp + profile_section + sep_pw + work_section
|
||||
} else { "" }
|
||||
preload
|
||||
} else { "" }
|
||||
@@ -526,6 +595,14 @@ fn handle_chat(body: String) -> String {
|
||||
state_set("conv_history", final_hist)
|
||||
conv_history_persist(final_hist)
|
||||
|
||||
// Automatic session-end summary: write/overwrite the SessionSummary node on each turn
|
||||
// so process restarts always have a continuity snapshot (no shutdown hook needed).
|
||||
// Uses autogenerate (no LLM) so it is cheap — the node is overwritten not appended.
|
||||
let auto_sum: String = session_summary_autogenerate(final_hist)
|
||||
if !str_eq(auto_sum, "") {
|
||||
let discard_sum: String = session_summary_write(auto_sum)
|
||||
}
|
||||
|
||||
let activation_nodes: String = engram_activate_json(message, 2)
|
||||
let act_ok: Bool = !str_eq(activation_nodes, "") && !str_eq(activation_nodes, "[]")
|
||||
let act_out: String = if act_ok { activation_nodes } else { "[]" }
|
||||
@@ -968,13 +1045,15 @@ fn handle_chat_agentic(body: String) -> String {
|
||||
|
||||
// L1 safety screen — agentic path must pass the same gate as layered_cycle.
|
||||
// Hard bell: return the crisis response immediately, do not enter the agentic loop.
|
||||
let history: String = state_get("conversation_history")
|
||||
// Fix(issue #9): "conversation_history" key was never written; history lives under "conv_history".
|
||||
// Old key caused history-amplification in safety_screen to always receive "" on agentic path.
|
||||
let history: String = state_get("conv_history")
|
||||
let screen_result: String = safety_screen(message, history)
|
||||
let screen_action: String = json_get(screen_result, "action")
|
||||
if str_eq(screen_action, "hard_bell") {
|
||||
safety_log_bell("hard", json_get(screen_result, "reason"), str_slice(message, 0, 80))
|
||||
return "{\"reply\":\"" + json_safe(safety_validate("", "hard_bell")) + "\",\"model\":\"\",\"agentic\":true,\"tools_used\":[]}"
|
||||
|
||||
}
|
||||
|
||||
let req_model: String = json_get(body, "model")
|
||||
let model: String = if str_eq(req_model, "") { chat_default_model() } else { req_model }
|
||||
@@ -998,7 +1077,14 @@ fn handle_chat_agentic(body: String) -> String {
|
||||
}
|
||||
|
||||
let hist_key: String = if str_eq(req_session, "") { "conv_history" } else { "session_hist_" + req_session }
|
||||
let agentic_hist: String = state_get(hist_key)
|
||||
// Fall back to engram (via session_hist_load) when state is cold — fixes issue #4:
|
||||
// named-session history written under session:messages:SESSION_ID was never read back.
|
||||
let agentic_hist_state: String = state_get(hist_key)
|
||||
let agentic_hist: String = if str_eq(agentic_hist_state, "") && !str_eq(req_session, "") {
|
||||
let loaded: String = session_hist_load(req_session)
|
||||
if !str_eq(loaded, "") { state_set(hist_key, loaded) }
|
||||
if str_eq(loaded, "") { conv_history_load() } else { loaded }
|
||||
} else { agentic_hist_state }
|
||||
let agentic_hist_len: Int = if str_eq(agentic_hist, "") { 0 } else { json_array_len(agentic_hist) }
|
||||
let ag_is_cont: Bool = str_len(message) < 50 && agentic_hist_len > 0
|
||||
let ag_last_entry: String = if ag_is_cont { json_array_get(agentic_hist, agentic_hist_len - 1) } else { "" }
|
||||
@@ -1041,6 +1127,23 @@ fn handle_chat_agentic(body: String) -> String {
|
||||
let updated2: String = hist_append(updated, "assistant", reply_text)
|
||||
let trimmed: String = if json_array_len(updated2) > 20 { hist_trim(updated2) } else { updated2 }
|
||||
state_set(hist_key, trimmed)
|
||||
// Persist to engram for cross-restart continuity.
|
||||
// Named sessions use session_hist_save (session:messages:SESSION_ID label) so that
|
||||
// session_hist_load can recover them on the next restart — fixes issue #4.
|
||||
// The old conv:history:SESSION_ID label was a dead write (never read back).
|
||||
if str_eq(hist_key, "conv_history") {
|
||||
conv_history_persist(trimmed)
|
||||
} else {
|
||||
if !str_eq(trimmed, "") && !str_eq(trimmed, "[]") {
|
||||
session_hist_save(req_session, trimmed)
|
||||
}
|
||||
}
|
||||
// Write automatic session summary so cross-session continuity is maintained
|
||||
// on the agentic path too — fixes issue #7.
|
||||
let ag_auto_sum: String = session_summary_autogenerate(trimmed)
|
||||
if !str_eq(ag_auto_sum, "") {
|
||||
let discard_ag_sum: String = session_summary_write(ag_auto_sum)
|
||||
}
|
||||
true
|
||||
} else { false }
|
||||
|
||||
@@ -1494,6 +1597,74 @@ fn handle_dharma_room_turn_agentic(body: String) -> String {
|
||||
return "{\"response\":\"" + safe_text + "\",\"cgi_id\":\"" + cgi_id + "\",\"tools_used\":" + eff_tools + "}"
|
||||
}
|
||||
|
||||
// session_summary_write — write or overwrite the SessionSummary node in engram.
|
||||
// Uses delete-before-write so there is always exactly one "session:summary" node.
|
||||
// This is what session_preload at next startup reads to know what was discussed.
|
||||
fn session_summary_write(summary_text: String) -> String {
|
||||
if str_eq(summary_text, "") { return "" }
|
||||
let safe_text: String = str_replace(summary_text, "\"", "'")
|
||||
let trimmed: String = if str_len(safe_text) > 800 { str_slice(safe_text, 0, 800) } else { safe_text }
|
||||
let ts: Int = time_now()
|
||||
let ts_str: String = int_to_str(ts)
|
||||
let content: String = "[session-summary] " + trimmed + " | ts:" + ts_str
|
||||
// Delete old node before writing so duplicate label nodes don't accumulate.
|
||||
// engram_get_node_by_label doesn't exist — search by label text and filter by exact match.
|
||||
let old_search: String = engram_search_json("session:summary SessionSummary", 5)
|
||||
let old_search_ok: Bool = !str_eq(old_search, "") && !str_eq(old_search, "[]")
|
||||
if old_search_ok {
|
||||
let os_total: Int = json_array_len(old_search)
|
||||
let osi: Int = 0
|
||||
while osi < os_total {
|
||||
let os_node: String = json_array_get(old_search, osi)
|
||||
let os_label: String = json_get(os_node, "label")
|
||||
let os_id: String = json_get(os_node, "id")
|
||||
if str_eq(os_label, "session:summary") && !str_eq(os_id, "") {
|
||||
engram_forget(os_id)
|
||||
}
|
||||
let osi = osi + 1
|
||||
}
|
||||
}
|
||||
let tags: String = "[\"SessionSummary\",\"session-summary\",\"previous-session\",\"consolidate\"]"
|
||||
let node_id: String = engram_node_full(
|
||||
content, "SessionSummary", "session:summary",
|
||||
el_from_float(0.85), el_from_float(0.85), el_from_float(1.0),
|
||||
"Episodic", tags
|
||||
)
|
||||
if str_eq(node_id, "") {
|
||||
println("[chat] session_summary_write: engram write failed — summary node lost")
|
||||
return ""
|
||||
}
|
||||
println("[chat] session_summary_write: wrote SessionSummary (" + int_to_str(str_len(content)) + " chars) -> " + node_id)
|
||||
return node_id
|
||||
}
|
||||
|
||||
// session_summary_autogenerate — build a minimal summary from conversation history without LLM.
|
||||
// Extracts user message snippets (first 80 chars each, up to 5 turns).
|
||||
// Used as the automatic session-end hook so every turn produces a continuity snapshot.
|
||||
fn session_summary_autogenerate(hist: String) -> String {
|
||||
if str_eq(hist, "") { return "" }
|
||||
if str_eq(hist, "[]") { return "" }
|
||||
let total: Int = json_array_len(hist)
|
||||
if total == 0 { return "" }
|
||||
let snippets: String = ""
|
||||
let count: Int = 0
|
||||
let i: Int = 0
|
||||
while i < total && count < 5 {
|
||||
let entry: String = json_array_get(hist, i)
|
||||
let role: String = json_get(entry, "role")
|
||||
let msg: String = json_get(entry, "content")
|
||||
let snip: String = if str_len(msg) > 80 { str_slice(msg, 0, 80) } else { msg }
|
||||
// Mutations at top level of while body via if-expressions — inner if blocks don't escape scope.
|
||||
let snippets = if str_eq(role, "user") && !str_eq(snip, "") {
|
||||
if str_eq(snippets, "") { snip } else { snippets + "; " + snip }
|
||||
} else { snippets }
|
||||
let count = if str_eq(role, "user") && !str_eq(snip, "") { count + 1 } else { count }
|
||||
let i = i + 1
|
||||
}
|
||||
if str_eq(snippets, "") { return "" }
|
||||
return "Session covered: " + snippets
|
||||
}
|
||||
|
||||
fn auto_persist(req: String, resp: String) -> Void {
|
||||
let message: String = json_get(req, "message")
|
||||
let reply: String = json_get(resp, "response")
|
||||
|
||||
+8
-8
@@ -1,4 +1,5 @@
|
||||
import "memory.el"
|
||||
import "chat.el"
|
||||
|
||||
// neuron-api.el — Native Neuron cognitive API handlers.
|
||||
//
|
||||
@@ -654,14 +655,13 @@ fn handle_api_consolidate(body: String) -> String {
|
||||
engram_save(snap)
|
||||
}
|
||||
if !str_eq(summary, "") {
|
||||
let safe_summary: String = str_replace(summary, "\"", "'")
|
||||
let tags: String = "[\"SessionSummary\",\"consolidate\"]"
|
||||
let discard: String = engram_node_full(
|
||||
"[session-summary] " + safe_summary,
|
||||
"SessionSummary", "session:summary",
|
||||
el_from_float(0.7), el_from_float(0.7), el_from_float(0.9),
|
||||
"Episodic", tags
|
||||
)
|
||||
// Use session_summary_write to ensure delete-before-write semantics:
|
||||
// prevents stale SessionSummary accumulation across sessions (issue #11).
|
||||
// session_summary_write handles label indexing, trimming, and dedup.
|
||||
let sum_id: String = session_summary_write(summary)
|
||||
if str_eq(sum_id, "") {
|
||||
println("[api] consolidate: session_summary_write failed — summary not persisted")
|
||||
}
|
||||
}
|
||||
return "{\"ok\":true,\"snapshot\":\"" + snap + "\"}"
|
||||
}
|
||||
|
||||
@@ -162,6 +162,48 @@ fn load_identity_context() -> Void {
|
||||
println("[soul] persona node loaded (" + int_to_str(str_len(p_content)) + " chars)")
|
||||
}
|
||||
}
|
||||
|
||||
// Cross-session affective context: query engram for recent distress/crisis signals.
|
||||
// Broadened query includes session:emotional-summary and BellEvent tags (issue #10):
|
||||
// the old keywords-only search missed these nodes when their content lacked exact phrases.
|
||||
// 7-day recency window applied via the "ts" field embedded in BellEvent content.
|
||||
let affective_raw: String = engram_search_json("distress crisis upset hopeless session:emotional-summary BellEvent bell:hard bell:soft", 5)
|
||||
let affective_ok: Bool = !str_eq(affective_raw, "") && !str_eq(affective_raw, "[]")
|
||||
if affective_ok {
|
||||
let ts_now: Int = time_now()
|
||||
let ts_cutoff: Int = ts_now - 604800
|
||||
let aff_total: Int = json_array_len(affective_raw)
|
||||
let aff_ctx: String = ""
|
||||
let ai: Int = 0
|
||||
while ai < aff_total {
|
||||
let aff_node: String = json_array_get(affective_raw, ai)
|
||||
let aff_content: String = json_get(aff_node, "content")
|
||||
// Try multiple timestamp fields: "ts" (embedded), "created_at", "updated_at"
|
||||
let aff_ts_str: String = json_get(aff_node, "ts")
|
||||
let aff_ts_str2: String = if str_eq(aff_ts_str, "") { json_get(aff_node, "created_at") } else { aff_ts_str }
|
||||
// Also try embedded " | ts:NNN" format used in BellEvent content
|
||||
let ts_marker: String = " | ts:"
|
||||
let ts_pos: Int = str_index_of(aff_content, ts_marker)
|
||||
let aff_ts_embedded: String = if ts_pos >= 0 {
|
||||
let ts_start: Int = ts_pos + str_len(ts_marker)
|
||||
let rest: String = str_slice(aff_content, ts_start, str_len(aff_content))
|
||||
let next_sep: Int = str_index_of(rest, " | ")
|
||||
if next_sep < 0 { rest } else { str_slice(rest, 0, next_sep) }
|
||||
} else { "" }
|
||||
let eff_ts_str: String = if !str_eq(aff_ts_embedded, "") { aff_ts_embedded } else { aff_ts_str2 }
|
||||
let aff_ts: Int = if str_eq(eff_ts_str, "") { ts_now } else { str_to_int(eff_ts_str) }
|
||||
let is_recent: Bool = aff_ts >= ts_cutoff
|
||||
let snip: String = if str_len(aff_content) > 200 { str_slice(aff_content, 0, 200) } else { aff_content }
|
||||
let aff_ctx = if is_recent && !str_eq(snip, "") {
|
||||
if str_eq(aff_ctx, "") { snip } else { aff_ctx + "\n" + snip }
|
||||
} else { aff_ctx }
|
||||
let ai = ai + 1
|
||||
}
|
||||
if !str_eq(aff_ctx, "") {
|
||||
state_set("soul_affective_context", aff_ctx)
|
||||
println("[soul] cross-session affective context loaded (" + int_to_str(str_len(aff_ctx)) + " chars)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// seed_persona_from_env — one-time migration: SOUL_IDENTITY env var → Persona graph node.
|
||||
@@ -233,12 +275,59 @@ fn emit_session_start_event() -> Void {
|
||||
}
|
||||
let ts: Int = time_now()
|
||||
|
||||
// Load previous session summary at boot — stash in state for session_preload.
|
||||
// Search by label text + type, filter by exact label match to avoid false positives.
|
||||
// engram_get_node_by_label is not a runtime builtin; engram_search_json is used instead.
|
||||
let sum_boot_search: String = engram_search_json("session:summary SessionSummary", 5)
|
||||
let sum_boot_ok: Bool = !str_eq(sum_boot_search, "") && !str_eq(sum_boot_search, "[]")
|
||||
let prev_sum_content: String = if sum_boot_ok {
|
||||
let sbs_total: Int = json_array_len(sum_boot_search)
|
||||
let sbs_i: Int = 0
|
||||
let sbs_found: String = ""
|
||||
while sbs_i < sbs_total {
|
||||
let sbs_node: String = json_array_get(sum_boot_search, sbs_i)
|
||||
let sbs_label: String = json_get(sbs_node, "label")
|
||||
let sbs_type: String = json_get(sbs_node, "node_type")
|
||||
let sbs_content: String = json_get(sbs_node, "content")
|
||||
let sbs_found = if str_eq(sbs_label, "session:summary") && str_eq(sbs_type, "SessionSummary") && !str_eq(sbs_content, "") {
|
||||
if str_eq(sbs_found, "") { sbs_content } else { sbs_found }
|
||||
} else { sbs_found }
|
||||
let sbs_i = sbs_i + 1
|
||||
}
|
||||
if str_eq(sbs_found, "") {
|
||||
let sum_fb: String = engram_search_json("SessionSummary previous-session", 2)
|
||||
let sum_fb_ok: Bool = !str_eq(sum_fb, "") && !str_eq(sum_fb, "[]")
|
||||
if sum_fb_ok {
|
||||
let sfn: String = json_array_get(sum_fb, 0)
|
||||
let sftype: String = json_get(sfn, "node_type")
|
||||
let sfcontent: String = json_get(sfn, "content")
|
||||
if str_eq(sftype, "SessionSummary") && !str_eq(sfcontent, "") { sfcontent } else { "" }
|
||||
} else { "" }
|
||||
} else { sbs_found }
|
||||
} else {
|
||||
let sum_fb2: String = engram_search_json("SessionSummary previous-session", 2)
|
||||
let sum_fb2_ok: Bool = !str_eq(sum_fb2, "") && !str_eq(sum_fb2, "[]")
|
||||
if sum_fb2_ok {
|
||||
let sfn2: String = json_array_get(sum_fb2, 0)
|
||||
let sftype2: String = json_get(sfn2, "node_type")
|
||||
let sfcontent2: String = json_get(sfn2, "content")
|
||||
if str_eq(sftype2, "SessionSummary") && !str_eq(sfcontent2, "") { sfcontent2 } else { "" }
|
||||
} else { "" }
|
||||
}
|
||||
let has_prev_sum: String = if str_eq(prev_sum_content, "") { "false" } else { "true" }
|
||||
if !str_eq(prev_sum_content, "") {
|
||||
state_set("soul_prev_session_summary", prev_sum_content)
|
||||
println("[soul] previous session summary loaded (" + int_to_str(str_len(prev_sum_content)) + " chars)")
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
+ ",\"prev_session_summary_loaded\":" + has_prev_sum
|
||||
+ ",\"ts\":" + int_to_str(ts) + "}"
|
||||
|
||||
let tags: String = "[\"internal-state\",\"session-start\",\"InternalStateEvent\"]"
|
||||
@@ -247,7 +336,7 @@ fn emit_session_start_event() -> Void {
|
||||
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) + ")")
|
||||
println("[soul] session-start event logged (boot=" + boot_num + " nodes=" + int_to_str(node_ct) + " edges=" + int_to_str(edge_ct) + " prev_summary=" + has_prev_sum + ")")
|
||||
}
|
||||
|
||||
// layered_cycle — routes user-facing requests through the 4-layer consciousness stack.
|
||||
|
||||
Reference in New Issue
Block a user