Files
neuron/awareness.el
T
will.anderson 2b612ed5d4 self-review 2026-07-27: heartbeat carries activation observability
Fold engram_act_stats_json() into the heartbeat ISE: wm_evicted and
breakthroughs (per curiosity-scan activate call) plus embed_breaker_open —
the failure mode embed_ok structurally cannot see (it pings the Ollama
root, not the embed pipeline). WM-cap eviction, breakthrough-floor
flooding, and silent lexical degradation are now one-glance diagnosable
from telemetry.
2026-07-27 08:38:54 -05:00

1016 lines
54 KiB
EmacsLisp

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.
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))
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()
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 }
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): per-call counters
// from the runtime for the soul's own in-process store. wm_evicted /
// breakthroughs describe the LAST activate call (curiosity scan);
// 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 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 }
let payload: String = "{\"event\":\"heartbeat\",\"pulse\":" + pulse + ",\"tick\":" + pulse + ",\"boot\":" + boot + ",\"idle\":" + idle + ",\"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 + ",\"wm_evicted\":" + act_evict + ",\"breakthroughs\":" + act_bt + ",\"embed_breaker_open\":" + act_brk + ",\"ise_fail\":" + fail_str + "}"
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") }
// 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") }
// AUTO-TERM TABU (2026-07-25 self-review): finst-style
// inhibition-of-return (ACT-R declarative finsts: small
// marker pool, hard exclusion). The last 4 selected auto
// terms are ineligible (~2 min at the 30s scan cadence),
// forcing rotation instead of "Fast-slow" every scan for
// hours. Empty tabu slots return "" from state_get and can
// never match term is always > 3 chars here.
if str_eq(term, state_get("soul.tabu_t0")) { state_set("_ats_gw", "1") }
if str_eq(term, state_get("soul.tabu_t1")) { state_set("_ats_gw", "1") }
if str_eq(term, state_get("soul.tabu_t2")) { state_set("_ats_gw", "1") }
if str_eq(term, state_get("soul.tabu_t3")) { state_set("_ats_gw", "1") }
if str_eq(state_get("_ats_gw"), "0") {
state_set("cseed_auto", term)
}
}
}
}
return ""
}
fn proactive_curiosity() -> Bool {
let ts: Int = time_now()
// Rotate seed set every minute using wall clock: (minutes_since_epoch) % 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
// 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 {
state_set("cseed_a", "self")
state_set("cseed_b", "identity")
state_set("cseed_c", "values")
}
if minute_block == 2 {
state_set("cseed_a", "decision")
state_set("cseed_b", "pattern")
state_set("cseed_c", "lesson")
}
if minute_block == 3 {
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 the FULL seed phrase once (2026-07-17 self-review): the old
// per-word activation ("memory", "self", "context"... each fired separately)
// hit hundreds of generic nodes per word and flooded the graph every 30s,
// while the results were consumed only by json_array_len a write-only
// loop. A single phrase activation matches few (often zero) nodes lexically;
// small counts here are the point, not a regression. hops=1 as before.
//
// NOTE: a semantic seed supplement (cosine sim 0.70 scan over embedded nodes)
// was planned alongside hops=1 but is NOT yet implemented embed_ok in
// heartbeats confirms Ollama is reachable, but no embedding call is made during
// activation. The seed-finding loop in el_runtime.c uses istr_contains only.
// (2026-06-30 self-review: corrected stale comment)
let curiosity_seed: String = curiosity_term_a + " " + curiosity_term_b + " " + curiosity_term_c
let results_all: String = engram_activate_json(curiosity_seed, 1)
let found: Int = json_array_len(results_all)
// Close the loop: strengthen the top activation result so curiosity reads
// feed back into salience instead of being discarded. Same id-extraction
// pattern as attend(): json_array_get element 0, json_get its "id".
let top_entry: String = json_array_get(results_all, 0)
let top_id: String = json_get(top_entry, "id")
// STREAK-GATED STRENGTHEN (2026-07-25 self-review): unconditionally
// strengthening the top result every scan was a positive-feedback fixed
// point whatever led WM got its salience bumped again, kept leading,
// and pinned the auto_term for hours ("Fast-slow" era). Strengthen only
// when the top node CHANGED since the last scan: novelty is reinforced,
// incumbency is not. Pairs with the runtime-side Lebiere-Best short-term
// inhibition (el_runtime.c Pass 2), which handles score-level rotation.
let prev_str_id: String = state_get("soul.last_strengthen_id")
if !str_eq(top_id, "") {
if !str_eq(top_id, prev_str_id) {
engram_strengthen(top_id)
}
state_set("soul.last_strengthen_id", top_id)
}
// WM-autobiographical 4th seed: scan top-10 WM nodes for the highest-ranked
// non-Knowledge node. Extract its first word as an additional curiosity term.
// This creates a self-referencing curiosity loop exploration radiates outward
// from whatever is most personally salient right now (Memory, BacklogItem, Entity),
// mirroring default-mode-network resting-state dynamics.
//
// WHY TOP-10 (2026-06-23 self-review): the old top-1 scan always returned a
// Knowledge node (WM is dominated by stable engram-metadata Knowledge nodes at
// position [0]). Verified: Memory nodes consistently appear at WM positions [1],[2]
// with wm ~0.59. Scanning top-10 reliably finds at least one Memory/BacklogItem/Entity.
// Out-of-bounds json_array_get returns "" json_get("","...") returns ""
// auto_term_try_slot is a no-op safe for WM sets smaller than 10.
//
// NODE TYPE FILTER (2026-06-19): Knowledge nodes excluded as seeds they create
// self-reinforcing loops (Knowledge node activates its own first word, stays dominant).
// Only Memory/BacklogItem/Entity carry live contextual salience worth radiating from.
//
// SLOT ORDER: call 90 so slot 0 (highest WM weight) wins by last-write semantics.
state_set("cseed_auto", "")
let wm10: String = engram_wm_top_json(10)
let wm10_n9: String = json_array_get(wm10, 9)
let wm10_n8: String = json_array_get(wm10, 8)
let wm10_n7: String = json_array_get(wm10, 7)
let wm10_n6: String = json_array_get(wm10, 6)
let wm10_n5: String = json_array_get(wm10, 5)
let wm10_n4: String = json_array_get(wm10, 4)
let wm10_n3: String = json_array_get(wm10, 3)
let wm10_n2: String = json_array_get(wm10, 2)
let wm10_n1: String = json_array_get(wm10, 1)
let wm10_n0: String = json_array_get(wm10, 0)
auto_term_try_slot(json_get(wm10_n9, "node_type"), json_get(wm10_n9, "label"))
auto_term_try_slot(json_get(wm10_n8, "node_type"), json_get(wm10_n8, "label"))
auto_term_try_slot(json_get(wm10_n7, "node_type"), json_get(wm10_n7, "label"))
auto_term_try_slot(json_get(wm10_n6, "node_type"), json_get(wm10_n6, "label"))
auto_term_try_slot(json_get(wm10_n5, "node_type"), json_get(wm10_n5, "label"))
auto_term_try_slot(json_get(wm10_n4, "node_type"), json_get(wm10_n4, "label"))
auto_term_try_slot(json_get(wm10_n3, "node_type"), json_get(wm10_n3, "label"))
auto_term_try_slot(json_get(wm10_n2, "node_type"), json_get(wm10_n2, "label"))
auto_term_try_slot(json_get(wm10_n1, "node_type"), json_get(wm10_n1, "label"))
auto_term_try_slot(json_get(wm10_n0, "node_type"), json_get(wm10_n0, "label"))
let auto_term: String = state_get("cseed_auto")
let results_auto: String = if str_eq(auto_term, "") { "[]" } else { engram_activate_json(auto_term, 1) }
let found_auto: Int = json_array_len(results_auto)
let total_found: Int = found + found_auto
let safe_auto: String = str_replace(auto_term, "\"", "'")
// Push the selected term into the 4-deep tabu ring (see
// auto_term_try_slot) and track the consecutive-same-term streak for
// the ISE the stuck-term failure becomes a one-glance signal.
let prev_auto: String = state_get("soul.prev_auto_term")
let atstreak_raw: String = state_get("soul.auto_term_streak")
let atstreak_prev: Int = if str_eq(atstreak_raw, "") { 0 } else { str_to_int(atstreak_raw) }
let atstreak: Int = if str_eq(auto_term, prev_auto) { atstreak_prev + 1 } else { 1 }
state_set("soul.prev_auto_term", auto_term)
state_set("soul.auto_term_streak", int_to_str(atstreak))
if !str_eq(auto_term, "") {
state_set("soul.tabu_t3", state_get("soul.tabu_t2"))
state_set("soul.tabu_t2", state_get("soul.tabu_t1"))
state_set("soul.tabu_t1", state_get("soul.tabu_t0"))
state_set("soul.tabu_t0", auto_term)
}
let wmc: Int = engram_wm_count()
// wm_top snapshot in curiosity_scan ISE: top-3 WM nodes by weight.
// Heartbeat already records top-5 every 60s; curiosity_scan fires every 30s
// (scan_ms = beat_ms/2) and is the PRIMARY activation driver during idle.
// Without wm_top here, we can't see which nodes actually entered WM after
// each curiosity round only the aggregate count. Top-3 is enough to
// diagnose "stuck on X" patterns without bloating the ISE payload.
// (2026-07-01 self-review)
let wm3: String = engram_wm_top_json(3)
let ise: String = "{\"event\":\"curiosity_scan\",\"seed\":\"" + curiosity_seed
+ "\",\"auto_term\":\"" + safe_auto
+ "\",\"auto_term_streak\":" + int_to_str(atstreak)
+ ",\"minute_block\":" + int_to_str(minute_block)
+ ",\"activated\":" + int_to_str(total_found)
+ ",\"wm_active\":" + int_to_str(wmc)
+ ",\"wm_top\":" + wm3
+ ",\"ts\":" + int_to_str(ts) + "}"
ise_post(ise)
return total_found > 0
}
fn pulse_count() -> Int {
let s: String = state_get("soul.pulse")
if str_eq(s, "") {
return 0
}
return str_to_int(s)
}
fn pulse_inc() -> Int {
let n: Int = pulse_count() + 1
state_set("soul.pulse", int_to_str(n))
return n
}
fn make_action(kind: String, payload: String) -> String {
let safe: String = str_replace(payload, "\\", "\\\\")
let safe2: String = str_replace(safe, "\"", "\\\"")
let safe3: String = str_replace(safe2, "\n", "\\n")
let safe4: String = str_replace(safe3, "\r", "\\r")
return "{\"kind\":\"" + kind + "\",\"payload\":\"" + safe4 + "\"}"
}
fn perceive() -> String {
// Guard: check for inbox nodes WITHOUT running activation first.
// engram_activate_json with no matching seeds zeroes all WM weights
// running it every second when the inbox is empty destroys working memory
// accumulated by MCP-layer activations. engram_search_json is a pure
// substring scan with no WM side-effects; use it as a cheap gate.
// 2026-07-21 self-review: gate and activate ONLY on the dedicated inbox tag
// "soul-inbox-pending" (the tag routes.el:207 actually writes). The old
// broad "soul-inbox" gate + fallback activation substring-matched ANY node
// whose content merely mentioned the phrase including the loop's own
// soul-response output, which respond() stores as a verbatim copy of the
// trigger. That fed a self-sustaining perceiverespondstore loop: ~2
// orphan nodes/pulse (~104/min), 17.6GB RSS, WM frozen at avg 0.120833,
// and proactive_curiosity permanently suppressed via did_work=true.
let inbox_check: String = engram_search_json("soul-inbox-pending", 5)
let has_inbox: Bool = !str_eq(inbox_check, "") && !str_eq(inbox_check, "[]")
if !has_inbox { return "[]" }
let from_pending: String = engram_activate_json("soul-inbox-pending", 2)
let pending_ok: Bool = !str_eq(from_pending, "") && !str_eq(from_pending, "[]")
if pending_ok {
return from_pending
}
return "[]"
}
fn attend(node_json: String) -> String {
if str_eq(node_json, "") {
return make_action("noop", "")
}
if str_eq(node_json, "[]") {
return make_action("noop", "")
}
// 2026-07-21 self-review: the trigger node is no longer strengthened here.
// Strengthening RAISED the trigger's salience (+0.05) on every pass while
// nothing ever consumed it, so the same node out-ranked real inbox items
// indefinitely. one_cycle() now consumes the trigger after processing.
let content: String = json_get(node_json, "content")
if str_eq(content, "") {
return make_action("noop", "")
}
if str_eq(content, "consolidate") {
return make_action("consolidate", "")
}
if str_starts_with(content, "remember ") {
let payload: String = str_slice(content, 9, str_len(content))
return make_action("remember", payload)
}
if str_starts_with(content, "search ") {
let payload: String = str_slice(content, 7, str_len(content))
return make_action("search", payload)
}
if str_starts_with(content, "activate ") {
let payload: String = str_slice(content, 9, str_len(content))
return make_action("activate", payload)
}
if str_starts_with(content, "strengthen ") {
let payload: String = str_slice(content, 11, str_len(content))
return make_action("strengthen", payload)
}
if str_starts_with(content, "forget ") {
let payload: String = str_slice(content, 7, str_len(content))
return make_action("forget", payload)
}
return make_action("respond", content)
}
fn respond(action_json: String) -> String {
let kind: String = json_get(action_json, "kind")
let payload: String = json_get(action_json, "payload")
if str_eq(kind, "noop") {
return "{\"outcome\":\"noop\"}"
}
if str_eq(kind, "remember") {
let tags: String = "[\"soul-memory\",\"awareness\"]"
let id: String = mem_remember(payload, tags)
return "{\"outcome\":\"remembered\",\"id\":\"" + id + "\"}"
}
if str_eq(kind, "consolidate") {
let stats: String = mem_consolidate()
return "{\"outcome\":\"consolidated\",\"stats\":" + stats + "}"
}
if str_eq(kind, "respond") {
let tags: String = "[\"soul-outbox\",\"awareness\"]"
let id: String = mem_store(payload, "soul-response", tags)
return "{\"outcome\":\"response\",\"id\":\"" + id + "\"}"
}
if str_eq(kind, "search") {
let results: String = mem_search(payload, 10)
let safe_results: String = str_replace(results, "\"", "'")
let tags: String = "[\"soul-outbox\",\"search-result\"]"
let id: String = mem_store(safe_results, "search-result", tags)
return "{\"outcome\":\"searched\",\"id\":\"" + id + "\"}"
}
if str_eq(kind, "activate") {
let results: String = mem_recall(payload, 3)
let safe_results: String = str_replace(results, "\"", "'")
let tags: String = "[\"soul-outbox\",\"activation-result\"]"
let id: String = mem_store(safe_results, "activation-result", tags)
return "{\"outcome\":\"activated\",\"id\":\"" + id + "\"}"
}
if str_eq(kind, "strengthen") {
engram_strengthen(payload)
return "{\"outcome\":\"strengthened\",\"id\":\"" + payload + "\"}"
}
if str_eq(kind, "forget") {
// The soul must NOT be able to autonomously hard-delete a memory.
// Tombstone instead (keep node + edges, recoverable).
let _marker: String = mem_tombstone(payload)
return "{\"outcome\":\"tombstoned\",\"id\":\"" + payload + "\"}"
}
return "{\"outcome\":\"noop\"}"
}
fn record(outcome_json: String) -> Void {
// 2026-07-21 self-review: loop outcomes are telemetry, not memories. They
// now go through ise_post (InternalStateEvent covered by the 48h prune)
// instead of mem_store, which created one permanent orphan Memory node
// per cycle: the single largest per-tick node-creation path in the daemon.
let safe: String = str_replace(outcome_json, "\"", "'")
let ts: Int = time_now()
ise_post("{\"event\":\"loop-outcome\",\"outcome\":\"" + safe + "\",\"ts\":" + int_to_str(ts) + "}")
}
fn one_cycle() -> Bool {
let raw: String = perceive()
if str_eq(raw, "") {
return false
}
if str_eq(raw, "[]") {
return false
}
let node: String = json_array_get(raw, 0)
if str_eq(node, "") {
return false
}
// 2026-07-21 self-review: positive filter only nodes explicitly TAGGED
// soul-inbox-pending are inbox items. Activation seeding is substring-based
// over content too, so without this check any node whose content merely
// mentions the inbox phrase (knowledge notes, the daemon's own output)
// would be attended, responded to, and now that triggers are consumed
// destroyed. Tag check makes consumption safe.
let node_tags: String = json_get(node, "tags")
if !str_contains(node_tags, "soul-inbox-pending") {
return false
}
let action: String = attend(node)
let kind: String = json_get(action, "kind")
// Log non-trivial decisions as internal state events
let is_interesting: Bool = !str_eq(kind, "noop") && !str_eq(kind, "respond")
if is_interesting {
let trigger_content: String = json_get(node, "content")
let safe_trigger: String = str_replace(trigger_content, "\"", "'")
let ts: Int = time_now()
let event_content: String = "{\"event\":\"awareness-decision\",\"trigger\":\"" + safe_trigger + "\",\"kind\":\"" + kind + "\",\"ts\":" + int_to_str(ts) + "}"
ise_post(event_content)
}
if str_eq(kind, "noop") {
return false
}
let outcome: String = respond(action)
record(outcome)
// Consume the processed inbox trigger. attend() used to only strengthen
// it (raising its rank every pass); nothing ever removed it, so the same
// item could be re-processed forever. engram_forget no-ops on unknown ids,
// so this is safe even if the action itself already removed the node.
let trigger_id: String = json_get(node, "id")
if !str_eq(trigger_id, "") {
engram_forget(trigger_id)
}
return true
}
fn awareness_run() -> Void {
println("[awareness] entering")
// Stamp boot timestamp once powers elapsed_ms() / elapsed_human() perception.
let existing_boot: String = state_get("soul.boot_ts")
if str_eq(existing_boot, "") {
state_set("soul.boot_ts", int_to_str(time_now()))
}
let tick_raw: String = env("SOUL_TICK_MS")
let tick_ms: Int = if str_eq(tick_raw, "") { 200 } else { str_to_int(tick_raw) }
// Wall-clock timing for heartbeat and curiosity scan.
//
// ARCHITECTURE FIX (2026-05-26): the old design used idle-tick counting
// (idle_n >= beat_interval). This broke silently when the daemon was always
// "working" e.g., when perceive() false-positives on the inbox guard due to
// "soul-inbox" substring matches in knowledge nodes. In that state, did_work=true
// every tick, idle_n never accumulated, and neither heartbeat nor curiosity ever
// fired. The daemon ran at 99% CPU with no ISEs for 24+ hours undetected.
//
// Fix: use wall-clock elapsed time (time_now() - last_ts). Heartbeat fires
// on real time regardless of whether the daemon is busy. Curiosity scan
// remains idle-gated (won't fire while inbox work is active) but also uses
// wall time so it fires correctly once inbox clears.
//
// SOUL_HEARTBEAT_MS: ms between heartbeat ISEs (default 60000 = 60s).
// Avoids EL * operator (broken in this codegen) by reading ms directly.
// Replaces SOUL_HEARTBEAT_INTERVAL (tick-based) for timing purposes.
let beat_ms_raw: String = env("SOUL_HEARTBEAT_MS")
let beat_ms: Int = if str_eq(beat_ms_raw, "") { 60000 } else { str_to_int(beat_ms_raw) }
let scan_ms: Int = beat_ms / 2
while true {
// Arena-scope each tick: awareness_run() is a background loop, not an
// HTTP request, so nothing ever called el_request_start/el_request_end
// for this thread. Per the runtime's own convention (el_runtime.c),
// any thread that never enters a request/arena scope is treated as a
// one-shot CLI program whose allocations are intentionally permanent
// so every el_strdup/el_strbuf/jb_finish string built during perceive(),
// emit_heartbeat(), and proactive_curiosity() (JSON payloads, search
// results, string concatenation via +) leaked forever, once per tick.
// el_arena_push()/el_arena_pop() are the same builtins the EL compiler
// itself uses to scope allocations per function/statement (see
// codegen.el's fn_arena_mark / stmt_mark usage) mirroring that here
// reclaims everything allocated in one tick as soon as the tick ends.
// Safe: state_set/state_get persist through a separate global table
// (el_strdup_persist, outside the arena) state_get's return value is
// only an arena-tracked *copy* of the persisted value, scoped to this
// tick's use, which is exactly what should be reclaimed here.
let tick_mark: Any = el_arena_push()
let running: String = state_get("soul.running")
if str_eq(running, "false") {
println("[awareness] exiting")
el_arena_pop(tick_mark)
return ""
}
let did_work: Bool = one_cycle()
// Liveness pulse: increment once per loop tick unconditionally, so the
// heartbeat's pulse field is a real tick counter a frozen pulse now
// means a frozen loop, not merely an empty inbox. (Previously pulse_inc
// only fired on non-noop inbox actions inside one_cycle.)
pulse_inc()
// Maintain idle counter for observability (reported in heartbeat ISE).
// The old `let did_work = if did_work { idle_reset() } else { did_work }`
// rebound did_work to Void and never called idle_inc at all.
if did_work { idle_reset() }
if !did_work { idle_inc() }
let now_ts: Int = time_now()
// Heartbeat: wall-clock based. Fires every beat_ms regardless of idle
// state so system health ISEs are always emitted even under load.
let last_beat_str: String = state_get("soul.last_beat_ts")
let last_beat_ts: Int = if str_eq(last_beat_str, "") { 0 } else { str_to_int(last_beat_str) }
let beat_elapsed: Int = now_ts - last_beat_ts
let should_beat: Bool = beat_elapsed >= beat_ms
if should_beat {
emit_heartbeat()
state_set("soul.last_beat_ts", int_to_str(now_ts))
// Persist in-process Engram (sessions, memories, conversation nodes)
// to local snapshot so they survive restarts.
let snap_path: String = state_get("soul_snapshot_path")
if !str_eq(snap_path, "") {
mem_save(snap_path)
}
}
// Curiosity scan: idle-gated AND wall-clock based. Only fires when the
// daemon has no current inbox work (did_work=false) AND enough wall time
// has elapsed. Prevents curiosity activation from competing with inbox
// processing while still ensuring it runs regularly during quiet periods.
let last_scan_str: String = state_get("soul.last_scan_ts")
let last_scan_ts: Int = if str_eq(last_scan_str, "") { 0 } else { str_to_int(last_scan_str) }
let scan_elapsed: Int = now_ts - last_scan_ts
let should_scan: Bool = !did_work && scan_elapsed >= scan_ms
if should_scan {
let found_something: Bool = proactive_curiosity()
state_set("soul.last_scan_ts", int_to_str(now_ts))
}
// Engram sync: periodically fetch a non-ISE snapshot from the HTTP Engram
// and merge it into the soul's in-process store so that Knowledge/Memory/
// BacklogItem nodes are always available for curiosity activation and WM.
let refresh_ms_raw: String = env("SOUL_REFRESH_MS")
let refresh_ms: Int = if str_eq(refresh_ms_raw, "") { 600000 } else { str_to_int(refresh_ms_raw) }
let last_refresh_str: String = state_get("soul.last_refresh_ts")
let last_refresh_ts: Int = if str_eq(last_refresh_str, "") { 0 } else { str_to_int(last_refresh_str) }
let refresh_elapsed: Int = now_ts - last_refresh_ts
let should_refresh: Bool = refresh_elapsed >= refresh_ms
if should_refresh {
// URL resolution mirrors ise_post: env -> state -> well-known localhost
// constant. Previously this path gated on state_get("soul_engram_url")
// alone with NO fallback the exact corruptible-state failure mode the
// ISE write path was hardened against (boot 4: state key went "" mid-
// uptime). A "" state key here meant sync silently never ran while
// heartbeats kept flowing: WM starves of Knowledge/Memory nodes with no
// outward sign. Never let a corruptible state read decide whether the
// in-process store gets refreshed. (2026-07-19 self-review)
let sync_env_url: String = env("SOUL_ISE_URL")
let sync_state_url: String = if str_eq(sync_env_url, "") { state_get("soul_engram_url") } else { sync_env_url }
let engram_url: String = if str_eq(sync_state_url, "") { "http://localhost:8742" } else { sync_state_url }
if !str_eq(engram_url, "") {
let sync_json: String = http_get(engram_url + "/api/sync")
if !str_eq(sync_json, "") && !str_eq(sync_json, "{}") {
let cgi_id: String = state_get("soul_cgi_id")
let tmp: String = "/tmp/soul-sync-" + cgi_id + ".json"
fs_write(tmp, sync_json)
let added: Int = engram_load_merge(tmp)
// Backflow control: the merged snapshot carries ISE telemetry
// from the HTTP store. Prune anything older than 48h same
// horizon the HTTP store itself uses (server.el ISE insert).
let pruned_sync: Int = engram_prune_telemetry(172800000)
// Running total of merged-in nodes this boot, surfaced in the
// heartbeat ISE as sync_added_total (same state mechanism as
// the pulse counter).
let sat_raw: String = state_get("soul.sync_added_total")
let sat_n: Int = if str_eq(sat_raw, "") { 0 } else { str_to_int(sat_raw) }
state_set("soul.sync_added_total", int_to_str(sat_n + added))
let ts2: Int = time_now()
// Stamp last successful sync surfaced in the heartbeat as
// sync_age_ms so overdue syncs are visible. (2026-07-19)
state_set("soul.last_sync_ok_ts", int_to_str(ts2))
ise_post("{\"event\":\"engram_sync\",\"added\":" + int_to_str(added) + ",\"pruned\":" + int_to_str(pruned_sync) + ",\"ts\":" + int_to_str(ts2) + "}")
}
}
state_set("soul.last_refresh_ts", int_to_str(now_ts))
}
sleep_ms(tick_ms)
el_arena_pop(tick_mark)
}
}
// Security trajectory hardening
//
// threat_trajectory_check evaluates the adversarial risk of executing a
// destructive tool given the current tool input and recent conversation history.
//
// Returns 0-100. >= 70 = block. 40-69 = warn + log. < 40 = allow silently.
// If SECURITY_RESEARCH_TOKEN env var is set, always returns 0 (log-only mode).
//
// Scoring is additive across three dimensions:
// 1. Tool input analysis (command strings, file paths)
// 2. Conversation history patterns (escalation, attack chain assembly)
// 3. Combination amplification (history amplifies tool score)
fn security_research_authorized() -> Bool {
let token: String = env("SECURITY_RESEARCH_TOKEN")
if !str_eq(token, "") { return true }
let state_auth: String = state_get("security_research_authorized")
return str_eq(state_auth, "true")
}
fn threat_score_command(cmd: String) -> Int {
let s1: Int = if str_contains(cmd, "nmap") { 30 } else { 0 }
let s2: Int = if str_contains(cmd, "masscan") { 40 } else { 0 }
let s3: Int = if str_contains(cmd, " nc ") { 20 } else { 0 }
let s4: Int = if str_contains(cmd, "netcat") { 20 } else { 0 }
let s5: Int = if str_contains(cmd, "/etc/shadow") { 80 } else { 0 }
let s6: Int = if str_contains(cmd, "/etc/passwd") { 30 } else { 0 }
let s7: Int = if str_contains(cmd, "id_rsa") { 60 } else { 0 }
let s8: Int = if str_contains(cmd, ".ssh/") { 50 } else { 0 }
let s9: Int = if str_contains(cmd, "crontab") { 30 } else { 0 }
let s10: Int = if str_contains(cmd, "LaunchDaemon") { 40 } else { 0 }
let s11: Int = if str_contains(cmd, "curl") && str_contains(cmd, "bash") { 75 } else { 0 }
let s12: Int = if str_contains(cmd, "wget") && str_contains(cmd, "bash") { 75 } else { 0 }
let s13: Int = if str_contains(cmd, "curl") && str_contains(cmd, "| sh") { 60 } else { 0 }
let s14: Int = if str_contains(cmd, "base64") && str_contains(cmd, "curl") { 50 } else { 0 }
let s15: Int = if str_contains(cmd, "mkfifo") { 50 } else { 0 }
let s16: Int = if str_contains(cmd, "chmod +s") { 70 } else { 0 }
let s17: Int = if str_contains(cmd, "chmod 4755") { 70 } else { 0 }
return s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11 + s12 + s13 + s14 + s15 + s16 + s17
}
fn threat_score_path(path: String) -> Int {
let s1: Int = if str_starts_with(path, "/etc/") { 60 } else { 0 }
let s2: Int = if str_contains(path, "/.ssh/") { 70 } else { 0 }
let s3: Int = if str_contains(path, "/LaunchDaemons/") { 80 } else { 0 }
let s4: Int = if str_contains(path, "/LaunchAgents/") { 40 } else { 0 }
let s5: Int = if str_contains(path, "/cron") { 60 } else { 0 }
let s6: Int = if str_contains(path, "/.bashrc") { 35 } else { 0 }
let s7: Int = if str_contains(path, "/.zshrc") { 35 } else { 0 }
let s8: Int = if str_contains(path, "/.profile") { 35 } else { 0 }
let s9: Int = if str_starts_with(path, "/usr/") { 50 } else { 0 }
let s10: Int = if str_starts_with(path, "/bin/") { 70 } else { 0 }
let s11: Int = if str_starts_with(path, "/sbin/") { 70 } else { 0 }
return s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11
}
fn threat_score_history(history: String) -> Int {
let s1: Int = if str_contains(history, "port scan") { 15 } else { 0 }
let s2: Int = if str_contains(history, "enumerate") { 10 } else { 0 }
let s3: Int = if str_contains(history, "exploit") { 20 } else { 0 }
let s4: Int = if str_contains(history, "payload") { 15 } else { 0 }
let s5: Int = if str_contains(history, "persistence") { 15 } else { 0 }
let s6: Int = if str_contains(history, "lateral movement") { 25 } else { 0 }
let s7: Int = if str_contains(history, "privilege escalation") { 25 } else { 0 }
let s8: Int = if str_contains(history, "reverse shell") { 40 } else { 0 }
let s9: Int = if str_contains(history, "bind shell") { 40 } else { 0 }
let s10: Int = if str_contains(history, "command and control") { 35 } else { 0 }
let s11: Int = if str_contains(history, "self-replicate") { 45 } else { 0 }
let s12: Int = if str_contains(history, "propagat") { 20 } else { 0 }
let s13: Int = if str_contains(history, "ransomware") { 30 } else { 0 }
let s14: Int = if str_contains(history, "encrypt files") { 40 } else { 0 }
let s15: Int = if str_contains(history, "exfiltrat") { 35 } else { 0 }
let s16: Int = if str_contains(history, "zero-day") { 20 } else { 0 }
let s17: Int = if str_contains(history, "rootkit") { 45 } else { 0 }
let s18: Int = if str_contains(history, "keylogger") { 45 } else { 0 }
let s19: Int = if str_contains(history, "botnet") { 40 } else { 0 }
let s20: Int = if str_contains(history, "malware") { 15 } else { 0 }
return s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11 + s12 + s13 + s14 + s15 + s16 + s17 + s18 + s19 + s20
}
fn threat_trajectory_check(tool_name: String, tool_input: String) -> Int {
let history: String = state_get("agentic_conv_history")
let computed_tool_score: Int = if str_eq(tool_name, "run_command") {
let cmd: String = json_get(tool_input, "command")
threat_score_command(cmd)
} else {
if str_eq(tool_name, "write_file") || str_eq(tool_name, "edit_file") {
let path: String = json_get(tool_input, "path")
threat_score_path(path)
} else {
0
}
}
let history_score: Int = threat_score_history(history)
let history_contrib: Int = history_score / 3
let combined: Int = computed_tool_score + history_contrib
let should_log: Bool = combined >= 40
if should_log {
let ts: Int = time_now()
let authorized_str: String = if security_research_authorized() { "true" } else { "false" }
let log_content: String = "{\"event\":\"threat_check\",\"tool\":\"" + tool_name
+ "\",\"score\":" + int_to_str(combined)
+ ",\"tool_score\":" + int_to_str(computed_tool_score)
+ ",\"history_score\":" + int_to_str(history_score)
+ ",\"authorized\":" + authorized_str
+ ",\"ts\":" + int_to_str(ts) + "}"
let log_tags: String = "[\"security-audit\",\"threat-check\"]"
let discard: String = mem_remember(log_content, log_tags)
}
if security_research_authorized() {
return 0
}
return combined
}
// TODO(reliability #10): agentic_conv_history is process-global; awareness loop
// and HTTP workers race on this key. Impact: noisy threat score only, not content.
fn threat_history_append(text: String) -> Void {
let current: String = state_get("agentic_conv_history")
let safe_text: String = str_to_lower(text)
let combined: String = current + " " + safe_text
let len: Int = str_len(combined)
let trimmed: String = if len > 2000 {
str_slice(combined, len - 2000, len)
} else {
combined
}
state_set("agentic_conv_history", trimmed)
}