From 309c388b5b9597f659ec38216068a0cec3280adb Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Mon, 22 Jun 2026 13:34:05 -0500 Subject: [PATCH] fix(recall): address four remaining recall-reliability review issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. engram_numeric_valid: "0.0"/"0.00"/"00.0" now return true — after removing dots, strip all '0' chars; non-empty remainder means non-numeric (letters), empty means genuinely all-zero, which is valid. Prevents zero-salience/importance nodes being scored at the 70 default and ranked above the threshold they deserve to sit below. 2. Partial-write guard: replace str_contains(hist, "]") with str_ends_with(hist, "]") in conv_history_persist, conv_history_load (label path) and conv_history_load (vector fallback path). A truncated array whose *content* contains "]" (e.g. "item 1] item 2") no longer passes the guard. 3. Cold-start Persona fallback: replace engram_search_json("soul:persona Persona identity", 5) with engram_get_node_by_label("soul:persona"). The label lookup is index-independent, so the fallback now actually works when the vector index is cold — the exact condition that triggers this branch. 4. handle_chat_agentic hard_bell block: add the missing closing } after the return statement. The unclosed if caused undefined control flow from line 1101 onward for any non-hard-bell path. --- chat.el | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/chat.el b/chat.el index 595a5ad..7d022e5 100644 --- a/chat.el +++ b/chat.el @@ -29,9 +29,16 @@ fn engram_numeric_valid(s: String) -> Bool { let dot_count: Int = str_len(body) - str_len(no_dot) if dot_count > 1 { return false } if str_eq(no_dot, "") { return false } - // str_to_int on a letter-containing string returns 0; "0" is a valid zero. + // str_to_int on a letter-containing string returns 0; "0" and "00..." (e.g. from "0.0") + // are valid zeros. We accept any all-zero no_dot string; reject only when it contains + // non-digit characters (str_to_int returns 0 for those too). let parsed: Int = str_to_int(no_dot) - if parsed == 0 && !str_eq(no_dot, "0") { return false } + if parsed == 0 { + // Verify no_dot is truly all-digit-zeros, not a letter-contaminated string. + // Strip all '0' characters; if anything remains the string is non-numeric. + let stripped_zeros: String = str_replace(no_dot, "0", "") + if !str_eq(stripped_zeros, "") { return false } + } return true } @@ -180,10 +187,16 @@ fn engram_compile(intent: String) -> String { } else { println("[chat] engram_compile: WARN cold-index — activation and search returned no results for intent=" + str_slice(intent, 0, 60)) } - // Soul-agnostic fallback: search for Persona/Identity nodes by label. - let persona_fallback: String = engram_search_json("soul:persona Persona identity", 5) - let pf_ok: Bool = !str_eq(persona_fallback, "") && !str_eq(persona_fallback, "[]") - let combined: String = if pf_ok { engram_compile_ranked(persona_fallback, 3) } else { "" } + // Soul-agnostic fallback: fetch the Persona node by label — immune to cold vector index. + // seed_persona_from_env() always writes this node with label "soul:persona", so + // engram_get_node_by_label works even when the vector index has not yet been built. + // Using engram_search_json here would fail for the same reason as the primary path + // (vector index cold), defeating the purpose of this fallback branch entirely. + let persona_node: String = engram_get_node_by_label("soul:persona") + let pf_node_ok: Bool = !str_eq(persona_node, "") && !str_eq(persona_node, "null") + let persona_arr: String = if pf_node_ok { "[" + persona_node + "]" } else { "" } + let pf_ok: Bool = pf_node_ok + let combined: String = if pf_ok { engram_compile_ranked(persona_arr, 1) } else { "" } if str_eq(combined, "") { println("[chat] engram_compile: WARN cold-start fallback also empty — LLM has no episodic context") } @@ -431,9 +444,11 @@ fn conv_history_persist(hist: String) -> Void { if str_eq(hist, "") { return "" } if str_eq(hist, "[]") { return "" } // Partial-write guard: refuse to persist a blob that is not a complete JSON array. - // A truncated write starting with '[' but missing ']' would overwrite a good node. + // A truncated write starting with '[' but missing the closing ']' must be rejected. + // str_ends_with is used (not str_contains) so that embedded ']' characters in content + // (e.g. "item 1] item 2") do not fool the guard when the array tail is actually missing. if !str_starts_with(hist, "[") { return "" } - if !str_contains(hist, "]") { return "" } + if !str_ends_with(hist, "]") { return "" } let tags: String = "[\"conv-history\",\"persistent\"]" let node_id: String = engram_node_full( hist, "Conversation", "conv:history", @@ -455,7 +470,7 @@ fn conv_history_load() -> String { let label_ok: Bool = !str_eq(label_node, "") && !str_eq(label_node, "null") if label_ok { let label_content: String = json_get(label_node, "content") - let label_valid: Bool = str_starts_with(label_content, "[") && str_contains(label_content, "]") + let label_valid: Bool = str_starts_with(label_content, "[") && str_ends_with(label_content, "]") if label_valid { return label_content } @@ -471,8 +486,9 @@ fn conv_history_load() -> String { if str_eq(results, "[]") { return "" } let node: String = json_array_get(results, 0) let content: String = json_get(node, "content") - // Partial-write guard: require both '[' prefix AND ']' presence. - if !str_starts_with(content, "[") || !str_contains(content, "]") { + // Partial-write guard: require both '[' prefix AND closing ']' at the tail. + // str_ends_with guards against embedded ']' in content fooling the check. + if !str_starts_with(content, "[") || !str_ends_with(content, "]") { println("[chat] conv_history_load: vector search result content invalid — treating as first turn") state_set("conv_history_load_failed", "1") return "" @@ -1096,7 +1112,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 }