self-review 2026-05-29: fix dampening floor and cleanup route_create_node auto-link

Two changes:

1. el_runtime.c — engram_activation_dampen(): add floor of 0.35.
   ISE nodes with ac=900+ had dampen=0.128, giving effective salience=0.038
   which fell below the epist>=0.1 gate in engram_activate. This silently
   killed curiosity seeds "self identity values" and "decision pattern lesson"
   — the only corpus matches were high-ac ISEs that were then excluded from
   results, causing activated=0 on 50% of proactive_curiosity scans.
   Floor at 0.35 keeps salience=0.3 nodes at effective_bg=0.105, above the
   visibility threshold, without disrupting relative ordering of content nodes.

2. server.el — route_create_node: replace stale inline auto-link with
   auto_link_content_node(). The inline logic used the old engram_search_json
   (substring, no ISE filter) while the better BM25-based auto_link_content_node
   was added in 2026-05-28 and wired to /api/neuron/* routes but not to the
   raw /api/nodes POST path. Removes ~40 lines of duplicated logic.
This commit is contained in:
2026-05-29 08:36:48 -05:00
parent a000599bfe
commit 412bd2744e
3 changed files with 20 additions and 103 deletions
+1 -49
View File
@@ -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;
}
+4 -52
View File
@@ -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) + "}"
}
+15 -2
View File
@@ -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.50.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