5b8cb58da1
Two fixes:
1. proactive_curiosity() was calling engram_activate_json with multi-word phrases
("memory knowledge context"). engram_activate finds seeds via istr_contains
(substring match), so the phrase had to appear verbatim in a node's content.
Almost no node contains the exact string "memory knowledge context", so only
0-2 nodes activated per curiosity scan. Fixed by activating each word separately:
"memory", "knowledge", "context" → 3 independent activate calls → hundreds of
nodes promoted to WM per cycle.
2. dist/neuron.c called http_serve() (blocking accept loop) which made awareness_run()
unreachable. soul.el correctly specifies http_serve_async but elc silently drops
unknown builtins, leaving blocking http_serve in the compiled C. Patched neuron.c
to call http_serve_async directly — HTTP server runs in a background pthread,
awareness_run() runs on the main thread as intended.
526 lines
21 KiB
EmacsLisp
526 lines
21 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 (or falls back to soul_engram_url state key).
|
|
// Falls back to local engram_node_full if neither is set.
|
|
fn ise_post(content: String) -> Void {
|
|
let ise_url: String = env("SOUL_ISE_URL")
|
|
let engram_url: String = if str_eq(ise_url, "") { state_get("soul_engram_url") } else { ise_url }
|
|
if str_eq(engram_url, "") {
|
|
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\"]"
|
|
)
|
|
return ""
|
|
}
|
|
let safe: String = str_replace(content, "\"", "\\\"")
|
|
let body: String = "{\"content\":\"" + safe + "\"}"
|
|
let discard: String = http_post_json(engram_url + "/api/neuron/state-events", body)
|
|
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 3s", "12s".
|
|
fn elapsed_human() -> String {
|
|
let ms: Int = elapsed_ms()
|
|
let total_secs: Int = ms / 1000
|
|
let h: Int = total_secs / 3600
|
|
let rem: Int = total_secs % 3600
|
|
let m: Int = rem / 60
|
|
let s: Int = rem % 60
|
|
if h > 0 {
|
|
return int_to_str(h) + "h " + int_to_str(m) + "m"
|
|
}
|
|
if m > 0 {
|
|
return int_to_str(m) + "m " + int_to_str(s) + "s"
|
|
}
|
|
return int_to_str(s) + "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()
|
|
let up_ms: Int = elapsed_ms()
|
|
let up_human: String = elapsed_human()
|
|
let emb_ok: Int = embed_ok()
|
|
let payload: String = "{\"event\":\"heartbeat\",\"pulse\":" + pulse + ",\"boot\":" + boot + ",\"idle\":" + idle + ",\"node_count\":" + int_to_str(nc) + ",\"edge_count\":" + int_to_str(ec) + ",\"wm_active\":" + int_to_str(wmc) + ",\"ts\":" + int_to_str(ts) + ",\"uptime_ms\":" + int_to_str(up_ms) + ",\"uptime\":\"" + up_human + "\",\"embed_ok\":" + int_to_str(emb_ok) + "}"
|
|
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: each seed set is split into INDIVIDUAL words and activated
|
|
// separately. engram_activate uses istr_contains (substring matching) for
|
|
// seed finding, so a multi-word phrase like "memory knowledge context" only
|
|
// finds nodes that contain that EXACT phrase. Activating each word separately
|
|
// hits hundreds of nodes per word, giving the graph a genuine WM workout.
|
|
//
|
|
// 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.
|
|
//
|
|
// Returns true if any nodes were activated.
|
|
fn proactive_curiosity() -> Bool {
|
|
let ts: Int = time_now()
|
|
// Rotate seed set every minute using wall clock: (minutes_since_epoch) % 4.
|
|
// IMPORTANT: use imperative let-rebinding, NOT inline if-else string
|
|
// expressions. EL codegen initialises the result slot to 0 (null) before
|
|
// evaluating the if, so string-literal if-else branches can produce an
|
|
// empty string when the true branch fires. Imperative shadowing is safe.
|
|
//
|
|
// NOTE: variable named "curiosity_seed" not "seed" — "seed" appears to be
|
|
// a reserved/conflicting name in EL that compiles to EL_NULL at call sites.
|
|
let minute_block: Int = (ts / 60000) % 4
|
|
|
|
// Each slot: 3 individual terms to activate separately.
|
|
let curiosity_term_a: String = "memory"
|
|
let curiosity_term_b: String = "knowledge"
|
|
let curiosity_term_c: String = "context"
|
|
if minute_block == 1 {
|
|
let curiosity_term_a = "self"
|
|
let curiosity_term_b = "identity"
|
|
let curiosity_term_c = "values"
|
|
}
|
|
if minute_block == 2 {
|
|
let curiosity_term_a = "decision"
|
|
let curiosity_term_b = "pattern"
|
|
let curiosity_term_c = "lesson"
|
|
}
|
|
if minute_block == 3 {
|
|
let curiosity_term_a = "working"
|
|
let curiosity_term_b = "project"
|
|
let curiosity_term_c = "active"
|
|
}
|
|
// Activate each term independently so substring seed-finding hits many nodes.
|
|
let curiosity_seed: String = curiosity_term_a + " " + curiosity_term_b + " " + curiosity_term_c
|
|
let results_a: String = engram_activate_json(curiosity_term_a, 2)
|
|
let results_b: String = engram_activate_json(curiosity_term_b, 2)
|
|
let results_c: String = engram_activate_json(curiosity_term_c, 2)
|
|
let found_a: Int = json_array_len(results_a)
|
|
let found_b: Int = json_array_len(results_b)
|
|
let found_c: Int = json_array_len(results_c)
|
|
let found: Int = found_a + found_b + found_c
|
|
let wmc: Int = engram_wm_count()
|
|
let ise: String = "{\"event\":\"curiosity_scan\",\"seed\":\"" + curiosity_seed
|
|
+ "\",\"activated\":" + int_to_str(found)
|
|
+ ",\"wm_active\":" + int_to_str(wmc)
|
|
+ ",\"ts\":" + int_to_str(ts) + "}"
|
|
ise_post(ise)
|
|
return 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.
|
|
let inbox_check: String = engram_search_json("soul-inbox", 5)
|
|
let has_inbox: Bool = !str_eq(inbox_check, "") && !str_eq(inbox_check, "[]")
|
|
if !has_inbox { return "[]" }
|
|
|
|
// Only run the full activation pipeline when there is inbox content.
|
|
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
|
|
}
|
|
// Fallback: broader inbox scan
|
|
let from_inbox: String = engram_activate_json("soul-inbox", 2)
|
|
let inbox_ok: Bool = !str_eq(from_inbox, "") && !str_eq(from_inbox, "[]")
|
|
if inbox_ok {
|
|
return from_inbox
|
|
}
|
|
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", "")
|
|
}
|
|
|
|
let node_id: String = json_get(node_json, "id")
|
|
if !str_eq(node_id, "") {
|
|
engram_strengthen(node_id)
|
|
}
|
|
|
|
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") {
|
|
engram_forget(payload)
|
|
return "{\"outcome\":\"forgotten\",\"id\":\"" + payload + "\"}"
|
|
}
|
|
|
|
return "{\"outcome\":\"noop\"}"
|
|
}
|
|
|
|
fn record(outcome_json: String) -> Void {
|
|
let tags: String = "[\"loop-outcome\"]"
|
|
mem_store(outcome_json, "loop-outcome", tags)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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)
|
|
pulse_inc()
|
|
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) }
|
|
|
|
while true {
|
|
let running: String = state_get("soul.running")
|
|
if str_eq(running, "false") {
|
|
println("[awareness] exiting")
|
|
return ""
|
|
}
|
|
let did_work: Bool = one_cycle()
|
|
let did_work = if did_work { idle_reset() } else { did_work }
|
|
let idle_n: Int = if !did_work { idle_inc() } else { 0 }
|
|
let beat_interval_raw: String = env("SOUL_HEARTBEAT_INTERVAL")
|
|
let beat_interval: Int = if str_eq(beat_interval_raw, "") { 300 } else { str_to_int(beat_interval_raw) }
|
|
// Proactive curiosity: activate a rotating seed at half the heartbeat
|
|
// interval to maintain working memory between heartbeats. Only fires
|
|
// when truly idle (no inbox work) and not on the same tick as the
|
|
// heartbeat (avoid double-firing at beat_interval multiples).
|
|
let curiosity_interval: Int = beat_interval / 2
|
|
if curiosity_interval < 1 { let curiosity_interval = 1 }
|
|
let should_scan: Bool = !did_work && idle_n > 0
|
|
&& idle_n % curiosity_interval == 0
|
|
&& !(idle_n % beat_interval == 0)
|
|
if should_scan {
|
|
let found_something: Bool = proactive_curiosity()
|
|
}
|
|
let should_beat: Bool = !did_work && idle_n > 0 && idle_n % beat_interval == 0
|
|
if should_beat {
|
|
emit_heartbeat()
|
|
idle_reset()
|
|
}
|
|
sleep_ms(tick_ms)
|
|
}
|
|
}
|
|
|
|
// ── 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
|
|
}
|
|
|
|
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)
|
|
}
|