nx_symbiote.nx source
↩ module page · 280 lines · 11395 B
1// nx_symbiote.nx -- foreign-process virtual cell.
2//
3// Biological analogue: a symbiote is a non-self organism living
4// cooperatively with the host -- mitochondria are the classic example
5// (originally free-living bacteria, now permanent residents). Nishi
6// symbiote wraps a non-Nishi process (Steam-hosted NMS, Chrome, OBS,
7// VS Code, a CUDA training run) and projects it into the same resource-
8// arbitration substrate as Nishi-native cells.
9//
10// THIS IS THE PARTNERSHIP INTERFACE. Per user 2026-05-19 NMS-via-Steam
11// scenario: "where can we build from the silicon up to get the best
12// experience on the nishi ecosystem and then as it moves off our
13// ecosystem into steam partnering with steam to improve things like
14// vram usage, disk space usage." The user runs NMS via Steam; we
15// can't make Steam speak Nishi grammar, but we can OBSERVE the Steam-
16// hosted process and NEGOTIATE through a treaty (see nx_treaty).
17//
18// CRITICAL HONESTY (per [[feedback-honest-perf-verdict-no-aspirational-
19// claims]]): symbiote resource numbers are AS-OBSERVED, not AS-PROMISED.
20// A native nx_cell honors its budget contractually -- the substrate
21// enforces it. A symbiote may consume MORE than declared at any moment
22// because it has no contractual obligation. Symbiote's job is to
23// MEASURE the reality, not GUARANTEE the limit.
24//
25// Composes:
26// nx_budget -- virtual_budget records the symbiote's
27// DECLARED resource ceiling (caller-supplied;
28// honest baseline for treaty negotiation)
29// nx_attention_class -- foreground game symbiote (NMS) gets
30// foreground priority just like native cells
31// nx_evict_journal -- throttle/pause/terminate events logged
32// nx_treaty -- bilateral resource agreement between
33// organism and symbiote
34// nx_organism -- the container that arbitrates across all
35// symbiotes + native pathways
36//
37// V1 ships an in-memory descriptor with caller-supplied observation
38// updates. Real OS probes (Linux /proc/<pid>/status + /proc/<pid>/
39// io, Windows Job Object + GetProcessMemoryInfo + nvidia-smi pmon)
40// are queued -- the integration layer between nx_symbiote and the
41// host OS lives outside this primitive.
42//
43// Gap list (V1 honest perf verdict):
44// - no actual OS probe (caller calls nx_symbiote_update with
45// externally-obtained numbers)
46// - throttle/pause/terminate are intent verbs; actual enforcement
47// is the integration layer's job (SIGSTOP/SetSuspendThread/etc)
48// - no GPU-specific VRAM probe (nvidia-smi binding queued)
49// - no per-process disk-bandwidth probe
50// - no cross-PID grouping (Steam spawns many PIDs; symbiote is
51// 1:1 with PID, treaty layer handles process trees)
52//
53// genealogy_id: biology_symbiosis + cardinal_2026-05-19_ecosystem_wide
54// lineage_id: substrate_symbiote_v1
55//
56// nx_safety_envelope:
57// intended_use: "Foreign-process virtual cell for ecosystem-
58// wide arbitration; OBSERVES, does not contract"
59// sil_target: SIL2
60// evidence: [observation_not_contract_explicit,
61// enforcement_intent_separate_from_observation]
62// hazard_register: [bug-tape-symbiote-pid-reuse-after-exit,
63// bug-tape-symbiote-stale-observation]
64// verdict: NOT_YET_EVALUATED
65
66import "nx_syscalls.nx"
67import "nx_tier.nx"
68import "nx_budget.nx"
69import "nx_attention_class.nx"
70import "nx_evict_journal.nx"
71const NX_MAGIC_1024: i64 = 1024
72
73// ===== Sealed enum: NxSymbioteState ===============================
74
75const NX_SYM_RUNNING: nx_int = 0
76const NX_SYM_THROTTLED: nx_int = 1
77const NX_SYM_PAUSED: nx_int = 2
78const NX_SYM_TERMINATED: nx_int = 3
79const NX_SYM_GONE: nx_int = 4 // process exited on its own
80const NX_SYM_N_STATES: nx_int = 5
81
82// ===== Sealed enum: NxSymbioteVerdict =============================
83
84const NX_SYM_OK: nx_int = 0
85const NX_SYM_ERR_BAD_STATE: nx_int = 1
86const NX_SYM_ERR_BAD_PID: nx_int = 2
87const NX_SYM_ERR_ALREADY_GONE: nx_int = 3
88
89// ===== Struct: NxSymbiote =========================================
90//
91// pid is the host OS process id (Linux pid_t / Windows DWORD). name
92// is a caller-supplied byte buffer (e.g. "steam-nms"). attention_class
93// is the priority the substrate gives it -- NMS gameplay would be
94// FOREGROUND_GAME, a background training run BACKGROUND_INFERENCE.
95//
96// virtual_budget is the DECLARED ceiling (used in treaty negotiation
97// and organism aggregate forecasts). observed_* fields hold the
98// last-probe ACTUAL consumption -- the truth, not the promise.
99
100struct NxSymbiote {
101 pid: nx_int,
102 name: *u8,
103 attention_class: nx_int,
104 state: nx_int,
105 virtual_budget: *NxBudget,
106 observed_ram_bytes: nx_size,
107 observed_vram_bytes: nx_size,
108 observed_cpu_us: nx_size,
109 observed_disk_bytes: nx_size,
110 last_observe_us: nx_size,
111 cooperative: nx_int, // 1 if it speaks treaty, 0 otherwise
112}
113
114// ===== nx_sym_state_is_valid =====================================
115
116func nx_sym_state_is_valid(s: nx_int) -> nx_int {
117 if s < 0 { return 0 }
118 if s >= NX_SYM_N_STATES { return 0 }
119 return 1
120}
121
122// ===== Constructor ===============================================
123
124func nx_symbiote_new(pid: nx_int,
125 name: *u8,
126 attention_class: nx_int,
127 virtual_budget: *NxBudget,
128 cooperative: nx_int) -> *NxSymbiote {
129 let s: *NxSymbiote = (sys_mmap(88)) as *NxSymbiote
130 s.pid = pid
131 s.name = name
132 s.attention_class = attention_class
133 s.state = NX_SYM_RUNNING
134 s.virtual_budget = virtual_budget
135 s.observed_ram_bytes = 0
136 s.observed_vram_bytes = 0
137 s.observed_cpu_us = 0
138 s.observed_disk_bytes = 0
139 s.last_observe_us = 0
140 s.cooperative = cooperative
141 return s
142}
143
144// ===== nx_symbiote_update ========================================
145//
146// Caller refreshes the observed_* fields with fresh OS-probe numbers.
147// V1 takes them all as arguments (caller pulls from /proc + nvidia-smi);
148// V2 will internalize a probe-source callback.
149
150func nx_symbiote_update(s: *NxSymbiote,
151 ram_bytes: nx_size,
152 vram_bytes: nx_size,
153 cpu_us: nx_size,
154 disk_bytes: nx_size,
155 now_us: nx_size) -> nx_int {
156 s.observed_ram_bytes = ram_bytes
157 s.observed_vram_bytes = vram_bytes
158 s.observed_cpu_us = cpu_us
159 s.observed_disk_bytes = disk_bytes
160 s.last_observe_us = now_us
161 return NX_SYM_OK
162}
163
164// ===== nx_symbiote_overshoot_q10 =================================
165//
166// Q10 ratio of observed/declared for one resource. > 1024 means the
167// symbiote is consuming MORE than its declared virtual_budget allows.
168// Treaty layer uses this to detect breach.
169
170func nx_symbiote_overshoot_q10(s: *NxSymbiote, kind: nx_int) -> nx_int {
171 if (s.virtual_budget as i64) == 0 { return 0 }
172 var max: nx_size = 0
173 var obs: nx_size = 0
174 if kind == NX_RES_RAM { max = s.virtual_budget.ram_max; obs = s.observed_ram_bytes }
175 if kind == NX_RES_VRAM { max = s.virtual_budget.vram_max; obs = s.observed_vram_bytes }
176 if kind == NX_RES_CPU { max = s.virtual_budget.cpu_us_max; obs = s.observed_cpu_us }
177 if kind == NX_RES_DISK { max = s.virtual_budget.disk_max; obs = s.observed_disk_bytes }
178 if max <= 0 { return 0 }
179 return ((obs as i64) * NX_MAGIC_1024) / (max as i64)
180}
181
182// ===== nx_symbiote_throttle ======================================
183//
184// Intent verb: caller wants the symbiote slowed (renice, CPU affinity
185// drop, JobObject CPU rate cap, etc). V1 just transitions state and
186// logs -- actual OS enforcement is the integration layer's job.
187
188func nx_symbiote_throttle(s: *NxSymbiote,
189 j: *NxEvictJournal,
190 now_us: nx_size,
191 displaced_by: nx_int) -> nx_int {
192 if s.state == NX_SYM_TERMINATED { return NX_SYM_ERR_ALREADY_GONE }
193 if s.state == NX_SYM_GONE { return NX_SYM_ERR_ALREADY_GONE }
194 s.state = NX_SYM_THROTTLED
195 if (j as i64) != 0 {
196 nx_evict_log(j, now_us, s.pid,
197 NX_EVR_YIELD_THROTTLED, NX_RES_CPU,
198 s.attention_class, displaced_by)
199 }
200 return NX_SYM_OK
201}
202
203// ===== nx_symbiote_pause =========================================
204//
205// Intent verb: caller wants the symbiote suspended (SIGSTOP / Sus-
206// pendThread). Stronger than throttle; useful when a foreground cell
207// needs immediate VRAM and the symbiote can wait.
208
209func nx_symbiote_pause(s: *NxSymbiote,
210 j: *NxEvictJournal,
211 now_us: nx_size,
212 displaced_by: nx_int) -> nx_int {
213 if s.state == NX_SYM_TERMINATED { return NX_SYM_ERR_ALREADY_GONE }
214 if s.state == NX_SYM_GONE { return NX_SYM_ERR_ALREADY_GONE }
215 s.state = NX_SYM_PAUSED
216 if (j as i64) != 0 {
217 nx_evict_log(j, now_us, s.pid,
218 NX_EVR_YIELD_DEMOTED, NX_RES_CPU,
219 s.attention_class, displaced_by)
220 }
221 return NX_SYM_OK
222}
223
224// ===== nx_symbiote_resume ========================================
225//
226// Wakes a throttled or paused symbiote. Cannot revive a terminated
227// or gone symbiote -- caller must re-spawn the process and create a
228// new nx_symbiote.
229
230func nx_symbiote_resume(s: *NxSymbiote) -> nx_int {
231 if s.state == NX_SYM_TERMINATED { return NX_SYM_ERR_ALREADY_GONE }
232 if s.state == NX_SYM_GONE { return NX_SYM_ERR_ALREADY_GONE }
233 s.state = NX_SYM_RUNNING
234 return NX_SYM_OK
235}
236
237// ===== nx_symbiote_terminate =====================================
238//
239// Intent verb: caller wants the symbiote killed (SIGKILL / Terminate-
240// Process). Logged loudly to the eviction journal -- terminating a
241// foreign process is a heavy action and should never be silent.
242
243func nx_symbiote_terminate(s: *NxSymbiote,
244 j: *NxEvictJournal,
245 now_us: nx_size,
246 displaced_by: nx_int) -> nx_int {
247 if s.state == NX_SYM_TERMINATED { return NX_SYM_ERR_ALREADY_GONE }
248 s.state = NX_SYM_TERMINATED
249 if (j as i64) != 0 {
250 nx_evict_log(j, now_us, s.pid,
251 NX_EVR_TERMINATED, NX_RES_CPU,
252 s.attention_class, displaced_by)
253 }
254 return NX_SYM_OK
255}
256
257// ===== nx_symbiote_is_responsive =================================
258//
259// Returns 1 if the symbiote is in a state that can do work
260// (RUNNING). Throttled/paused/terminated/gone all return 0.
261
262func nx_symbiote_is_responsive(s: *NxSymbiote) -> nx_int {
263 if s.state == NX_SYM_RUNNING { return 1 }
264 return 0
265}
266
267// ===== nx_symbiote_stale_observation =============================
268//
269// Returns 1 if the last observation is older than max_age_us, meaning
270// our resource numbers may not reflect reality. Used by treaty layer
271// to refuse renegotiation on stale data.
272
273func nx_symbiote_stale_observation(s: *NxSymbiote,
274 now_us: nx_size,
275 max_age_us: nx_size) -> nx_int {
276 if s.last_observe_us == 0 { return 1 }
277 let age: nx_size = now_us - s.last_observe_us
278 if age > max_age_us { return 1 }
279 return 0
280}