code wiki / (root) / nx_companion_memory.nx

nx_companion_memory.nx source

↩ module page · 128 lines · 6142 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_8192: i64 = 8192 11 12const EPI_PREFIX: *u8 = "knowledge/episodic-" as *u8 13const EPI_KP: *u8 = "epi:" as *u8 14 15// record fields (rule 11: named). record = session \t ts \t speaker \t text 16const MF_SESSION: i64 = 0 17const MF_TS: i64 = 1 18const MF_SPEAKER: i64 = 2 19const MF_TEXT: i64 = 3 20 21// ---- id / index-key construction --------------------------------------------------------------------- 22func mem_strlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 23func 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 } 24// zero-padded 8-digit seq (lexical order == chronological order) 25func mem_pad8(dst: *u8, o: i64, v: i64) -> i64 { 26 var m: i64 = v 27 var i: i64 = 7 28 while i >= 0 { dst[o+i] = (48 + (m % 10)) as u8; m = m / 10; i = i - 1 } 29 return o + 8 30} 31// "<persona>:<seq8>" into buf, NUL-terminated 32func mem_epi_id(buf: *u8, persona: *u8, seq: i64) -> i64 { 33 var o: i64 = mem_catz(buf, 0, persona) 34 buf[o] = 0x3A as u8; o = o + 1 // ':' 35 o = mem_pad8(buf, o, seq) 36 buf[o] = 0 as u8 37 return o 38} 39// "epidx:<persona>" into buf, NUL-terminated (per-persona enumeration index) 40func mem_idxkey(buf: *u8, persona: *u8) -> i64 { 41 var o: i64 = mem_catz(buf, 0, "epidx:" as *u8) 42 o = mem_catz(buf, o, persona) 43 buf[o] = 0 as u8 44 return o 45} 46 47// case-insensitive substring: 1 if needle occurs in hay[0,hlen) 48func mem_lc(c: u8) -> u8 { if c >= (65 as u8) { if c <= (90 as u8) { return (c + (32 as u8)) as u8 } } return c } 49func mem_ci_find(hay: *u8, hlen: i64, needle: *u8) -> i64 { 50 var nl: i64 = 0; while needle[nl] != (0 as u8) { nl = nl + 1 } 51 if nl == 0 { return 1 } 52 if hlen < nl { return 0 } 53 var i: i64 = 0 54 while i <= hlen - nl { 55 var m: i64 = 1; var c: i64 = 0 56 while c < nl { if mem_lc(hay[i+c]) != mem_lc(needle[c]) { m = 0; c = nl } else { c = c + 1 } } 57 if m == 1 { return 1 } 58 i = i + 1 59 } 60 return 0 61} 62 63// ---- count / append / get ---------------------------------------------------------------------------- 64// number of episodes for this persona (== next seq, since we never delete) 65func mem_count_pfx(prefix: *u8, persona: *u8) -> i64 { 66 let ik: *u8 = sys_mmap(256); mem_idxkey(ik, persona) 67 // SIZE-TO-NEED: buffer derived from the index itself. The pair this replaces 68 // returned -1 once the episode index passed 1 MiB, and -1 skips the walk 69 // below -- a companion would have reported NO MEMORIES for exactly the 70 // personas it had talked to most. 71 let ibox: *i64 = sys_mmap(16) as *i64 72 let n: i64 = reg_index_read(prefix, ik, ibox) 73 let idxbuf: *u8 = ibox[0] as *u8 74 var cnt: i64 = 0; var i: i64 = 0 75 while i < n { if idxbuf[i] == (10 as u8) { cnt = cnt + 1 } i = i + 1 } 76 return cnt 77} 78 79// append one episode; returns its seq. seq derives from the current count (append-ordered, no separate counter). 80func mem_append_pfx(prefix: *u8, persona: *u8, session: *u8, ts: *u8, speaker: *u8, text: *u8) -> i64 { 81 let seq: i64 = mem_count_pfx(prefix, persona) 82 let id: *u8 = sys_mmap(256); mem_epi_id(id, persona, seq) 83 let ik: *u8 = sys_mmap(256); mem_idxkey(ik, persona) 84 let rec: *u8 = sys_mmap(EPI_MAGIC_8192); var o: i64 = 0 85 o = tr_cat(rec, o, session); o = tr_tab(rec, o) 86 o = tr_cat(rec, o, ts); o = tr_tab(rec, o) 87 o = tr_cat(rec, o, speaker); o = tr_tab(rec, o) 88 o = tr_cat(rec, o, text) 89 reg_put(prefix, EPI_KP, ik, id, rec, o) 90 return seq 91} 92 93func mem_get_pfx(prefix: *u8, persona: *u8, seq: i64, ptrout: *i64, lenout: *i64) -> i64 { 94 let id: *u8 = sys_mmap(256); mem_epi_id(id, persona, seq) 95 return reg_get(prefix, EPI_KP, id, ptrout, lenout) 96} 97 98// mem_search: LEXICAL recall -- scan every episode's TEXT field for `needle`; write matching seqs into 99// out_seqs (bounded by cap), return the match count. This is the plant-and-probe recall path (plant a fact 100// at turn 3, find it at turn 300). The semantic-paraphrase path is R1c. 101func mem_search_pfx(prefix: *u8, persona: *u8, needle: *u8, out_seqs: *i64, cap: i64) -> i64 { 102 let n: i64 = mem_count_pfx(prefix, persona) 103 let p: *i64 = sys_mmap(16) as *i64 104 let l: *i64 = sys_mmap(16) as *i64 105 let f: *i64 = sys_mmap(16) as *i64 106 var found: i64 = 0 107 var s: i64 = 0 108 while s < n { 109 if mem_get_pfx(prefix, persona, s, p, l) == 1 { 110 let rec: *u8 = p[0] as *u8 111 let rl: i64 = l[0] 112 if tr_field(rec, 0, rl, MF_TEXT, f) == 1 { 113 if mem_ci_find((rec as i64 + f[0]) as *u8, f[1], needle) == 1 { 114 if found < cap { out_seqs[found] = s } 115 found = found + 1 116 } 117 } 118 } 119 s = s + 1 120 } 121 return found 122} 123 124// production-prefix convenience wrappers 125func mem_count(persona: *u8) -> i64 { return mem_count_pfx(EPI_PREFIX, persona) } 126func mem_append(persona: *u8, session: *u8, ts: *u8, speaker: *u8, text: *u8) -> i64 { return mem_append_pfx(EPI_PREFIX, persona, session, ts, speaker, text) } 127func mem_get(persona: *u8, seq: i64, ptrout: *i64, lenout: *i64) -> i64 { return mem_get_pfx(EPI_PREFIX, persona, seq, ptrout, lenout) } 128func mem_search(persona: *u8, needle: *u8, out_seqs: *i64, cap: i64) -> i64 { return mem_search_pfx(EPI_PREFIX, persona, needle, out_seqs, cap) }