self-review 2026-05-25: fix curiosity rotation and awareness_run timing
Three bugs fixed in awareness.el: 1. EL let-rebinding inside if-blocks creates inner scope only — outer variable unchanged after block exits. Curiosity seed terms were always "memory/knowledge/context" regardless of minute_block. Fix: state_set inside if-blocks, state_get after to retrieve selected values. 2. EL % operator completely broken in v1.0.0-20260501 — compiles as dead code (left operand assigned, modulo dropped). minute_block was always ts/60000 (a large int, never 0-3). Fix: arithmetic workaround: x%4 = x - (q+q+q+q) where q = x/4. 3. awareness_run idle_n % beat_interval == 0 also broken by same % bug — should_scan and should_beat fired every idle tick instead of every N ticks. Fix: idle_n >= interval comparisons with idle_reset() after firing, so the counter restarts cleanly after each event. EL % and * operators filed as P1 backlog item for elc compiler fix. Also adds minute_block field to curiosity_scan ISE for observability.
This commit is contained in:
+65
-32
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user