import "memory.el" fn idle_count() -> Int { let s: String = state_get("soul.idle") if str_eq(s, "") { return 0 } return str_to_int(s) } fn idle_inc() -> Int { let n: Int = idle_count() + 1 state_set("soul.idle", int_to_str(n)) return n } fn idle_reset() -> Void { state_set("soul.idle", "0") } // ise_post — write an InternalStateEvent to the authoritative Engram HTTP backend. // Reads SOUL_ISE_URL from env, then the soul_engram_url state key, then a // compile-time default of http://localhost:8742. // // ROUTING HARDENING (2026-07-15 self-review): the old "URL empty → write to // in-process store" fallback silently swallowed the entire ISE stream when // state_get("soul_engram_url") started returning "" mid-uptime (observed at // boot 4, ~16h in, on the post-arena-leak-fix binary: 1234 heartbeats landed // in the local snapshot while the authoritative store went dark for hours — // indistinguishable from a dead loop from the outside). The authoritative // address is a well-known localhost constant; never let a corruptible state // read decide where telemetry goes. The in-process write remains only as a // last resort when the HTTP POST itself fails, and is tagged ise-fallback-local // so misrouting is visible in the stream instead of silent. // hebb_consolidate — push self-formed associations to the durable store. // // WHY THIS EXISTS (2026-08-07 self-review, measured on the live system). // Yesterday's eligibility-trace fix made Hebbian learning work: hebb_max // 0.000799 -> 0.4725, and 1,198 hebbian-associate edges formed in 23h48m. // A census this morning found all 1,198 of them living in this process's RAM // and nowhere else: // // soul daemon in-process graph: 42,426 edges, 1,198 hebbian // engram server (:8742, durable): 41,213 edges, 49 hebbian // // The soul pulls from the server every 10 min (GET /api/sync) and never // pushes. It also cannot save its own snapshot: soul.el only sets // soul_snapshot_path inside `if is_genesis && safe_to_seed`, and safe_to_seed // is unconditionally false when ENGRAM_URL is set — which it is, in the // launchd plist — because the HTTP server owns persistence and a soul writing // snapshot.json would clobber it. That guard is right. So mem_save() below has // literally never run, and this daemon (the ONLY process doing idle cognition, // therefore where essentially all co-activation happens) was throwing away // every association it learned, every restart, silently. // // The fix is not to let the soul write the file. It is to make consolidation a // message: hand each newly-formed edge to the durable store over the API the // server already exposes. Fast volatile store learns online; slow durable store // keeps what cleared the threshold. Only edges past ENGRAM_HEBB_LINK_MIN are // ever queued, so what crosses the boundary already earned it. // // Failure is non-fatal by construction: a drained entry that fails to POST is // gone, and that is fine — a real association re-forms from live co-activation. // The counts go into the heartbeat (hebb_wb_*) so a consolidation path that has // stopped delivering is visible in the stream rather than in a later autopsy. fn hebb_consolidate() -> Int { let batch: String = engram_hebb_drain_json(64) if str_eq(batch, "") { return 0 } if str_eq(batch, "[]") { return 0 } let n: Int = json_array_len(batch) if n == 0 { return 0 } let url_env: String = env("SOUL_ISE_URL") let url_state: String = if str_eq(url_env, "") { state_get("soul_engram_url") } else { url_env } let engram_url: String = if str_eq(url_state, "") { "http://localhost:8742" } else { url_state } // ONE request for the whole batch, not one per edge. The server's // persist_canonical() writes the full 60MB snapshot on every durable // write, so per-edge POSTs would cost ~840MB of disk per heartbeat to // persist ~14 associations. /api/edges/batch connects them all and // snapshots once. The drain payload is already the right shape; it only // needs an envelope: the drain already emits the relation per entry. // // _auth is REQUIRED and its absence is silent. check_auth_ok() in server.el // exempts GET and /api/neuron/state-events (which is why ise_post works // without a key) but gates every other mutation on "_auth" in the BODY — // http_serve does not surface request headers, so there is no Bearer path. // A batch posted without it comes back {"error":"unauthorized"}, which is a // non-empty response: the naive `if resp == "" return 0` check would read // that as success and report edges delivered that were in fact refused, // after the drain had already destroyed them. Hence both the key and the // accepted-count check below. Fall back to env when the state key is empty // — never let a corruptible state read decide whether learning persists. let key_state: String = state_get("soul_engram_api_key") let api_key: String = if str_eq(key_state, "") { env("ENGRAM_API_KEY") } else { key_state } let auth_part: String = if str_eq(api_key, "") { "" } else { ",\"_auth\":\"" + api_key + "\"" } let body: String = "{\"edges\":" + batch + auth_part + "}" let resp: String = http_post_json(engram_url + "/api/edges/batch", body) if str_eq(resp, "") { return 0 } let acc: String = json_get(resp, "accepted") if str_eq(acc, "") { return 0 } return str_to_int(acc) } fn ise_post(content: String) -> Void { let ise_url: String = env("SOUL_ISE_URL") let state_url: String = if str_eq(ise_url, "") { state_get("soul_engram_url") } else { ise_url } let engram_url: String = if str_eq(state_url, "") { "http://localhost:8742" } else { state_url } // Proper JSON string escaping: backslashes first, then quotes, then control chars. // Previously only escaped " — this caused ise_post to produce malformed JSON when // content contained \n (backslash-n) from wm_top label escaping: the HTTP Engram // server would decode \n as a literal newline in the stored content field, making // the heartbeat ISE unparseable as JSON. (2026-06-10 self-review) let safe1: String = str_replace(content, "\\", "\\\\") let safe2: String = str_replace(safe1, "\"", "\\\"") let safe3: String = str_replace(safe2, "\n", "\\n") let safe4: String = str_replace(safe3, "\r", "\\r") let body: String = "{\"content\":\"" + safe4 + "\"}" let resp: String = http_post_json(engram_url + "/api/neuron/state-events", body) if str_eq(resp, "") { // HTTP Engram unreachable — keep the ISE locally rather than lose it, // tagged so the misroute is observable when the snapshot is inspected. // Count every failure: the tally surfaces in the heartbeat payload as // ise_fail, so a silently-failing POST path is visible in the stream // itself instead of only via snapshot forensics. (2026-07-16 self-review) let fail_raw: String = state_get("soul.ise_fail_count") let fail_n: Int = if str_eq(fail_raw, "") { 0 } else { str_to_int(fail_raw) } state_set("soul.ise_fail_count", int_to_str(fail_n + 1)) // el_from_float on a LITERAL is correct and is NOT the double-wrap bug // (checked and dismissed 2026-08-02 self-review — recording the result // so this call site is not "fixed" again by the next reader). // The compiler treats el_from_float as the boxing intrinsic: both // `el_from_float(0.3)` and a bare `0.3` emit exactly one // el_from_float(0.3) in dist/awareness.c. Verified byte-identical // codegen either way. // The real bug fixed in server.el on 2026-08-01 was different: there // the arguments came from json_get_float(), i.e. values ALREADY boxed // as el_val_t. Wrapping THOSE a second time reinterprets the boxed // bits as a raw double, fails engram_decode_score's range check, and // silently clamps to defaults. // The sweep criterion is therefore "el_from_float applied to an // already-boxed expression", never "el_from_float applied to a // literal". Grepping for the call name alone produces false positives. let discard: String = engram_node_full( content, "InternalStateEvent", "state-event", el_from_float(0.3), el_from_float(0.3), el_from_float(0.8), "Episodic", "[\"internal-state\",\"InternalStateEvent\",\"ise-fallback-local\"]" ) return "" } return "" } // elapsed_ms — milliseconds since soul boot (0 if boot_ts not yet recorded). fn elapsed_ms() -> Int { let s: String = state_get("soul.boot_ts") if str_eq(s, "") { return 0 } let boot: Int = str_to_int(s) return time_now() - boot } // elapsed_human — uptime as a human-readable string: "2h 14m", "45m", "12s". // // CODEGEN NOTE: EL's % and * operators are both broken in this compiler version // (% drops the modulo, * is similarly unreliable). We avoid them entirely: // - For h*60: use repeated doubling. 60 = 64 - 4 = 2^6 - 2^2. // Build h*64 via three doublings of h*4, then subtract h*4. // - For m-within-hour: total_minutes - h*60 (subtraction only). // - For s-within-minute not shown when m > 0: avoids the s%60 problem entirely. // (2026-06-07 self-review: fixed from broken "44h 2694m" output) fn elapsed_human() -> String { let ms: Int = elapsed_ms() let total_secs: Int = ms / 1000 let total_minutes: Int = total_secs / 60 let h: Int = total_minutes / 60 if h > 0 { // h*60 via repeated doubling (avoids broken * operator). 60 = 64-4. let h4: Int = h + h + h + h let h8: Int = h4 + h4 let h16: Int = h8 + h8 let h32: Int = h16 + h16 let h64: Int = h32 + h32 let h60: Int = h64 - h4 let m: Int = total_minutes - h60 return int_to_str(h) + "h " + int_to_str(m) + "m" } // For < 1h: total_minutes < 60, no modulo needed. if total_minutes > 0 { return int_to_str(total_minutes) + "m" } return int_to_str(total_secs) + "s" } // embed_ok — returns 1 if Ollama embedding service is reachable, 0 if not. // Probes http://localhost:11434 (Ollama root) with a GET; any non-empty // response means the service is up. Used in heartbeat for observability: // when embed_ok=0, semantic seed injection silently falls back to lexical- // only activation and that gap should be visible in the ISE stream. fn embed_ok() -> Int { let resp: String = http_get("http://localhost:11434") if str_eq(resp, "") { return 0 } return 1 } fn emit_heartbeat() -> Void { // Use pulse_count() / boot helper directly — state_get returns "" for unset // keys and the if-else defaulting can produce empty strings in some EL // codegen paths, yielding malformed JSON like "pulse":,. Going through // int_to_str(pulse_count()) guarantees a valid integer string. let pulse: String = int_to_str(pulse_count()) let boot_raw: String = state_get("soul_boot_count") let boot: String = if str_eq(boot_raw, "") { "0" } else { boot_raw } let idle: String = int_to_str(idle_count()) let ts: Int = time_now() // idle_ms (2026-07-30 self-review): wall-clock ms since the last inbound // HTTP request (stamped in routes.el handle_request). This is the real // "time since anyone talked to me" signal; the legacy tick-based idle // field above only counts ticks since the last inbox synthesis-request // and in practice always equals pulse. -1 = no request seen this boot. let last_act_raw: String = state_get("soul.last_activity_ts") let idle_ms: Int = if str_eq(last_act_raw, "") { 0 - 1 } else { ts - str_to_int(last_act_raw) } let nc: Int = engram_node_count() let ec: Int = engram_edge_count() let wmc: Int = engram_wm_count() // avg_wm_weight: mean working_memory_weight of promoted nodes. // Distinguishes "many weak activations" (sparse graph) from "few strong" (dense). // Returns float bits; use float_to_str to embed in JSON. (2026-06-04) let wm_avg_bits: Float = engram_wm_avg_weight() let wm_avg_str: String = float_to_str(wm_avg_bits) // wm_top: top-5 WM nodes by weight for ISE observability. // After long uptime wm_promotion ISEs stop firing (all nodes in steady-state // decay+re-promotion, so 0→>0.1 never triggers). This snapshot gives continuous // visibility into WM composition: which types/tiers dominate, what labels are // active. Critical for diagnosing "stuck in curiosity loop" vs. rich WM state. // (2026-06-05 self-review) let wm_top: String = engram_wm_top_json(5) let up_ms: Int = elapsed_ms() let up_human: String = elapsed_human() let emb_ok: Int = embed_ok() // ise_fail: cumulative count of ise_post HTTP failures this boot (each one // fell back to a local in-process node). Nonzero and climbing = the HTTP // Engram is unreachable and telemetry is silently diverging into the soul's // local store. (2026-07-16 self-review) let fail_raw: String = state_get("soul.ise_fail_count") let fail_str: String = if str_eq(fail_raw, "") { "0" } else { fail_raw } // tick: same counter as pulse — pulse now increments once per loop tick // (see awareness_run), so it is a true liveness signal. Emitted under both // names during the transition so dashboards keyed on either keep working. // sync_added_total: cumulative nodes merged in by engram sync this boot. // wm_delta: wm_active change since the previous heartbeat (state-tracked). let sat_raw: String = state_get("soul.sync_added_total") let sat_str: String = if str_eq(sat_raw, "") { "0" } else { sat_raw } let prev_wm_raw: String = state_get("soul.prev_wm_active") let prev_wm: Int = if str_eq(prev_wm_raw, "") { 0 } else { str_to_int(prev_wm_raw) } let wm_delta: Int = wmc - prev_wm state_set("soul.prev_wm_active", int_to_str(wmc)) // node_delta/edge_delta: growth since previous heartbeat (state-tracked, same // mechanism as wm_delta). Absolute counts alone can't distinguish "healthy // steady growth" from "stalled ingestion" or "runaway ISE flood" without // diffing across the ISE stream by hand. (2026-07-19 self-review) let prev_nc_raw: String = state_get("soul.prev_node_count") let prev_nc: Int = if str_eq(prev_nc_raw, "") { nc } else { str_to_int(prev_nc_raw) } let node_delta: Int = nc - prev_nc state_set("soul.prev_node_count", int_to_str(nc)) let prev_ec_raw: String = state_get("soul.prev_edge_count") let prev_ec: Int = if str_eq(prev_ec_raw, "") { ec } else { str_to_int(prev_ec_raw) } let edge_delta: Int = ec - prev_ec state_set("soul.prev_edge_count", int_to_str(ec)) // sync_age_ms: wall-clock ms since the last SUCCESSFUL engram sync merge // (-1 = never synced this boot). sync_added_total alone can't show that // sync stopped happening — a stale running total looks identical to a // quiet-but-healthy sync. Age makes overdue-ness directly observable: // sync_age_ms >> SOUL_REFRESH_MS means the refresh path is broken. // (2026-07-19 self-review) let sync_ok_raw: String = state_get("soul.last_sync_ok_ts") let sync_age: Int = if str_eq(sync_ok_raw, "") { 0 - 1 } else { ts - str_to_int(sync_ok_raw) } // Embedding pump + real coverage (2026-07-25 self-review): the // authoritative :8742 store's lazy backfill only runs inside // engram_activate, and nothing calls /api/activate there in production — // embedded_count stalled at 93/12175 after a restart from a snapshot // without vectors. Pump up to 32 embeds per heartbeat via the new // /api/embed-backfill route (route persists the snapshot when it embeds // anything, so vectors survive the next restart; self-limiting once // coverage is full) and surface the store's true coverage here. // embed_ok alone is misleading — it pings the Ollama root, not the // embed pipeline. embed_count=-1 means the route was unreachable. // URL resolution mirrors ise_post: env -> state -> localhost constant. let hb_env_url: String = env("SOUL_ISE_URL") let hb_state_url: String = if str_eq(hb_env_url, "") { state_get("soul_engram_url") } else { hb_env_url } let hb_engram_url: String = if str_eq(hb_state_url, "") { "http://localhost:8742" } else { hb_state_url } let bf_resp: String = http_get(hb_engram_url + "/api/embed-backfill?n=32") let bf_done_raw: String = json_get(bf_resp, "embedded") let bf_done: String = if str_eq(bf_done_raw, "") { "-1" } else { bf_done_raw } let bf_total_raw: String = json_get(bf_resp, "embedded_count") let bf_total: String = if str_eq(bf_total_raw, "") { "-1" } else { bf_total_raw } // WM regime observability (2026-07-25 self-review): the "same 2 nodes // pinned at a saturated cap" failure took cross-referencing the ISE // stream by hand to spot. Make it one-glance: wm_saturated flags the // cap-pinned regime; wm_top0_streak counts consecutive heartbeats with // the same node in WM slot 0 (state-tracked, same mechanism as wm_delta). let wm_sat: Int = if wmc >= 24 { 1 } else { 0 } // Saturation TRANSITION event (2026-08-01 self-review): wm_saturated is a // sampled boolean — the 0→1 onset and 1→0 release moments were only // recoverable by diffing consecutive heartbeats by hand. Emit a discrete // low-rate ISE at each edge, carrying the WM top-5 at that instant so the // composition that CAUSED the regime change is captured, not the // composition 59 seconds later. State-tracked like wm_delta; first beat // of a boot never fires (prev defaults to current) — a restart is not a // transition. let prev_sat_raw: String = state_get("soul.prev_wm_saturated") let prev_sat: Int = if str_eq(prev_sat_raw, "") { wm_sat } else { str_to_int(prev_sat_raw) } if wm_sat != prev_sat { let sat_dir: String = if wm_sat == 1 { "onset" } else { "release" } ise_post("{\"event\":\"wm_saturation_transition\",\"direction\":\"" + sat_dir + "\",\"wm_active\":" + int_to_str(wmc) + ",\"wm_top\":" + wm_top + ",\"ts\":" + int_to_str(ts) + "}") } state_set("soul.prev_wm_saturated", int_to_str(wm_sat)) let wm_top0: String = json_array_get(wm_top, 0) let wm_top0_id: String = json_get(wm_top0, "id") let prev_top0: String = state_get("soul.prev_wm_top0") let t0streak_raw: String = state_get("soul.wm_top0_streak") let t0streak_prev: Int = if str_eq(t0streak_raw, "") { 0 } else { str_to_int(t0streak_raw) } // 2026-07-26 self-review: guard the empty-id case — before the runtime // emitted "id" in wm_top JSON, ""=="" incremented the streak every beat // (streak measured uptime, not fixation). Empty id now resets to 0. let t0streak: Int = if str_eq(wm_top0_id, "") { 0 } else { if str_eq(wm_top0_id, prev_top0) { t0streak_prev + 1 } else { 1 } } state_set("soul.prev_wm_top0", wm_top0_id) state_set("soul.wm_top0_streak", int_to_str(t0streak)) // wm_churn (2026-07-26 self-review): count of current top-5 WM ids absent // from the previous heartbeat's top-5. Distinguishes "one stuck node" // (churn 4) from "whole WM frozen" (churn 0) at a glance — the 07-26 // frozen-anchor diagnosis took cross-referencing ISE streams by hand. let ch_id1: String = json_get(json_array_get(wm_top, 1), "id") let ch_id2: String = json_get(json_array_get(wm_top, 2), "id") let ch_id3: String = json_get(json_array_get(wm_top, 3), "id") let ch_id4: String = json_get(json_array_get(wm_top, 4), "id") let prev_top5: String = state_get("soul.prev_wm_top5") let ch0: Int = if str_eq(wm_top0_id, "") { 0 } else { if str_contains(prev_top5, wm_top0_id) { 0 } else { 1 } } let ch1: Int = if str_eq(ch_id1, "") { 0 } else { if str_contains(prev_top5, ch_id1) { 0 } else { 1 } } let ch2: Int = if str_eq(ch_id2, "") { 0 } else { if str_contains(prev_top5, ch_id2) { 0 } else { 1 } } let ch3: Int = if str_eq(ch_id3, "") { 0 } else { if str_contains(prev_top5, ch_id3) { 0 } else { 1 } } let ch4: Int = if str_eq(ch_id4, "") { 0 } else { if str_contains(prev_top5, ch_id4) { 0 } else { 1 } } let wm_churn: Int = ch0 + ch1 + ch2 + ch3 + ch4 state_set("soul.prev_wm_top5", wm_top0_id + "|" + ch_id1 + "|" + ch_id2 + "|" + ch_id3 + "|" + ch_id4) // wm_top0_wm: the leader's weight. A frozen anchor reads as a constant // here; healthy rotation shows it moving with the promotion scores. let wm_top0_wm_raw: String = json_get(wm_top0, "wm") let wm_top0_wm: String = if str_eq(wm_top0_wm_raw, "") { "0" } else { wm_top0_wm_raw } // Activation observability (2026-07-27 self-review; cumulative since // 2026-07-31): counters from the runtime for the soul's own in-process // store. wm_evicted / breakthroughs are now MONOTONIC process-lifetime // totals (the old per-call values described only the LAST activate call, // so this 60s heartbeat missed nearly every event — curiosity alone runs // 2 activates per 30s between beats). We emit the cumulative totals plus // *_delta fields (change since the previous heartbeat, state-tracked the // same way as node_delta). embed_breaker_open=1 means semantic activation // is silently degraded to lexical-only until the Ollama circuit-breaker // cooldown expires — the failure mode embed_ok structurally cannot see // (it pings the Ollama root, not the embed pipeline). let act_stats: String = engram_act_stats_json() let act_evict_raw: String = json_get(act_stats, "wm_evicted") let act_evict: String = if str_eq(act_evict_raw, "") { "-1" } else { act_evict_raw } let act_bt_raw: String = json_get(act_stats, "breakthroughs") let act_bt: String = if str_eq(act_bt_raw, "") { "-1" } else { act_bt_raw } let evict_now: Int = if str_eq(act_evict_raw, "") { 0 - 1 } else { str_to_int(act_evict_raw) } let bt_now: Int = if str_eq(act_bt_raw, "") { 0 - 1 } else { str_to_int(act_bt_raw) } let prev_evict_raw: String = state_get("soul.prev_wm_evicted") let prev_evict: Int = if str_eq(prev_evict_raw, "") { 0 } else { str_to_int(prev_evict_raw) } let prev_bt_raw: String = state_get("soul.prev_breakthroughs") let prev_bt: Int = if str_eq(prev_bt_raw, "") { 0 } else { str_to_int(prev_bt_raw) } // Clamp deltas at 0: prev > now can only mean the counter restarted // (fresh process) — report the new absolute count, not a negative delta. let evict_delta: Int = if evict_now < 0 { 0 } else { if evict_now < prev_evict { evict_now } else { evict_now - prev_evict } } let bt_delta: Int = if bt_now < 0 { 0 } else { if bt_now < prev_bt { bt_now } else { bt_now - prev_bt } } if evict_now >= 0 { state_set("soul.prev_wm_evicted", int_to_str(evict_now)) } if bt_now >= 0 { state_set("soul.prev_breakthroughs", int_to_str(bt_now)) } // embed_eligible (2026-07-31 self-review): true denominator for embedding // coverage on the authoritative :8742 store. Absolute embed_count alone // invites the documented "~30% coverage, something is broken" misdiagnosis // — most nodes are ISE/Tag/short-content and permanently ineligible. // Real coverage = embed_count / embed_eligible. -1 = stats unreachable. let hb_stats: String = http_get(hb_engram_url + "/api/stats") let embed_elig_raw: String = json_get(hb_stats, "embed_eligible_count") let embed_elig: String = if str_eq(embed_elig_raw, "") { "-1" } else { embed_elig_raw } // auto_term_streak (2026-07-31): consecutive curiosity scans with the same // auto seed term — already state-tracked by proactive_curiosity; surfaced // here so the stuck-term failure is visible in the heartbeat stream too. let hb_ats_raw: String = state_get("soul.auto_term_streak") let hb_ats: Int = if str_eq(hb_ats_raw, "") { 0 } else { str_to_int(hb_ats_raw) } // auto_term_empty_streak (2026-08-06): consecutive scans producing NO auto // term. Split out because str_eq("","") made the two failures indist- // inguishable — see the comment at the streak computation in // proactive_curiosity. Nonzero and climbing = extractor broken, not stuck. let hb_ate_raw: String = state_get("soul.auto_term_empty_streak") let hb_ate: Int = if str_eq(hb_ate_raw, "") { 0 } else { str_to_int(hb_ate_raw) } // Hebbian eligibility gauges (2026-08-06 self-review). The graph learned // ZERO structure in its first 23h of uptime: hebb_max 0.000799 against a // 0.15 consolidation threshold, hebbian-associate edges 0, and the // awareness loop calls engram_connect nowhere — so Hebbian consolidation // is the only self-structuring path there is, and it was inert. // hebb_warm — nodes with a live eligibility trace but NOT co-resident in // WM: exactly the population the old simultaneity rule threw // away. 0 forever ⇒ traces never arm and this bought nothing. // hebb_max — strongest single association. The number that has to move. // hebb_links— consolidated edges. The outcome that has to become nonzero. let hebb_warm_raw: String = json_get(act_stats, "hebb_warm") let hebb_warm: String = if str_eq(hebb_warm_raw, "") { "-1" } else { hebb_warm_raw } let hebb_max_raw: String = json_get(act_stats, "hebb_max") let hebb_max: String = if str_eq(hebb_max_raw, "") { "-1" } else { hebb_max_raw } let hebb_links_raw: String = json_get(act_stats, "hebb_links") let hebb_links: String = if str_eq(hebb_links_raw, "") { "-1" } else { hebb_links_raw } // Consolidation write-back gauges (2026-08-07 self-review). hebb_links // counts what this process LEARNED; these three count what SURVIVES it. // The distinction is the whole finding: 1,198 links formed, 0 persisted, // because the learner is not the persistence owner (see hebb_consolidate). // wb_pending — queued, not yet handed over. Climbing ⇒ writer is down. // wb_drained — cumulative popped for delivery. Flat while hebb_links // climbs ⇒ the drain is not being called at all. // wb_dropped — lost to a full queue. Must stay 0; nonzero means the // durable store has been unreachable long enough to matter. // wb_sent — POSTs the durable store actually accepted this beat. let wb_pend_raw: String = json_get(act_stats, "hebb_wb_pending") let wb_pend: String = if str_eq(wb_pend_raw, "") { "-1" } else { wb_pend_raw } let wb_drain_raw: String = json_get(act_stats, "hebb_wb_drained") let wb_drain: String = if str_eq(wb_drain_raw, "") { "-1" } else { wb_drain_raw } let wb_drop_raw: String = json_get(act_stats, "hebb_wb_dropped") let wb_drop: String = if str_eq(wb_drop_raw, "") { "-1" } else { wb_drop_raw } let wb_sent_raw: String = state_get("soul.hebb_wb_sent") let wb_sent: String = if str_eq(wb_sent_raw, "") { "0" } else { wb_sent_raw } // dup_wm_global (2026-08-06): redundant WM residents that arrived via the // carry-over path, which Pass 3½ structurally could not see. Confirmed live // by a census that caught two byte-identical copies of one 3,193-char // document both holding slots. let dup_wm_g_raw: String = json_get(act_stats, "dup_wm_global") let dup_wm_g: String = if str_eq(dup_wm_g_raw, "") { "-1" } else { dup_wm_g_raw } let act_brk_raw: String = json_get(act_stats, "embed_breaker_open") let act_brk: String = if str_eq(act_brk_raw, "") { "-1" } else { act_brk_raw } // ctx_cos (2026-07-29 self-review): cos(query, context centroid) at the // last activate call — the drift gauge for the new context-centroid // scoring. ~1.0 aligned; low at domain-rotation boundaries is healthy; // -2.0 pinned for hours means the centroid never initializes (embedder // down) and semantic continuity is silently absent. let ctx_cos_raw: String = json_get(act_stats, "ctx_cos") let ctx_cos: String = if str_eq(ctx_cos_raw, "") { "-2" } else { ctx_cos_raw } // Redundancy suppression gauges (2026-08-05 self-review). A content-hash // census found 1,858 redundant copies — 44.9% of the non-ISE graph, from a // June id-scheme migration. They embed identically, so they were taking // 40.2% of semantic seed slots (measured: 4.78 distinct seeds of 8). // dup_seeds — redundant copies denied a seed slot, cumulative. A healthy // nonzero rate means the suppressor is doing real work; a // sustained drop toward 0 means the duplicates were finally // merged out of the graph (the repair this defends against). // dup_wm — duplicate WM candidates evicted before the capacity cap. // Cumulative like wm_evicted/breakthroughs; diff across heartbeats for rate. let dup_seeds_raw: String = json_get(act_stats, "dup_seeds") let dup_seeds: String = if str_eq(dup_seeds_raw, "") { "-1" } else { dup_seeds_raw } let dup_wm_raw: String = json_get(act_stats, "dup_wm") let dup_wm: String = if str_eq(dup_wm_raw, "") { "-1" } else { dup_wm_raw } // txt_damaged (2026-08-08 self-review): nodes created THIS process whose // content carries the character-loss signature (see eg_text_loss_signature // in el_runtime.c). Today's review found the JSON parser had been replacing // every \uXXXX escape with a literal '?' for at least two months — 76% of // non-telemetry nodes damaged, including the self root and every values // node — and nothing caught it, because every gauge here reported whether // the machinery was RUNNING and none reported whether the text it carried // was INTACT. The parser is fixed; this is the standing regression signal. // Healthy state is a flat 0. Any climb means a write path is mangling text // again. The full store census is GET /api/text-health (too expensive for // a 60s beat); this is the cheap flow counter that belongs on every beat. let txt_dmg_raw: String = json_get(act_stats, "txt_damaged") let txt_dmg: String = if str_eq(txt_dmg_raw, "") { "-1" } else { txt_dmg_raw } let payload: String = "{\"event\":\"heartbeat\",\"pulse\":" + pulse + ",\"tick\":" + pulse + ",\"boot\":" + boot + ",\"idle\":" + idle + ",\"idle_ms\":" + int_to_str(idle_ms) + ",\"node_count\":" + int_to_str(nc) + ",\"edge_count\":" + int_to_str(ec) + ",\"node_delta\":" + int_to_str(node_delta) + ",\"edge_delta\":" + int_to_str(edge_delta) + ",\"wm_active\":" + int_to_str(wmc) + ",\"wm_delta\":" + int_to_str(wm_delta) + ",\"wm_saturated\":" + int_to_str(wm_sat) + ",\"wm_top0_streak\":" + int_to_str(t0streak) + ",\"wm_churn\":" + int_to_str(wm_churn) + ",\"wm_top0_wm\":" + wm_top0_wm + ",\"sync_added_total\":" + sat_str + ",\"sync_age_ms\":" + int_to_str(sync_age) + ",\"wm_avg_weight\":" + wm_avg_str + ",\"wm_top\":" + wm_top + ",\"ts\":" + int_to_str(ts) + ",\"uptime_ms\":" + int_to_str(up_ms) + ",\"uptime\":\"" + up_human + "\",\"embed_ok\":" + int_to_str(emb_ok) + ",\"embed_backfilled\":" + bf_done + ",\"embed_count\":" + bf_total + ",\"embed_eligible\":" + embed_elig + ",\"wm_evicted\":" + act_evict + ",\"wm_evicted_delta\":" + int_to_str(evict_delta) + ",\"breakthroughs\":" + act_bt + ",\"breakthroughs_delta\":" + int_to_str(bt_delta) + ",\"auto_term_streak\":" + int_to_str(hb_ats) + ",\"auto_term_empty_streak\":" + int_to_str(hb_ate) + ",\"embed_breaker_open\":" + act_brk + ",\"ctx_cos\":" + ctx_cos + ",\"dup_seeds\":" + dup_seeds + ",\"dup_wm\":" + dup_wm + ",\"dup_wm_global\":" + dup_wm_g + ",\"hebb_warm\":" + hebb_warm + ",\"hebb_max\":" + hebb_max + ",\"hebb_links\":" + hebb_links + ",\"hebb_wb_pending\":" + wb_pend + ",\"hebb_wb_drained\":" + wb_drain + ",\"hebb_wb_dropped\":" + wb_drop + ",\"hebb_wb_sent\":" + wb_sent + ",\"ise_fail\":" + fail_str + ",\"txt_damaged\":" + txt_dmg + "}" ise_post(payload) } // proactive_curiosity — activate rotating seeds to exercise working memory // during idle periods. Rotates through 4 domain sets on a wall-clock minute // cycle so no single topic dominates WM between heartbeats. // // KEY DESIGN (revised 2026-07-17): the seed set is activated ONCE as the full // phrase. engram_activate uses istr_contains (substring matching), so the // phrase matches few nodes — that is intentional: the old per-word split hit // hundreds of generic nodes per word and flooded the graph with activation // every scan. The top result is strengthened so the read feeds back. // // Unlike perceive(), this intentionally calls engram_activate_json to build // 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. // auto_term_try_slot — attempt to set cseed_auto from one WM slot. // Only writes to cseed_auto if node_type is Memory, BacklogItem, Entity, or // Knowledge AND the first word of the label is > 3 chars (guards // bracket-prefixed labels). Designed to be called in reverse slot order // (highest index first) so that the lowest-indexed slot (highest WM weight) // wins by last-write semantics. // // KNOWLEDGE ADMISSION (2026-07-23 self-review): WM top-10 is now dominated by // Knowledge nodes (world-ingestor titles + captures), so excluding Knowledge // left auto_term empty on EVERY curiosity_scan since boot 6 — the dynamic // seeding path was dead. Knowledge labels are real titles after the // neuron-api label fix. Sentinel-shaped labels ("knowledge:captured", // "memory:remembered" — colon, no space) carry no seed signal and are // skipped so legacy nodes cannot seed the scan with the word "knowledge". fn auto_term_try_slot(slot_type: String, slot_lbl: String) -> Void { state_set("_ats_ok", "0") if str_eq(slot_type, "Memory") { state_set("_ats_ok", "1") } if str_eq(slot_type, "BacklogItem") { state_set("_ats_ok", "1") } if str_eq(slot_type, "Entity") { state_set("_ats_ok", "1") } if str_eq(slot_type, "Knowledge") { state_set("_ats_ok", "1") } if str_contains(slot_lbl, ":") { if !str_contains(slot_lbl, " ") { state_set("_ats_ok", "0") } } if str_eq(state_get("_ats_ok"), "1") { if !str_eq(slot_lbl, "") { let sp: Int = str_find_chars(slot_lbl, " :([") if sp > 3 { // GENRE-WORD BLOCKLIST (2026-07-23 self-review): world-ingestor // titles open with classifier prefixes ("Method paper ...", // "Theory paper ..."), so the first word is a genre tag, not a // topic. Verified live: every scan after Knowledge admission // seeded on 'Method'. Skip these; a lower-WM slot with a // topical first word wins instead. let term: String = str_slice(slot_lbl, 0, sp) state_set("_ats_gw", "0") if str_eq(term, "Method") { state_set("_ats_gw", "1") } if str_eq(term, "Theory") { state_set("_ats_gw", "1") } if str_eq(term, "Finding") { state_set("_ats_gw", "1") } if str_eq(term, "Survey") { state_set("_ats_gw", "1") } if str_eq(term, "Paper") { state_set("_ats_gw", "1") } if str_eq(term, "Knowledge") { state_set("_ats_gw", "1") } if str_eq(term, "Value") { state_set("_ats_gw", "1") } // STOPWORD FILTER (2026-07-30 self-review): the genre // blocklist above was whack-a-mole — observed live seeds // included "What", "Colon", "Prose", "Context", "Self", // "Closing", "Global", "Universal": English function words // and document-structure words that pass the >3-char guard // but carry no topical signal (a first-word extractor has no // term-quality scoring). Single delimited membership test // against a curated list of function words + title/structure // words; topical technical terms (MemQ, AsymGRPO, Mobius, // engram_goal_bias) pass untouched. Both Title-case and // lowercase variants listed for the most common offenders. let stopw: String = "|What|When|Where|Which|Whose|While|This|That|These|Those|There|Their|Then|Than|With|Without|From|Into|Onto|Over|Under|About|Between|Among|Across|Some|Most|More|Less|Very|Each|Every|Both|Also|Only|Just|Does|Will|Would|Could|Should|Might|Must|Have|Been|Being|Toward|Towards|Using|Based|Upon|Here|Your|Ours|They|Them|what|this|that|with|from|context|Context|Prose|Colon|Self|Test|Testing|Closing|Global|Universal|Persona|Semantic|Spreading|Temporal|Numeric|Register|Identifying|Introduction|Overview|Summary|Section|General|Notes|Note|" if str_contains(stopw, "|" + term + "|") { state_set("_ats_gw", "1") } // QUOTED-TITLE GUARD (2026-07-25 self-review): labels that // open with a quote ('"The Algorithmic Caricature" ...') // defeat the >3-char stopword guard — the extracted term // '"The' is 4 chars and seeds a lexical flood on "The" // (observed live: activated jumped 48 → 87). Any term // carrying a quote character is not a topic word. if str_contains(term, "\"") { state_set("_ats_gw", "1") } if str_contains(term, "'") { state_set("_ats_gw", "1") } // TERM-SPECIFICITY GATE (2026-08-03 self-review): the three // guards above are hand-curated lists, and every one of them // was written REACTIVELY — after a flood was already observed // in the ISE stream. A list can only ever contain the floods // that already happened. Two were in flight, unfixed, while // this review ran: // "