code wiki / (root) / nx_companion_memory.nx

nx_companion_memory.nx source

↩ module page · 124 lines · 5893 B

1// nx_companion_memory.nx -- R1b of the ELDER AI companion spine: EPISODIC MEMORY. The Replika-differentiator 2// is that a real companion does not forget -- Elder's memory is APPEND-ONLY and total by construction (rule 13: 3// additive; history is sacred). Every interaction is one episode appended to the canonical nx_registry over 4// seg_store, keyed uniquely and enumerable per-persona in chronological order. Recall is (a) total (mem_count 5// == everything ever said) and (b) content-searchable (mem_search = plant a fact early, find it many turns 6// later -- LEXICAL here; the semantic layer is R1c via nx_distrib_embed + nx_vec_index). One memory stream per 7// persona, so Aria's history and Vera's history never mix. license_tier: ORIGINAL 8import "nx_registry.nx" 9import "nx_tabrec.nx" 10const EPI_MAGIC_1048576: i64 = 1048576 11const EPI_MAGIC_8192: i64 = 8192 12 13const EPI_PREFIX: *u8 = "knowledge/episodic-" as *u8 14const EPI_KP: *u8 = "epi:" as *u8 15 16// record fields (rule 11: named). record = session \t ts \t speaker \t text 17const MF_SESSION: i64 = 0 18const MF_TS: i64 = 1 19const MF_SPEAKER: i64 = 2 20const MF_TEXT: i64 = 3 21 22// ---- id / index-key construction --------------------------------------------------------------------- 23func mem_strlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 24func mem_catz(dst: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ dst[o+i]=s[i]; i=i+1 } return o+i } 25// zero-padded 8-digit seq (lexical order == chronological order) 26func mem_pad8(dst: *u8, o: i64, v: i64) -> i64 { 27 var m: i64 = v 28 var i: i64 = 7 29 while i >= 0 { dst[o+i] = (48 + (m % 10)) as u8; m = m / 10; i = i - 1 } 30 return o + 8 31} 32// "<persona>:<seq8>" into buf, NUL-terminated 33func mem_epi_id(buf: *u8, persona: *u8, seq: i64) -> i64 { 34 var o: i64 = mem_catz(buf, 0, persona) 35 buf[o] = 0x3A as u8; o = o + 1 // ':' 36 o = mem_pad8(buf, o, seq) 37 buf[o] = 0 as u8 38 return o 39} 40// "epidx:<persona>" into buf, NUL-terminated (per-persona enumeration index) 41func mem_idxkey(buf: *u8, persona: *u8) -> i64 { 42 var o: i64 = mem_catz(buf, 0, "epidx:" as *u8) 43 o = mem_catz(buf, o, persona) 44 buf[o] = 0 as u8 45 return o 46} 47 48// case-insensitive substring: 1 if needle occurs in hay[0,hlen) 49func mem_lc(c: u8) -> u8 { if c >= (65 as u8) { if c <= (90 as u8) { return (c + (32 as u8)) as u8 } } return c } 50func mem_ci_find(hay: *u8, hlen: i64, needle: *u8) -> i64 { 51 var nl: i64 = 0; while needle[nl] != (0 as u8) { nl = nl + 1 } 52 if nl == 0 { return 1 } 53 if hlen < nl { return 0 } 54 var i: i64 = 0 55 while i <= hlen - nl { 56 var m: i64 = 1; var c: i64 = 0 57 while c < nl { if mem_lc(hay[i+c]) != mem_lc(needle[c]) { m = 0; c = nl } else { c = c + 1 } } 58 if m == 1 { return 1 } 59 i = i + 1 60 } 61 return 0 62} 63 64// ---- count / append / get ---------------------------------------------------------------------------- 65// number of episodes for this persona (== next seq, since we never delete) 66func mem_count_pfx(prefix: *u8, persona: *u8) -> i64 { 67 let ik: *u8 = sys_mmap(256); mem_idxkey(ik, persona) 68 let idxbuf: *u8 = sys_mmap(EPI_MAGIC_1048576) 69 let n: i64 = reg_index(prefix, ik, idxbuf, EPI_MAGIC_1048576) 70 var cnt: i64 = 0; var i: i64 = 0 71 while i < n { if idxbuf[i] == (10 as u8) { cnt = cnt + 1 } i = i + 1 } 72 return cnt 73} 74 75// append one episode; returns its seq. seq derives from the current count (append-ordered, no separate counter). 76func mem_append_pfx(prefix: *u8, persona: *u8, session: *u8, ts: *u8, speaker: *u8, text: *u8) -> i64 { 77 let seq: i64 = mem_count_pfx(prefix, persona) 78 let id: *u8 = sys_mmap(256); mem_epi_id(id, persona, seq) 79 let ik: *u8 = sys_mmap(256); mem_idxkey(ik, persona) 80 let rec: *u8 = sys_mmap(EPI_MAGIC_8192); var o: i64 = 0 81 o = tr_cat(rec, o, session); o = tr_tab(rec, o) 82 o = tr_cat(rec, o, ts); o = tr_tab(rec, o) 83 o = tr_cat(rec, o, speaker); o = tr_tab(rec, o) 84 o = tr_cat(rec, o, text) 85 reg_put(prefix, EPI_KP, ik, id, rec, o) 86 return seq 87} 88 89func mem_get_pfx(prefix: *u8, persona: *u8, seq: i64, ptrout: *i64, lenout: *i64) -> i64 { 90 let id: *u8 = sys_mmap(256); mem_epi_id(id, persona, seq) 91 return reg_get(prefix, EPI_KP, id, ptrout, lenout) 92} 93 94// mem_search: LEXICAL recall -- scan every episode's TEXT field for `needle`; write matching seqs into 95// out_seqs (bounded by cap), return the match count. This is the plant-and-probe recall path (plant a fact 96// at turn 3, find it at turn 300). The semantic-paraphrase path is R1c. 97func mem_search_pfx(prefix: *u8, persona: *u8, needle: *u8, out_seqs: *i64, cap: i64) -> i64 { 98 let n: i64 = mem_count_pfx(prefix, persona) 99 let p: *i64 = sys_mmap(16) as *i64 100 let l: *i64 = sys_mmap(16) as *i64 101 let f: *i64 = sys_mmap(16) as *i64 102 var found: i64 = 0 103 var s: i64 = 0 104 while s < n { 105 if mem_get_pfx(prefix, persona, s, p, l) == 1 { 106 let rec: *u8 = p[0] as *u8 107 let rl: i64 = l[0] 108 if tr_field(rec, 0, rl, MF_TEXT, f) == 1 { 109 if mem_ci_find((rec as i64 + f[0]) as *u8, f[1], needle) == 1 { 110 if found < cap { out_seqs[found] = s } 111 found = found + 1 112 } 113 } 114 } 115 s = s + 1 116 } 117 return found 118} 119 120// production-prefix convenience wrappers 121func mem_count(persona: *u8) -> i64 { return mem_count_pfx(EPI_PREFIX, persona) } 122func mem_append(persona: *u8, session: *u8, ts: *u8, speaker: *u8, text: *u8) -> i64 { return mem_append_pfx(EPI_PREFIX, persona, session, ts, speaker, text) } 123func mem_get(persona: *u8, seq: i64, ptrout: *i64, lenout: *i64) -> i64 { return mem_get_pfx(EPI_PREFIX, persona, seq, ptrout, lenout) } 124func mem_search(persona: *u8, needle: *u8, out_seqs: *i64, cap: i64) -> i64 { return mem_search_pfx(EPI_PREFIX, persona, needle, out_seqs, cap) }