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}