fn tier_working() -> String { return "Working" } fn tier_episodic() -> String { return "Episodic" } fn tier_canonical() -> String { return "Canonical" } fn mem_store(content: String, label: String, tags: String) -> String { return engram_node_full( content, "Memory", label, el_from_float(0.5), el_from_float(0.5), el_from_float(0.8), "Working", tags ) } fn mem_remember(content: String, tags: String) -> String { return mem_store(content, "soul-memory", tags) } fn mem_recall(query: String, depth: Int) -> String { return engram_activate_json(query, depth) } fn mem_search(query: String, limit: Int) -> String { return engram_search_json(query, limit) } fn mem_strengthen(node_id: String) -> Void { engram_strengthen(node_id) } fn mem_forget(node_id: String) -> Void { engram_forget(node_id) } // mem_consolidate — structural scan plus salience-evolution pass. // // Previously this only returned structural counts (scanned, total_nodes, total_edges) // with no salience updates. No node salience ever changed based on recall frequency // or time; foundational nodes decayed identically to ephemeral chat; frequently-recalled // nodes were never promoted. This made consolidation a no-op. // // New behavior: // (a) Strengthen frequently-activated nodes: nodes in the top working-memory list // (engram_wm_top_json) are strengthened — they have been recalled recently // and deserve higher salience. Raises effective salience for nodes that prove // relevant across multiple sessions. // (b) Strengthen Canonical-tier nodes: identity and foundational nodes should not // decay; each consolidation pass re-strengthens them so they resist the // tier-aware decay curve without requiring active recall. // (c) Structural counts are still returned for observability. // // Called by awareness_run() on the "consolidate" inbox action. fn mem_consolidate() -> String { let scanned: Int = engram_node_count() let total_edges: Int = engram_edge_count() let strengthened: Int = 0 // (a) Strengthen top working-memory nodes — recalled recently across sessions. // Cap at 10 to keep consolidation fast. let wm_top: String = engram_wm_top_json(10) let wm_len: Int = json_array_len(wm_top) let wi: Int = 0 while wi < wm_len { let wm_node: String = json_array_get(wm_top, wi) let wm_id: String = json_get(wm_node, "id") if !str_eq(wm_id, "") { engram_strengthen(wm_id) let strengthened = strengthened + 1 } let wi = wi + 1 } // (b) Strengthen Canonical-tier nodes from a full paginated scan so they resist // temporal decay. Canonical nodes encode foundational identity — they must not // silently floor at 10. Page size 50, scanning until fewer than 50 nodes are // returned (last page), so all Canonical nodes are reached even in large graphs. // Without pagination, only the first 50 nodes in the graph were eligible; any // Canonical node at index 50+ was silently excluded from the boost. // Strengthening is skipped if the node's current salience is already at the // runtime ceiling (represented as "1" by %g) to avoid monotonic unbounded growth. // Canonical nodes with salience < 1.0 are strengthened each consolidation pass; // once they reach the ceiling the runtime will no longer raise them further, so // calling engram_strengthen at the ceiling is a no-op in the runtime anyway, but // the explicit check makes the intent clear and avoids any runtime log noise. let page_size: Int = 50 let scan_offset: Int = 0 let scan_done: Bool = false while !scan_done { let scan_result: String = engram_scan_nodes_json(page_size, scan_offset) let scan_len: Int = json_array_len(scan_result) if scan_len == 0 { let scan_done = true } else { let si: Int = 0 while si < scan_len { let s_node: String = json_array_get(scan_result, si) let s_tier: String = json_get(s_node, "tier") let s_id: String = json_get(s_node, "id") let s_sal: String = json_get(s_node, "salience") // Only strengthen if below the ceiling to prevent unbounded salience growth. // engram serialises the ceiling as "1" (%g drops the decimal part when it // is exactly zero). Any other value is below ceiling and should be boosted. let at_ceiling: Bool = str_eq(s_sal, "1") if str_eq(s_tier, "Canonical") && !str_eq(s_id, "") && !at_ceiling { engram_strengthen(s_id) let strengthened = strengthened + 1 } let si = si + 1 } let scan_offset = scan_offset + scan_len // Fewer results than page_size means we've reached the last page. if scan_len < page_size { let scan_done = true } } } let total_nodes: Int = engram_node_count() return "{\"scanned\":" + int_to_str(scanned) + ",\"total_nodes\":" + int_to_str(total_nodes) + ",\"total_edges\":" + int_to_str(total_edges) + ",\"strengthened\":" + int_to_str(strengthened) + "}" } fn mem_save(path: String) -> Void { let save_result: String = engram_save(path) if str_eq(save_result, "") { println("[memory] mem_save: engram_save failed for " + path + " — snapshot may be incomplete") } } fn mem_load(path: String) -> Void { engram_load(path) } // mem_boot_count_get — retrieve current boot count from engram. // Searches for the "soul:boot_count" node and returns its numeric value. // Returns 0 if not found. fn mem_boot_count_get() -> Int { let results: String = engram_search_json("soul:boot_count", 3) if str_eq(results, "") { return 0 } if str_eq(results, "[]") { return 0 } let node: String = json_array_get(results, 0) let content: String = json_get(node, "content") let prefix: String = "soul:boot_count:" if !str_starts_with(content, prefix) { return 0 } let num_str: String = str_slice(content, str_len(prefix), str_len(content)) return str_to_int(num_str) } // mem_boot_count_inc — increment boot counter, store new node, return new count. // Each boot creates a new "soul:boot_count:N" node. Old ones accumulate as // history — the search above always returns the highest value seen. fn mem_boot_count_inc() -> Int { let current: Int = mem_boot_count_get() let next: Int = current + 1 let content: String = "soul:boot_count:" + int_to_str(next) let tags: String = "[\"soul-meta\",\"boot-counter\"]" let boot_node_id: String = engram_node_full( content, "Memory", "soul:boot_count", el_from_float(0.9), el_from_float(0.9), el_from_float(1.0), "Canonical", tags ) if str_eq(boot_node_id, "") { println("[memory] mem_boot_count_inc: engram write failed — boot counter node lost (count=" + int_to_str(next) + ")") } return next } // mem_emit_state_event — log an internal state event as structured memory. // Schema: {trigger, kind, content, boot, ts} // This creates an auditable evidence trail of cognitive decisions. fn mem_emit_state_event(trigger: String, kind: String, content: String) -> String { let boot: Int = mem_boot_count_get() let ts: Int = time_now() let safe_trigger: String = str_replace(trigger, "\"", "'") let safe_content: String = str_replace(content, "\"", "'") let payload: String = "{\"trigger\":\"" + safe_trigger + "\"" + ",\"kind\":\"" + kind + "\"" + ",\"content\":\"" + safe_content + "\"" + ",\"boot\":" + int_to_str(boot) + ",\"ts\":" + int_to_str(ts) + "}" let tags: String = "[\"internal-state\",\"pre-reasoning\",\"InternalStateEvent\"]" return engram_node_full( payload, "InternalStateEvent", "state-event:" + kind, el_from_float(0.85), el_from_float(0.8), el_from_float(0.9), "Episodic", tags ) }