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}