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) }