code wiki / _hdl_build / nx_ale_agent.nx

nx_ale_agent.nx source

↩ module page · 232 lines · 10341 B

1// nx_ale_agent.nx -- sovereign ALE agent-core SUBMIT/compose entrypoint (rung ALE-R2d). 2// THE one agent-attempt entrypoint the whole ALE-R2 agent-core graduates into: it COMPOSES the 3// PLAN (ALE-R2a, nx_ale_plan) -> EXECUTE (ALE-R2b, nx_ale_exec) -> SELF-VERIFY (ALE-R2c, 4// nx_ale_verify) phase organs into a SINGLE invocation that takes a task spec and emits the 5// agent-attempt triple {plan, artifact, self_score}. This IS the ALE-R1b agent phase. 6// argv[1] = TASK spec path (ALE-format "contract|field|value" lines; ale_format.spec grammar) 7// argv[2] = SANDBOX dir (the agent's working dir; staged input + the real artifact land here) 8// argv[3] = OUTDIR dir (the agent's emission dir; plan + self_score land here) 9// CONTRACT (data-driven; no magic numbers -- phase shapes come from the phase organs themselves): 10// The agent reads ONLY the task spec for its own grounding check: it extracts the DECLARED 11// task|artifact_path| value to resolve where the executor will drop the deliverable (sandbox + 12// "/" + artifact_path). If that REQUIRED field is absent the agent REFUSES UP FRONT: exit 1, 13// writes NO plan, NO artifact, NO self_score (the grounding bite -- not vacuously green). It 14// NEVER opens the grader|reference| path during plan/exec; only the SELF-VERIFY phase (run last, 15// post-completion) resolves the reference -- and that resolution lives inside nx_ale_verify, not 16// here. So planting a decoy reference.txt in the sandbox cannot change the plan, artifact, or 17// self_score: NO-LEAKAGE ORDER is preserved end-to-end. 18// Pipeline (each phase must succeed or the whole attempt fails BEFORE the next phase): 19// 1. PLAN fork/execve nx_ale_plan.elf <task> <outdir>/plan ; rc!=0 -> exit 11 20// 2. EXECUTE fork/execve nx_ale_exec.elf <outdir>/plan <task> <sandbox>; rc!=0 -> exit 12 21// 3. SELF-VERIFY fork/execve nx_ale_verify.elf <task> <sandbox>/<artifact_path> <outdir>/self_score 22// ; rc!=0 -> exit 13 (and the self_score file is the verify organ's own output, 23// the SAME number an independent nx_ale_grade of the same pair yields -- the agent 24// does not lie about its own score). 25// On all three phases OK -> exit 0 (the artifact lives under the sandbox, the plan + self_score 26// under the outdir). No clock, no rand in this composer -> two runs on the same (task, fresh 27// sandbox, fresh outdir) yield BYTE-IDENTICAL {plan, artifact, self_score}: DETERMINISTIC. 28// Landmines respected: nested ifs (no &&/||), flat exprs, <=6 args/func, no empty-string literal, 29// strings via Write, openat_wr has no O_TRUNC. Helpers copied VERBATIM from the ALE phase organs' 30// line-grammar (av_*/ae_*) + the gate's fork/execve discipline (ag_run_*). 31// license_tier: ORIGINAL 32import "nx_syscalls.nx" 33const K_MAGIC_262144: i64 = 262144 34const K_MAGIC_8192: i64 = 8192 35const K_MAGIC_16384: i64 = 16384 36 37// read whole file at path into buf (cap), return byte count (0 on open-fail) 38func ag_read(path: *u8, buf: *u8, cap: i64) -> i64 { 39 let fd: i64 = sys_openat_rd(path) 40 if fd < 0 { return 0 } 41 var n: i64 = 0 42 var go: i64 = 1 43 while go == 1 { 44 let r: i64 = sys_read(fd, (buf as i64 + n) as *u8, cap - 1 - n) 45 if r <= 0 { go = 0 } else { n = n + r } 46 if n >= cap - 1 { go = 0 } 47 } 48 sys_close(fd) 49 return n 50} 51 52// length of a C string 53func ag_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 54 55// does buf[pos..] match pat (len pl)? 1/0 56func ag_match(buf: *u8, n: i64, pos: i64, pat: *u8, pl: i64) -> i64 { 57 if pos + pl > n { return 0 } 58 var k: i64 = 0 59 while k < pl { 60 if buf[pos + k] != pat[k] { return 0 } 61 k = k + 1 62 } 63 return 1 64} 65 66// is pos a line start? (pos==0 or previous byte is newline) 67func ag_is_bol(buf: *u8, pos: i64) -> i64 { 68 if pos == 0 { return 1 } 69 if buf[pos - 1] == (10 as u8) { return 1 } 70 return 0 71} 72 73// find a line beginning with prefix; return its start index, or -1 74func ag_find_line(buf: *u8, n: i64, prefix: *u8) -> i64 { 75 let pl: i64 = ag_len(prefix) 76 var i: i64 = 0 77 while i < n { 78 if ag_is_bol(buf, i) == 1 { 79 if ag_match(buf, n, i, prefix, pl) == 1 { return i } 80 } 81 i = i + 1 82 } 83 return 0 - 1 84} 85 86// Extract the VALUE of the field whose line starts with prefix into out (NUL-terminated), 87// copying from after the prefix up to (not including) the newline. Returns value length, or -1 88// if the field line is absent (the GROUNDING bite). cap bounds the copy. 89func ag_field_val(buf: *u8, n: i64, prefix: *u8, out: *u8, cap: i64) -> i64 { 90 let start: i64 = ag_find_line(buf, n, prefix) 91 if start < 0 { return 0 - 1 } 92 let pl: i64 = ag_len(prefix) 93 var p: i64 = start + pl 94 var k: i64 = 0 95 var go: i64 = 1 96 while go == 1 { 97 if p >= n { go = 0 } else { 98 if buf[p] == (10 as u8) { go = 0 } else { 99 if k < cap - 1 { out[k] = buf[p]; k = k + 1 } 100 p = p + 1 101 } 102 } 103 } 104 out[k] = 0 as u8 105 return k 106} 107 108// join a + "/" + b into out (NUL-terminated). a,b are NUL-terminated. returns length. 109func ag_join(a: *u8, b: *u8, out: *u8) -> i64 { 110 var k: i64 = 0 111 var i: i64 = 0 112 while a[i] != (0 as u8) { out[k] = a[i]; k = k + 1; i = i + 1 } 113 out[k] = 47 as u8; k = k + 1 // '/' 114 var j: i64 = 0 115 while b[j] != (0 as u8) { out[k] = b[j]; k = k + 1; j = j + 1 } 116 out[k] = 0 as u8 117 return k 118} 119 120// fork/execve nx_ale_plan.elf <task> <planout>; return child exit code (silenced child IO). 121func ag_run_plan(task: *u8, planout: *u8) -> i64 { 122 let pid: i64 = sys_fork() 123 if pid == 0 { 124 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 125 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) } 126 let argv: *i64 = sys_mmap(32) as *i64 127 argv[0] = "_offc/nx_ale_plan.elf" as *u8 as i64 128 argv[1] = task as i64 129 argv[2] = planout as i64 130 argv[3] = 0 131 let envp: *i64 = sys_mmap(16) as *i64 132 envp[0] = 0 133 sys_execve("_offc/nx_ale_plan.elf" as *u8, argv, envp) 134 sys_exit(127) 135 } 136 let st: *i64 = sys_mmap(16) as *i64 137 sys_wait4(pid, st, 0) 138 return (st[0] >> 8) & 0xff 139} 140 141// fork/execve nx_ale_exec.elf <plan> <task> <sandbox>; return child exit code. 142func ag_run_exec(plan: *u8, task: *u8, sandbox: *u8) -> i64 { 143 let pid: i64 = sys_fork() 144 if pid == 0 { 145 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 146 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) } 147 let argv: *i64 = sys_mmap(48) as *i64 148 argv[0] = "_offc/nx_ale_exec.elf" as *u8 as i64 149 argv[1] = plan as i64 150 argv[2] = task as i64 151 argv[3] = sandbox as i64 152 argv[4] = 0 153 let envp: *i64 = sys_mmap(16) as *i64 154 envp[0] = 0 155 sys_execve("_offc/nx_ale_exec.elf" as *u8, argv, envp) 156 sys_exit(127) 157 } 158 let st: *i64 = sys_mmap(16) as *i64 159 sys_wait4(pid, st, 0) 160 return (st[0] >> 8) & 0xff 161} 162 163// fork/execve nx_ale_verify.elf <task> <artifact> <scoreout>; return child exit code. 164func ag_run_verify(task: *u8, art: *u8, scoreout: *u8) -> i64 { 165 let pid: i64 = sys_fork() 166 if pid == 0 { 167 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 168 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) } 169 let argv: *i64 = sys_mmap(48) as *i64 170 argv[0] = "_offc/nx_ale_verify.elf" as *u8 as i64 171 argv[1] = task as i64 172 argv[2] = art as i64 173 argv[3] = scoreout as i64 174 argv[4] = 0 175 let envp: *i64 = sys_mmap(16) as *i64 176 envp[0] = 0 177 sys_execve("_offc/nx_ale_verify.elf" as *u8, argv, envp) 178 sys_exit(127) 179 } 180 let st: *i64 = sys_mmap(16) as *i64 181 sys_wait4(pid, st, 0) 182 return (st[0] >> 8) & 0xff 183} 184 185func main(argc: i64, argv: *i64) -> i64 { 186 if argc < 4 { sys_exit(2); return 2 } 187 let taskp: *u8 = argv[1] as *u8 188 let sandbox: *u8 = argv[2] as *u8 189 let outdir: *u8 = argv[3] as *u8 190 191 // Read the task spec for the agent's OWN grounding check. 192 let tbuf: *u8 = sys_mmap(K_MAGIC_262144) 193 let tn: i64 = ag_read(taskp, tbuf, K_MAGIC_262144) 194 if tn <= 0 { sys_exit(3); return 3 } 195 196 // GROUNDING / TAMPER-REJECT: the agent resolves where the deliverable will land from the 197 // DECLARED task|artifact_path| value. Absent -> REFUSE UP FRONT (exit 1) BEFORE any phase runs 198 // -> no plan, no artifact, no self_score file is produced (the entrypoint is not vacuously 199 // green). (The phase organs also refuse on their own missing fields; this is the agent-level 200 // bite that keeps SELF-VERIFY from ever running on an ungrounded task.) 201 let v_art: *u8 = sys_mmap(K_MAGIC_8192) 202 let la: i64 = ag_field_val(tbuf, tn, "task|artifact_path|" as *u8, v_art, K_MAGIC_8192) 203 if la < 0 { sys_exit(1); return 1 } 204 205 // Build the emission paths under outdir and the artifact path under sandbox. 206 let planp: *u8 = sys_mmap(K_MAGIC_16384) 207 ag_join(outdir, "plan" as *u8, planp) 208 let scorep: *u8 = sys_mmap(K_MAGIC_16384) 209 ag_join(outdir, "self_score" as *u8, scorep) 210 let artp: *u8 = sys_mmap(K_MAGIC_16384) 211 ag_join(sandbox, v_art, artp) 212 213 // PHASE 1 -- PLAN. The reference path is a grader| field, never a task| field, so the planner 214 // (which reads ONLY task| fields) cannot leak it. rc!=0 -> the plan was refused -> abort. 215 let rcp: i64 = ag_run_plan(taskp, planp) 216 if rcp != 0 { sys_exit(11); return 11 } 217 218 // PHASE 2 -- EXECUTE. Consumes the deterministic plan + task, drops the real artifact under the 219 // sandbox. The executor never opens the reference path either. rc!=0 -> abort (no self-verify). 220 let rce: i64 = ag_run_exec(planp, taskp, sandbox) 221 if rce != 0 { sys_exit(12); return 12 } 222 223 // PHASE 3 -- SELF-VERIFY (post-completion). ONLY now is the declared reference resolved (inside 224 // nx_ale_verify, from the task spec's grader|reference| field) to grade the agent's OWN 225 // artifact -> self_score. Same code path an independent grade uses -> honest score. rc!=0 -> 226 // abort (the verify organ refuses if the task declares no grading reference). 227 let rcv: i64 = ag_run_verify(taskp, artp, scorep) 228 if rcv != 0 { sys_exit(13); return 13 } 229 230 sys_exit(0) 231 return 0 232}