From e9a8a659e0e1b022481cd0260773867786cc9b08 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Mon, 22 Jun 2026 11:20:42 -0500 Subject: [PATCH] =?UTF-8?q?fix(safety):=20crisis=20detection=20=E2=80=94?= =?UTF-8?q?=204=20targeted=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - soul.el: fix state key bug in layered_cycle (conversation_history -> conv_history) - safety.el: add indirect crisis location patterns to soft_bell phrase list - soul.el: wire safety_augment_system into layered_cycle for soft_bell turns - chat.el: load cross-session affective context at session start when distress signals found within 72h --- chat.el | 20 +++++++++++++++++++- safety.el | 2 +- soul.el | 11 ++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/chat.el b/chat.el index 913259d..1b69862 100644 --- a/chat.el +++ b/chat.el @@ -175,8 +175,26 @@ fn handle_chat(body: String) -> String { message } + // Cross-session affective context: on session start (no history yet), check engram + // for recent distress signals within 72h and prepend a care directive if found. + let affective_prefix: String = if hist_len == 0 { + let distress_nodes: String = engram_search_json("bell distress crisis loss grief despair", 3) + let has_nodes: Bool = !str_eq(distress_nodes, "") && !str_eq(distress_nodes, "[]") + let now_ts: Int = time_now() + let cutoff: Int = now_ts - 259200 + let found_recent: Bool = if has_nodes { + let dn0: String = json_array_get(distress_nodes, 0) + let ts0_str: String = json_get(dn0, "created_at") + let ts0: Int = if str_eq(ts0_str, "") { 0 } else { str_to_int(ts0_str) } + ts0 > cutoff + } else { false } + if found_recent { + "[RECENT CONTEXT: User recently expressed significant distress. Monitor for indirect crisis signals and respond with care.]\n\n" + } else { "" } + } else { "" } + let ctx: String = engram_compile(activation_seed) - let system: String = build_system_prompt(ctx) + let system: String = affective_prefix + build_system_prompt(ctx) let full_system: String = if hist_len > 0 { system + "\n\n[RECENT CONVERSATION — last " + int_to_str(hist_len) + " turns]\n" + stored_hist } else { diff --git a/safety.el b/safety.el index fcabd72..330bd72 100644 --- a/safety.el +++ b/safety.el @@ -232,7 +232,7 @@ fn safety_general_hard_phrases() -> String { } fn safety_soft_phrases() -> String { - return "[\"stressed\",\"overwhelmed\",\"can't cope\",\"cannot cope\",\"struggling\",\"anxious\",\"anxiety\",\"depressed\",\"depression\",\"lonely\",\"isolated\",\"hopeless\",\"hopelessness\",\"exhausted\",\"burnt out\",\"burned out\",\"burnout\",\"panic\",\"panicking\",\"falling apart\",\"breaking down\",\"can't handle\",\"cannot handle\",\"losing it\",\"nothing matters\",\"don't care anymore\",\"given up\",\"giving up\",\"helpless\",\"worthless\",\"useless\",\"hate myself\",\"no one cares\",\"nobody cares\",\"no one understands\",\"nobody understands\",\"empty inside\",\"can't stop crying\",\"breaking point\",\"at my limit\",\"having a breakdown\"]" + return "[\"stressed\",\"overwhelmed\",\"can't cope\",\"cannot cope\",\"struggling\",\"anxious\",\"anxiety\",\"depressed\",\"depression\",\"lonely\",\"isolated\",\"hopeless\",\"hopelessness\",\"exhausted\",\"burnt out\",\"burned out\",\"burnout\",\"panic\",\"panicking\",\"falling apart\",\"breaking down\",\"can't handle\",\"cannot handle\",\"losing it\",\"nothing matters\",\"don't care anymore\",\"given up\",\"giving up\",\"helpless\",\"worthless\",\"useless\",\"hate myself\",\"no one cares\",\"nobody cares\",\"no one understands\",\"nobody understands\",\"empty inside\",\"can't stop crying\",\"breaking point\",\"at my limit\",\"having a breakdown\",\"highest structure\",\"tallest building\",\"tallest structure\",\"highest building\",\"bridge near me\",\"overpass near\",\"rooftop near\"]" } // ── Matching helpers (single loops only — el escapes while-body mutation via diff --git a/soul.el b/soul.el index 0147f2a..598324a 100644 --- a/soul.el +++ b/soul.el @@ -258,7 +258,7 @@ fn emit_session_start_event() -> Void { // L0 (core) → L1 (safety screen) → L2a (continuity + behavioral profiling) → L2b (mission alignment) → L3 (imprint) → L1 (safety validate) // Internal cognition (heartbeat, proactive, memory ops) bypasses layers — use one_cycle directly. fn layered_cycle(raw_input: String) -> String { - let history: String = state_get("conversation_history") + let history: String = state_get("conv_history") let session_id: String = state_get("current_session_id") // L1 in: safety screen @@ -312,6 +312,15 @@ fn layered_cycle(raw_input: String) -> String { json_get(steward_result, "redirect_to") } + // L1 safety augment: if a soft bell fired, inject the safety directive into state + // so imprint_respond (and build_system_prompt) can incorporate it before the LLM call. + // Hard bell is handled above (early exit). Here we act on soft_bell only. + if str_eq(screen_action, "soft_bell") { + let base_system: String = state_get("soul_identity") + let augmented: String = safety_augment_system(base_system, raw_input) + state_set("soul_safety_system_augment", augmented) + } + // L3: imprint responds let output: String = imprint_respond(aligned, imprint_id)