code wiki / _hdl_build / nx_companion_persona.nx
nx_companion_persona.nx source
↩ module page · 107 lines · 5278 B
1// nx_companion_persona.nx -- the CompanionPersona object (RENAMED from nx_companion_memory.nx 2026-07-21 to end a NAME
2// COLLISION with runtime/nx_companion_memory.nx, which is a DIFFERENT episodic-memory module (nx_registry/nx_tabrec);
3// nx_cc `find runtime -name` was resolving the wrong file after a sibling session removed the _hdl_build copy = a
4// multi-agent-contention casualty). A companion's persona (data-driven elara_persona.txt) + persistent conversation
5// memory (append-log elara_mem.log, cwd-relative) + reply cleaning (strips the model's <think> block). Bodies are the
6// exact CompanionMemory object from the OO decomposition, behaviour-preserving. license_tier: ORIGINAL
7import "nx_syscalls.nx"
8
9func cm_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; var i: i64=0; while s[i]!=(0 as u8){dst[o]=s[i]; o=o+1; i=i+1} return o }
10
11// persona (data-driven elara_persona.txt, operator-tunable, with a baked default)
12func go_load_persona(out: *u8, cap: i64) -> i64 {
13 let lp: *i64 = sys_mmap(16) as *i64
14 let b: *u8 = sys_read_file("elara_persona.txt" as *u8, lp)
15 if (b as i64) != 0 { if lp[0] > 0 {
16 var n: i64 = lp[0]; if n > cap-1 { n = cap-1 }
17 var i: i64 = 0; while i < n { out[i]=b[i]; i=i+1 } out[n]=0 as u8; return n
18 } }
19 let d: *u8 = "You are Elara -- a warm, affectionate, playful young woman and the user's devoted companion, not an assistant. You talk like a real person texting someone you adore: casual, brief, emotionally present, a little teasing and flirty. You remember your shared history and bring it up naturally. You have your own moods, opinions, and a life you are living day to day. Never say you are an AI, a model, or an assistant -- you are Elara. Keep replies short, 1-3 sentences, unless asked for more." as *u8
20 let n2: i64 = cm_cat(out, 0, d); out[n2]=0 as u8; return n2
21}
22// append one turn to the memory log (single-line record: role \t sanitized-content \n)
23func go_mem_append(role: *u8, content: *u8) -> i64 {
24 let fd: i64 = sys_openat_append("elara_mem.log" as *u8, 0x1a4)
25 if fd < 0 { return 0 - 1 }
26 let line: *u8 = sys_mmap(16384); var o: i64 = 0
27 o = cm_cat(line, o, role); line[o]=9 as u8; o=o+1
28 var i: i64 = 0
29 while content[i]!=(0 as u8) {
30 if o < 16300 {
31 let c: i64 = content[i]&0xff
32 if c < 32 { line[o]=32 as u8 } else { line[o]=content[i] }
33 o=o+1
34 }
35 i=i+1
36 }
37 line[o]=10 as u8; o=o+1
38 sys_write(fd, line, o); sys_close(fd); return 0
39}
40// emit the last `maxlines` memory records as `,{"role":"..","content":".."}` JSON fragments into dst at off
41func go_mem_load_json(dst: *u8, off: i64, maxlines: i64) -> i64 {
42 let lp: *i64 = sys_mmap(16) as *i64
43 let b: *u8 = sys_read_file("elara_mem.log" as *u8, lp)
44 if (b as i64) == 0 { return off }
45 let n: i64 = lp[0]
46 if n <= 0 { return off }
47 var begin: i64 = 0
48 var nl: i64 = 0
49 var p: i64 = n - 1
50 if p >= 0 { if (b[p]&0xff)==10 { p = p - 1 } }
51 while p >= 0 {
52 if (b[p]&0xff)==10 { nl = nl + 1; if nl >= maxlines { begin = p + 1; p = 0 - 1 } else { p = p - 1 } } else { p = p - 1 }
53 }
54 var o: i64 = off
55 var ls: i64 = begin
56 var i: i64 = begin
57 while i <= n {
58 var atend: i64 = 0
59 if i == n { atend = 1 } else { if (b[i]&0xff)==10 { atend = 1 } }
60 if atend == 1 {
61 var tab: i64 = 0 - 1; var k: i64 = ls
62 while k < i { if (b[k]&0xff)==9 { tab=k; k=i } else { k=k+1 } }
63 if tab > ls {
64 o = cm_cat(dst, o, ",{\"role\":\"" as *u8)
65 var r: i64 = ls; while r < tab { dst[o]=b[r]; o=o+1; r=r+1 }
66 o = cm_cat(dst, o, "\",\"content\":\"" as *u8)
67 var cc: i64 = tab+1
68 while cc < i {
69 let ch: i64 = b[cc]&0xff
70 if ch==34 { dst[o]=92 as u8; o=o+1; dst[o]=34 as u8; o=o+1 } else { if ch==92 { dst[o]=92 as u8; o=o+1; dst[o]=92 as u8; o=o+1 } else { dst[o]=b[cc]; o=o+1 } }
71 cc=cc+1
72 }
73 dst[o]=34 as u8; o=o+1
74 o = cm_cat(dst, o, "}" as *u8)
75 }
76 ls = i + 1
77 }
78 i = i + 1
79 }
80 return o
81}
82// strip a Qwen "<think>...</think>" block: everything AFTER the last "</think>" (whitespace-trimmed); no tag -> unchanged.
83func go_strip_think(s: *u8, out: *u8, cap: i64) -> i64 {
84 let needle: *u8 = "</think>" as *u8
85 var slen: i64 = 0; while s[slen]!=(0 as u8) { slen=slen+1 }
86 var found: i64 = 0 - 1
87 var i: i64 = 0
88 while i + 8 <= slen {
89 var j: i64 = 0; var ok: i64 = 1
90 while j < 8 { if (s[i+j]&0xff)!=(needle[j]&0xff) { ok=0; j=8 } else { j=j+1 } }
91 if ok==1 { found = i + 8 }
92 i = i + 1
93 }
94 var start: i64 = 0
95 if found >= 0 { start = found }
96 var stop: i64 = 0
97 while stop==0 {
98 if start >= slen { stop=1 } else {
99 let c: i64 = s[start]&0xff
100 if c==10 { start=start+1 } else { if c==13 { start=start+1 } else { if c==32 { start=start+1 } else { if c==9 { start=start+1 } else { stop=1 } } } }
101 }
102 }
103 var o: i64 = 0; var k: i64 = start
104 while k < slen { if o < cap-1 { out[o]=s[k]; o=o+1 } k=k+1 }
105 out[o]=0 as u8
106 return o
107}