From e9a8a659e0e1b022481cd0260773867786cc9b08 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Mon, 22 Jun 2026 11:20:42 -0500 Subject: [PATCH 1/2] =?UTF-8?q?fix(safety):=20crisis=20detection=20?= =?UTF-8?q?=E2=80=94=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) From c87a536da33b4b5d66f1d63c5f4d6e2a215322de Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Mon, 22 Jun 2026 12:07:18 -0500 Subject: [PATCH 2/2] fix(safety): wire safety augment into system prompt, fix timestamp fallback - Remove dead soft_bell block in layered_cycle that wrote soul_safety_system_augment to state but was never read; safety augmentation now goes through the correct layered_cycle_safety_system_addendum state key read by build_system_prompt - build_system_prompt now reads layered_cycle_safety_system_addendum and appends it to the system prompt, clearing the key after consumption - Timestamp extraction for distress nodes falls back to updated_at when created_at is empty, preventing the 72h recency check from always treating nodes as stale --- chat.el | 13 +++++++++++-- soul.el | 9 --------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/chat.el b/chat.el index 1b69862..4e6c2af 100644 --- a/chat.el +++ b/chat.el @@ -81,7 +81,15 @@ fn build_system_prompt(ctx: String) -> String { "\n\n[ENGRAM CONTEXT — compiled from your graph]\n" + ctx } - return identity + date_line + voice_rules + security_rules + identity_block + engram_block + let safety_addendum: String = state_get("layered_cycle_safety_system_addendum") + let safety_block: String = if str_eq(safety_addendum, "") { + "" + } else { + state_set("layered_cycle_safety_system_addendum", "") + safety_addendum + } + + return identity + date_line + voice_rules + security_rules + identity_block + engram_block + safety_block } fn hist_append(hist: String, role: String, content: String) -> String { @@ -184,7 +192,8 @@ fn handle_chat(body: String) -> String { 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_raw: String = json_get(dn0, "created_at") + let ts0_str: String = if str_eq(ts0_raw, "") { json_get(dn0, "updated_at") } else { ts0_raw } let ts0: Int = if str_eq(ts0_str, "") { 0 } else { str_to_int(ts0_str) } ts0 > cutoff } else { false } diff --git a/soul.el b/soul.el index 598324a..a77f736 100644 --- a/soul.el +++ b/soul.el @@ -312,15 +312,6 @@ 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)