diff --git a/engram/dist/engram.c b/engram/dist/engram.c index a9f7599..d41693c 100644 --- a/engram/dist/engram.c +++ b/engram/dist/engram.c @@ -354,55 +354,7 @@ el_val_t route_create_node(el_val_t method, el_val_t path, el_val_t body) { salience = el_from_float(0.5); } el_val_t id = engram_node(content, node_type, salience); - el_val_t auto_linked = 0; - el_val_t clen = str_len(content); - if (clen >= 20) { - el_val_t sp1 = str_index_of(content, EL_STR(" ")); - el_val_t w1end = sp1; - if (sp1 < 0) { - w1end = clen; - } - el_val_t word1 = str_slice(content, 0, w1end); - el_val_t search_term = EL_STR(""); - if (str_len(word1) >= 5) { - search_term = word1; - } - if (str_eq(search_term, EL_STR(""))) { - if (sp1 >= 0) { - el_val_t rest = str_slice(content, (sp1 + 1), clen); - el_val_t sp2 = str_index_of(rest, EL_STR(" ")); - el_val_t w2end = sp2; - if (sp2 < 0) { - w2end = str_len(rest); - } - el_val_t word2 = str_slice(rest, 0, w2end); - if (str_len(word2) >= 5) { - search_term = word2; - } - } - } - if (!str_eq(search_term, EL_STR(""))) { - el_val_t results = engram_search_json(search_term, 10); - el_val_t n = json_array_len(results); - el_val_t i = 0; - while (i < n) { - if (auto_linked >= 5) { - i = n; - } - if (auto_linked < 5) { - el_val_t elem = json_array_get(results, i); - el_val_t rid = json_get_string(elem, EL_STR("id")); - if (!str_eq(rid, EL_STR(""))) { - if (!str_eq(rid, id)) { - engram_connect(id, rid, el_from_float(0.5), EL_STR("related")); - auto_linked = (auto_linked + 1); - } - } - i = (i + 1); - } - } - } - } + el_val_t auto_linked = auto_link_content_node(id, content); return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), id), EL_STR("\",\"content\":\"")), content), EL_STR("\",\"node_type\":\"")), node_type), EL_STR("\",\"auto_linked\":")), int_to_str(auto_linked)), EL_STR("}")); return 0; } diff --git a/engram/src/server.el b/engram/src/server.el index 0c7b698..1c767eb 100644 --- a/engram/src/server.el +++ b/engram/src/server.el @@ -366,58 +366,10 @@ fn route_create_node(method: String, path: String, body: String) -> String { if salience == 0.0 { let salience = 0.5 } let id: String = engram_node(content, node_type, salience) - // Auto-link: find semantically related existing nodes and form edges. - // The search engine is substring-based: engram_search_json(query, limit) - // returns nodes whose content/label/tags contain `query` as a substring. - // Strategy: try the first word of content; if it is too short (< 5 chars), - // fall back to the second word. Connect the top 5 unique matches (no self). - let auto_linked: Int = 0 - let clen: Int = str_len(content) - if clen >= 20 { - // Locate first and second spaces to extract first two words. - let sp1: Int = str_index_of(content, " ") - let w1end: Int = sp1 - if sp1 < 0 { let w1end = clen } - let word1: String = str_slice(content, 0, w1end) - - // Pick the search term: use word1 if >= 5 chars, else try word2. - let search_term: String = "" - if str_len(word1) >= 5 { - let search_term = word1 - } - if str_eq(search_term, "") { - if sp1 >= 0 { - let rest: String = str_slice(content, sp1 + 1, clen) - let sp2: Int = str_index_of(rest, " ") - let w2end: Int = sp2 - if sp2 < 0 { let w2end = str_len(rest) } - let word2: String = str_slice(rest, 0, w2end) - if str_len(word2) >= 5 { - let search_term = word2 - } - } - } - - if !str_eq(search_term, "") { - let results: String = engram_search_json(search_term, 10) - let n: Int = json_array_len(results) - let i: Int = 0 - while i < n { - if auto_linked >= 5 { let i = n } - if auto_linked < 5 { - let elem: String = json_array_get(results, i) - let rid: String = json_get_string(elem, "id") - if !str_eq(rid, "") { - if !str_eq(rid, id) { - engram_connect(id, rid, 0.5, "related") - let auto_linked = auto_linked + 1 - } - } - let i = i + 1 - } - } - } - } + // Auto-link via BM25 search — reuse auto_link_content_node which skips + // ISE nodes and links to up to 3 semantically related non-ISE nodes. + // Replaces the old inline substring-search auto-link (2026-05-29 cleanup). + let auto_linked: Int = auto_link_content_node(id, content) "{\"id\":\"" + id + "\",\"content\":\"" + content + "\",\"node_type\":\"" + node_type + "\",\"auto_linked\":" + int_to_str(auto_linked) + "}" } diff --git a/lang/releases/v1.0.0-20260501/el_runtime.c b/lang/releases/v1.0.0-20260501/el_runtime.c index 4696c5f..2314047 100644 --- a/lang/releases/v1.0.0-20260501/el_runtime.c +++ b/lang/releases/v1.0.0-20260501/el_runtime.c @@ -6603,9 +6603,22 @@ static double engram_temporal_decay(const EngramNode* n, int64_t now_ms) { /* Activation dampening: high activation_count nodes are "well-known" context * and get less marginal boost per firing. - * count=0 → 1.0, count=2 → ~0.74, count=9 → ~0.59, count=99 → ~0.43 */ + * count=0 → 1.0, count=2 → ~0.74, count=9 → ~0.59, count=99 → ~0.43 + * + * FLOOR (2026-05-29 self-review): without a floor, ISE infrastructure nodes + * (salience=0.3, activation_count=900+) reach effective_salience=0.038, which + * falls below the epist>=0.1 visibility gate in engram_activate's result filter. + * This made entire curiosity seed sets permanently invisible — "self identity + * values" and "decision pattern lesson" reported activated=0 on every scan + * because the only corpus matches were high-ac ISE nodes that were then excluded. + * + * Fix: clamp dampen to a minimum of 0.35. At salience=0.3, this gives + * effective_bg = 0.105, which clears the 0.1 epist threshold. The floor is + * intentionally low enough that high-salience content nodes (0.5–0.8) still + * dominate — it only saves infrastructure nodes from total invisibility. */ static double engram_activation_dampen(const EngramNode* n) { - return 1.0 / (1.0 + log(1.0 + (double)n->activation_count)); + double d = 1.0 / (1.0 + log(1.0 + (double)n->activation_count)); + return d < 0.35 ? 0.35 : d; } /* Temporal proximity bonus: boost propagation along edges connecting