self-review 2026-08-14: plumb the eviction-cause decomposition to the heartbeat
el_runtime.c gained evict_floor / evict_cap / evict_bll today so that
wm_evicted == floor + cap + bll + dup_wm + dup_wm_global
is an identity rather than one opaque integer. This carries them the rest of
the way, into the heartbeat ISE.
Doing it in the same change is the point. The 2026-08-10 review found that
nineteen keys crossed the C boundary and only fourteen reached the ISE stream,
and named the lesson: an instrument that is computed but not plumbed to
durable storage is not an instrument, it is a local variable. Today's audit
found that defect had recurred -- ten act-stats keys (aff_*, fan_*) are still
orphaned. Adding three more C-side counters and stopping there would have made
it thirteen.
Read the three as a ratio, not a level:
cap-dominant -> genuine contention for the 24 slots
bll-dominant -> carried-over residents decaying out; healthy forgetting
floor-dominant -> retrieval is returning weak candidates
Measured this morning: 175,547 evictions over 13.5h, ~216/min against 24
slots, with no way to say which of those three it was.
Not restarting the soul to pick this up. It holds 5,882 nodes and 40,375 edges
that exist only in process RAM (mem_save is unreachable while ENGRAM_URL is
set), so a restart destroys them. Filed separately as P0.
This commit is contained in:
+21
-1
@@ -355,6 +355,26 @@ fn emit_heartbeat() -> Void {
|
||||
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 }
|
||||
// Eviction CAUSE decomposition (2026-08-14 self-review). wm_evicted alone
|
||||
// cannot distinguish healthy WM rotation from cap contention from decay:
|
||||
// six increment sites, four causes, one integer. Measured this morning:
|
||||
// 175,547 evictions over 13.5h (~216/min against 24 slots) with no way to
|
||||
// say why. These three make the aggregate decomposable —
|
||||
// wm_evicted == floor + cap + bll + dup_wm + dup_wm_global
|
||||
// and each term implies a different correction. Read as a RATIO:
|
||||
// cap-dominant -> genuine contention for the 24 slots
|
||||
// bll-dominant -> carried-over residents decaying out; healthy
|
||||
// floor-dominant -> retrieval is returning weak candidates
|
||||
// Plumbed here in the same change that added them to the C stats, because
|
||||
// the 08-10 review's finding was that fourteen of nineteen keys crossed
|
||||
// the C boundary and the rest died as local variables. An instrument that
|
||||
// is computed but not plumbed is not an instrument.
|
||||
let ev_floor_raw: String = json_get(act_stats, "evict_floor")
|
||||
let ev_floor: String = if str_eq(ev_floor_raw, "") { "-1" } else { ev_floor_raw }
|
||||
let ev_cap_raw: String = json_get(act_stats, "evict_cap")
|
||||
let ev_cap: String = if str_eq(ev_cap_raw, "") { "-1" } else { ev_cap_raw }
|
||||
let ev_bll_raw: String = json_get(act_stats, "evict_bll")
|
||||
let ev_bll: String = if str_eq(ev_bll_raw, "") { "-1" } else { ev_bll_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) }
|
||||
@@ -544,7 +564,7 @@ fn emit_heartbeat() -> Void {
|
||||
let dmg_scan: String = if str_eq(dmg_scan_raw, "") { "-1" } else { dmg_scan_raw }
|
||||
let dmg_ts_raw: String = state_get("soul.txt_census_ts")
|
||||
let dmg_age: Int = if str_eq(dmg_ts_raw, "") { 0 - 1 } else { ts - str_to_int(dmg_ts_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_cands\":" + hebb_cands + ",\"hebb_cand_max\":" + hebb_cmax + ",\"hebb_mass\":" + hebb_mass + ",\"hebb_edges\":" + hebb_edges + ",\"embed_consec_fail\":" + emb_cf + ",\"txt_damaged_pct\":" + dmg_pct + ",\"txt_damaged_n\":" + dmg_n + ",\"txt_scanned_n\":" + dmg_scan + ",\"txt_census_age_ms\":" + int_to_str(dmg_age) + ",\"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 + "}"
|
||||
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) + ",\"evict_floor\":" + ev_floor + ",\"evict_cap\":" + ev_cap + ",\"evict_bll\":" + ev_bll + ",\"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_cands\":" + hebb_cands + ",\"hebb_cand_max\":" + hebb_cmax + ",\"hebb_mass\":" + hebb_mass + ",\"hebb_edges\":" + hebb_edges + ",\"embed_consec_fail\":" + emb_cf + ",\"txt_damaged_pct\":" + dmg_pct + ",\"txt_damaged_n\":" + dmg_n + ",\"txt_scanned_n\":" + dmg_scan + ",\"txt_census_age_ms\":" + int_to_str(dmg_age) + ",\"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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user