@@ -12,47 +12,113 @@ fn chat_default_model() -> String {
return " claude-sonnet-4-5 "
}
// parse_salience_100 — convert a %g-serialized float to integer * 100.
// The C runtime serializes floats with %g which trims trailing zeros:
// 0.70 → " 0.7 " , 0.60 → " 0.6 " , 0.50 → " 0.5 " , 1.0 → " 1 "
// The naive str_replace ( " . " , " " ) approach breaks for single-decimal strings:
// " 0.7 " → " 07 " → str_to_int → 7 ( WRONG, should be 70 )
// " 0.5 " → " 05 " → str_to_int → 5 ( WRONG, should be 50 )
// " 0.85 " → " 085 " → str_to_int → 85 ( accidentally correct — two decimal digits )
// Fix: use str_index_of to find the decimal point and scale accordingly:
// No decimal ( " 1 " ) : multiply raw by 100
// One decimal digit ( " 0.7 " ) : multiply stripped value by 10
// Two+ decimal digits ( " 0.85 " ) : stripped value is already in hundredths
fn parse_salience_100 ( s: String ) -> Int {
if str_eq ( s, " " ) { return 70 }
let dot_pos: Int = str_index_of ( s, " . " )
let raw: Int = if dot_pos < 0 {
// No decimal point — integer like " 1 " means 100%
str_to_int ( s ) * 100
} else {
let after_dot: String = str_slice ( s, dot_pos + 1 , str_len ( s ) )
let decimal_digits: Int = str_len ( after_dot )
let stripped: Int = str_to_int ( str_replace ( s, " . " , " " ) )
if decimal_digits == 1 { stripped * 10 } else { stripped }
}
if raw > 100 { 100 } else { if raw < 0 { 0 } else { raw } }
}
// engram_score_node — compute a recency x relevance score for a single engram
// node JSON object. Higher is better. Score = salience * importance * recency_factor.
// recency_factor decays linearly over 30 days: nodes updated today score 1.0 ,
// nodes 30+ days old score 0.1 ( floor ) . Nodes with no created_at score 0.5.
// This keeps fresh, high-salience nodes at the top and pushes stale low-signal
// nodes to the bottom so they get trimmed when we cap context size.
// node JSON object. Higher is better.
//
// Bugs fixed vs original implementation:
// 1. FLOAT PARSING: parse_salience_100 correctly handles %g single-decimal output.
// " 0.7 " → 70 , " 0.6 " → 60 , " 0.5 " → 50 ( was: 7 , 6 , 5 — scored near zero and
// were filtered by threshold=25, making the function broken for the majority
// of the graph where conv/utterance nodes have salience/importance ≈ 0.6/0.7 ) .
// 2. RECENCY USES LAST TOUCH: uses max ( created_at, updated_at, last_activated ) so
// nodes strengthened by engram_strengthen ( ) after chat turns are not penalised
// for a stale created_at. A node referenced yesterday but created 25 days ago
// now correctly scores as fresh rather than borderline-filtered.
// 3. COMPRESSED RECENCY RANGE: old formula ( sal * imp * recency / 10000 ) gave
// recency a 10x dynamic range ( 10-100 ) vs 1.9x for salience/importance. A
// canonical high-importance node at 30 days scored the same as a fresh noise
// node. New formula compresses recency to 1.54x via ( 50 + recency/2 ) weight.
// 4. SOFTER FLOOR: recency floor raised from 10 to 30 with tier-aware decay windows
// so canonical identity/persona nodes never bottom out to near-zero.
fn engram_score_node ( node_json: String ) -> Int {
let salience_str: String = json_get ( node_json, " salience " )
let importance_str: String = json_get ( node_json, " importance " )
let created_str: String = json_get ( node_json, " created_at " )
let updated_str: String = json_get ( node_json, " updated_at " )
let activated_str: String = json_get ( node_json, " last_activated " )
let tier_str: String = json_get ( node_json, " tier " )
// Parse as floats via * 100 integer arithmetic ( el has no float math )
let salience_100: Int = if str_eq ( salience_str, " " ) { 70 } else {
let s : Int = str_to_int ( str_replace ( salience_str, " . " , " " ) )
// Clamp to 0-100 range ( value was e.g. " 0.85 " -> parsed " 085 " = 85 )
if s > 100 { 100 } else { if s < 0 { 0 } else { s } }
}
let importance_100: Int = if str_eq ( importance_str, " " ) { 70 } else {
let v: Int = str_to_int ( str_replace ( importance_str, " . " , " " ) )
if v > 100 { 100 } else { if v < 0 { 0 } else { v } }
}
// parse_salience_100 handles " 0.7 " → 70 , " 0.85 " → 85 , " 1.0 " → 100 , " 1 " → 100
let salience_100: Int = parse_salience_100 ( salience_str )
let importance_100 : Int = parse_salience_100 ( importance_str )
// Recency: decay from 100 ( today ) to 10 ( 30+ days ) . created_at is Unix seconds.
// Recency: use max ( created_at, updated_at, last_activated ) .
// last_activated is updated by engram_strengthen ( ) every chat turn — nodes
// actively referenced score fresh regardless of original write time.
let now_ts: Int = time_now ( )
let recency_100 : Int = if str_eq ( created_str, " " ) { 50 } else {
let cre ated_ts: Int = str_to_int ( created_str )
let age_secs: Int = now_ts - created_ts
let age_days : Int = age_secs / 86400
let decay : Int = if age_days >= 30 { 10 } else { 100 - ( age_days * 3 ) }
if decay < 10 { 10 } else { decay }
let c reated_ts : Int = if str_eq ( created_str, " " ) { 0 } else { str_to_int ( created_str ) }
let upd ated_ts: Int = if str_eq ( updated_str, " " ) { 0 } else { str_to_int ( updated_str ) }
let activated_ts: Int = if str_eq ( activated_str, " " ) { 0 } else { str_to_int ( activated_str ) }
let best_ts_ab : Int = if updated_ts > created_ts { updated_ts } else { created_ts }
let best_ts : Int = if activated_ts > best_ts_ab { activated_ts } else { best_ts_ab }
let recency_100: Int = if best_ts == 0 { 50 } else {
let age_secs: Int = now_ts - best_ts
// Guard against clock skew ( future timestamps ) : treat as brand new.
let age_days: Int = if age_secs < 0 { 0 } else { age_secs / 86400 }
// Tier-aware decay, softer floor ( 30 not 10 ) :
// Canonical: 365-day window — foundational identity/persona nodes.
// Episodic: 90-day window — conversation context fades moderately.
// Working/untiered: 35-day window — transient task state.
let is_canonical: Bool = str_eq ( tier_str, " Canonical " )
let is_episodic: Bool = str_eq ( tier_str, " Episodic " )
let decay: Int = if is_canonical {
let drop: Int = if age_days >= 365 { 70 } else { age_days * 70 / 365 }
100 - drop
} else {
if is_episodic {
if age_days >= 90 { 30 } else { 100 - ( age_days * 70 / 90 ) }
} else {
if age_days >= 35 { 30 } else { 100 - ( age_days * 2 ) }
}
}
if decay < 30 { 30 } else { decay }
}
// Combined score 0-1000000 ( no floats ) : salience * importance * recency / 10000
return salience_100 * importance_100 * recency_100 / 10000
// Compressed recency weight ( 50 + recency/2 ) : range 65-100 ( 1.54x dynamic range ) .
// Old formula had 10x recency range which drowned out relevance for old-but-important
// nodes. New: relevance ( 0-100 ) × recency_weight ( 65-100 ) / 100 → score 0-100.
// salience_100 and importance_100 are already in the 0-100 range ( parse_salience_100
// returns e.g. 70 for " 0.7 " ) . Dividing by 100 keeps relevance in 0-100.
// Dividing by 10000 caused integer truncation to 0 for all real-world nodes
// ( e.g., sal=0.7, imp=0.7 → 70*70/10000 = 0 instead of 49 ) .
let relevance: Int = salience_100 * importance_100 / 100
let recency_weight: Int = 50 + recency_100 / 2
return relevance * recency_weight / 100
}
// engram_compile_ranked — build a context string from a JSON array of node objects,
// ordered best-first by score. Only nodes above a minimum score ( 25 = salience 0.5 *
// importance 0.5 * recency 1.0 ) are included ; the rest are noise. Returns at most
// max_nodes entries concatenated as JSON array text. Because el has no sort primitive,
// we do a single selection pass picking the top N by linear scan ( N=10 cap ) .
// ordered best-first by score. Only nodes above threshold=10 are included.
// With corrected formula ( sal*imp/100 ) : sal=0.5*imp=0.5 at max recency scores 25 ;
// sal=0.5*imp=0.5 at Working floor ( recency=30, weight=65 ) scores 16.
// Threshold=10 gives safe headroom for low-salience nodes near the recency floor,
// while still filtering near-zero noise ( e.g., sal=0.1*imp=0.1 → score ≤ 1 ) .
// Returns at most max_nodes entries. max_nodes must not exceed 20 ( sentinel limit ) .
fn engram_compile_ranked ( nodes_json: String, max_nodes: Int ) -> String {
if str_eq ( nodes_json, " " ) { return " " }
if str_eq ( nodes_json, " [] " ) { return " " }
@@ -73,9 +139,10 @@ fn engram_compile_ranked(nodes_json: String, max_nodes: Int) -> String {
while ci < total {
let node: String = json_array_get ( nodes_json, ci )
let score: Int = engram_score_node ( node )
// Threshold lowered from 25 to 15 : includes moderately-relevant older nodes.
// A 3-week-old node with salience 0.6 and importance 0.6 scores ~18 — was dropped, now included.
let above_thresh: Bool = score >= 15
// Threshold=10 : allows moderately-relevant older nodes while filtering noise.
// Example: sal=0.5 imp=0.5 at Working recency floor ( 35+ days ) → score 16 ,
// which passes. A near-zero node ( sal=0.1 imp=0.1 ) → score ≤ 1 , filtered.
let above_thresh: Bool = score >= 10
// Check this index wasn 't already selected ( sentinel: look for idx marker )
let idx_marker: String = " \" _sel_ " + int_to_str ( ci ) + " \" "
let already_picked: Bool = str_contains ( selected, idx_marker )
@@ -102,7 +169,7 @@ fn engram_compile_ranked(nodes_json: String, max_nodes: Int) -> String {
// Strip the _sel_N sentinel fields that were used for duplicate-detection bookkeeping.
// The sentinels have the form " \" _sel_N \" :1, " ( trailing comma, space before next key ) .
// We injected them as the first field in each object, so the pattern is predictable.
// Because el has no regex, remove up to 10 possible sentinel variants by literal replace.
// Because el has no regex, remove up to 20 possible sentinel variants by literal replace.
let clean: String = " [ " + selected + " ] "
let c0: String = str_replace ( clean, " \" _sel_0 \" :1, " , " " )
let c1: String = str_replace ( c0, " \" _sel_1 \" :1, " , " " )
@@ -119,284 +186,67 @@ fn engram_compile_ranked(nodes_json: String, max_nodes: Int) -> String {
let c12: String = str_replace ( c11, " \" _sel_12 \" :1, " , " " )
let c13: String = str_replace ( c12, " \" _sel_13 \" :1, " , " " )
let c14: String = str_replace ( c13, " \" _sel_14 \" :1, " , " " )
return c14
}
// engram_split_topics — split message into sub-queries on explicit conjunctions.
// " health goals AND startup progress " becomes two independent searches.
fn engram_split_topics ( message: String ) -> String {
let sep: String = if str_contains ( message, " AND " ) { " AND " } else {
if str_contains ( message, " and " ) { " and " } else {
if str_contains ( message, " also " ) { " also " } else {
if str_contains ( message, " plus " ) { " plus " } else { " " }
}
}
}
if str_eq ( sep, " " ) { return message }
let sep_pos: Int = str_index_of ( message, sep )
let part1: String = str_slice ( message, 0 , sep_pos )
let part2: String = str_slice ( message, sep_pos + str_len ( sep ) , str_len ( message ) )
let part2_topics: String = engram_split_topics ( part2 )
if str_eq ( part1, " " ) { return part2_topics }
return part1 + " \n " + part2_topics
}
// engram_extract_entities — extract probable named entities ( capital-first, 3+ chars,
// not stop-words ) from a message. Returns newline-separated list.
fn engram_extract_entities ( message: String ) -> String {
let stops: String = " |I|A|The|An|In|On|At|To|Of|For|And|But|Or|So|My|Me|We|Us|He|She|It|Is|Are|Was|Were|Has|Have|Had|Do|Does|Did|Can|Could|Will|Would|Should|May|Might|Must|Be|Been|Being|This|That|These|Those|What|When|Where|Who|How|Why|Which|If|Then|Now|Just|Also|Not|No|Yes|Oh|Hi|Hey|Ok|Okay|Please|Thank|Thanks|You|Your|Our|Its|His|Her|Their|Any|All|Some|Get|Got|Let|Say|Think|Know|See|Look|Go|Come|Make|Take|Give|Tell|Ask|Need|Want|Like|Love|Feel|Try|Use|Find|Keep|Put|Set|Run|Start|Stop|Show|Help|Work|Play|Move|Change|Follow|Call|Talk|Check|Remind|Update|Create|Delete|Fix|Add|Remove|Open|Close|Read|Write|Send|Receive| "
let capitals: String = " ABCDEFGHIJKLMNOPQRSTUVWXYZ "
let entities: String = " "
let entity_count: Int = 0
let msg_len: Int = str_len ( message )
let pos: Int = 0
while pos < msg_len && entity_count < 10 {
let wend: Int = pos
let scanning: Bool = true
while scanning && wend < msg_len {
let wch: String = str_slice ( message, wend, wend + 1 )
let is_sep: Bool = str_eq ( wch, " " ) || str_eq ( wch, " \n " ) || str_eq ( wch, " \t " )
|| str_eq ( wch, " , " ) || str_eq ( wch, " . " ) || str_eq ( wch, " ? " )
|| str_eq ( wch, " ! " ) || str_eq ( wch, " : " ) || str_eq ( wch, " ; " )
|| str_eq ( wch, " ( " ) || str_eq ( wch, " ) " ) || str_eq ( wch, " \' " ) || str_eq ( wch, " - " )
let scanning = if is_sep { false } else { scanning }
let wend = if !is_sep { wend + 1 } else { wend }
}
let word: String = str_slice ( message, pos, wend )
let word_len: Int = str_len ( word )
let first_ch: String = if word_len >= 3 { str_slice ( word, 0 , 1 ) } else { " " }
let is_capital: Bool = word_len >= 3 && str_contains ( capitals, first_ch )
let is_stop: Bool = str_contains ( stops, " | " + word + " | " )
let already_have: Bool = str_contains ( entities, word )
let should_add: Bool = is_capital && !is_stop && !already_have && word_len >= 3
let entities = if should_add {
let entity_count = entity_count + 1
if str_eq ( entities, " " ) { word } else { entities + " \n " + word }
} else { entities }
let pos = if wend > pos { wend + 1 } else { pos + 1 }
}
return entities
}
// engram_detect_recall_intent — true when message explicitly requests memory recall.
fn engram_detect_recall_intent ( message: String ) -> Bool {
return str_contains ( message, " remind me " )
|| str_contains ( message, " do you remember " )
|| str_contains ( message, " what do you know " )
|| str_contains ( message, " what happened " )
|| str_contains ( message, " tell me about " )
|| str_contains ( message, " what was " )
|| str_contains ( message, " what were " )
|| str_contains ( message, " how is it going " )
|| str_contains ( message, " how are things " )
|| str_contains ( message, " catch me up " )
|| str_contains ( message, " fill me in " )
|| str_contains ( message, " what's the status " )
|| str_contains ( message, " whats the status " )
|| str_contains ( message, " any updates " )
|| str_contains ( message, " recap " )
|| str_contains ( message, " look up " )
|| str_contains ( message, " check on " )
|| str_contains ( message, " how did " )
|| str_contains ( message, " what happened with " )
}
// engram_is_continuation — semantic continuation detection replacing the brittle 50-char
// threshold. Returns true when message starts with a pronoun, continuation opener, or is
// < 80 chars ( raised from 50 to catch " Can you remind me what Prism's architecture
// looks like? " at 57 chars which is clearly a continuation in an active thread ) .
fn engram_is_continuation ( message: String, hist_len: Int ) -> Bool {
if hist_len <= 0 { return false }
let has_pronoun: Bool = str_starts_with ( message, " It " )
|| str_starts_with ( message, " it " )
|| str_starts_with ( message, " That " ) || str_starts_with ( message, " that " )
|| str_starts_with ( message, " This " ) || str_starts_with ( message, " this " )
|| str_starts_with ( message, " They " ) || str_starts_with ( message, " they " )
|| str_starts_with ( message, " He " ) || str_starts_with ( message, " he " )
|| str_starts_with ( message, " She " ) || str_starts_with ( message, " she " )
|| str_starts_with ( message, " We " ) || str_starts_with ( message, " we " )
if has_pronoun { return true }
let is_cont_opener: Bool = str_starts_with ( message, " Go on " )
|| str_starts_with ( message, " go on " )
|| str_starts_with ( message, " Continue " ) || str_starts_with ( message, " continue " )
|| str_starts_with ( message, " Yes " ) || str_starts_with ( message, " yes " )
|| str_starts_with ( message, " No, " ) || str_starts_with ( message, " no, " )
|| str_starts_with ( message, " Ok " ) || str_starts_with ( message, " ok " )
|| str_starts_with ( message, " And " ) || str_starts_with ( message, " and " )
|| str_starts_with ( message, " But " ) || str_starts_with ( message, " but " )
|| str_starts_with ( message, " What about " ) || str_starts_with ( message, " what about " )
|| str_starts_with ( message, " Why " ) || str_starts_with ( message, " why " )
|| str_starts_with ( message, " How " ) || str_starts_with ( message, " how " )
|| str_starts_with ( message, " When " ) || str_starts_with ( message, " when " )
if is_cont_opener { return true }
if str_len ( message ) < 80 { return true }
return false
}
// engram_compile_multi — run activation + search for one topic with expanded pools.
// Activation depth 8 ( was 5 ) . Search 30 candidates ranked to 12 ( was 20/8 ) .
// Per-topic result pool: up to 20 nodes ( was 13 ) .
fn engram_compile_multi ( topic: String ) -> String {
let activate_json: String = engram_activate_json ( topic, 8 )
let search_json: String = engram_search_json ( topic, 30 )
let act_ok: Bool = !str_eq ( activate_json, " " ) && !str_eq ( activate_json, " [] " )
let srch_ok: Bool = !str_eq ( search_json, " " ) && !str_eq ( search_json, " [] " )
let act_nodes: String = if act_ok { activate_json } else { " " }
let srch_nodes: String = if srch_ok { engram_compile_ranked ( search_json, 12 ) } else { " " }
if !str_eq ( act_nodes, " " ) && !str_eq ( srch_nodes, " " ) {
let act_inner: String = str_slice ( act_nodes, 1 , str_len ( act_nodes ) - 1 )
let srch_inner: String = str_slice ( srch_nodes, 1 , str_len ( srch_nodes ) - 1 )
return engram_dedup_nodes ( " [ " + act_inner + " , " + srch_inner + " ] " )
}
if !str_eq ( act_nodes, " " ) { return act_nodes }
if !str_eq ( srch_nodes, " " ) { return srch_nodes }
return " "
}
// engram_nodes_merge — merge two node arrays, deduplicating by node id.
fn engram_nodes_merge ( a: String, b: String ) -> String {
let ok_a: Bool = !str_eq ( a, " " ) && !str_eq ( a, " [] " )
let ok_b: Bool = !str_eq ( b, " " ) && !str_eq ( b, " [] " )
if !ok_a && !ok_b { return " " }
if !ok_a { return b }
if !ok_b { return a }
let ai: String = str_slice ( a, 1 , str_len ( a ) - 1 )
let bi: String = str_slice ( b, 1 , str_len ( b ) - 1 )
return engram_dedup_nodes ( " [ " + ai + " , " + bi + " ] " )
}
// id_in_seen — check if node_id appears in the comma-delimited seen accumulator.
// Pads both sides with commas to avoid false substring matches.
fn id_in_seen ( node_id: String, seen: String ) -> Bool {
if str_eq ( node_id, " " ) { return false }
if str_eq ( seen, " " ) { return false }
return str_contains ( " , " + seen + " , " , " , " + node_id + " , " )
}
// add_to_seen — append node_id to the comma-delimited seen accumulator.
fn add_to_seen ( seen: String, node_id: String ) -> String {
if str_eq ( node_id, " " ) { return seen }
if str_eq ( seen, " " ) { return node_id }
return seen + " , " + node_id
}
// engram_extract_ids — extract all non-empty " id " fields from a JSON node array
// into a comma-delimited string for use with id_in_seen / add_to_seen.
fn engram_extract_ids ( nodes_json: String ) -> String {
if str_eq ( nodes_json, " " ) { return " " }
if str_eq ( nodes_json, " [] " ) { return " " }
let total: Int = json_array_len ( nodes_json )
if total == 0 { return " " }
let ids: String = " "
let i: Int = 0
while i < total {
let node: String = json_array_get ( nodes_json, i )
let nid: String = json_get ( node, " id " )
let ids = if str_eq ( nid, " " ) { ids } else { add_to_seen ( ids, nid ) }
let i = i + 1
}
return ids
let c15: String = str_replace ( c14, " \" _sel_15 \" :1, " , " " )
let c16: String = str_replace ( c15, " \" _sel_16 \" :1, " , " " )
let c17: String = str_replace ( c16, " \" _sel_17 \" :1, " , " " )
let c18: String = str_replace ( c17, " \" _sel_18 \" :1, " , " " )
let c19: String = str_replace ( c18, " \" _sel_19 \" :1, " , " " )
return c19
}
fn engram_compile ( intent: String ) -> String {
// Issue 1: decompose multi-topic messages into sub-queries.
let topics: String = engram_split_topics ( intent )
let has_multi_topic : Bool = str_contains ( topics, " \n " )
let activate_json: String = engram_activate_json ( intent, 5 )
// Fetch more search results than we 'll use so ranking has a real pool to pick from.
let search_json : String = engram_search_json ( intent, 20 )
// Issue 4: detect explicit recall intent and run boosted search.
let is_recall_intent: Bool = engram_detect_recall_intent ( intent )
let act_ok: Bool = !str_eq ( activate_json, " " ) && !str_eq ( activate_json, " [] " )
let srch_ok: Bool = !str_eq ( search_json, " " ) && !str_eq ( search_json, " [] " )
// Issue 2: extract named entities for dedicated per-entity searches.
let entity_list: String = engram_extract_entities ( intent )
let has_entities: Bool = !str_eq ( entity_list, " " )
// Activation nodes ( spreading activation ) are high-signal but apply scoring via
// engram_compile_ranked with threshold=5 to exclude genuinely zero-quality stale
// nodes that happen to be graph-connected. The threshold of 5 is well below the
// search path threshold of 15 to preserve the activation path 's higher recall.
let act_part: String = if act_ok { engram_compile_ranked ( activate_json, 5 ) } else { " " }
// Primary topic search ( first or only topic ) .
let topic0: String = if has_multi_topic {
let nl0 : Int = str_index_of ( topics, " \n " )
str_slice ( topics, 0 , nl0 )
} else { topics }
let nodes0: String = engram_compile_multi ( topic0 )
// Rank search results and keep only the top 8 ( was: flat 15 unranked ) .
// This cuts context noise roughly in half while preserving the best-scoring nodes.
let srch_ranked : String = if srch_ok { engram_compile_ranked ( search_json, 8 ) } else { " " }
let srch_part: String = srch_ranked
// Second topic segment.
let nodes1: String = if has_multi_topic {
let nl0: Int = str_index_of ( topics, " \n " )
let rest1: String = str_slice ( topics, nl0 + 1 , str_len ( topics ) )
let nl1 : Int = str_index_of ( rest1, " \n " )
let topic1: String = if nl1 < 0 { rest1 } else { str_slice ( rest1, 0 , nl1 ) }
if str_eq ( topic1, " " ) { " " } else { engram_compile_multi ( topic1 ) }
} else { " " }
// Fallback: when vector search returns nothing ( no embeddings ) , fetch pinned
// high-salience nodes by their known IDs. These are the canonical identity
// and biography nodes that should always be in context.
// engram_get_node_json ( id ) returns a single node as JSON or " " if missing.
let scan_part : String = if !act_ok && !srch_ok {
let family_node: String = engram_get_node_json ( " knw-35940684-abc4-42f0-b942-818f66b1f69a " )
let origin_node: String = engram_get_node_json ( " knw-729fc901-8335-44c4-9f3a-b150b4aa0915 " )
let fam_ok: Bool = !str_eq ( family_node, " " ) && !str_eq ( family_node, " null " )
let orig_ok: Bool = !str_eq ( origin_node, " " ) && !str_eq ( origin_node, " null " )
let fam_str: String = if fam_ok { family_node } else { " " }
let orig_str: String = if orig_ok { origin_node } else { " " }
let sep: String = if fam_ok && orig_ok { " \n " } else { " " }
let combined: String = fam_str + sep + orig_str
if str_eq ( combined, " " ) { " " } else { combined }
} else {
" "
}
// Third topic segment.
let nodes2: String = if has_multi_topic {
let nl0: Int = str_index_of ( topics, " \n " )
let rest1: String = str_slice ( topics, nl0 + 1 , str_len ( topics ) )
let nl1: Int = str_index_of ( rest1, " \n " )
if nl1 < 0 { " " } else {
let rest2: String = str_slice ( rest1, nl1 + 1 , str_len ( rest1 ) )
let nl2: Int = str_index_of ( rest2, " \n " )
let topic2: String = if nl2 < 0 { rest2 } else { str_slice ( rest2, 0 , nl2 ) }
if str_eq ( topic2, " " ) { " " } else { engram_compile_multi ( topic2 ) }
}
} else { " " }
// Issue 2 cont.: entity 0 dedicated search ( 15 candidates, ranked 6 ) .
let entity_nodes0: String = if has_entities {
let nl_e0: Int = str_index_of ( entity_list, " \n " )
let entity0: String = if nl_e0 < 0 { entity_list } else { str_slice ( entity_list, 0 , nl_e0 ) }
if str_eq ( entity0, " " ) { " " } else {
let ent_srch: String = engram_search_json ( entity0, 15 )
let ent_ok: Bool = !str_eq ( ent_srch, " " ) && !str_eq ( ent_srch, " [] " )
if ent_ok { engram_compile_ranked ( ent_srch, 6 ) } else { " " }
}
} else { " " }
// Entity 1 dedicated search.
let entity_nodes1: String = if has_entities {
let nl_e0: Int = str_index_of ( entity_list, " \n " )
if nl_e0 < 0 { " " } else {
let rest_e: String = str_slice ( entity_list, nl_e0 + 1 , str_len ( entity_list ) )
let nl_e1: Int = str_index_of ( rest_e, " \n " )
let entity1: String = if nl_e1 < 0 { rest_e } else { str_slice ( rest_e, 0 , nl_e1 ) }
if str_eq ( entity1, " " ) { " " } else {
let ent_srch1: String = engram_search_json ( entity1, 15 )
let ent1_ok: Bool = !str_eq ( ent_srch1, " " ) && !str_eq ( ent_srch1, " [] " )
if ent1_ok { engram_compile_ranked ( ent_srch1, 6 ) } else { " " }
}
}
} else { " " }
// Issue 4 cont.: boosted search for recall-intent ( 40 candidates, ranked 15 ) .
let recall_boost: String = if is_recall_intent {
let boost_srch: String = engram_search_json ( intent, 40 )
let boost_ok: Bool = !str_eq ( boost_srch, " " ) && !str_eq ( boost_srch, " [] " )
if boost_ok { engram_compile_ranked ( boost_srch, 15 ) } else { " " }
} else { " " }
// Merge all pools, deduplicating at each step.
let merged: String = engram_nodes_merge ( nodes0, nodes1 )
let merged: String = engram_nodes_merge ( merged, nodes2 )
let merged: String = engram_nodes_merge ( merged, entity_nodes0 )
let merged: String = engram_nodes_merge ( merged, entity_nodes1 )
let merged: String = engram_nodes_merge ( merged, recall_boost )
let merged_nodes: String = merged
// Fallback: when all searches return nothing, fetch persona nodes.
let scan_part: String = if str_eq ( merged_nodes, " " ) || str_eq ( merged_nodes, " [] " ) {
let persona_fallback: String = engram_search_json ( " soul:persona Persona identity " , 5 )
let pf_ok: Bool = !str_eq ( persona_fallback, " " ) && !str_eq ( persona_fallback, " [] " )
if pf_ok {
let pf_ranked: String = engram_compile_ranked ( persona_fallback, 3 )
if str_eq ( pf_ranked, " " ) { " " } else { pf_ranked }
} else { " " }
} else { " " }
// Affective context: always include the most recent high-emotion memory within 72h.
// Affective context: always include the most recent high-emotion memory if one
// exists within 72 hours. This ensures continuity of care across turns — when
// the user was in distress earlier in the session ( or recently ) , that context
// travels into every subsequent LLM call so the response register stays aware.
// We search for BellEvent nodes specifically ; these are written by auto_persist
// when safety_detect_bell_level fires. The 72h window ( 259200 seconds ) is wide
// enough to span a multi-session day without pulling ancient history.
let bell_nodes: String = engram_search_json ( " bell:soft bell:hard BellEvent " , 3 )
let bell_ok: Bool = !str_eq ( bell_nodes, " " ) && !str_eq ( bell_nodes, " [] " )
let now_ts: Int = time_now ( )
let cutoff_ts: Int = now_ts - 259200
let recent_bell: String = if bell_ok {
let bn0: String = json_array_get ( bell_nodes, 0 )
// created_at is not present in engram node JSON for BellEvent nodes.
// Extract the timestamp embedded in the content string as " | ts:NNNNN " .
// Fall back to created_at / updated_at JSON fields if the marker is absent.
let bn_content: String = json_get ( bn0, " content " )
let ts_marker: String = " | ts: "
let ts_pos: Int = str_index_of ( bn_content, ts_marker )
@@ -414,43 +264,20 @@ fn engram_compile(intent: String) -> String {
} else { " " }
let affective_part: String = if !str_eq ( recent_bell, " " ) { recent_bell } else { " " }
let has_main : Bool = !str_eq ( merged_nodes , " " ) && !str_eq ( merged_nodes , " [] " )
let main_part : String = if has_main { merged_nodes } else { scan_part }
let sep_ma : String = if !str_eq ( mai n_part, " " ) && !str_eq ( affective_part, " " ) { " \n " } else { " " }
let ctx: String = main_part + sep_ma + affective_part
// Dedup fix: publish seen node IDs so downstream callers ( session_preload ) can skip
// nodes already present in the compiled context. Must be computed after scan_part and
// affective_part are resolved so all three segments are represented in the seen set.
// EL has no tuple returns so we use state as an out-param.
// scan_part is a JSON array — extract with engram_extract_ids.
// affective_part is a bare JSON object ( bn0 ) , not an array — extract its id directly.
let ids_from_merged: String = engram_extract_ids ( merged_nodes )
let ids_from_scan: String = engram_extract_ids ( scan_part )
let ids_from_affective: String = json_get ( affective_part, " id " )
let compile_seen_ids: String = add_to_seen ( add_to_seen ( ids_from_merged, ids_from_scan ) , ids_from_affective )
state_set ( " engram_compile_seen_ids " , compile_seen_ids )
let sep1 : String = if !str_eq ( act_part , " " ) && !str_eq ( srch_part , " " ) { " \n " } else { " " }
let sep2 : String = if ( !str_eq ( act_part, " " ) || !str_eq ( srch_part, " " ) ) && !str_eq ( scan_part, " " ) { " \n " } else { " " }
let sep3 : String = if ( !str_eq ( act_part, " " ) || !str_eq ( srch_part, " " ) || !str_eq ( sca n_part, " " ) ) && !str_eq ( affective_part, " " ) { " \n " } else { " " }
let ctx: String = act_part + sep1 + srch_part + sep2 + scan_part + sep3 + affective_part
if str_eq ( ctx, " " ) { return " " }
// Issue 7 fix: safe JSON truncation — find last closing brace before budget cap.
// Budget raised from 6000 to 8000 for the larger multi-topic pool.
let budget: Int = 8000
if str_len ( ctx ) <= budget { return ctx }
let search_end: Int = budget - 1
let scan_limit: Int = if search_end > 500 { search_end - 500 } else { 0 }
let found_pos: Int = -1
let si: Int = search_end
while si >= scan_limit {
let ch: String = str_slice ( ctx, si, si + 1 )
let found_pos = if str_eq ( ch, " } " ) && found_pos < 0 { si } else { found_pos }
let si = if found_pos >= 0 { scan_limit - 1 } else { si - 1 }
// Raise the cap slightly to match the ranked ( higher-signal ) output.
if str_len ( ctx ) > 6000 {
return str_slice ( ctx, 0 , 6000 )
}
if found_pos < 0 { return str_slice ( ctx, 0 , budget ) }
let truncated: String = str_slice ( ctx, 0 , found_pos + 1 )
if str_starts_with ( ctx, " [ " ) { return truncated + " ] " }
return truncated
return ctx
}
fn json_safe ( s: String ) -> String {
let s1: String = str_replace ( s, " \\ " , " \\ \\ " )
let s2: String = str_replace ( s1, " \" " , " \\ \" " )
@@ -634,18 +461,18 @@ fn handle_chat(body: String) -> String {
}
// Load history BEFORE compiling context so we can anchor activation to the thread.
// TODO ( reliability # 3 — conv_history global race ) : process-global key ; concurrent
// /api/chat requests without session_id race on this read-append-write.
let state_hist: String = state_get ( " conv_history " )
let stored_hist: String = if str_eq ( state_hist, " " ) { conv_history_load ( ) } else { state_hist }
let hist_len: Int = if str_eq ( stored_hist, " " ) { 0 } else { json_array_len ( stored_hist ) }
// Issue 8 fix: use semantic continuation detection instead of brittle 50-char threshold.
let is_continuation: Bool = engram_is_continuation ( message, hist_len )
// Thread-aware activation: short/ambiguous messages ( continuations like " go on " ,
// " what else? " , " yes " ) activate on the last reply instead of the bare message.
// This prevents a strong off-topic memory node from hijacking the reply when the
// user is clearly continuing an existing thread.
let is_continuation: Bool = str_len ( message ) < 50 && hist_len > 0
let last_entry: String = if is_continuation { json_array_get ( stored_hist, hist_len - 1 ) } else { " " }
let last_content: String = if !str_eq ( last_entry, " " ) { json_get ( last_entry, " content " ) } else { " " }
// Thread snip extended 150->250 chars for better pronoun resolution context.
let thread_snip: String = if str_len ( last_content ) > 250 { str_slice ( last_content, 0 , 250 ) } else { last_content }
let thread_snip: String = if str_len ( last_content ) > 150 { str_slice ( last_content, 0 , 150 ) } else { last_content }
let activation_seed: String = if !str_eq ( thread_snip, " " ) {
thread_snip + " " + message
} else {
@@ -672,114 +499,73 @@ fn handle_chat(body: String) -> String {
} else { " " }
let ctx: String = engram_compile ( activation_seed )
// Read IDs published by engram_compile so session_preload can skip duplicate nodes.
// EL has no multiple return values ; engram_compile writes its seen set to state.
let seen_ids: String = state_get ( " engram_compile_seen_ids " )
let system: String = affective_prefix + build_system_prompt ( ctx )
// Issue 9 fix: add project-specific and session-summary searches to session preload.
// Old hardcoded " user profile " and " in_progress active project " miss project-specific
// nodes stored under names like " Prism " unless those exact words appear in content.
// Dedup fix: skip any node whose ID already appeared in engram_compile 's output.
// First message of the session: proactively load user profile and active work context.
// These two searches give the soul grounding before any conversation history exists.
// Results are rendered as brief bullets — not raw JSON — so they don 't inflate context.
let session_preload: String = if hist_len == 0 {
let profile_nodes: String = engram_search_json ( " user profile identity preferences " , 5 )
let work_nodes: String = engram_search_json ( " in_progress active project work " , 5 )
let project_nodes: String = engram_search_json ( " project status current ongoing active " , 5 )
let summary_nodes: String = engram_search_json ( " SessionSummary session:summary previous-session recent " , 3 )
let work_nodes: String = engram_search_json ( " in_progress active project " , 5 )
let profile_ok: Bool = !str_eq ( profile_nodes, " " ) && !str_eq ( profile_nodes, " [] " )
let work_ok: Bool = !str_eq ( work_nodes, " " ) && !str_eq ( work_nodes, " [] " )
let project_ok: Bool = !str_eq ( project_nodes, " " ) && !str_eq ( project_nodes, " [] " )
let summary_ok: Bool = !str_eq ( summary_nodes, " " ) && !str_eq ( summary_nodes, " [] " )
// Extract content fields and render as bullet points ( one per node, first 120 chars ) .
let profile_bullets: String = if profile_ok {
let pn: Int = json_array_len ( profile_nodes )
let bullets: String = " "
let bullets = if pn > 0 {
let pi: Int = 0
// Collect up to 3 profile bullets
let bullets = if pi < pn {
let n0: String = json_array_get ( profile_nodes, 0 )
let n0_id: String = json_get ( n0, " id " )
let c0: String = json_get ( n0, " content " )
let s0: String = if str_len ( c0 ) > 120 { str_slice ( c0, 0 , 120 ) } else { c0 }
if str_eq ( s0, " " ) || id_in_seen ( n0_id, seen_ids ) { bullets } else { " - " + s0 }
let snip 0: String = if str_len ( c0 ) > 120 { str_slice ( c0, 0 , 120 ) } else { c0 }
if str_eq ( snip 0, " " ) { bullets } else { " - " + snip0 }
} else { bullets }
let bullets = if pn > 1 {
let n1: String = json_array_get ( profile_nodes, 1 )
let n1_id: String = json_get ( n1, " id " )
let c1: String = json_get ( n1, " content " )
let s1: String = if str_len ( c1 ) > 120 { str_slice ( c1, 0 , 120 ) } else { c1 }
if str_eq ( s1, " " ) || id_in_seen ( n1_id, seen_ids ) { bullets } else { bullets + " \n - " + s1 }
let snip 1: String = if str_len ( c1 ) > 120 { str_slice ( c1, 0 , 120 ) } else { c1 }
if str_eq ( snip 1, " " ) { bullets } else { bullets + " \n - " + snip1 }
} else { bullets }
let bullets = if pn > 2 {
let n2: String = json_array_get ( profile_nodes, 2 )
let n2_id: String = json_get ( n2, " id " )
let c2: String = json_get ( n2, " content " )
let s2: String = if str_len ( c2 ) > 120 { str_slice ( c2, 0 , 120 ) } else { c2 }
if str_eq ( s2, " " ) || id_in_seen ( n2_id, seen_ids ) { bullets } else { bullets + " \n - " + s2 }
let snip 2: String = if str_len ( c2 ) > 120 { str_slice ( c2, 0 , 120 ) } else { c2 }
if str_eq ( snip 2, " " ) { bullets } else { bullets + " \n - " + snip2 }
} else { bullets }
bullets
} else { " " }
let work_bullets: String = if work_ok {
let wn: Int = json_array_len ( work_nodes )
let wb: String = " "
let wb = if wn > 0 {
let wbullets : String = " "
let wbullets = if wn > 0 {
let w0: String = json_array_get ( work_nodes, 0 )
let w0_id: String = json_get ( w0, " id " )
let wc0: String = json_get ( w0, " content " )
let ws0: String = if str_len ( wc0 ) > 120 { str_slice ( wc0, 0 , 120 ) } else { wc0 }
if str_eq ( ws0, " " ) || id_in_seen ( w0_id, seen_ids ) { wb } else { " - " + ws0 }
} else { wb }
let wb = if wn > 1 {
let wsnip 0: String = if str_len ( wc0 ) > 120 { str_slice ( wc0, 0 , 120 ) } else { wc0 }
if str_eq ( wsnip 0, " " ) { wbullets } else { " - " + wsnip0 }
} else { wbullets }
let wbullets = if wn > 1 {
let w1: String = json_array_get ( work_nodes, 1 )
let w1_id: String = json_get ( w1, " id " )
let wc1: String = json_get ( w1, " content " )
let ws1: String = if str_len ( wc1 ) > 120 { str_slice ( wc1, 0 , 120 ) } else { wc1 }
if str_eq ( ws1, " " ) || id_in_seen ( w1_id, seen_ids ) { wb } else { wb + " \n - " + ws1 }
} else { wb }
wb
let wsnip 1: String = if str_len ( wc1 ) > 120 { str_slice ( wc1, 0 , 120 ) } else { wc1 }
if str_eq ( wsnip 1, " " ) { wbullets } else { wbullets + " \n - " + wsnip1 }
} else { wbullets }
wbullets
} else { " " }
let project _bullets: String = if project_ok {
let prn : Int = json_array_len ( project_nodes )
let pb: String = " "
let pb = if prn > 0 {
let pr0: String = json_array_get ( project_nodes, 0 )
let pr0_id: String = json_get ( pr0, " id " )
let prc0: String = json_get ( pr0, " content " )
let ps0: String = if str_len ( prc0 ) > 120 { str_slice ( prc0, 0 , 120 ) } else { prc0 }
if str_eq ( ps0, " " ) || id_in_seen ( pr0_id, seen_ids ) { pb } else { " - " + ps0 }
} else { pb }
let pb = if prn > 1 {
let pr1: String = json_array_get ( project_nodes, 1 )
let pr1_id: String = json_get ( pr1, " id " )
let prc1: String = json_get ( pr1, " content " )
let ps1: String = if str_len ( prc1 ) > 120 { str_slice ( prc1, 0 , 120 ) } else { prc1 }
if str_eq ( ps1, " " ) || id_in_seen ( pr1_id, seen_ids ) { pb } else { pb + " \n - " + ps1 }
} else { pb }
pb
} else { " " }
let summary_bullet: String = if summary_ok {
let sn0: String = json_array_get ( summary_nodes, 0 )
let sn0_id: String = json_get ( sn0, " id " )
let sc0: String = json_get ( sn0, " content " )
let ss0: String = if str_len ( sc0 ) > 200 { str_slice ( sc0, 0 , 200 ) } else { sc0 }
if str_eq ( ss0, " " ) || id_in_seen ( sn0_id, seen_ids ) { " " } else { " - " + ss0 }
} else { " " }
let hp: Bool = !str_eq ( profile_bullets, " " )
let hw: Bool = !str_eq ( work_bullets, " " )
let hpr: Bool = !str_eq ( project_bullets, " " )
let hs: Bool = !str_eq ( summary_bullet, " " )
let preload: String = if hp || hw || hpr || hs {
let sec_p: String = if hp { " [USER CONTEXT — from memory] \n " + profile_bullets } else { " " }
let sec_w: String = if hw { " [ACTIVE WORK — from memory] \n " + work_bullets } else { " " }
let sec_pr: String = if hpr { " [PROJECTS — from memory] \n " + project_bullets } else { " " }
let sec_s: String = if hs { " [PREVIOUS SESSION — from memory] \n " + summary_bullet } else { " " }
let sep1: String = if hp && ( hw || hpr || hs ) { " \n \n " } else { " " }
let sep2: String = if hw && ( hpr || hs ) { " \n \n " } else { " " }
let sep3: String = if hpr && hs { " \n \n " } else { " " }
" \n \n " + sec_p + sep1 + sec_w + sep2 + sec_pr + sep3 + sec_s
let has_profile: Bool = !str_eq ( profile _bullets, " " )
let has_work : Bool = !str_eq ( work_bullets, " " )
let preload: String = if has_profile || has_work {
let profile_section: String = if has_profile {
" [USER CONTEXT — from memory] \n " + profile_bullets
} else { " " }
let work_section: String = if has_work {
" [ACTIVE WORK — from memory] \n " + work_bullets
} else { " " }
let sep_pw: String = if has_profile && has_work { " \n \n " } else { " " }
" \n \n " + profile_section + sep_pw + work_section
} else { " " }
preload
} else { " " }
@@ -1233,18 +1019,15 @@ fn is_builtin_tool(tool_name: String) -> Bool {
|| str_starts_with ( tool_name, " neuron_ " )
}
// next_bridge_id — unique correlation id for a suspended agentic turn.
// Uses uuid_v4 ( ) as the primary uniqueness guarantee — concurrent calls cannot collide.
//
// TODO ( reliability # 6 ) : mcp_bridge_seq RMW is non-atomic. Now benign because
// uuid_v4 ( ) provides collision-free uniqueness. Counter is kept for readability only.
// next_bridge_id — monotonic correlation id for a suspended agentic turn.
// Combines boot-relative time with a per-process counter so two unknown-tool
// suspensions in the same second still get distinct ids.
fn next_bridge_id ( ) -> String {
let prev: String = state_get ( " mcp_bridge_seq " )
let n: Int = if str_eq ( prev, " " ) { 0 } else { str_to_int ( prev ) }
let next: Int = n + 1
state_set ( " mcp_bridge_seq " , int_to_str ( next ) )
let uid: String = uuid_v4 ( )
return " br- " + uid
return " br- " + int_to_str ( time_now ( ) ) + " - " + int_to_str ( next )
}
fn handle_chat_agentic ( body: String ) -> String {
@@ -1272,7 +1055,7 @@ fn handle_chat_agentic(body: String) -> String {
if str_eq ( screen_action, " hard_bell " ) {
safety_log_bell ( " hard " , json_get ( screen_result, " reason " ) , str_slice ( message, 0 , 80 ) )
return " { \" reply \" : \" " + json_safe ( safety_validate ( " " , " hard_bell " ) ) + " \" , \" model \" : \" \" , \" agentic \" :true, \" tools_used \" :[]} "
}
let req_model: String = json_get ( body, " model " )
let model: String = if str_eq ( req_model, " " ) { chat_default_model ( ) } else { req_model }
@@ -1298,8 +1081,7 @@ fn handle_chat_agentic(body: String) -> String {
let hist_key: String = if str_eq ( req_session, " " ) { " conv_history " } else { " session_hist_ " + req_session }
let agentic_hist: String = state_get ( hist_key )
let agentic_hist_len: Int = if str_eq ( agentic_hist, " " ) { 0 } else { json_array_len ( agentic_hist ) }
// Issue 8 fix: use engram_is_continuation instead of brittle 50-char threshold.
let ag_is_cont: Bool = engram_is_continuation ( message, agentic_hist_len )
let ag_is_cont: Bool = str_len ( message ) < 50 && agentic_hist_len > 0
let ag_last_entry: String = if ag_is_cont { json_array_get ( agentic_hist, agentic_hist_len - 1 ) } else { " " }
let ag_last_content: String = if !str_eq ( ag_last_entry, " " ) { json_get ( ag_last_entry, " content " ) } else { " " }
let ag_thread_snip: String = if str_len ( ag_last_content ) > 150 { str_slice ( ag_last_content, 0 , 150 ) } else { ag_last_content }