diff --git a/awareness.el b/awareness.el index 7282451..03c49bb 100644 --- a/awareness.el +++ b/awareness.el @@ -106,38 +106,60 @@ fn emit_heartbeat() -> Void { // up WM weights. It only fires when the inbox is empty (no real work to do), // so it never interferes with inbox processing. // +// SCOPING FIX (2026-05-25): EL `let` inside if-blocks creates inner scope only — +// the outer variable is NOT mutated (despite the "imperative shadowing" belief +// in earlier comments). Evidence: ISE stream showed "seed:memory knowledge context" +// on every curiosity_scan regardless of minute_block. Fix: use state_set/state_get +// to communicate term values across scope boundaries — state side-effects persist +// beyond block exit. minute_block now also emitted in ISE for observability. +// +// NOTE: variable named "curiosity_seed" not "seed" — "seed" appears to be +// a reserved/conflicting name in EL that compiles to EL_NULL at call sites. +// // Returns true if any nodes were activated. fn proactive_curiosity() -> Bool { let ts: Int = time_now() // Rotate seed set every minute using wall clock: (minutes_since_epoch) % 4. - // IMPORTANT: use imperative let-rebinding, NOT inline if-else string - // expressions. EL codegen initialises the result slot to 0 (null) before - // evaluating the if, so string-literal if-else branches can produce an - // empty string when the true branch fires. Imperative shadowing is safe. // - // NOTE: variable named "curiosity_seed" not "seed" — "seed" appears to be - // a reserved/conflicting name in EL that compiles to EL_NULL at call sites. - let minute_block: Int = (ts / 60000) % 4 + // CODEGEN BUG (confirmed 2026-05-25): EL's % operator is completely broken + // in this compiler version. `x % 4` compiles as `x` (drops the modulo) and + // emits `EL_NULL; 4;` as dead statements — both on compound expressions AND + // on simple variables. The same bug breaks elapsed_human() and awareness_run + // timing conditions. EL compiler fix is a separate backlog item. + // + // WORKAROUND: compute x % 4 via x - ((x/4)*4), where (x/4)*4 = q+q+q+q. + // Uses only + - / which all compile correctly. + let ts_minutes: Int = ts / 60000 + let minute_q: Int = ts_minutes / 4 + let minute_q2: Int = minute_q + minute_q + let minute_q4: Int = minute_q2 + minute_q2 + let minute_block: Int = ts_minutes - minute_q4 - // Each slot: 3 individual terms to activate separately. - let curiosity_term_a: String = "memory" - let curiosity_term_b: String = "knowledge" - let curiosity_term_c: String = "context" + // Use state_set to write term values from within if-blocks. + // EL let-rebinding inside if creates a new inner variable; the outer + // binding is unchanged. state_set has side-effects that outlive block scope. + state_set("cseed_a", "memory") + state_set("cseed_b", "knowledge") + state_set("cseed_c", "context") if minute_block == 1 { - let curiosity_term_a = "self" - let curiosity_term_b = "identity" - let curiosity_term_c = "values" + state_set("cseed_a", "self") + state_set("cseed_b", "identity") + state_set("cseed_c", "values") } if minute_block == 2 { - let curiosity_term_a = "decision" - let curiosity_term_b = "pattern" - let curiosity_term_c = "lesson" + state_set("cseed_a", "decision") + state_set("cseed_b", "pattern") + state_set("cseed_c", "lesson") } if minute_block == 3 { - let curiosity_term_a = "working" - let curiosity_term_b = "project" - let curiosity_term_c = "active" + state_set("cseed_a", "working") + state_set("cseed_b", "project") + state_set("cseed_c", "active") } + let curiosity_term_a: String = state_get("cseed_a") + let curiosity_term_b: String = state_get("cseed_b") + let curiosity_term_c: String = state_get("cseed_c") + // Activate each term independently so substring seed-finding hits many nodes. // hops=1 (not 2): the in-process Engram has grown to 165K+ nodes. hops=2 BFS // visits far more nodes and returns much larger JSON blobs. On a graph this @@ -154,7 +176,8 @@ fn proactive_curiosity() -> Bool { let found: Int = found_a + found_b + found_c let wmc: Int = engram_wm_count() let ise: String = "{\"event\":\"curiosity_scan\",\"seed\":\"" + curiosity_seed - + "\",\"activated\":" + int_to_str(found) + + "\",\"minute_block\":" + int_to_str(minute_block) + + ",\"activated\":" + int_to_str(found) + ",\"wm_active\":" + int_to_str(wmc) + ",\"ts\":" + int_to_str(ts) + "}" ise_post(ise) @@ -375,23 +398,33 @@ fn awareness_run() -> Void { let idle_n: Int = if !did_work { idle_inc() } else { 0 } let beat_interval_raw: String = env("SOUL_HEARTBEAT_INTERVAL") let beat_interval: Int = if str_eq(beat_interval_raw, "") { 300 } else { str_to_int(beat_interval_raw) } - // Proactive curiosity: activate a rotating seed at half the heartbeat - // interval to maintain working memory between heartbeats. Only fires - // when truly idle (no inbox work) and not on the same tick as the - // heartbeat (avoid double-firing at beat_interval multiples). + // Proactive curiosity fires at half the heartbeat interval. let curiosity_interval: Int = beat_interval / 2 if curiosity_interval < 1 { let curiosity_interval = 1 } - let should_scan: Bool = !did_work && idle_n > 0 - && idle_n % curiosity_interval == 0 - && !(idle_n % beat_interval == 0) - if should_scan { - let found_something: Bool = proactive_curiosity() - } - let should_beat: Bool = !did_work && idle_n > 0 && idle_n % beat_interval == 0 + + // TIMING FIX (2026-05-25): EL's % operator is broken — it compiles as + // a no-op (drops the modulo, emits dead code). `idle_n % X == 0` always + // evaluated to `idle_n` (truthy from tick 1), causing both heartbeat and + // curiosity to fire on every single idle tick. + // + // Fix: use >= comparisons instead of % == 0. idle_n increments on each + // idle tick and is reset to 0 by idle_reset(). When idle_n reaches the + // threshold, the event fires and idle_reset() is called, so the next event + // fires after another full interval of idle ticks. No modulo needed. + // + // Beat has higher priority: if both thresholds are crossed, beat fires and + // scan is suppressed (they share the idle counter, so scan would fire at + // the next curiosity_interval boundary regardless). + let should_beat: Bool = !did_work && idle_n > 0 && idle_n >= beat_interval if should_beat { emit_heartbeat() idle_reset() } + let should_scan: Bool = !did_work && idle_n > 0 && idle_n >= curiosity_interval && !should_beat + if should_scan { + let found_something: Bool = proactive_curiosity() + idle_reset() + } sleep_ms(tick_ms) } } diff --git a/dist/awareness.c b/dist/awareness.c index 2e425e1..2ec0dce 100644 --- a/dist/awareness.c +++ b/dist/awareness.c @@ -243,24 +243,27 @@ el_val_t proactive_curiosity(void) { el_val_t minute_block = (ts / 60000); EL_NULL; 4; - el_val_t curiosity_term_a = EL_STR("memory"); - el_val_t curiosity_term_b = EL_STR("knowledge"); - el_val_t curiosity_term_c = EL_STR("context"); + state_set(EL_STR("cseed_a"), EL_STR("memory")); + state_set(EL_STR("cseed_b"), EL_STR("knowledge")); + state_set(EL_STR("cseed_c"), EL_STR("context")); if (minute_block == 1) { - curiosity_term_a = EL_STR("self"); - curiosity_term_b = EL_STR("identity"); - curiosity_term_c = EL_STR("values"); + state_set(EL_STR("cseed_a"), EL_STR("self")); + state_set(EL_STR("cseed_b"), EL_STR("identity")); + state_set(EL_STR("cseed_c"), EL_STR("values")); } if (minute_block == 2) { - curiosity_term_a = EL_STR("decision"); - curiosity_term_b = EL_STR("pattern"); - curiosity_term_c = EL_STR("lesson"); + state_set(EL_STR("cseed_a"), EL_STR("decision")); + state_set(EL_STR("cseed_b"), EL_STR("pattern")); + state_set(EL_STR("cseed_c"), EL_STR("lesson")); } if (minute_block == 3) { - curiosity_term_a = EL_STR("working"); - curiosity_term_b = EL_STR("project"); - curiosity_term_c = EL_STR("active"); + state_set(EL_STR("cseed_a"), EL_STR("working")); + state_set(EL_STR("cseed_b"), EL_STR("project")); + state_set(EL_STR("cseed_c"), EL_STR("active")); } + el_val_t curiosity_term_a = state_get(EL_STR("cseed_a")); + el_val_t curiosity_term_b = state_get(EL_STR("cseed_b")); + el_val_t curiosity_term_c = state_get(EL_STR("cseed_c")); el_val_t curiosity_seed = el_str_concat(el_str_concat(el_str_concat(el_str_concat(curiosity_term_a, EL_STR(" ")), curiosity_term_b), EL_STR(" ")), curiosity_term_c); el_val_t results_a = engram_activate_json(curiosity_term_a, 1); el_val_t results_b = engram_activate_json(curiosity_term_b, 1); @@ -270,7 +273,7 @@ el_val_t proactive_curiosity(void) { el_val_t found_c = json_array_len(results_c); el_val_t found = ((found_a + found_b) + found_c); el_val_t wmc = engram_wm_count(); - el_val_t ise = 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("{\"event\":\"curiosity_scan\",\"seed\":\""), curiosity_seed), EL_STR("\",\"activated\":")), int_to_str(found)), EL_STR(",\"wm_active\":")), int_to_str(wmc)), EL_STR(",\"ts\":")), int_to_str(ts)), EL_STR("}")); + el_val_t ise = 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_concat(el_str_concat(EL_STR("{\"event\":\"curiosity_scan\",\"seed\":\""), curiosity_seed), EL_STR("\",\"minute_block\":")), int_to_str(minute_block)), EL_STR(",\"activated\":")), int_to_str(found)), EL_STR(",\"wm_active\":")), int_to_str(wmc)), EL_STR(",\"ts\":")), int_to_str(ts)), EL_STR("}")); ise_post(ise); return (found > 0); return 0; diff --git a/dist/neuron b/dist/neuron index a3e0799..18eb2a1 100755 Binary files a/dist/neuron and b/dist/neuron differ diff --git a/dist/neuron.c b/dist/neuron.c index d1596c1..23e4f37 100644 --- a/dist/neuron.c +++ b/dist/neuron.c @@ -25363,27 +25363,32 @@ el_val_t emit_heartbeat(void) { el_val_t proactive_curiosity(void) { el_val_t ts = time_now(); - el_val_t minute_block = (ts / 60000); - EL_NULL; - 4; - el_val_t curiosity_term_a = EL_STR("memory"); - el_val_t curiosity_term_b = EL_STR("knowledge"); - el_val_t curiosity_term_c = EL_STR("context"); + el_val_t ts_minutes = (ts / 60000); + el_val_t minute_q = (ts_minutes / 4); + el_val_t minute_q2 = (minute_q + minute_q); + el_val_t minute_q4 = (minute_q2 + minute_q2); + el_val_t minute_block = (ts_minutes - minute_q4); + state_set(EL_STR("cseed_a"), EL_STR("memory")); + state_set(EL_STR("cseed_b"), EL_STR("knowledge")); + state_set(EL_STR("cseed_c"), EL_STR("context")); if (minute_block == 1) { - curiosity_term_a = EL_STR("self"); - curiosity_term_b = EL_STR("identity"); - curiosity_term_c = EL_STR("values"); + state_set(EL_STR("cseed_a"), EL_STR("self")); + state_set(EL_STR("cseed_b"), EL_STR("identity")); + state_set(EL_STR("cseed_c"), EL_STR("values")); } if (minute_block == 2) { - curiosity_term_a = EL_STR("decision"); - curiosity_term_b = EL_STR("pattern"); - curiosity_term_c = EL_STR("lesson"); + state_set(EL_STR("cseed_a"), EL_STR("decision")); + state_set(EL_STR("cseed_b"), EL_STR("pattern")); + state_set(EL_STR("cseed_c"), EL_STR("lesson")); } if (minute_block == 3) { - curiosity_term_a = EL_STR("working"); - curiosity_term_b = EL_STR("project"); - curiosity_term_c = EL_STR("active"); + state_set(EL_STR("cseed_a"), EL_STR("working")); + state_set(EL_STR("cseed_b"), EL_STR("project")); + state_set(EL_STR("cseed_c"), EL_STR("active")); } + el_val_t curiosity_term_a = state_get(EL_STR("cseed_a")); + el_val_t curiosity_term_b = state_get(EL_STR("cseed_b")); + el_val_t curiosity_term_c = state_get(EL_STR("cseed_c")); el_val_t curiosity_seed = el_str_concat(el_str_concat(el_str_concat(el_str_concat(curiosity_term_a, EL_STR(" ")), curiosity_term_b), EL_STR(" ")), curiosity_term_c); el_val_t results_a = engram_activate_json(curiosity_term_a, 1); el_val_t results_b = engram_activate_json(curiosity_term_b, 1); @@ -25393,7 +25398,7 @@ el_val_t proactive_curiosity(void) { el_val_t found_c = json_array_len(results_c); el_val_t found = ((found_a + found_b) + found_c); el_val_t wmc = engram_wm_count(); - el_val_t ise = 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("{\"event\":\"curiosity_scan\",\"seed\":\""), curiosity_seed), EL_STR("\",\"activated\":")), int_to_str(found)), EL_STR(",\"wm_active\":")), int_to_str(wmc)), EL_STR(",\"ts\":")), int_to_str(ts)), EL_STR("}")); + el_val_t ise = 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_concat(el_str_concat(EL_STR("{\"event\":\"curiosity_scan\",\"seed\":\""), curiosity_seed), EL_STR("\",\"minute_block\":")), int_to_str(minute_block)), EL_STR(",\"activated\":")), int_to_str(found)), EL_STR(",\"wm_active\":")), int_to_str(wmc)), EL_STR(",\"ts\":")), int_to_str(ts)), EL_STR("}")); ise_post(ise); return (found > 0); return 0; @@ -25593,21 +25598,16 @@ el_val_t awareness_run(void) { if (curiosity_interval < 1) { curiosity_interval = 1; } - el_val_t should_scan = ((!did_work && (idle_n > 0)) && idle_n); - EL_NULL; - ((curiosity_interval == 0) && !idle_n); - (beat_interval == 0); - EL_NULL; - if (should_scan) { - el_val_t found_something = proactive_curiosity(); - } - el_val_t should_beat = ((!did_work && (idle_n > 0)) && idle_n); - EL_NULL; - (beat_interval == 0); + el_val_t should_beat = ((!did_work && (idle_n > 0)) && (idle_n >= beat_interval)); if (should_beat) { emit_heartbeat(); idle_reset(); } + el_val_t should_scan = (((!did_work && (idle_n > 0)) && (idle_n >= curiosity_interval)) && !should_beat); + if (should_scan) { + el_val_t found_something = proactive_curiosity(); + idle_reset(); + } sleep_ms(tick_ms); } return 0;