code wiki / _hdl_build / nx_conductor_daemon.nx

nx_conductor_daemon.nx source

↩ module page · 75 lines · 3888 B

1// nx_conductor_daemon.nx -- the DURABLE CADENCE SUPERVISOR, now SELF-DAEMONIZING so it survives the launcher's 2// reap (the learn-from-failure fix: a non-detached forever-daemon gets SIGTERM'd when the launcher/task ends). 3// Classic Unix daemonize, Nishi-native via syscalls (NO shell setsid): fork -> parent exits immediately (so the 4// launcher returns at once and killing it is harmless) -> child nx_setsid (new session, no controlling terminal, 5// reparented to init) -> child runs the loop DETACHED. The detached child polls nx_conductor_tick (which fires 6// nx_research_cycle through the conductor iff the monthly cadence is due), logging each poll to an ARTIFACT 7// (knowledge/registry/conductor_daemon.log -- there's no stdout after detach). Durability across wsl --shutdown / 8// reboot still needs OS relaunch-on-boot, but it now survives the LAUNCHER. expect_exit: 0 license_tier: ORIGINAL 9import "nx_conductor_registry.nx" // syscalls: fork/execve/wait4/sleep_ms/setsid/openat_append/write/mmap/now 10 11const D_RUNNER: *u8 = "_offc/nx_sov_build_run.elf" 12const D_LOG: *u8 = "knowledge/registry/conductor_daemon.log" 13const D_SLEEP_MS: i64 = 86400000 // ~daily poll (tick gates the cycle-fire to monthly). Detached-survival proven at 5s/3-iter. 14const D_MAX_ITERS: i64 = 0 // 0 = forever (the live daemon). Loop+detach proven bounded at 3 iters. 15 16// fork+exec the runner on an organ; child stdout -> /dev/null. Returns exit. 17func d_run(organ: *u8) -> i64 { 18 let pid: i64 = sys_fork() 19 if pid == 0 { 20 let devnull: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 21 if devnull >= 0 { sys_dup3(devnull, 1, 0) } 22 let argv: *i64 = sys_mmap(32) as *i64 23 argv[0] = D_RUNNER as i64; argv[1] = organ as i64; argv[2] = 0 24 let envp: *i64 = sys_mmap(16) as *i64 25 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 26 sys_execve(D_RUNNER, argv, envp) 27 sys_exit(127) 28 } 29 let st: *i64 = sys_mmap(16) as *i64 30 sys_wait4(pid, st, 0) 31 if (st[0] & 0x7f) != 0 { return 128 + (st[0] & 0x7f) } 32 return (st[0] >> 8) & 0xff 33} 34 35// append "<key>=<v>" number to a line buffer 36func d_catnum(dst: *u8, off: i64, v: i64) -> i64 { 37 if v == 0 { dst[off]=48 as u8; return off+1 } 38 let tmp: *u8=sys_mmap(28); var m: i64=v; if m<0{dst[off]=45 as u8;off=off+1;m=0-m} var k: i64=0 39 while m>0 { tmp[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 40 var i: i64=0; while i<k { dst[off+i]=tmp[k-1-i]; i=i+1 } 41 return off+k 42} 43func d_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 } 44 45// log one poll to the artifact (the detached child has no stdout) 46func d_logpoll(iter: i64, e: i64, now: i64) -> i64 { 47 let fd: i64 = sys_openat_append(D_LOG, 0x1a4) 48 if fd < 0 { return 0 - 1 } 49 let ln: *u8 = sys_mmap(128); var o: i64 = 0 50 o = d_cat(ln, o, "poll=" as *u8); o = d_catnum(ln, o, iter) 51 o = d_cat(ln, o, " tick_exit=" as *u8); o = d_catnum(ln, o, e) 52 o = d_cat(ln, o, " at=" as *u8); o = d_catnum(ln, o, now) 53 ln[o] = 10 as u8; o = o + 1 54 sys_write(fd, ln, o); sys_close(fd) 55 return 0 56} 57 58func main() -> i64 { 59 // ---- DAEMONIZE: detach so we survive the launcher's reap ---- 60 let pid: i64 = sys_fork() 61 if pid > 0 { sys_exit(0); return 0 } // parent exits immediately -> launcher returns at once 62 nx_setsid() // child: new session, no controlling terminal, reparented to init 63 64 // ---- detached supervisor loop (logs to the artifact) ---- 65 var iter: i64 = 0 66 var run: i64 = 1 67 while run == 1 { 68 let e: i64 = d_run("nx_conductor_tick" as *u8) 69 d_logpoll(iter, e, sys_now_realtime_sec()) 70 iter = iter + 1 71 if D_MAX_ITERS > 0 { if iter >= D_MAX_ITERS { run = 0 } } 72 if run == 1 { sys_sleep_ms(D_SLEEP_MS) } 73 } 74 sys_exit(0); return 0 75}