diff --git a/chat.el b/chat.el index 174c927..b3e0940 100644 --- a/chat.el +++ b/chat.el @@ -46,6 +46,8 @@ fn engram_score_node(node_json: String) -> Int { let salience_str: String = json_get(node_json, "salience") let importance_str: String = json_get(node_json, "importance") let created_str: String = json_get(node_json, "created_at") + let updated_str: String = json_get(node_json, "updated_at") + let tier_str: String = json_get(node_json, "tier") // Q1 fix: validate before str_to_int. Non-numeric values fall back to safe defaults. // Parse as floats via * 100 integer arithmetic (el has no float math). @@ -58,7 +60,6 @@ fn engram_score_node(node_json: String) -> Int { if v > 100 { 100 } else { if v < 0 { 0 } else { v } } } - // Recency: decay from 100 (today) to 10 (30+ days). created_at is Unix seconds. let now_ts: Int = time_now() let recency_100: Int = if !engram_numeric_valid(created_str) { 50 } else { let created_ts: Int = str_to_int(created_str) @@ -69,7 +70,6 @@ fn engram_score_node(node_json: String) -> Int { if decay < 10 { 10 } else { decay } } - // Combined score 0-1000000 (no floats): salience * importance * recency / 10000 return salience_100 * importance_100 * recency_100 / 10000 } @@ -393,7 +393,7 @@ fn engram_compile(intent: String) -> String { let bell_nodes: String = engram_search_json("bell:soft bell:hard BellEvent", 3) let bell_ok: Bool = !str_eq(bell_nodes, "") && !str_eq(bell_nodes, "[]") let now_ts: Int = time_now() - let cutoff_ts: Int = now_ts - 259200 + let cutoff_ts: Int = now_ts - 1209600 let recent_bell: String = if bell_ok { let bn0: String = json_array_get(bell_nodes, 0) let bn_content: String = json_get(bn0, "content") @@ -1447,7 +1447,8 @@ fn handle_chat_agentic(body: String) -> String { let discard_hist: Bool = if !str_eq(reply_text, "") { let updated: String = hist_append(agentic_hist, "user", message) let updated2: String = hist_append(updated, "assistant", reply_text) - let trimmed: String = if json_array_len(updated2) > 20 { hist_trim(updated2) } else { updated2 } + // Increased from 20 to 40 turns: consistent with handle_chat window expansion. + let trimmed: String = if json_array_len(updated2) > 40 { hist_trim(updated2) } else { updated2 } state_set(hist_key, trimmed) true } else { false } diff --git a/memory.el b/memory.el index eae12c6..684b4c4 100644 --- a/memory.el +++ b/memory.el @@ -35,14 +35,65 @@ 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 dummy: String = engram_scan_nodes_json(100, 0) - let total_nodes: 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 scan so they resist temporal decay. + // Canonical nodes encode foundational identity — they must not silently floor at 10. + let scan_result: String = engram_scan_nodes_json(50, 0) + let scan_len: Int = json_array_len(scan_result) + 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") + if str_eq(s_tier, "Canonical") && !str_eq(s_id, "") { + engram_strengthen(s_id) + let strengthened = strengthened + 1 + } + let si = si + 1 + } + + 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) + "}" + + ",\"total_edges\":" + int_to_str(total_edges) + + ",\"strengthened\":" + int_to_str(strengthened) + "}" } fn mem_save(path: String) -> Void {