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}