code wiki / (root) / nx_ask_lib.nx

nx_ask_lib.nx source

↩ module page · 246 lines · 11690 B

1// nx_ask_lib.nx -- USER-QUESTION capability, LIBRARY half (ak_main dispatcher + pure helpers) (closes the coverage BUILD-queue gap "user-question" -- the 2// Claude AskUserQuestion peer for sovereign organs). An autonomous organ/agent that hits a decision only 3// the OPERATOR can make posts a question and polls for the answer; the operator lists pending and answers. 4// Backed by the sovereign seg store (knowledge/store/ask-): append-only, additive (rule 13), no TSV files. 5// ask <from> <question> -> posts; prints "NX-ASK id=<epoch-id>" (agent -> operator) 6// pending -> "<id>\t<from>\t<question>" per OPEN question (operator view) 7// answer <id> <text> -> marks ANSWERED with the text (operator -> agent) 8// get <id> -> labeled state/question/answer; agents poll until state=ANSWERED 9// Record: ask:<id> = from \t state \t question \t answer (answer "-" while OPEN); ask:ids = TAB id index. 10// TABs inside question/answer are flattened to spaces at the boundary (rule 12) so the record stays parseable. 11// exit: 0 ok | 2 usage | 3 unknown id license_tier: ORIGINAL 12import "nx_syscalls.nx" 13import "nx_seg_store.nx" 14 15const AK_BUF: i64 = 65536 // question/record work buffer 16const AK_IDS: i64 = 262144 // ask:ids working copy 17const AK_KEY: i64 = 64 // "ask:<id>" key buffer 18const AK_RC_USAGE: i64 = 2 // exit: usage 19const AK_RC_ABSENT: i64 = 3 // exit: unknown id 20const AK_ARG_VERB: i64 = 1 // argv slot: verb 21const AK_ARG_P1: i64 = 2 // argv slot: from | id 22const AK_ARG_P2: i64 = 3 // argv slot: question | answer text 23const AK_ARGC_ASK: i64 = 4 // ask from question 24const AK_ARGC_PEND: i64 = 2 // pending 25const AK_ARGC_ANS: i64 = 4 // answer id text 26const AK_ARGC_GET: i64 = 3 // get id 27const AK_F_FROM: i64 = 0 // record field: from 28const AK_F_STATE: i64 = 1 // record field: state 29const AK_F_Q: i64 = 2 // record field: question 30const AK_F_A: i64 = 3 // record field: answer 31const AK_TAB: i64 = 9 // '\t' 32const AK_NL: i64 = 10 // '\n' 33const AK_SPACE: i64 = 32 // ' ' (TAB/NL flatten target) 34const AK_ASCII_0: i64 = 48 // '0' 35const AK_ASCII_9: i64 = 57 // '9' 36const AK_DEC: i64 = 10 // decimal base 37 38func ak_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 39func ak_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 40func ak_seq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 } 41func ak_putn(v: i64) -> i64 { 42 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 43 var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 44 let d: *u8 = sys_mmap(32); var k: i64 = 0 45 while m > 0 { d[k] = (AK_ASCII_0 + (m % AK_DEC)) as u8; m = m / AK_DEC; k = k + 1 } 46 let o: *u8 = sys_mmap(32); var i: i64 = 0 47 while i < k { o[i] = d[k-1-i]; i = i + 1 } sys_write(1, o, k) 48 return 0 49} 50// epoch seconds via CLOCK_REALTIME (the id source; monotonic-enough at operator scale) 51func ak_epoch() -> i64 { 52 let ts: *i64 = sys_mmap(16) as *i64 53 sys_clock_gettime_real(ts) 54 return ts[0] 55} 56// "ask:<id>" key from a NUL-terminated numeric id string 57func ak_key(out: *u8, id: *u8) -> i64 { 58 let pre: *u8 = "ask:" as *u8 59 var o: i64 = 0 60 var i: i64 = 0 61 while pre[i] != (0 as u8) { out[o] = pre[i]; o = o + 1; i = i + 1 } 62 i = 0 63 while id[i] != (0 as u8) { out[o] = id[i]; o = o + 1; i = i + 1 } 64 out[o] = 0 as u8 65 return o 66} 67// render v as decimal into out (NUL-terminated), return len 68func ak_ntos(v: i64, out: *u8) -> i64 { 69 if v <= 0 { out[0] = AK_ASCII_0 as u8; out[1] = 0 as u8; return 1 } 70 let d: *u8 = sys_mmap(32); var k: i64 = 0 71 var m: i64 = v 72 while m > 0 { d[k] = (AK_ASCII_0 + (m % AK_DEC)) as u8; m = m / AK_DEC; k = k + 1 } 73 var i: i64 = 0 74 while i < k { out[i] = d[k-1-i]; i = i + 1 } 75 out[i] = 0 as u8 76 return k 77} 78// copy src into out at *op, flattening TAB/NL to space (boundary defense: record stays one-line parseable) 79func ak_flat(out: *u8, op: *i64, src: *u8) -> i64 { 80 var i: i64 = 0 81 var o: i64 = op[0] 82 while src[i] != (0 as u8) { 83 var c: i64 = src[i] as i64 84 if c == AK_TAB { c = AK_SPACE } 85 if c == AK_NL { c = AK_SPACE } 86 out[o] = c as u8 87 o = o + 1 88 i = i + 1 89 } 90 op[0] = o 91 return 0 92} 93// f-th TAB field of rec[0..rl) -> out (NUL-terminated), returns len (mirrors ws_field) 94func ak_field(rec: *u8, rl: i64, f: i64, out: *u8) -> i64 { 95 var cur: i64 = 0 96 var i: i64 = 0 97 var o: i64 = 0 98 while cur < f { 99 if i >= rl { out[0] = 0 as u8; return 0 } 100 if rec[i] == (AK_TAB as u8) { cur = cur + 1 } 101 i = i + 1 102 } 103 var go: i64 = 1 104 while go == 1 { 105 if i >= rl { go = 0 } else { 106 if rec[i] == (AK_TAB as u8) { go = 0 } else { out[o] = rec[i]; o = o + 1; i = i + 1 } 107 } 108 } 109 out[o] = 0 as u8 110 return o 111} 112// commit one record (+ ids-index update when idadd=1) as a fresh segment. Returns 0 ok. 113func ak_commit(key: *u8, rec: *u8, rlen: i64, id: *u8, idadd: i64) -> i64 { 114 let w: *i64 = ss_begin() 115 if idadd == 1 { 116 let pq: *i64 = sys_mmap(16) as *i64 117 let lq: *i64 = sys_mmap(16) as *i64 118 let ids: *u8 = sys_mmap(AK_IDS) 119 var n: i64 = 0 120 if ss_get("knowledge/store/ask-" as *u8, "ask:ids" as *u8, pq, lq) == 1 { 121 let src: *u8 = pq[0] as *u8 122 n = lq[0] 123 var i: i64 = 0 124 while i < n { ids[i] = src[i]; i = i + 1 } 125 } 126 if n > 0 { ids[n] = AK_TAB as u8; n = n + 1 } 127 var j: i64 = 0 128 while id[j] != (0 as u8) { ids[n] = id[j]; n = n + 1; j = j + 1 } 129 ss_add(w, 1, "ask:ids" as *u8, ids, n) 130 } 131 ss_add(w, 1, key, rec, rlen) 132 let segid: i64 = ss_next_segid("knowledge/store/ask-" as *u8) 133 var sid: i64 = segid 134 if sid < 1 { sid = 1 } 135 return ss_commit("knowledge/store/ask-" as *u8, w, sid) 136} 137 138func ak_main(argc: i64, argv: *i64) -> i64 { 139 if argc < AK_ARGC_PEND { 140 ak_puts("usage: nx_ask ask <from> <question> | pending | answer <id> <text> | get <id>\n" as *u8) 141 return AK_RC_USAGE 142 } 143 let verb: *u8 = argv[AK_ARG_VERB] as *u8 144 let pq: *i64 = sys_mmap(16) as *i64 145 let lq: *i64 = sys_mmap(16) as *i64 146 if ak_seq(verb, "ask" as *u8) == 1 { 147 if argc < AK_ARGC_ASK { ak_puts("usage: nx_ask ask <from> <question>\n" as *u8); return AK_RC_USAGE } 148 let idb: *u8 = sys_mmap(AK_KEY) 149 var id: i64 = ak_epoch() 150 // avoid same-second id collision: bump until unused (bounded by the second's ask volume) 151 let kb: *u8 = sys_mmap(AK_KEY) 152 var probe: i64 = 1 153 while probe == 1 { 154 ak_ntos(id, idb) 155 ak_key(kb, idb) 156 if ss_get("knowledge/store/ask-" as *u8, kb, pq, lq) == 1 { id = id + 1 } else { probe = 0 } 157 } 158 let rec: *u8 = sys_mmap(AK_BUF) 159 let op: *i64 = sys_mmap(8) as *i64 160 op[0] = 0 161 ak_flat(rec, op, argv[AK_ARG_P1] as *u8) // from 162 rec[op[0]] = AK_TAB as u8; op[0] = op[0] + 1 163 ak_flat(rec, op, "OPEN" as *u8) 164 rec[op[0]] = AK_TAB as u8; op[0] = op[0] + 1 165 ak_flat(rec, op, argv[AK_ARG_P2] as *u8) // question (TAB/NL flattened) 166 rec[op[0]] = AK_TAB as u8; op[0] = op[0] + 1 167 ak_flat(rec, op, "-" as *u8) 168 if ak_commit(kb, rec, op[0], idb, 1) != 0 { ak_puts("NX-ASK COMMIT FAILED\n" as *u8); return 1 } 169 ak_puts("NX-ASK id=" as *u8); ak_puts(idb); ak_puts(" (poll: nx_ask get " as *u8); ak_puts(idb); ak_puts(")\n" as *u8) 170 return 0 171 } 172 if ak_seq(verb, "pending" as *u8) == 1 { 173 if ss_get("knowledge/store/ask-" as *u8, "ask:ids" as *u8, pq, lq) != 1 { ak_puts("NX-ASK pending: none\n" as *u8); return 0 } 174 let ids: *u8 = pq[0] as *u8 175 let n: i64 = lq[0] 176 let idb: *u8 = sys_mmap(AK_KEY) 177 let kb: *u8 = sys_mmap(AK_KEY) 178 let fb: *u8 = sys_mmap(AK_BUF) 179 var shown: i64 = 0 180 var i: i64 = 0 181 var o: i64 = 0 182 while i <= n { 183 var eol: i64 = 0 184 if i == n { eol = 1 } else { if ids[i] == (AK_TAB as u8) { eol = 1 } } 185 if eol == 1 { 186 if o > 0 { 187 idb[o] = 0 as u8 188 ak_key(kb, idb) 189 if ss_get("knowledge/store/ask-" as *u8, kb, pq, lq) == 1 { 190 let rec: *u8 = pq[0] as *u8 191 let rl: i64 = lq[0] 192 ak_field(rec, rl, AK_F_STATE, fb) 193 if ak_seq(fb, "OPEN" as *u8) == 1 { 194 ak_puts(idb); sys_write(1, "\t" as *u8, 1) 195 ak_field(rec, rl, AK_F_FROM, fb); ak_puts(fb); sys_write(1, "\t" as *u8, 1) 196 ak_field(rec, rl, AK_F_Q, fb); ak_puts(fb); sys_write(1, "\n" as *u8, 1) 197 shown = shown + 1 198 } 199 } 200 o = 0 201 } 202 } else { idb[o] = ids[i]; o = o + 1 } 203 i = i + 1 204 } 205 if shown == 0 { ak_puts("NX-ASK pending: none\n" as *u8) } 206 return 0 207 } 208 if ak_seq(verb, "answer" as *u8) == 1 { 209 if argc < AK_ARGC_ANS { ak_puts("usage: nx_ask answer <id> <text>\n" as *u8); return AK_RC_USAGE } 210 let kb: *u8 = sys_mmap(AK_KEY) 211 ak_key(kb, argv[AK_ARG_P1] as *u8) 212 if ss_get("knowledge/store/ask-" as *u8, kb, pq, lq) != 1 { ak_puts("NX-ASK ABSENT: unknown id\n" as *u8); return AK_RC_ABSENT } 213 let old: *u8 = pq[0] as *u8 214 let ol: i64 = lq[0] 215 let fb: *u8 = sys_mmap(AK_BUF) 216 let rec: *u8 = sys_mmap(AK_BUF) 217 let op: *i64 = sys_mmap(8) as *i64 218 op[0] = 0 219 ak_field(old, ol, AK_F_FROM, fb); ak_flat(rec, op, fb) 220 rec[op[0]] = AK_TAB as u8; op[0] = op[0] + 1 221 ak_flat(rec, op, "ANSWERED" as *u8) 222 rec[op[0]] = AK_TAB as u8; op[0] = op[0] + 1 223 ak_field(old, ol, AK_F_Q, fb); ak_flat(rec, op, fb) 224 rec[op[0]] = AK_TAB as u8; op[0] = op[0] + 1 225 ak_flat(rec, op, argv[AK_ARG_P2] as *u8) 226 if ak_commit(kb, rec, op[0], "" as *u8, 0) != 0 { ak_puts("NX-ASK COMMIT FAILED\n" as *u8); return 1 } 227 ak_puts("NX-ASK ANSWERED id=" as *u8); ak_puts(argv[AK_ARG_P1] as *u8); ak_puts("\n" as *u8) 228 return 0 229 } 230 if ak_seq(verb, "get" as *u8) == 1 { 231 if argc < AK_ARGC_GET { ak_puts("usage: nx_ask get <id>\n" as *u8); return AK_RC_USAGE } 232 let kb: *u8 = sys_mmap(AK_KEY) 233 ak_key(kb, argv[AK_ARG_P1] as *u8) 234 if ss_get("knowledge/store/ask-" as *u8, kb, pq, lq) != 1 { ak_puts("NX-ASK ABSENT: unknown id\n" as *u8); return AK_RC_ABSENT } 235 let rec: *u8 = pq[0] as *u8 236 let rl: i64 = lq[0] 237 let fb: *u8 = sys_mmap(AK_BUF) 238 ak_puts("state: " as *u8); ak_field(rec, rl, AK_F_STATE, fb); ak_puts(fb); ak_puts("\n" as *u8) 239 ak_puts("from: " as *u8); ak_field(rec, rl, AK_F_FROM, fb); ak_puts(fb); ak_puts("\n" as *u8) 240 ak_puts("question: " as *u8); ak_field(rec, rl, AK_F_Q, fb); ak_puts(fb); ak_puts("\n" as *u8) 241 ak_puts("answer: " as *u8); ak_field(rec, rl, AK_F_A, fb); ak_puts(fb); ak_puts("\n" as *u8) 242 return 0 243 } 244 ak_puts("usage: nx_ask ask <from> <question> | pending | answer <id> <text> | get <id>\n" as *u8) 245 return AK_RC_USAGE 246}