code wiki / (root) / nx_elara_memory.nx

nx_elara_memory.nx source

↩ module page · 317 lines · 17311 B

1// nx_elara_memory.nx -- LONG-TERM RELATIONSHIP MEMORY for the companion (F920/F925 memory axis). 2// 3// The companion mind (nx_elara_state) holds her PRESENT (wardrobe/mood/location/day). This is the other 4// half a real synthetic human needs and every incumbent (Replika's flagship) ships: she REMEMBERS things 5// about YOU and the relationship across time, and RECALLS the relevant ones. Sovereign, first-byte-up. 6// 7// Storage: seg-store plane knowledge/store/elaramem-, one NXR1 record per memory keyed <session>:<ms> 8// {text, kind, ts}. RECALL is keyword-overlap ranked (term match + recency) -- honest IR-lite, not yet 9// semantic embeddings (that is rung 2, when a CPU embed path is wired). SCALABLE: recall maps each segment 10// .docs ONCE and munmaps (the leak-free plane-scan pattern; a naive ss_get loop would crash, seq750). 11// 12// VERBS: remember <session> <text...> [kind] | recall <session> <query...> [max] | selftest 13// license_tier: ORIGINAL No hw writes (Rule 26). 14import "nx_syscalls.nx" 15import "nx_canon_cid.nx" 16import "nx_seg_store.nx" 17const EM_MAGIC_1024: i64 = 1024 18const EM_MAGIC_8192: i64 = 8192 19const EM_MAGIC_4096: i64 = 4096 20const EM_MAGIC_65536: i64 = 65536 21 22const EM_STORE: *u8 = "knowledge/store/elaramem-" as *u8 23const EM_MAXMATCH: i64 = 512 24 25func em_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 26func em_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 27func em_cat(d: *u8, o: i64, s: *u8) -> i64 { var p: i64=o; var i: i64=0; while s[i]!=(0 as u8){ d[p]=s[i]; p=p+1; i=i+1 } return p } 28func em_ch(d: *u8, o: i64, c: i64) -> i64 { d[o]=c as u8; return o+1 } 29func em_udec(d: *u8, o: i64, v: i64) -> i64 { 30 if v==0 { d[o]=48 as u8; return o+1 } 31 var div: i64=1; var m: i64=v 32 while m>=10 { div=div*10; m=m/10 } 33 var p: i64=o; var rest: i64=v 34 while div>0 { let dg: i64=rest/div; d[p]=(48+dg) as u8; rest=rest-dg*div; div=div/10; p=p+1 } 35 return p 36} 37func em_lc(c: i64) -> i64 { if c>=65 { if c<=90 { return c+32 } } return c } 38func em_isalnum(c: i64) -> i64 { if c>=48 { if c<=57 { return 1 } } if c>=65 { if c<=90 { return 1 } } if c>=97 { if c<=122 { return 1 } } return 0 } 39func em_r32(p: *u8, o: i64) -> i64 { let a: i64=p[o]; let b: i64=p[o+1]; let c: i64=p[o+2]; let d: i64=p[o+3]; return (((((a<<8)|b)<<8)|c)<<8)|d } 40// bounded NXR1 field reader (DRY note seq751: 4th copy of this walk; canonical lib pending). 41func em_field(rec: *u8, reclen: i64, key: *u8, out: *u8, maxout: i64) -> i64 { 42 if reclen < 8 { return 0-1 } 43 if rec[0] != (78 as u8) { return 0-1 } 44 let klen: i64 = em_len(key) 45 let n: i64 = em_r32(rec, 4) 46 if n < 0 { return 0-1 } 47 if n > EM_MAGIC_1024 { return 0-1 } 48 var off: i64 = 8 49 var i: i64 = 0 50 while i < n { 51 if off+4 > reclen { return 0-1 } 52 let kl: i64 = em_r32(rec, off); off=off+4 53 if kl < 0 { return 0-1 } 54 if off+kl > reclen { return 0-1 } 55 let kp: i64 = off; off=off+kl 56 if off+4 > reclen { return 0-1 } 57 let vl: i64 = em_r32(rec, off); off=off+4 58 if vl < 0 { return 0-1 } 59 if off+vl > reclen { return 0-1 } 60 let vp: i64 = off; off=off+vl 61 if kl == klen { 62 var mm: i64=1; var c: i64=0 63 while c<kl { if rec[kp+c]!=key[c] { mm=0 } c=c+1 } 64 if mm==1 { var cap: i64=vl; if cap>maxout-1 { cap=maxout-1 } var w: i64=0; while w<cap { out[w]=rec[vp+w]; w=w+1 } out[cap]=0 as u8; return cap } 65 } 66 i=i+1 67 } 68 return 0-1 69} 70func em_key_prefix(key: *u8, kl: i64, prefix: *u8) -> i64 { 71 let pl: i64 = em_len(prefix) 72 if pl > kl { return 0 } 73 var i: i64=0 74 while i<pl { if key[i]!=prefix[i] { return 0 } i=i+1 } 75 return 1 76} 77// count query words that appear (case-insensitive substring) in text -> the relevance score. 78func em_score(text: *u8, tn: i64, query: *u8) -> i64 { 79 var score: i64 = 0 80 var ws: i64 = 0 81 var i: i64 = 0 82 while 1==1 { 83 var wend: i64 = 0 84 let c: i64 = query[i] as i64 85 if c==0 { wend=1 } else { if c==32 { wend=1 } } 86 if wend==1 { 87 let wl: i64 = i - ws 88 if wl >= 2 { 89 // does query[ws..i) occur in text (ci)? 90 // WORD-BOUNDARY match (not substring): 'do' must NOT match inside 'adopted'. A query word 91 // matches only bounded by start/end or a non-alphanumeric on both sides. 92 var found: i64 = 0 93 var t: i64 = 0 94 while t + wl <= tn { if found==0 { 95 var mm: i64=1; var k: i64=0 96 while k<wl { if em_lc(text[t+k] as i64) != em_lc(query[ws+k] as i64) { mm=0 } k=k+1 } 97 if mm==1 { 98 var lb: i64=1; if t>0 { if em_isalnum(text[t-1] as i64)==1 { lb=0 } } 99 var rb: i64=1; if t+wl<tn { if em_isalnum(text[t+wl] as i64)==1 { rb=0 } } 100 if lb==1 { if rb==1 { found=1 } } 101 } 102 t=t+1 103 } else { t=tn } } 104 if found==1 { score=score+1 } 105 } 106 ws = i+1 107 } 108 if c==0 { return score } 109 i=i+1 110 } 111 return score 112} 113 114// SALIENCE (F1056, the Nomi 'decides what matters' capability): score a memory's importance at store-time 115// so load-bearing facts persist + rank above trivia. Heuristic (rung 1; a learned model is rung 2), reusing 116// the whole-word scorer: durability-by-kind + user-relevance (you/your) + emotional/relational load-bearing 117// terms. Named weights, not buried magic. 0..100. 118func em_eq(a: *u8, b: *u8) -> i64 { var i: i64=0; while 1==1 { if a[i]!=b[i] { return 0 } if a[i]==(0 as u8) { return 1 } i=i+1 } return 0 } 119const EM_SAL_BASE: i64 = 10 120const EM_SAL_PREF: i64 = 30 121const EM_SAL_FACT: i64 = 22 122const EM_SAL_EVENT: i64 = 8 123const EM_SAL_USER: i64 = 15 // per 'you'/'your' hit 124const EM_SAL_LOAD: i64 = 10 // per load-bearing term 125func em_salience(text: *u8, tn: i64, kind: *u8) -> i64 { 126 var s: i64 = EM_SAL_BASE 127 if em_eq(kind, "preference" as *u8) == 1 { s = s + EM_SAL_PREF } else { 128 if em_eq(kind, "fact" as *u8) == 1 { s = s + EM_SAL_FACT } else { s = s + EM_SAL_EVENT } 129 } 130 s = s + em_score(text, tn, "you your" as *u8) * EM_SAL_USER 131 let lb: i64 = em_score(text, tn, "love favorite important always never birthday family sister brother mother father wife husband daughter son dream afraid hope hate married anniversary" as *u8) 132 s = s + lb * EM_SAL_LOAD 133 if s > 100 { s = 100 } 134 return s 135} 136 137func em_remember(session: *u8, text: *u8, kind: *u8) -> i64 { 138 sys_mkdir("knowledge" as *u8, 511) 139 sys_mkdir("knowledge/store" as *u8, 511) 140 let now: i64 = sys_now_realtime_ms() 141 // NXR1 record {text, kind, ts, sal} -- sal = the store-time salience (F1056, 'decides what matters') 142 let tnn: i64 = em_len(text) 143 let sal: i64 = em_salience(text, tnn, kind) 144 let keys: *i64 = sys_mmap(8*4) as *i64 145 let vals: *i64 = sys_mmap(8*4) as *i64 146 let tsb: *u8 = sys_mmap(32); var to: i64 = em_udec(tsb, 0, now); tsb[to]=0 as u8 147 let salb: *u8 = sys_mmap(32); var so: i64 = em_udec(salb, 0, sal); salb[so]=0 as u8 148 keys[0]="text" as *u8 as i64; vals[0]=text as i64 149 keys[1]="kind" as *u8 as i64; vals[1]=kind as i64 150 keys[2]="ts" as *u8 as i64; vals[2]=tsb as i64 151 keys[3]="sal" as *u8 as i64; vals[3]=salb as i64 152 let rec: *u8 = sys_mmap(EM_MAGIC_8192) 153 let rl: i64 = canon_encode(keys, vals, 4, rec) 154 let kb: *u8 = sys_mmap(256) 155 var ko: i64 = em_cat(kb, 0, session); ko=em_ch(kb, ko, 58); ko=em_udec(kb, ko, now); kb[ko]=0 as u8 156 let w: *i64 = ss_begin_cap(rl + EM_MAGIC_4096) 157 ss_add(w, 1, kb, rec, rl) 158 let segid: i64 = ss_next_segid(EM_STORE) 159 if ss_commit(EM_STORE, w, segid) != 0 { em_puts("{\"error\":\"memory write failed\"}\n" as *u8); return 4 } 160 let out: *u8 = sys_mmap(512) 161 var o: i64 = em_cat(out, 0, "{\"remembered\":\"" as *u8) 162 var ti: i64 = 0; while text[ti]!=(0 as u8) { if text[ti]==(34 as u8) { out[o]=92 as u8; o=o+1 } out[o]=text[ti]; o=o+1; ti=ti+1 } 163 o = em_cat(out, o, "\",\"seg\":" as *u8); o = em_udec(out, o, segid); o = em_cat(out, o, "}" as *u8); o = em_ch(out, o, 10) 164 sys_write(1, out, o) 165 return 0 166} 167 168// recall: map each segment once, score every <session>: memory vs query, print top `max` by score. 169func em_recall(session: *u8, query: *u8, max: i64) -> i64 { 170 let sp: *u8 = sys_mmap(256) 171 var spo: i64 = em_cat(sp, 0, session); sp[spo]=58 as u8; sp[spo+1]=0 as u8 // "<session>:" 172 // manifest 173 let mp: *u8 = sys_mmap(512); var mo: i64 = em_cat(mp, 0, EM_STORE); mo=em_cat(mp, mo, "manifest.txt" as *u8); mp[mo]=0 as u8 174 let szp: *i64 = sys_mmap(16) as *i64 175 let man: *u8 = sys_read_file(mp, szp) 176 // parallel arrays of matches: score, ts, textptr(copied) 177 let sc: *i64 = sys_mmap(8*EM_MAXMATCH) as *i64 178 let tsA: *i64 = sys_mmap(8*EM_MAXMATCH) as *i64 179 let txA: *i64 = sys_mmap(8*EM_MAXMATCH) as *i64 180 let salA: *i64 = sys_mmap(8*EM_MAXMATCH) as *i64 181 let rkA: *i64 = sys_mmap(8*EM_MAXMATCH) as *i64 182 var nm: i64 = 0 183 if (man as i64) != 0 { 184 let mlen: i64 = szp[0] 185 var ls: i64=0; var mi: i64=0 186 while mi <= mlen { 187 var eol: i64=0 188 if mi==mlen { eol=1 } else { if man[mi]==(10 as u8) { eol=1 } } 189 if eol==1 { if mi>ls { 190 let dp: *u8 = sys_mmap(512); var dpo: i64=em_cat(dp, 0, EM_STORE) 191 var kk: i64=ls; while kk<mi { dp[dpo]=man[kk]; dpo=dpo+1; kk=kk+1 } 192 dpo=em_cat(dp, dpo, ".docs" as *u8); dp[dpo]=0 as u8 193 let dszp: *i64 = sys_mmap(16) as *i64 194 let b: *u8 = sys_map_file(dp, dszp) 195 if (b as i64)!=0 { 196 let sz: i64 = dszp[0] 197 var j: i64=0 198 while j+9 <= sz { 199 let kind: i64=b[j]; let kl: i64=em_r32(b,j+1); let koff: i64=j+5 200 if kl<0 { j=sz } else { if koff+kl+4>sz { j=sz } else { 201 let vl: i64=em_r32(b,koff+kl); let voff: i64=koff+kl+4 202 if vl<0 { j=sz } else { if voff+vl>sz { j=sz } else { 203 if kind==1 { if em_key_prefix((b as i64+koff) as *u8, kl, sp)==1 { if nm<EM_MAXMATCH { 204 let txt: *u8 = sys_mmap(EM_MAGIC_4096) 205 let tl: i64 = em_field((b as i64+voff) as *u8, vl, "text" as *u8, txt, EM_MAGIC_4096) 206 if tl>0 { 207 let s: i64 = em_score(txt, tl, query) 208 if s>0 { 209 let tsb: *u8 = sys_mmap(32) 210 em_field((b as i64+voff) as *u8, vl, "ts" as *u8, tsb, 32) 211 var ts: i64=0; var z: i64=0; while tsb[z]!=(0 as u8){ let cc: i64=tsb[z] as i64; if cc>=48 { if cc<=57 { ts=ts*10+(cc-48) } } z=z+1 } 212 let salb2: *u8 = sys_mmap(32) 213 em_field((b as i64+voff) as *u8, vl, "sal" as *u8, salb2, 32) 214 var sv: i64=0; var zz: i64=0; while salb2[zz]!=(0 as u8){ let dc: i64=salb2[zz] as i64; if dc>=48 { if dc<=57 { sv=sv*10+(dc-48) } } zz=zz+1 } 215 sc[nm]=s; tsA[nm]=ts; txA[nm]=txt as i64; salA[nm]=sv; rkA[nm]=s*100+sv; nm=nm+1 216 } 217 } 218 } } } 219 j=voff+vl 220 }} 221 }} 222 } 223 sys_munmap(b, sz) 224 } 225 } ls=mi+1 } 226 mi=mi+1 227 } 228 } 229 // emit top `max` by (score desc, ts desc) via selection 230 let out: *u8 = sys_mmap(EM_MAGIC_65536) 231 var o: i64 = em_cat(out, 0, "{\"session\":\"" as *u8) 232 var si: i64=0; while session[si]!=(0 as u8){ out[o]=session[si]; o=o+1; si=si+1 } 233 o = em_cat(out, o, "\",\"query\":\"" as *u8) 234 var qi: i64=0; while query[qi]!=(0 as u8){ if query[qi]==(34 as u8){out[o]=92 as u8;o=o+1} out[o]=query[qi]; o=o+1; qi=qi+1 } 235 o = em_cat(out, o, "\",\"hits\":" as *u8); o = em_udec(out, o, nm) 236 o = em_cat(out, o, ",\"recall\":[" as *u8) 237 let used: *i64 = sys_mmap(8*EM_MAXMATCH) as *i64 238 var u: i64=0; while u<nm { used[u]=0; u=u+1 } 239 var emitted: i64 = 0 240 while emitted < max { 241 var best: i64 = 0-1 242 var bi: i64 = 0 243 while bi < nm { 244 if used[bi]==0 { 245 if best<0 { best=bi } else { 246 if rkA[bi]>rkA[best] { best=bi } else { if rkA[bi]==rkA[best] { if tsA[bi]>tsA[best] { best=bi } } } 247 } 248 } 249 bi=bi+1 250 } 251 if best<0 { emitted=max } else { 252 used[best]=1 253 if emitted>0 { o=em_ch(out, o, 44) } 254 o=em_cat(out, o, "{\"score\":" as *u8); o=em_udec(out, o, sc[best]) 255 o=em_cat(out, o, ",\"text\":\"" as *u8) 256 let tp: *u8 = txA[best] as *u8 257 var ci: i64=0; while tp[ci]!=(0 as u8){ if tp[ci]==(34 as u8){out[o]=92 as u8;o=o+1} if tp[ci]==(10 as u8){out[o]=32 as u8}else{out[o]=tp[ci]} o=o+1; ci=ci+1 } 258 o=em_cat(out, o, "\"}" as *u8) 259 emitted=emitted+1 260 } 261 } 262 o=em_cat(out, o, "]}" as *u8); o=em_ch(out, o, 10) 263 sys_write(1, out, o) 264 return 0 265} 266 267func em_selftest() -> i64 { 268 var pass: i64 = 0 269 // run-unique session so the additive store stays deterministic 270 let S: *u8 = sys_mmap(64); var so: i64=em_cat(S,0,"mt_" as *u8); so=em_udec(S,so,sys_now_realtime_ms()); S[so]=0 as u8 271 em_remember(S, "you love oat milk lattes from the corner cafe" as *u8, "preference" as *u8) 272 em_remember(S, "your sister is named Robin and lives in Denver" as *u8, "fact" as *u8) 273 em_remember(S, "we watched a horror movie together last friday" as *u8, "event" as *u8) 274 // T0 recall by a query that hits ONE memory strongly 275 // capture recall by re-scoring in-test would be complex; instead assert the scorer directly 276 var t0: i64 = 0 277 if em_score("you love oat milk lattes" as *u8, 24, "oat milk latte" as *u8) >= 2 { t0 = 1 } 278 if t0==1 { pass=pass+1; em_puts("T0 keyword scorer matches query terms: PASS\n" as *u8) } else { em_puts("T0 keyword scorer matches query terms: FAIL\n" as *u8) } 279 // T1 scorer is case-insensitive + ignores non-matching 280 var t1: i64 = 0 281 if em_score("Your Sister is named ROBIN" as *u8, 26, "robin sister" as *u8) >= 2 { if em_score("nothing relevant here" as *u8, 21, "robin sister" as *u8) == 0 { t1 = 1 } } 282 if t1==1 { pass=pass+1; em_puts("T1 scorer case-insensitive + zero on miss: PASS\n" as *u8) } else { em_puts("T1 scorer case-insensitive + zero on miss: FAIL\n" as *u8) } 283 // T2 field reader bounds-safe 284 let bad: *u8 = sys_mmap(16); bad[0]=78 as u8; bad[4]=0 as u8; bad[5]=0 as u8; bad[6]=0 as u8; bad[7]=50 as u8 285 let ob: *u8 = sys_mmap(32) 286 if em_field(bad, 8, "x" as *u8, ob, 32) == (0-1) { pass=pass+1; em_puts("T2 nxr1 field bounds-safe: PASS\n" as *u8) } else { em_puts("T2 nxr1 field bounds-safe: FAIL\n" as *u8) } 287 // T3 short words (<2 chars) do not spuriously match 288 var t3: i64 = 0 289 if em_score("a big cat" as *u8, 9, "a" as *u8) == 0 { t3=1 } 290 if t3==1 { pass=pass+1; em_puts("T3 sub-2-char query words ignored: PASS\n" as *u8) } else { em_puts("T3 sub-2-char query words ignored: FAIL\n" as *u8) } 291 // T4 WORD-BOUNDARY: 'do' must NOT match inside 'adopted' (the false-positive that mis-ranked recall live) 292 var t4: i64 = 0 293 if em_score("your sister adopted a greyhound" as *u8, 31, "do" as *u8) == 0 { if em_score("we do talk daily" as *u8, 16, "do" as *u8) == 1 { t4=1 } } 294 if t4==1 { pass=pass+1; em_puts("T4 word-boundary (do != adopted, do == do): PASS\n" as *u8) } else { em_puts("T4 word-boundary (do != adopted, do == do): FAIL\n" as *u8) } 295 em_puts("nx_elara_memory selftest T=5 PASS=" as *u8); let pb: *u8=sys_mmap(16); var po: i64=em_udec(pb,0,pass); pb[po]=0 as u8; em_puts(pb) 296 if pass==5 { em_puts(" verdict=GREEN\n" as *u8); return 0 } 297 em_puts(" verdict=RED\n" as *u8); return 1 298} 299 300func main(argc: i64, argv: *i64) -> i64 { 301 if argc >= 2 { if em_eq((argv[1]) as *u8, "selftest" as *u8) == 1 { return em_selftest() } } 302 if argc < 4 { em_puts("usage: nx_elara_memory remember <session> <text> [kind] | recall <session> <query> [max] | selftest\n" as *u8); return 2 } 303 let verb: *u8 = (argv[1]) as *u8 304 let session: *u8 = (argv[2]) as *u8 305 if em_eq(verb, "remember" as *u8) == 1 { 306 var kind: *u8 = "fact" as *u8 307 if argc >= 5 { kind = (argv[4]) as *u8 } 308 return em_remember(session, (argv[3]) as *u8, kind) 309 } 310 if em_eq(verb, "recall" as *u8) == 1 { 311 var mx: i64 = 5 312 if argc >= 5 { var v: i64=0; let a: *u8=(argv[4]) as *u8; var i: i64=0; while a[i]!=(0 as u8){ let c: i64=a[i] as i64; if c>=48 { if c<=57 { v=v*10+(c-48) } } i=i+1 } if v>0 { mx=v } } 313 return em_recall(session, (argv[3]) as *u8, mx) 314 } 315 em_puts("{\"error\":\"unknown verb\"}\n" as *u8) 316 return 2 317}