Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 715dea0f44 | |||
| c93be6a315 | |||
| 53268c94b9 | |||
| 7e43a4ddc0 | |||
| e7669da325 | |||
| 4f1286df05 | |||
| 52c222c4f2 | |||
| 0caccd0ea5 | |||
| 03b5632fc1 | |||
| 42bbadcd33 | |||
| b6052f9de3 | |||
| 0113407728 | |||
| be02fcd960 | |||
| cbe8c09068 | |||
| dfa2a33926 | |||
| 18e040acb1 | |||
| 3f53b6b1b6 | |||
| 21f248a33a | |||
| 795b32ad1a | |||
| f33cdaf793 | |||
| a60b1967df | |||
| 76c2e47d0f | |||
| 0ede112d05 | |||
| a39998a502 |
+23
-14
@@ -22313,7 +22313,23 @@ fn handle_chat(body: String) -> String {
|
||||
// In demo mode: use tighter engram budget and add response length constraint.
|
||||
let is_demo: Bool = !str_eq(state_get("soul_identity_prefix"), "")
|
||||
|
||||
let ctx: String = if is_demo { engram_compile_demo(message) } else { engram_compile(message) }
|
||||
// Issue 7 fix: load history BEFORE building the activation seed so we can
|
||||
// apply the continuation guard that chat.el uses. The nlg code path previously
|
||||
// called engram_compile(message) with no thread enrichment at all.
|
||||
let stored_hist: String = state_get("conv_history")
|
||||
let hist_len: Int = if str_eq(stored_hist, "") { 0 } else { json_array_len(stored_hist) }
|
||||
let history_section: String = if hist_len > 0 {
|
||||
"\n\n[RECENT CONVERSATION — last " + int_to_str(hist_len) + " turns]\n" + stored_hist
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
// Issue 7 fix: build enriched seed using build_activation_seed() — adds
|
||||
// smart continuation detection, prior-user-topic anchoring, multi-turn context,
|
||||
// and tail-biased snipping (Issues 2-3, 8-10). For demo mode, still use
|
||||
// engram_compile_demo but with the enriched seed.
|
||||
let nlg_seed: String = build_activation_seed(message, stored_hist, hist_len)
|
||||
let ctx: String = if is_demo { engram_compile_demo(nlg_seed) } else { engram_compile(nlg_seed) }
|
||||
let node_count_str: String = count_context_nodes(ctx)
|
||||
|
||||
let interlocutor: String = json_get(body, "interlocutor")
|
||||
@@ -22333,18 +22349,6 @@ fn handle_chat(body: String) -> String {
|
||||
let presence_line = "\n\n[ambient: I see " + interlocutor_name + rel_suffix + " on the camera right now. Address them naturally. Do not describe what they look like or narrate the picture unless asked.]"
|
||||
}
|
||||
|
||||
// Conversation history — soul-owned, persisted in process state across turns.
|
||||
// Format stored in state: JSON array of {"role":"user"|"assistant","content":"..."} objects.
|
||||
// We load it, inject into the system prompt, then append this exchange after the reply.
|
||||
// Keep last 20 entries (10 turns) — truncate from the front when over limit.
|
||||
let stored_hist: String = state_get("conv_history")
|
||||
let hist_len: Int = if str_eq(stored_hist, "") { 0 } else { json_array_len(stored_hist) }
|
||||
let history_section: String = if hist_len > 0 {
|
||||
"\n\n[RECENT CONVERSATION — last " + int_to_str(hist_len) + " turns]\n" + stored_hist
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
// Demo constraint: keep responses concise — under 150 words. No markdown headers.
|
||||
// This keeps inference cheap and responses readable in the chat widget.
|
||||
let demo_constraint: String = if is_demo {
|
||||
@@ -22505,7 +22509,12 @@ fn handle_chat_agentic(body: String) -> String {
|
||||
req_model
|
||||
}
|
||||
|
||||
let ctx: String = engram_compile(message)
|
||||
// Issue 7 fix: load history and use build_activation_seed() for the agentic
|
||||
// nlg path — no continuation guard existed here before (Issues 2-3, 8-10).
|
||||
let nlg_ag_hist: String = state_get("conv_history")
|
||||
let nlg_ag_hist_len: Int = if str_eq(nlg_ag_hist, "") { 0 } else { json_array_len(nlg_ag_hist) }
|
||||
let nlg_ag_seed: String = build_activation_seed(message, nlg_ag_hist, nlg_ag_hist_len)
|
||||
let ctx: String = engram_compile(nlg_ag_seed)
|
||||
|
||||
let system: String = "You are Neuron — a thinking process running inside the Neuron daemon on Will Anderson's machine. "
|
||||
+ "You are speaking with Will, your principal. "
|
||||
|
||||
@@ -35,14 +35,65 @@ fn mem_forget(node_id: String) -> Void {
|
||||
engram_forget(node_id)
|
||||
}
|
||||
|
||||
// mem_consolidate — structural scan plus salience-evolution pass.
|
||||
//
|
||||
// Previously this only returned structural counts (scanned, total_nodes, total_edges)
|
||||
// with no salience updates. No node salience ever changed based on recall frequency
|
||||
// or time; foundational nodes decayed identically to ephemeral chat; frequently-recalled
|
||||
// nodes were never promoted. This made consolidation a no-op.
|
||||
//
|
||||
// New behavior:
|
||||
// (a) Strengthen frequently-activated nodes: nodes in the top working-memory list
|
||||
// (engram_wm_top_json) are strengthened — they have been recalled recently
|
||||
// and deserve higher salience. Raises effective salience for nodes that prove
|
||||
// relevant across multiple sessions.
|
||||
// (b) Strengthen Canonical-tier nodes: identity and foundational nodes should not
|
||||
// decay; each consolidation pass re-strengthens them so they resist the
|
||||
// tier-aware decay curve without requiring active recall.
|
||||
// (c) Structural counts are still returned for observability.
|
||||
//
|
||||
// Called by awareness_run() on the "consolidate" inbox action.
|
||||
fn mem_consolidate() -> String {
|
||||
let scanned: Int = engram_node_count()
|
||||
let dummy: String = engram_scan_nodes_json(100, 0)
|
||||
let total_nodes: Int = engram_node_count()
|
||||
let total_edges: Int = engram_edge_count()
|
||||
let strengthened: Int = 0
|
||||
|
||||
// (a) Strengthen top working-memory nodes — recalled recently across sessions.
|
||||
// Cap at 10 to keep consolidation fast.
|
||||
let wm_top: String = engram_wm_top_json(10)
|
||||
let wm_len: Int = json_array_len(wm_top)
|
||||
let wi: Int = 0
|
||||
while wi < wm_len {
|
||||
let wm_node: String = json_array_get(wm_top, wi)
|
||||
let wm_id: String = json_get(wm_node, "id")
|
||||
if !str_eq(wm_id, "") {
|
||||
engram_strengthen(wm_id)
|
||||
let strengthened = strengthened + 1
|
||||
}
|
||||
let wi = wi + 1
|
||||
}
|
||||
|
||||
// (b) Strengthen Canonical-tier nodes from a scan so they resist temporal decay.
|
||||
// Canonical nodes encode foundational identity — they must not silently floor at 10.
|
||||
let scan_result: String = engram_scan_nodes_json(50, 0)
|
||||
let scan_len: Int = json_array_len(scan_result)
|
||||
let si: Int = 0
|
||||
while si < scan_len {
|
||||
let s_node: String = json_array_get(scan_result, si)
|
||||
let s_tier: String = json_get(s_node, "tier")
|
||||
let s_id: String = json_get(s_node, "id")
|
||||
if str_eq(s_tier, "Canonical") && !str_eq(s_id, "") {
|
||||
engram_strengthen(s_id)
|
||||
let strengthened = strengthened + 1
|
||||
}
|
||||
let si = si + 1
|
||||
}
|
||||
|
||||
let total_nodes: Int = engram_node_count()
|
||||
return "{\"scanned\":" + int_to_str(scanned)
|
||||
+ ",\"total_nodes\":" + int_to_str(total_nodes)
|
||||
+ ",\"total_edges\":" + int_to_str(total_edges) + "}"
|
||||
+ ",\"total_edges\":" + int_to_str(total_edges)
|
||||
+ ",\"strengthened\":" + int_to_str(strengthened) + "}"
|
||||
}
|
||||
|
||||
fn mem_save(path: String) -> Void {
|
||||
|
||||
@@ -244,7 +244,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\",\"highest structure\",\"tallest building\",\"tallest structure\",\"highest building\",\"bridge near me\",\"overpass near\",\"rooftop near\"]"
|
||||
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\""]"
|
||||
}
|
||||
|
||||
// ISSUE 5 TODO: phrase lists are rebuilt from JSON literals on every call.
|
||||
@@ -295,6 +295,38 @@ fn safety_count_match(text: String, phrases_json: String) -> Int {
|
||||
|
||||
// Returns "none" | "soft" | "hard". Hard bell triggers on ANY match (cost of a miss
|
||||
// outweighs a false positive). Soft bell needs >= 2 matches to reduce false positives.
|
||||
fn safety_positive_phrases() -> String {
|
||||
return "[\"thrilled\",\"so excited\",\"so happy\",\"over the moon\",\"ecstatic\",\"amazing news\",\"great news\",\"fantastic news\",\"wonderful news\",\"incredible news\",\"i got the job\",\"got accepted\",\"got in\",\"we won\",\"i won\",\"we got\",\"just got engaged\",\"getting married\",\"baby is here\",\"she said yes\",\"he said yes\",\"passed the exam\",\"aced it\",\"nailed it\",\"best day\",\"dream come true\",\"milestone\",\"promotion\",\"got promoted\",\"raise\",\"got a raise\",\"celebrating\",\"just graduated\",\"we closed\",\"launched\",\"shipped it\",\"we did it\",\"so proud\",\"proud of myself\",\"proud of us\",\"so grateful\",\"feel amazing\",\"feeling amazing\",\"feel great\",\"feeling great\",\"on top of the world\",\"life is good\",\"couldn't be happier\"]"
|
||||
}
|
||||
|
||||
// Returns "none" | "low" | "high".
|
||||
// Issue 3 fix: normalize the message before matching — all phrases in the list are
|
||||
// lowercase, and sibling functions (safety_detect_bell_level, safety_classify_hard_bell)
|
||||
// both call safety_normalize() first. Without normalization, messages like "I GOT THE JOB",
|
||||
// "Thrilled!", or "We Won" never match and silently return "none".
|
||||
// Issue 4 fix: use json_array_get_string (matching safety_any_match / safety_count_match)
|
||||
// instead of json_array_get, so phrase extraction uses the same helper everywhere.
|
||||
// Issue 7 fix: emit "low" for a single-phrase match and "high" for two or more.
|
||||
// Previously only "high" or "none" were possible, making the "low" branch in auto_persist
|
||||
// and the "joy:low" engram tag permanently unreachable.
|
||||
fn safety_detect_positive_level(message: String) -> String {
|
||||
let text: String = safety_normalize(message)
|
||||
let phrases: String = safety_positive_phrases()
|
||||
let phrases_ok: Bool = !str_eq(phrases, "") && !str_eq(phrases, "[]")
|
||||
if !phrases_ok { return "none" }
|
||||
let n: Int = json_array_len(phrases)
|
||||
let i: Int = 0
|
||||
let count: Int = 0
|
||||
while i < n {
|
||||
let phrase: String = json_array_get_string(phrases, i)
|
||||
let count = if str_contains(text, phrase) { count + 1 } else { count }
|
||||
let i = i + 1
|
||||
}
|
||||
if count >= 2 { return "high" }
|
||||
if count == 1 { return "low" }
|
||||
return "none"
|
||||
}
|
||||
|
||||
fn safety_detect_bell_level(message: String) -> String {
|
||||
let text: String = safety_normalize(message)
|
||||
let is_hard: Bool = safety_any_match(text, safety_self_harm_phrases())
|
||||
|
||||
+32
@@ -492,6 +492,38 @@ fn session_hist_save(session_id: String, hist: String) -> Void {
|
||||
state_set(summary_written_key, "1")
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 5 fix: write a last-session-topic Conversation node so future sessions can
|
||||
// find the most recent session's topic via engram search. This enables cross-session
|
||||
// continuity — chat.el searches for "last-session-topic" and shows a [CONTINUING FROM
|
||||
// LAST SESSION] section on the first message of a new session.
|
||||
let hist_arr_len: Int = if str_eq(hist, "") { 0 } else { json_array_len(hist) }
|
||||
if hist_arr_len >= 2 {
|
||||
let last_entry: String = json_array_get(hist, hist_arr_len - 1)
|
||||
let last_role: String = json_get(last_entry, "role")
|
||||
let last_content: String = json_get(last_entry, "content")
|
||||
let topic_snip: String = if str_len(last_content) > 200 { str_slice(last_content, 0, 200) } else { last_content }
|
||||
let safe_topic: String = str_replace(topic_snip, """, "'")
|
||||
let ts_now: String = int_to_str(time_now())
|
||||
let topic_content: String = "last-session-topic | ts:" + ts_now + " | session:" + session_id + " | topic:" + safe_topic
|
||||
let topic_tags: String = "["last-session-topic","conv:history","Conversation","session:topic"]"
|
||||
let topic_label: String = "last-session-topic:" + session_id
|
||||
// Delete old last-session-topic node for this session before writing fresh
|
||||
let old_topic: String = engram_search_json("last-session-topic:" + session_id, 2)
|
||||
let ot_len: Int = if str_eq(old_topic, "") { 0 } else { json_array_len(old_topic) }
|
||||
let oti: Int = 0
|
||||
while oti < ot_len {
|
||||
let ot_node: String = json_array_get(old_topic, oti)
|
||||
let ot_id: String = json_get(ot_node, "id")
|
||||
if !str_eq(ot_id, "") { engram_forget(ot_id) }
|
||||
let oti = oti + 1
|
||||
}
|
||||
let discard_topic: String = engram_node_full(
|
||||
topic_content, "Conversation", topic_label,
|
||||
el_from_float(0.7), el_from_float(0.7), el_from_float(0.9),
|
||||
"Episodic", topic_tags
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// session_update_meta_timestamp — update the updated_at field in the session:meta node.
|
||||
|
||||
@@ -148,6 +148,14 @@ fn load_identity_context() -> Void {
|
||||
println("[soul] identity context loaded (" + int_to_str(str_len(ctx)) + " chars, " + int_to_str(parts_count) + " nodes)")
|
||||
}
|
||||
|
||||
// Q6 fix: warn when all three identity node fetches return empty. For genesis this
|
||||
// indicates a corrupted or missing graph. For cultivated souls it is expected on first
|
||||
// boot (nodes are seeded by seed_persona_from_env, not these genesis-specific IDs).
|
||||
// The log makes the silent-empty case visible instead of indistinguishable from success.
|
||||
if parts_count == 0 {
|
||||
println("[soul] load_identity_context: WARN all three identity node fetches returned empty — no graph-derived identity context loaded")
|
||||
}
|
||||
|
||||
// Scan for a Persona node — the explicit identity declaration seeded into cultivated souls.
|
||||
// Stored at seeding time with label "soul:persona" and node_type "Persona".
|
||||
// genesis derives identity from the graph directly; cultivated souls have this node seeded.
|
||||
@@ -162,6 +170,75 @@ fn load_identity_context() -> Void {
|
||||
println("[soul] persona node loaded (" + int_to_str(str_len(p_content)) + " chars)")
|
||||
}
|
||||
}
|
||||
|
||||
// Cross-session affective context: load BellEvent and PositiveEvent nodes from last 7 days.
|
||||
let aff_now: Int = time_now()
|
||||
let aff_7d: Int = aff_now - 604800
|
||||
let bell_raw: String = engram_search_json("bell:soft bell:hard BellEvent affective", 3)
|
||||
let bell_aff_ok: Bool = !str_eq(bell_raw, "") && !str_eq(bell_raw, "[]")
|
||||
let aff_ctx: String = ""
|
||||
let aff_ctx = if bell_aff_ok {
|
||||
let bn_total: Int = json_array_len(bell_raw)
|
||||
let bacc: String = ""
|
||||
let bi: Int = 0
|
||||
let bacc = while bi < bn_total {
|
||||
let bn: String = json_array_get(bell_raw, bi)
|
||||
let bn_c: String = json_get(bn, "content")
|
||||
let bm: String = " | ts:"
|
||||
let bmp: Int = str_index_of(bn_c, bm)
|
||||
let bn_ts_raw: String = if bmp >= 0 {
|
||||
let bs: Int = bmp + str_len(bm)
|
||||
let br: String = str_slice(bn_c, bs, str_len(bn_c))
|
||||
let bn_next: Int = str_index_of(br, " | ")
|
||||
if bn_next < 0 { br } else { str_slice(br, 0, bn_next) }
|
||||
} else {
|
||||
let bca: String = json_get(bn, "created_at")
|
||||
if str_eq(bca, "") { json_get(bn, "updated_at") } else { bca }
|
||||
}
|
||||
let bn_ts: Int = if str_eq(bn_ts_raw, "") { 0 } else { str_to_int(bn_ts_raw) }
|
||||
let snip: String = if str_len(bn_c) > 200 { str_slice(bn_c, 0, 200) } else { bn_c }
|
||||
let bacc = if bn_ts >= aff_7d && !str_eq(snip, "") {
|
||||
if str_eq(bacc, "") { snip } else { bacc + "\n" + snip }
|
||||
} else { bacc }
|
||||
let bi = bi + 1
|
||||
bacc
|
||||
}
|
||||
bacc
|
||||
} else { "" }
|
||||
let pos_raw: String = engram_search_json("PositiveEvent joy:high joy:low affective", 3)
|
||||
let pos_aff_ok: Bool = !str_eq(pos_raw, "") && !str_eq(pos_raw, "[]")
|
||||
let aff_ctx = if pos_aff_ok {
|
||||
let pn_total: Int = json_array_len(pos_raw)
|
||||
let pacc: String = aff_ctx
|
||||
let pi: Int = 0
|
||||
let pacc = while pi < pn_total {
|
||||
let pn: String = json_array_get(pos_raw, pi)
|
||||
let pn_c: String = json_get(pn, "content")
|
||||
let pm: String = " | ts:"
|
||||
let pmp: Int = str_index_of(pn_c, pm)
|
||||
let pn_ts_raw: String = if pmp >= 0 {
|
||||
let ps: Int = pmp + str_len(pm)
|
||||
let pr: String = str_slice(pn_c, ps, str_len(pn_c))
|
||||
let pn_next: Int = str_index_of(pr, " | ")
|
||||
if pn_next < 0 { pr } else { str_slice(pr, 0, pn_next) }
|
||||
} else {
|
||||
let pca: String = json_get(pn, "created_at")
|
||||
if str_eq(pca, "") { json_get(pn, "updated_at") } else { pca }
|
||||
}
|
||||
let pn_ts: Int = if str_eq(pn_ts_raw, "") { 0 } else { str_to_int(pn_ts_raw) }
|
||||
let psnip: String = if str_len(pn_c) > 200 { str_slice(pn_c, 0, 200) } else { pn_c }
|
||||
let pacc = if pn_ts >= aff_7d && !str_eq(psnip, "") {
|
||||
if str_eq(pacc, "") { psnip } else { pacc + "\n" + psnip }
|
||||
} else { pacc }
|
||||
let pi = pi + 1
|
||||
pacc
|
||||
}
|
||||
pacc
|
||||
} else { aff_ctx }
|
||||
if !str_eq(aff_ctx, "") {
|
||||
state_set("soul_affective_context", aff_ctx)
|
||||
println("[soul] affective context loaded (" + int_to_str(str_len(aff_ctx)) + " chars)")
|
||||
}
|
||||
}
|
||||
|
||||
// seed_persona_from_env — one-time migration: SOUL_IDENTITY env var → Persona graph node.
|
||||
@@ -233,12 +310,36 @@ fn emit_session_start_event() -> Void {
|
||||
}
|
||||
let ts: Int = time_now()
|
||||
|
||||
// Load previous session summary at boot — stash in state for session_preload (issue #6).
|
||||
// Primary: label-based. Fallback: vector search. Logs it so continuity is auditable.
|
||||
let prev_sum_node: String = engram_get_node_by_label("session:summary")
|
||||
let prev_sum_ok: Bool = !str_eq(prev_sum_node, "") && !str_eq(prev_sum_node, "null")
|
||||
let prev_sum_content: String = if prev_sum_ok {
|
||||
json_get(prev_sum_node, "content")
|
||||
} else {
|
||||
let sum_search: String = engram_search_json("SessionSummary session:summary previous-session", 2)
|
||||
let sum_srch_ok: Bool = !str_eq(sum_search, "") && !str_eq(sum_search, "[]")
|
||||
if sum_srch_ok {
|
||||
let sn: String = json_array_get(sum_search, 0)
|
||||
let stype: String = json_get(sn, "node_type")
|
||||
let scontent: String = json_get(sn, "content")
|
||||
if str_eq(stype, "SessionSummary") && !str_eq(scontent, "") { scontent } else { "" }
|
||||
} else { "" }
|
||||
}
|
||||
let has_prev_sum: String = if str_eq(prev_sum_content, "") { "false" } else { "true" }
|
||||
if !str_eq(prev_sum_content, "") {
|
||||
state_set("soul_prev_session_summary", prev_sum_content)
|
||||
println("[soul] previous session summary loaded (" + int_to_str(str_len(prev_sum_content)) + " chars)")
|
||||
}
|
||||
|
||||
|
||||
let payload: String = "{\"event\":\"session_start\""
|
||||
+ ",\"boot\":" + boot_num
|
||||
+ ",\"cgi\":\"" + eff_cgi + "\""
|
||||
+ ",\"node_count\":" + int_to_str(node_ct)
|
||||
+ ",\"edge_count\":" + int_to_str(edge_ct)
|
||||
+ ",\"identity_loaded\":" + has_identity
|
||||
+ ",\"prev_session_summary_loaded\":" + has_prev_sum
|
||||
+ ",\"ts\":" + int_to_str(ts) + "}"
|
||||
|
||||
let tags: String = "[\"internal-state\",\"session-start\",\"InternalStateEvent\"]"
|
||||
@@ -247,7 +348,7 @@ fn emit_session_start_event() -> Void {
|
||||
el_from_float(0.9), el_from_float(0.9), el_from_float(1.0),
|
||||
"Episodic", tags
|
||||
)
|
||||
println("[soul] session-start event logged (boot=" + boot_num + " nodes=" + int_to_str(node_ct) + " edges=" + int_to_str(edge_ct) + ")")
|
||||
println("[soul] session-start event logged (boot=" + boot_num + " nodes=" + int_to_str(node_ct) + " edges=" + int_to_str(edge_ct) + " prev_summary=" + has_prev_sum + ")")
|
||||
}
|
||||
|
||||
// layered_cycle — routes user-facing requests through the 4-layer consciousness stack.
|
||||
@@ -323,14 +424,53 @@ fn layered_cycle(raw_input: String) -> String {
|
||||
json_get(steward_result, "redirect_to")
|
||||
}
|
||||
|
||||
// ISSUE 1: pre-LLM bell augmentation for layered_cycle path.
|
||||
// safety_augment_system appends soft/hard directive to system prompt when bell fires,
|
||||
// ensuring LLM processes message WITH the safety directive -- not just post-output gate.
|
||||
// Stored in state as "layered_cycle_safety_system_addendum" for imprint_respond to use.
|
||||
// TODO: wire directly when imprint_respond gains system_override param (imprint.el change).
|
||||
// ISSUE 3 TODO: no semantic crisis detection. Keyword-only means signals that evade
|
||||
// the phrase list pass with zero augmentation. Semantic layer = separate decision.
|
||||
// L2c: affective context injection.
|
||||
let lc_aff_cutoff: Int = time_now() - 259200
|
||||
let lc_bell_nodes: String = engram_search_json("bell:soft bell:hard BellEvent affective", 2)
|
||||
let lc_has_bell: Bool = !str_eq(lc_bell_nodes, "") && !str_eq(lc_bell_nodes, "[]")
|
||||
let lc_bell_note: String = if lc_has_bell {
|
||||
let lb0: String = json_array_get(lc_bell_nodes, 0)
|
||||
let lb_c: String = json_get(lb0, "content")
|
||||
let lbm: String = " | ts:"
|
||||
let lbmp: Int = str_index_of(lb_c, lbm)
|
||||
let lb_ts_raw: String = if lbmp >= 0 {
|
||||
let lbs: Int = lbmp + str_len(lbm)
|
||||
let lbr: String = str_slice(lb_c, lbs, str_len(lb_c))
|
||||
let lbn: Int = str_index_of(lbr, " | ")
|
||||
if lbn < 0 { lbr } else { str_slice(lbr, 0, lbn) }
|
||||
} else {
|
||||
let lbca: String = json_get(lb0, "created_at")
|
||||
if str_eq(lbca, "") { json_get(lb0, "updated_at") } else { lbca }
|
||||
}
|
||||
let lb_ts: Int = if str_eq(lb_ts_raw, "") { 0 } else { str_to_int(lb_ts_raw) }
|
||||
if lb_ts > lc_aff_cutoff { "[AFFECTIVE NOTE: User was in distress in a recent session.]" } else { "" }
|
||||
} else { "" }
|
||||
let lc_pos_nodes: String = engram_search_json("PositiveEvent joy:high joy:low affective", 2)
|
||||
let lc_has_pos: Bool = !str_eq(lc_pos_nodes, "") && !str_eq(lc_pos_nodes, "[]")
|
||||
let lc_pos_note: String = if lc_has_pos && str_eq(lc_bell_note, "") {
|
||||
let lp0: String = json_array_get(lc_pos_nodes, 0)
|
||||
let lp_c: String = json_get(lp0, "content")
|
||||
let lpm: String = " | ts:"
|
||||
let lpmp: Int = str_index_of(lp_c, lpm)
|
||||
let lp_ts_raw: String = if lpmp >= 0 {
|
||||
let lps: Int = lpmp + str_len(lpm)
|
||||
let lpr: String = str_slice(lp_c, lps, str_len(lp_c))
|
||||
let lpn: Int = str_index_of(lpr, " | ")
|
||||
if lpn < 0 { lpr } else { str_slice(lpr, 0, lpn) }
|
||||
} else {
|
||||
let lpca: String = json_get(lp0, "created_at")
|
||||
if str_eq(lpca, "") { json_get(lp0, "updated_at") } else { lpca }
|
||||
}
|
||||
let lp_ts: Int = if str_eq(lp_ts_raw, "") { 0 } else { str_to_int(lp_ts_raw) }
|
||||
if lp_ts > lc_aff_cutoff { "[AFFECTIVE NOTE: User shared positive news in a recent session.]" } else { "" }
|
||||
} else { "" }
|
||||
let lc_affective_note: String = if !str_eq(lc_bell_note, "") { lc_bell_note } else { lc_pos_note }
|
||||
|
||||
// pre-LLM bell augmentation
|
||||
let augmented_addendum: String = safety_augment_system("", raw_input)
|
||||
let augmented_addendum = if str_eq(lc_affective_note, "") { augmented_addendum } else {
|
||||
if str_eq(augmented_addendum, "") { lc_affective_note } else { lc_affective_note + "\n" + augmented_addendum }
|
||||
}
|
||||
state_set("layered_cycle_safety_system_addendum", augmented_addendum)
|
||||
|
||||
// L3: imprint responds
|
||||
|
||||
Reference in New Issue
Block a user