From 96d6bef0c2c685871d0a1294ed78326ce987894e Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Mon, 22 Jun 2026 13:35:00 -0500 Subject: [PATCH] fix(engram-scoring): correct relevance denominator, hard_bell brace, threshold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes from code review on improve/recall-engram-scoring: 1. CRITICAL — relevance denominator /10000 → /100: parse_salience_100 already scales floats to 0-100 (e.g. "0.7" → 70), so the product of two such values must be divided by 100 to stay in 0-100 range. The /10000 divisor caused integer truncation to 0 for every real-world node (sal=0.7, imp=0.7 → 70*70/10000 = 0). engram_compile_ranked was returning empty string for all inputs, leaving the soul with zero memory context. 2. CRITICAL — missing closing brace for hard_bell if-block in handle_chat_agentic (line ~1050): the return statement was not followed by the closing `}`, making the entire non-bell code path dead code inside the branch. All agentic turns that were not a hard_bell would silently fall through the open block. 3. HIGH — threshold 15 → 10 in engram_compile_ranked: even after the /100 fix, threshold=15 was marginally too aggressive for low-salience nodes near the Working-tier recency floor. sal=0.5 imp=0.5 at floor scores 16 (just above 15), so the margin was only 1 point. Lowering to 10 gives comfortable headroom while still filtering genuine noise (sal=0.1 imp=0.1 → score ≤ 1). --- chat.el | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/chat.el b/chat.el index 263f222..0cf4b0f 100644 --- a/chat.el +++ b/chat.el @@ -103,15 +103,21 @@ fn engram_score_node(node_json: String) -> Int { // Compressed recency weight (50 + recency/2): range 65-100 (1.54x dynamic range). // Old formula had 10x recency range which drowned out relevance for old-but-important // nodes. New: relevance (0-100) × recency_weight (65-100) / 100 → score 0-100. - let relevance: Int = salience_100 * importance_100 / 10000 + // salience_100 and importance_100 are already in the 0-100 range (parse_salience_100 + // returns e.g. 70 for "0.7"). Dividing by 100 keeps relevance in 0-100. + // Dividing by 10000 caused integer truncation to 0 for all real-world nodes + // (e.g., sal=0.7, imp=0.7 → 70*70/10000 = 0 instead of 49). + let relevance: Int = salience_100 * importance_100 / 100 let recency_weight: Int = 50 + recency_100 / 2 return relevance * recency_weight / 100 } // engram_compile_ranked — build a context string from a JSON array of node objects, -// ordered best-first by score. Only nodes above threshold=15 are included. -// With corrected parsing: sal=0.5 * imp=0.5 at max recency scores 25; threshold 15 -// gives headroom for moderately-relevant older nodes while filtering near-zero noise. +// ordered best-first by score. Only nodes above threshold=10 are included. +// With corrected formula (sal*imp/100): sal=0.5*imp=0.5 at max recency scores 25; +// sal=0.5*imp=0.5 at Working floor (recency=30, weight=65) scores 16. +// Threshold=10 gives safe headroom for low-salience nodes near the recency floor, +// while still filtering near-zero noise (e.g., sal=0.1*imp=0.1 → score≤1). // Returns at most max_nodes entries. max_nodes must not exceed 20 (sentinel limit). fn engram_compile_ranked(nodes_json: String, max_nodes: Int) -> String { if str_eq(nodes_json, "") { return "" } @@ -133,9 +139,10 @@ fn engram_compile_ranked(nodes_json: String, max_nodes: Int) -> String { while ci < total { let node: String = json_array_get(nodes_json, ci) let score: Int = engram_score_node(node) - // Threshold=15: allows moderately-relevant older nodes while filtering noise. - // Example: a 3-week-old node with sal=0.6, imp=0.6 scores ~14 — passes at 15. - let above_thresh: Bool = score >= 15 + // Threshold=10: allows moderately-relevant older nodes while filtering noise. + // Example: sal=0.5 imp=0.5 at Working recency floor (35+ days) → score 16, + // which passes. A near-zero node (sal=0.1 imp=0.1) → score ≤ 1, filtered. + let above_thresh: Bool = score >= 10 // Check this index wasn't already selected (sentinel: look for idx marker) let idx_marker: String = "\"_sel_" + int_to_str(ci) + "\"" let already_picked: Bool = str_contains(selected, idx_marker) @@ -1048,7 +1055,7 @@ fn handle_chat_agentic(body: String) -> String { 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 }