code wiki / (root) / nx_nxtask_main.nx

nx_nxtask_main.nx source

↩ module page · 279 lines · 10487 B

1// nxtask_main.nx -- CLI driver for the sovereign agent task graph. 2// 3// Usage: 4// nxtask demo build + dump sample graph as JSON 5// nxtask ready build + dump ready-task count 6// nxtask show N build + show task N's JSON 7// nxtask save <path> serialize demo graph to <path> 8// nxtask load <path> deserialize <path> + dump as JSON 9// nxtask roundtrip <path> save+load e2e smoke test 10// 11// Once multi-agent merge + durable event log ship (see 12// docs/NXTASK_AGENT_TASK_GRAPH.md v0.1.0), the CLI will manage a 13// default store under ~/.nxtask/ so the <path> argument becomes 14// optional. Today every subcommand takes an explicit path. 15// 16// When Mormon v0 picks this up, the shape becomes: 17// 18// run("nxtask load ~/.nxtask/graph.bin | jq '.tasks[] | select(.state==\"open\")'") 19// let next_task = parse_json(...).id 20// ...do the work... 21// run("nxtask close " + next_task.id + " 'summary' && nxtask save ~/.nxtask/graph.bin") 22// 23// Exit codes: 24// 0 success 25// 1 no args 26// 2 unknown subcommand / missing arg 27// 3 I/O failure (open/read/write) 28// 4 malformed input (deserialize failure) 29 30// nx_safety_envelope: 31// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 32// sil_target: SIL1 33// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 34// verdict: NOT_YET_EVALUATED 35 36import "nx_syscalls.nx" 37import "nx_json_emit.nx" 38import "nx_nxtask.nx" 39import "nx_nxtask_store.nx" 40 41// === JSON serialisation ============================================= 42// 43// Emit a task graph as a JSON object: { "tasks": [ { ... }, ... ] } 44 45const STATE_OPEN: i64 = 0 46const STATE_CLAIMED: i64 = 1 47const STATE_CLOSED: i64 = 2 48 49func state_name(s: i64) -> *u8 { 50 if s == STATE_OPEN { return "open" as *u8 } 51 if s == STATE_CLAIMED { return "claimed" as *u8 } 52 if s == STATE_CLOSED { return "closed" as *u8 } 53 return "unknown" as *u8 54} 55 56func state_name_len(s: i64) -> i64 { 57 if s == STATE_OPEN { return 4 } 58 if s == STATE_CLAIMED { return 7 } 59 if s == STATE_CLOSED { return 6 } 60 return 7 61} 62 63func emit_task(w: *JsonWriter, t: *Task, idx: i64) -> i64 { 64 json_begin_object(w) 65 json_emit_key(w, "idx" as *u8, 3) 66 json_emit_int(w, idx) 67 json_emit_key(w, "state" as *u8, 5) 68 json_emit_string(w, state_name(t.state), state_name_len(t.state)) 69 json_emit_key(w, "desc" as *u8, 4) 70 json_emit_string(w, t.desc, t.desc_len) 71 if t.claimed_by != (0 as *u8) { 72 json_emit_key(w, "claimed_by" as *u8, 10) 73 json_emit_string(w, t.claimed_by, t.claimed_by_len) 74 } 75 if t.summary != (0 as *u8) { 76 json_emit_key(w, "summary" as *u8, 7) 77 json_emit_string(w, t.summary, t.summary_len) 78 } 79 json_emit_key(w, "n_blocked" as *u8, 9) 80 json_emit_int(w, t.n_blocked) 81 json_emit_key(w, "created_ns" as *u8, 10) 82 json_emit_int(w, t.created_ns) 83 json_emit_key(w, "updated_ns" as *u8, 10) 84 json_emit_int(w, t.updated_ns) 85 json_end_object(w) 86 return 0 87} 88 89func emit_graph(w: *JsonWriter, g: *TaskGraph) -> i64 { 90 json_begin_object(w) 91 json_emit_key(w, "tasks" as *u8, 5) 92 json_begin_array(w) 93 var i: i64 = 0 94 while i < g.n_tasks { 95 let t: *Task = task_at(g, i) 96 emit_task(w, t, i) 97 i = i + 1 98 } 99 json_end_array(w) 100 json_end_object(w) 101 return 0 102} 103 104// === demo graph ==================================================== 105 106// Build a representative overnight-build graph so CLI has content 107// to emit without persistence. Mirrors the real overnight 108// workflow: big work items with cross-dependencies. 109func build_demo_graph() -> *TaskGraph { 110 let g: *TaskGraph = nxtask_graph_new(32) 111 nxtask_add(g, "Port regalloc to runtime/regalloc.nx" as *u8, 37, 1) 112 nxtask_add(g, "Port riscv 12-bit imm expansion to runtime" as *u8, 42, 2) 113 nxtask_add(g, "Wire F-extension lowering in runtime/riscv.nx" as *u8, 46, 3) 114 nxtask_add(g, "Add SIMD RVV intrinsic recognition" as *u8, 34, 4) 115 nxtask_add(g, "Build Mormon v0 with llama.cpp + corpus" as *u8, 39, 5) 116 117 // Dependencies: SIMD needs F-ext first; Mormon needs Phase 2.4. 118 nxtask_add_dep(g, 3, 2) // SIMD blocked by F-ext 119 nxtask_add_dep(g, 4, 3) // Mormon blocked by SIMD (via runtime speed) 120 121 // Simulate mid-workflow state. 122 nxtask_claim(g, 0, "mormon-v0" as *u8, 9, 10) 123 nxtask_close(g, 0, "Rematerialisation + cross-block liveness ported" as *u8, 47, 11) 124 return g 125} 126 127// === simple argv helpers ============================================ 128 129// streq(a, b, n) -- true if the first n bytes match + both have a 130// NUL at n (to avoid prefix-matching "list" against "listxyz"). 131func streq(a: *u8, b: *u8, n: i64) -> i64 { 132 var i: i64 = 0 133 while i < n { 134 if a[i] != b[i] { return 0 } 135 i = i + 1 136 } 137 if a[n] != 0 { return 0 } 138 if b[n] != 0 { return 0 } 139 return 1 140} 141 142func write_out(o: *u8, n: i64) -> i64 { 143 return sys_write(1, o, n) 144} 145 146// === main ========================================================== 147 148func main(argc: i64, argv: *i64) -> i64 { 149 if argc < 2 { 150 write_out("nxtask: usage: nxtask <demo|ready|show N>\n" as *u8, 42) 151 return __syscall(93, 1, 0, 0, 0, 0, 0) 152 } 153 let cmd: *u8 = argv[1] as *u8 154 155 let g: *TaskGraph = build_demo_graph() 156 let buf: *u8 = sys_mmap(65536) 157 let w_raw: *u8 = sys_mmap(128) 158 let w: *JsonWriter = w_raw as *JsonWriter 159 json_writer_init(w, buf, 65536) 160 161 if streq(cmd, "demo" as *u8, 4) == 1 { 162 emit_graph(w, g) 163 write_out(buf, w.pos) 164 write_out("\n" as *u8, 1) 165 return __syscall(93, 0, 0, 0, 0, 0, 0) 166 } 167 if streq(cmd, "ready" as *u8, 5) == 1 { 168 let nxt: i64 = nxtask_next_ready(g) 169 if nxt < 0 { 170 write_out("{\"ready\":null}\n" as *u8, 15) 171 } else { 172 let t: *Task = task_at(g, nxt) 173 json_begin_object(w) 174 json_emit_key(w, "ready" as *u8, 5) 175 emit_task(w, t, nxt) 176 json_end_object(w) 177 write_out(buf, w.pos) 178 write_out("\n" as *u8, 1) 179 } 180 return __syscall(93, 0, 0, 0, 0, 0, 0) 181 } 182 if streq(cmd, "show" as *u8, 4) == 1 { 183 if argc < 3 { 184 write_out("nxtask show: need task idx\n" as *u8, 28) 185 return __syscall(93, 2, 0, 0, 0, 0, 0) 186 } 187 // For v0.0.1 parse a single digit only (argv-index parsing 188 // is a separate commit). Usable for demo graph with idx < 10. 189 let idx_str: *u8 = argv[2] as *u8 190 let idx: i64 = idx_str[0] - 0x30 191 if idx < 0 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 192 if idx >= g.n_tasks { return __syscall(93, 2, 0, 0, 0, 0, 0) } 193 let t: *Task = task_at(g, idx) 194 emit_task(w, t, idx) 195 write_out(buf, w.pos) 196 write_out("\n" as *u8, 1) 197 return __syscall(93, 0, 0, 0, 0, 0, 0) 198 } 199 // ---- persistence subcommands ------------------------------------- 200 if streq(cmd, "save" as *u8, 4) == 1 { 201 if argc < 3 { 202 write_out("nxtask save: need path\n" as *u8, 24) 203 return __syscall(93, 2, 0, 0, 0, 0, 0) 204 } 205 let path: *u8 = argv[2] as *u8 206 let store_buf: *u8 = sys_mmap(1048576) // 1 MB headroom 207 let n: i64 = nxtask_serialize(g, store_buf, 1048576) 208 if n < 0 { 209 write_out("nxtask save: serialise overflow\n" as *u8, 33) 210 return __syscall(93, 3, 0, 0, 0, 0, 0) 211 } 212 let fd: i64 = sys_openat_wr(path, 0x1B6) // 0666 213 if fd < 0 { 214 write_out("nxtask save: open failed\n" as *u8, 26) 215 return __syscall(93, 3, 0, 0, 0, 0, 0) 216 } 217 let wrote: i64 = sys_write(fd, store_buf, n) 218 sys_close(fd) 219 if wrote != n { 220 write_out("nxtask save: short write\n" as *u8, 26) 221 return __syscall(93, 3, 0, 0, 0, 0, 0) 222 } 223 // Brief ack so the shell sees success without needing exit code. 224 write_out("{\"saved\":true}\n" as *u8, 15) 225 return __syscall(93, 0, 0, 0, 0, 0, 0) 226 } 227 if streq(cmd, "load" as *u8, 4) == 1 { 228 if argc < 3 { 229 write_out("nxtask load: need path\n" as *u8, 24) 230 return __syscall(93, 2, 0, 0, 0, 0, 0) 231 } 232 let path: *u8 = argv[2] as *u8 233 let len_slot_raw: *u8 = sys_mmap(16) 234 let len_slot: *i64 = len_slot_raw as *i64 235 let file_buf: *u8 = sys_read_file(path, len_slot) 236 if file_buf == (0 as *u8) { 237 write_out("nxtask load: read failed\n" as *u8, 26) 238 return __syscall(93, 3, 0, 0, 0, 0, 0) 239 } 240 let n: i64 = *len_slot 241 let g_loaded: *TaskGraph = nxtask_deserialize(file_buf, n) 242 if g_loaded == (0 as *TaskGraph) { 243 write_out("nxtask load: malformed\n" as *u8, 24) 244 return __syscall(93, 4, 0, 0, 0, 0, 0) 245 } 246 emit_graph(w, g_loaded) 247 write_out(buf, w.pos) 248 write_out("\n" as *u8, 1) 249 return __syscall(93, 0, 0, 0, 0, 0, 0) 250 } 251 if streq(cmd, "roundtrip" as *u8, 9) == 1 { 252 // End-to-end smoke test: save + load + assert n_tasks matches. 253 // Returns exit 0 on success, 3 on I/O failure, 4 on mismatch. 254 if argc < 3 { 255 write_out("nxtask roundtrip: need path\n" as *u8, 29) 256 return __syscall(93, 2, 0, 0, 0, 0, 0) 257 } 258 let path: *u8 = argv[2] as *u8 259 let store_buf: *u8 = sys_mmap(1048576) 260 let n1: i64 = nxtask_serialize(g, store_buf, 1048576) 261 if n1 < 0 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 262 let fd: i64 = sys_openat_wr(path, 0x1B6) 263 if fd < 0 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 264 sys_write(fd, store_buf, n1) 265 sys_close(fd) 266 let len_slot_raw: *u8 = sys_mmap(16) 267 let len_slot: *i64 = len_slot_raw as *i64 268 let file_buf: *u8 = sys_read_file(path, len_slot) 269 if file_buf == (0 as *u8) { return __syscall(93, 3, 0, 0, 0, 0, 0) } 270 let g_loaded: *TaskGraph = nxtask_deserialize(file_buf, *len_slot) 271 if g_loaded == (0 as *TaskGraph) { return __syscall(93, 4, 0, 0, 0, 0, 0) } 272 if g_loaded.n_tasks != g.n_tasks { return __syscall(93, 4, 0, 0, 0, 0, 0) } 273 write_out("{\"roundtrip\":\"ok\"}\n" as *u8, 19) 274 return __syscall(93, 0, 0, 0, 0, 0, 0) 275 } 276 277 write_out("nxtask: unknown subcommand\n" as *u8, 27) 278 return __syscall(93, 2, 0, 0, 0, 0, 0) 279}