fix(engram-scoring): correct relevance denominator, hard_bell brace, threshold

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).
This commit is contained in:
2026-06-22 13:35:00 -05:00
parent 76c2e47d0f
commit 96d6bef0c2
+15 -8
View File
@@ -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 score1).
// 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 }