nx_charmem.nx
buildroot/runtime/nx_charmem.nx
about
nx_charmem.nx -- SOVEREIGN CHARACTER MEMORY (the Nomi/Replika-class relationship state, in Nishi).
Operator 2026-08-05: "SQLite-backed character memory (affection/trust/drift/learned user
preferences), we want in nishi sota."
DESIGN: EVENT-SOURCED, APPEND-ONLY (rule 13 -- history is sacred). The Python original mutates
SQLite rows; this organ never mutates anything: every interaction APPENDS one line to
knowledge/status/charmem_<char>.jrnl and current state is a FOLD over the journal at read time.
That buys what the mutable version cannot: full audit (why is affection 72? read the lines),
time-travel debugging, and no lost-update races between writers.
event <char> <kind> <args...> append one event. kinds:
aff|trust|intim|excit <+/-N> relationship metric delta (fold clamps to 0..100)
mood <word> current mood (latest wins)
pref <key>=<value> learned user preference (latest value per key wins)
mem <imp 1-9> <text...> an important memory with importance weight
state <char> fold -> metrics, mood, counts (machine-parseable line)
ctx <char> fold -> the LLM-ready system-prompt block (top prefs+memories)
selftest <char> hermetic assertions on a throwaway fixture journal
Char names sanitize to [a-z0-9_] max 32 (boundary defense: the name builds a path).
license_tier: ORIGINAL module: nishi-core.companion.charmem
dependencies 2 imports · 0 importers
imports: nx_syscalls.nxnx_lane_conf.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 25 | const CM_BUF: i64 = 1048576 |
| 26 | const CM_LINE: i64 = 4096 |
| 27 | const CM_PREF_MAX: i64 = 64 |
| 28 | const CM_MEM_MAX: i64 = 256 |
| 30 | const CM_CTX_PREFS: i64 = 6 |
| 31 | const CM_CTX_MEMS: i64 = 6 |
| 32 | const CM_DEF_AFF: i64 = 50 |
| 33 | const CM_DEF_TRUST: i64 = 50 |
| 34 | const CM_DEF_INTIM: i64 = 30 |
| 35 | const CM_DEF_EXCIT: i64 = 50 |
functions
| 37 | func cm_def(slot: i64) -> i64 |
| 44 | func cm_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } called by 1: cm_w |
| 45 | func cm_w(s: *u8) -> i64 { sys_write(1, s, cm_len(s)); return 0 } |
| 46 | func cm_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){dst[off+i]=s[i];i=i+1} return off+i } |
| 47 | func cm_u(dst: *u8, off: i64, v: i64) -> i64 |
| 57 | func cm_eq(a: *u8, b: *u8) -> i64 |
| 67 | func cm_int(s: *u8, pi: *i64) -> i64 |
| 83 | func cm_clamp(v: i64) -> i64 { if v<0 { return 0 } if v>100 { return 100 } return v } |
| 88 | func cm_eol(buf: *u8, idx: i64) -> i64 |
| 99 | func cm_line_copy(buf: *u8, idx: i64, dst: *u8, cap: i64) -> i64 |
| 116 | func cm_sane(s: *u8, out: *u8) -> i64 |
| 133 | func cm_path(char: *u8, out: *u8) -> i64 |
| 141 | func cm_now() -> i64 { let ts: *i64=sys_mmap(16) as *i64; ts[0]=0; sys_clock_gettime_real(ts); return ts[0] } |
| 144 | func cm_kind(k: *u8) -> i64 |
| 156 | func cm_event(char: *u8, argc: i64, argv: *i64) -> i64 |
| 194 | func cm_fold(char: *u8, m: *i64, moodbuf: *u8, buf: *u8) -> i64 called by 3: cm_statecm_ctxcm_selftest calls 12: cm_defsys_mmapcm_pathsys_openat_rdsys_readsys_close+6 |
| 253 | func cm_state(char: *u8) -> i64 |
| 276 | func cm_ctx(char: *u8) -> i64 |
| 400 | func cm_file_size(path: *u8) -> i64 |
| 408 | func cm_selftest(char: *u8) -> i64 |
| 471 | func main(argc: i64, argv: *i64) -> i64 |