code wiki / (root) / nx_elder_supervisor_main.nx

nx_elder_supervisor_main.nx source

↩ module page · 182 lines · 6963 B

1// nx_elder_supervisor_main.nx -- substrate-side Elder AI supervisor. 2// 3// Cardinal 2026-05-21 dual-mandate (top-down + bottom-up): the 4// SUBSTRATE owns the service-lifecycle decision (which services 5// exist, which ports, which healthchecks). The OS adapter 6// (bench/nx_elder_supervise.sh) is now a dumb pipe -- read the 7// substrate-emitted manifest, launch python, poll the URL. 8// 9// V1 contract -- emit one tab-separated line per registered 10// service on stdout (fd 1): 11// 12// SVC<TAB>service_name<TAB>port<TAB>healthcheck_url<NEWLINE> 13// 14// Lines prefixed by anything other than "SVC" are diagnostic 15// (logged by the adapter, not parsed as service entries). 16// 17// V1 mechanics: builds an nx_elder_registry with the services the 18// substrate currently knows how to host (svc-config today; 19// svc-discovery + the data-* + orch-* tier when those are wired), 20// then iterates and writes one manifest line per service. Adapter 21// reads stdin, parses, launches. 22// 23// Why not bash-only? Because then bash owns the source of truth 24// about services + ports + healthchecks, and as Elder AI's 150- 25// service inventory ports over, that bash table becomes the 26// substrate's most important policy artifact -- shadow state. 27// Putting it in nx_elder_registry means the registry IS the 28// policy, with canary protection + sealed-enum validation + the 29// supervisor state machine attached. 30// 31// genealogy_id: erlang_otp_application_start_1996 + 32// systemd_unit_files_2010 + 33// docker_compose_v3_2021 + 34// cardinal_2026-05-21_top_down_bottom_up_dual_mandate 35// lineage_id: substrate_elder_supervisor_main_v1 36 37import "nx_syscalls.nx" 38import "nx_hal.nx" 39import "nx_elder_registry.nx" 40const K_MAGIC_8423: i64 = 8423 41const K_MAGIC_8600: i64 = 8600 42const K_MAGIC_8100: i64 = 8100 43const K_MAGIC_8101: i64 = 8101 44const K_MAGIC_8102: i64 = 8102 45 46// ===== Output helpers (writes to stdout, fd 1) ================================================= 47// 48// nx_hal_console_write goes to fd 2 (debug channel). For the 49// MANIFEST we want fd 1 (substrate-to-adapter communication channel). 50func _emit_line(buf: *u8, len: i64) -> i64 { 51 return sys_write(1, buf, len) 52} 53 54func _emit_byte(b: i64) -> i64 { 55 let tmp: *u8 = (sys_mmap(8)) as *u8 56 tmp[0] = b 57 return sys_write(1, tmp, 1) 58} 59 60// Write a small positive integer as ASCII decimal. V1 single-digit 61// minimum + multi-digit via simple division-by-10 loop. 62func _emit_i64(n: i64) -> i64 { 63 if n == 0 { 64 return _emit_byte(48) // ASCII '0' 65 } 66 // Buffer up to 20 digits (i64 max). 67 let buf: *u8 = (sys_mmap(32)) as *u8 68 var v: i64 = n 69 var i: i64 = 0 70 if v < 0 { 71 _emit_byte(45) // ASCII '-' 72 v = 0 - v 73 } 74 while v > 0 { 75 buf[i] = (v - (v / 10) * 10) + 48 76 v = v / 10 77 i = i + 1 78 } 79 // buf currently has digits in reverse; emit in correct order. 80 var j: i64 = i - 1 81 while j >= 0 { 82 _emit_byte(buf[j]) 83 j = j - 1 84 } 85 return i 86} 87 88// ===== Manifest line emission ================================================= 89// 90// Format: "SVC\tNAME\tPORT\tHEALTHCHECK\n" 91func _emit_manifest_line(name: *u8, name_len: i64, port: i64, hc: *u8, hc_len: i64) -> i64 { 92 let prefix: *u8 = "SVC\t" as *u8 93 _emit_line(prefix, 4) 94 _emit_line(name, name_len) 95 _emit_byte(9) // TAB 96 _emit_i64(port) 97 _emit_byte(9) // TAB 98 _emit_line(hc, hc_len) 99 _emit_byte(10) // NEWLINE 100 return 0 101} 102 103// ===== Registry build ================================================= 104// 105// Hardcoded V1 -- substrate knows svc-config today. As services port 106// over, additional nx_elder_register calls land here. Future V2 107// reads the service inventory from a substrate-owned KV store 108// (composes with nx_vacuole / future nx_kv_store). 109func _build_registry(r: *NxElderRegistry) -> i64 { 110 let py: *u8 = "python" as *u8 111 let py_args: *u8 = "-m uvicorn app:app --host 127.0.0.1 --port <PORT> --app-dir <SVC_DIR>" as *u8 112 113 // svc-config (port 8423) 114 let cfg_name: *u8 = "svc-config" as *u8 115 let cfg_hc: *u8 = "http://127.0.0.1:8423/health" as *u8 116 let rc0: i64 = nx_elder_register(r, 100, cfg_name, 10, K_MAGIC_8423, py, 6, py_args, 73, cfg_hc, 28) 117 if rc0 < 0 { return 0 - 1 } 118 119 // svc-discovery (port 8600 -- ONLY hardcoded port in Elder AI; 120 // every other service registers here at startup and gets a 121 // dynamically assigned port per CLAUDE.md) 122 let disc_name: *u8 = "svc-discovery" as *u8 123 let disc_hc: *u8 = "http://127.0.0.1:8600/health" as *u8 124 let rc1: i64 = nx_elder_register(r, 101, disc_name, 13, K_MAGIC_8600, py, 6, py_args, 73, disc_hc, 28) 125 if rc1 < 0 { return 0 - 2 } 126 127 // data-character (port 8100) -- FastAPI + BaseDataService over 128 // characters.db. No discovery auto-registration; lifespan is 129 // just startup log + yield + shutdown log. 130 let char_name: *u8 = "data-character" as *u8 131 let char_hc: *u8 = "http://127.0.0.1:8100/health" as *u8 132 let rc2: i64 = nx_elder_register(r, 200, char_name, 14, K_MAGIC_8100, py, 6, py_args, 73, char_hc, 28) 133 if rc2 < 0 { return 0 - 3 } 134 135 // data-memory (port 8101) -- conversation memories CRUD. 136 let mem_name: *u8 = "data-memory" as *u8 137 let mem_hc: *u8 = "http://127.0.0.1:8101/health" as *u8 138 let rc3: i64 = nx_elder_register(r, 201, mem_name, 11, K_MAGIC_8101, py, 6, py_args, 73, mem_hc, 28) 139 if rc3 < 0 { return 0 - 4 } 140 141 // data-relationship (port 8102) -- affection/trust/loyalty/romance. 142 let rel_name: *u8 = "data-relationship" as *u8 143 let rel_hc: *u8 = "http://127.0.0.1:8102/health" as *u8 144 let rc4: i64 = nx_elder_register(r, 202, rel_name, 17, K_MAGIC_8102, py, 6, py_args, 73, rel_hc, 28) 145 if rc4 < 0 { return 0 - 5 } 146 147 return 0 148} 149 150// ===== Banner ================================================= 151func _emit_banner() -> i64 { 152 let banner: *u8 = "# nx_elder_supervisor_main -- substrate-emitted manifest follows\n# Format: SVC\\tNAME\\tPORT\\tHEALTHCHECK\n" as *u8 153 sys_write(2, banner, 110) // debug channel 154 return 0 155} 156 157// ===== Main ================================================= 158func main() -> i64 { 159 _emit_banner() 160 161 let r: *NxElderRegistry = nx_elder_registry_new(NX_ELDER_MAX_SERVICES) 162 if nx_elder_registry_is_valid(r) != 1 { return 1 } 163 164 if _build_registry(r) < 0 { return 2 } 165 166 let n: i64 = nx_elder_n_services(r) 167 if n <= 0 { return 3 } 168 169 var i: i64 = 0 170 while i < n { 171 let s: *NxElderService = nx_elder_lookup_by_index(r, i) 172 if nx_elder_service_is_valid(s) != 1 { return 4 } 173 _emit_manifest_line(s.name, s.name_len, s.port, s.healthcheck_url, s.healthcheck_len) 174 i = i + 1 175 } 176 177 // Trailer line so the adapter knows the manifest is complete. 178 let done: *u8 = "DONE\n" as *u8 179 _emit_line(done, 5) 180 181 return 0 182}