code wiki / (root) / nxtask_main.nx

nxtask_main.nx source

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