code wiki / (root) / nx_closed_loop.nx

nx_closed_loop.nx source

↩ module page · 295 lines · 10520 B

1// nx_closed_loop.nx -- measure-adjust-rerender closed-loop primitive. 2// 3// THE substrate's structural answer to the LoRA / negative-prompt / 4// ControlNet patch problem. Per cardinal 5// feedback-loras-and-negatives-are-patches-not-systems: 6// 7// * Modern image-gen + LLM stacks are BLACK BOXES. You prompt, 8// you hope. Predictability is patched via LoRAs / neg prompts. 9// * Substrate's offer: closed-loop measurement. Generate -> 10// measure -> if off-target, adjust + re-generate. Iterate 11// until target verdict hits or budget exhausted. 12// * User's example: "predictably make an 18 year old human." 13// Today's systems can't. This primitive + caller's age- 14// measurement callback CAN. 15// 16// L4 generic composer. Pure composition -- no new math, no new 17// containers. Caller plugs in the four callbacks: 18// 19// generate_fn : generates a candidate output given a seed and 20// adjustment state 21// measure_fn : measures the candidate and returns a verdict 22// (per-axis i64 values, e.g. measured age, 23// fidelity score, hallucination flag) 24// verdict_fn : judges measurement vs target spec; returns 25// 0 = OK / 1 = ADJUST / 2 = ABORT 26// adjust_fn : given current state + measurement, mutates 27// state for next attempt 28// 29// The substrate handles the loop discipline + budget + sealed 30// verdict semantics. Caller owns the domain logic (what age 31// is "18", what hallucination is, etc). 32// 33// ===== Why this is the anti-LoRA ================================== 34// 35// LoRA approach: 36// 1. Train a LoRA on 1000 examples of "18-year-old faces" 37// 2. Hope the LoRA biases the base model correctly 38// 3. Can't measure: no closed loop; just generate + visually check 39// 4. Fails on out-of-distribution prompts 40// 41// Closed-loop substrate approach: 42// 1. Generate face 43// 2. Measure: facial-landmark + age estimator returns predicted age 44// 3. If predicted age in [17, 19]: OK; emit 45// 4. Else: adjust seed / sampler temperature / guidance / prompt 46// embedding; re-generate 47// 5. Loop until OK or budget exhausted; emit verdict either way 48// 49// No model retraining. No LoRA file. No hallucinated "should be 50// good enough." Measurable predictability. 51// 52// Caller picks the measurement at the right granularity (per-token 53// for LLM hallucination, per-frame for video, per-element for 54// structured output). 55// 56// genealogy_id: cybernetics_wiener_1948 + feedback_control_bode_1945 + 57// rejection_sampling_von_neumann_1951 + 58// quality_engineering_taguchi_1986 59// lineage_id: substrate_closed_loop_v1 60 61// nx_safety_envelope: 62// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 63// sil_target: SIL1 64// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 65// verdict: NOT_YET_EVALUATED 66 67import "nx_syscalls.nx" 68import "nx_tier.nx" 69import "nx_loop.nx" 70 71// ===== Sealed-enum: ClosedLoopVerdict ============================= 72 73const NX_CL_OK: nx_int = 0 // generation matched target 74const NX_CL_BUDGET: nx_int = 1 // out of attempts, last attempt off-target 75const NX_CL_ABORT: nx_int = 2 // measurement function asked to abort 76const NX_CL_ERR_BAD_BUDGET: nx_int = 3 77const NX_CL_N_VERDICTS: nx_int = 4 78 79func nx_cl_verdict_is_valid(v: nx_int) -> nx_int { 80 if v < 0 { return 0 } 81 if v >= NX_CL_N_VERDICTS { return 0 } 82 return 1 83} 84 85// ===== Sealed-enum: VerdictFnReturn =============================== 86// 87// What the caller's verdict_fn can return per measurement: 88// NX_CL_VFN_OK -> on-target, stop 89// NX_CL_VFN_ADJUST -> off-target, try again 90// NX_CL_VFN_ABORT -> abandoned (e.g. measurement saw catastrophic 91// hallucination, no point continuing) 92 93const NX_CL_VFN_OK: nx_int = 0 94const NX_CL_VFN_ADJUST: nx_int = 1 95const NX_CL_VFN_ABORT: nx_int = 2 96const NX_CL_VFN_N: nx_int = 3 97 98func nx_cl_vfn_is_valid(v: nx_int) -> nx_int { 99 if v < 0 { return 0 } 100 if v >= NX_CL_VFN_N { return 0 } 101 return 1 102} 103 104// ===== Result envelope ============================================ 105// 106// One per closed-loop run. Caller reads .verdict to know what 107// happened; .n_attempts for the budget side; .last_measurement_ptr 108// for the final measurement bytes (or null if none). 109 110struct NxClosedLoopResult { 111 verdict: nx_int, // NX_CL_OK / BUDGET / ABORT 112 n_attempts: nx_int, // attempts consumed (1-based) 113 last_vfn_ret: nx_int // last verdict_fn return value 114} 115 116const NX_CL_RESULT_BYTES: nx_int = 24 // 3 fields * 8 117 118// ===== Main closed-loop entrypoint ================================ 119// 120// state_ptr: caller-owned opaque pointer (cast to i64). Holds 121// the seed / adjustment params / shared buffers. 122// max_attempts: hard budget (per JPL Rule 2 + bounded-loop cardinal) 123// 124// generate_fn(state_ptr) -> nx_int (caller's result code; 0 = OK) 125// measure_fn(state_ptr) -> nx_int (caller's measurement code; same) 126// verdict_fn(state_ptr) -> nx_int (NX_CL_VFN_*) 127// adjust_fn(state_ptr) -> nx_int (caller's update result; 0 = OK) 128// 129// Loop body: 130// 1. generate 131// 2. measure 132// 3. verdict 133// 4. if OK -> exit 134// 5. if ABORT -> exit with abort verdict 135// 6. if ADJUST -> call adjust_fn, increment attempt counter, 136// goto 1 137// 138// Returns *NxClosedLoopResult (caller-owned via sys_mmap). 139 140func nx_closed_loop_run( 141 state_ptr: i64, max_attempts: nx_int, 142 generate_fn: func(i64) -> nx_int, 143 measure_fn: func(i64) -> nx_int, 144 verdict_fn: func(i64) -> nx_int, 145 adjust_fn: func(i64) -> nx_int) -> *NxClosedLoopResult { 146 147 let r: *NxClosedLoopResult = sys_mmap(NX_CL_RESULT_BYTES) as *NxClosedLoopResult 148 r.verdict = NX_CL_BUDGET 149 r.n_attempts = 0 150 r.last_vfn_ret = NX_CL_VFN_ADJUST 151 152 if max_attempts <= 0 { 153 r.verdict = NX_CL_ERR_BAD_BUDGET 154 return r 155 } 156 157 var iter: nx_int = 0 158 var verdict: nx_int = NX_LOOP_RUNNING 159 let BUDGET: nx_int = max_attempts 160 while verdict == NX_LOOP_RUNNING && iter < BUDGET { 161 r.n_attempts = iter + 1 162 163 // 1. Generate candidate. 164 let v_gen: nx_int = generate_fn(state_ptr) 165 if v_gen != 0 { verdict = NX_LOOP_ABORTED } 166 167 // 2. Measure. 168 if verdict == NX_LOOP_RUNNING { 169 let v_meas: nx_int = measure_fn(state_ptr) 170 if v_meas != 0 { verdict = NX_LOOP_ABORTED } 171 } 172 173 // 3. Verdict. 174 if verdict == NX_LOOP_RUNNING { 175 let v_vfn: nx_int = verdict_fn(state_ptr) 176 r.last_vfn_ret = v_vfn 177 if v_vfn == NX_CL_VFN_OK { 178 r.verdict = NX_CL_OK 179 verdict = NX_LOOP_DONE_EXIT 180 } 181 if v_vfn == NX_CL_VFN_ABORT { 182 r.verdict = NX_CL_ABORT 183 verdict = NX_LOOP_DONE_EXIT 184 } 185 } 186 187 // 4. Adjust + re-loop. 188 if verdict == NX_LOOP_RUNNING { 189 adjust_fn(state_ptr) 190 } 191 192 iter = iter + 1 193 } 194 // If we exited the while normally (budget hit) the verdict 195 // field already has NX_CL_BUDGET from init. 196 return r 197} 198 199// ===== Self-test ================================================== 200// 201// Tiny closed-loop: synthesize a target-value-converging walker. 202// State: a counter we step toward a target. Measurement: how far 203// from target. Verdict: OK if within tolerance, ADJUST else. 204// Adjust: step toward target. 205// 206// Closed-form invariants: 207// (a) Walker converges within tolerance before budget -> NX_CL_OK 208// (b) With tight tolerance + small budget -> NX_CL_BUDGET 209// (c) Verdict-range gate 210 211const NX_TEST_TARGET: nx_int = 100 212const NX_TEST_TOLERANCE: nx_int = 2 213 214struct TestState { 215 counter: nx_int, 216 measurement:nx_int, 217 target: nx_int, 218 tolerance: nx_int 219} 220 221func _test_state_alloc(initial: nx_int, target: nx_int, tol: nx_int) -> *TestState { 222 let s: *TestState = sys_mmap(32) as *TestState 223 s.counter = initial 224 s.measurement = 0 225 s.target = target 226 s.tolerance = tol 227 return s 228} 229 230func _test_generate(state_ptr: i64) -> nx_int { 231 // No-op generate in the test (the walker IS the generation). 232 return 0 233} 234 235func _test_measure(state_ptr: i64) -> nx_int { 236 let s: *TestState = state_ptr as *TestState 237 let diff: nx_int = s.counter - s.target 238 if diff < 0 { s.measurement = 0 - diff } 239 if diff >= 0 { s.measurement = diff } 240 return 0 241} 242 243func _test_verdict(state_ptr: i64) -> nx_int { 244 let s: *TestState = state_ptr as *TestState 245 if s.measurement <= s.tolerance { return NX_CL_VFN_OK } 246 return NX_CL_VFN_ADJUST 247} 248 249func _test_adjust(state_ptr: i64) -> nx_int { 250 let s: *TestState = state_ptr as *TestState 251 if s.counter < s.target { s.counter = s.counter + 1 } 252 if s.counter > s.target { s.counter = s.counter - 1 } 253 return 0 254} 255 256func main() -> i64 { 257 // --- (a) Converges within budget --- 258 let s1: *TestState = _test_state_alloc(0, NX_TEST_TARGET, NX_TEST_TOLERANCE) 259 let r1: *NxClosedLoopResult = nx_closed_loop_run( 260 s1 as i64, 200, 261 _test_generate, _test_measure, _test_verdict, _test_adjust) 262 if r1.verdict != NX_CL_OK { return 10 } 263 // n_attempts should be ~100 (one per step toward target). 264 if r1.n_attempts < 95 { return 11 } 265 if r1.n_attempts > 105 { return 12 } 266 267 // --- (b) Budget exhaustion --- 268 let s2: *TestState = _test_state_alloc(0, NX_TEST_TARGET, NX_TEST_TOLERANCE) 269 let r2: *NxClosedLoopResult = nx_closed_loop_run( 270 s2 as i64, 10, // tiny budget, can't reach 100 271 _test_generate, _test_measure, _test_verdict, _test_adjust) 272 if r2.verdict != NX_CL_BUDGET { return 20 } 273 if r2.n_attempts != 10 { return 21 } 274 275 // --- (c) Bad budget rejected --- 276 let s3: *TestState = _test_state_alloc(0, NX_TEST_TARGET, NX_TEST_TOLERANCE) 277 let r3: *NxClosedLoopResult = nx_closed_loop_run( 278 s3 as i64, 0, 279 _test_generate, _test_measure, _test_verdict, _test_adjust) 280 if r3.verdict != NX_CL_ERR_BAD_BUDGET { return 30 } 281 282 // --- (d) Verdict + vfn gates --- 283 var vi: nx_int = 0 284 while vi < NX_CL_N_VERDICTS { 285 if nx_cl_verdict_is_valid(vi) != 1 { return 40 + vi } 286 vi = vi + 1 287 } 288 var vj: nx_int = 0 289 while vj < NX_CL_VFN_N { 290 if nx_cl_vfn_is_valid(vj) != 1 { return 50 + vj } 291 vj = vj + 1 292 } 293 294 return 0 295}