feat(recall): temporal-precision
This commit is contained in:
@@ -46,6 +46,8 @@ fn engram_score_node(node_json: String) -> Int {
|
|||||||
let salience_str: String = json_get(node_json, "salience")
|
let salience_str: String = json_get(node_json, "salience")
|
||||||
let importance_str: String = json_get(node_json, "importance")
|
let importance_str: String = json_get(node_json, "importance")
|
||||||
let created_str: String = json_get(node_json, "created_at")
|
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.
|
// 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).
|
// 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 } }
|
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 now_ts: Int = time_now()
|
||||||
let recency_100: Int = if !engram_numeric_valid(created_str) { 50 } else {
|
let recency_100: Int = if !engram_numeric_valid(created_str) { 50 } else {
|
||||||
let created_ts: Int = str_to_int(created_str)
|
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 }
|
if decay < 10 { 10 } else { decay }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combined score 0-1000000 (no floats): salience * importance * recency / 10000
|
|
||||||
return salience_100 * importance_100 * recency_100 / 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_nodes: String = engram_search_json("bell:soft bell:hard BellEvent", 3)
|
||||||
let bell_ok: Bool = !str_eq(bell_nodes, "") && !str_eq(bell_nodes, "[]")
|
let bell_ok: Bool = !str_eq(bell_nodes, "") && !str_eq(bell_nodes, "[]")
|
||||||
let now_ts: Int = time_now()
|
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 recent_bell: String = if bell_ok {
|
||||||
let bn0: String = json_array_get(bell_nodes, 0)
|
let bn0: String = json_array_get(bell_nodes, 0)
|
||||||
let bn_content: String = json_get(bn0, "content")
|
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 discard_hist: Bool = if !str_eq(reply_text, "") {
|
||||||
let updated: String = hist_append(agentic_hist, "user", message)
|
let updated: String = hist_append(agentic_hist, "user", message)
|
||||||
let updated2: String = hist_append(updated, "assistant", reply_text)
|
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)
|
state_set(hist_key, trimmed)
|
||||||
true
|
true
|
||||||
} else { false }
|
} else { false }
|
||||||
|
|||||||
@@ -35,14 +35,65 @@ fn mem_forget(node_id: String) -> Void {
|
|||||||
engram_forget(node_id)
|
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 {
|
fn mem_consolidate() -> String {
|
||||||
let scanned: Int = engram_node_count()
|
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 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)
|
return "{\"scanned\":" + int_to_str(scanned)
|
||||||
+ ",\"total_nodes\":" + int_to_str(total_nodes)
|
+ ",\"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 {
|
fn mem_save(path: String) -> Void {
|
||||||
|
|||||||
Reference in New Issue
Block a user