@guangnao/agent-os 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/dist/abi/capability.d.ts +45 -0
  4. package/dist/abi/driver.d.ts +23 -0
  5. package/dist/abi/ids.d.ts +20 -0
  6. package/dist/abi/index.d.ts +11 -0
  7. package/dist/abi/memory.d.ts +20 -0
  8. package/dist/abi/message.d.ts +18 -0
  9. package/dist/abi/syscall.d.ts +158 -0
  10. package/dist/drivers/gateway.d.ts +8 -0
  11. package/dist/drivers/index.d.ts +8 -0
  12. package/dist/drivers/kv.d.ts +2 -0
  13. package/dist/drivers/llm.d.ts +46 -0
  14. package/dist/drivers/tools.d.ts +7 -0
  15. package/dist/drivers/wasm.d.ts +2 -0
  16. package/dist/drivers/worker.d.ts +3 -0
  17. package/dist/index.d.ts +41 -0
  18. package/dist/index.js +2527 -0
  19. package/dist/kernel/account.d.ts +34 -0
  20. package/dist/kernel/captable.d.ts +38 -0
  21. package/dist/kernel/clock.d.ts +30 -0
  22. package/dist/kernel/journal.d.ts +25 -0
  23. package/dist/kernel/kernel.d.ts +227 -0
  24. package/dist/kernel/log.d.ts +71 -0
  25. package/dist/kernel/memory.d.ts +97 -0
  26. package/dist/kernel/pcb.d.ts +60 -0
  27. package/dist/kernel/store.d.ts +36 -0
  28. package/dist/kernel/transport.d.ts +26 -0
  29. package/dist/net/tcp.d.ts +22 -0
  30. package/dist/userland/agent.d.ts +62 -0
  31. package/dist/userland/distill.d.ts +9 -0
  32. package/dist/userland/evolve.d.ts +29 -0
  33. package/dist/userland/expert.d.ts +28 -0
  34. package/dist/userland/longmem.d.ts +32 -0
  35. package/dist/userland/nameservice.d.ts +6 -0
  36. package/dist/userland/nursery.d.ts +9 -0
  37. package/dist/userland/persona.d.ts +34 -0
  38. package/dist/userland/promptsmith.d.ts +18 -0
  39. package/dist/userland/sandbox.d.ts +14 -0
  40. package/dist/userland/supervisor.d.ts +21 -0
  41. package/package.json +49 -0
package/dist/index.js ADDED
@@ -0,0 +1,2527 @@
1
+ // src/abi/ids.ts
2
+ var _capSeq = 0;
3
+ var _msgSeq = 0;
4
+ var _segSeq = 0;
5
+ function newSegId() {
6
+ return `seg_${(_segSeq++).toString(36)}`;
7
+ }
8
+ function newCapRef() {
9
+ return `cap_${(_capSeq++).toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
10
+ }
11
+ function newMsgId() {
12
+ return `msg_${(_msgSeq++).toString(36)}`;
13
+ }
14
+ var drv = (name) => name;
15
+
16
+ // src/abi/capability.ts
17
+ var hasRight = (cap, r) => !cap.revoked && cap.rights.has(r);
18
+
19
+ // src/abi/index.ts
20
+ var ABI_VERSION = 1;
21
+
22
+ // src/kernel/account.ts
23
+ var QuotaExceeded = class extends Error {
24
+ constructor(resource) {
25
+ super(`quota exceeded: ${resource}`);
26
+ this.resource = resource;
27
+ this.name = "QuotaExceeded";
28
+ }
29
+ };
30
+ var Account = class _Account {
31
+ constructor(quota = {}, now = Date.now, parent = null) {
32
+ this.now = now;
33
+ this.parent = parent;
34
+ this.rem = { tokens: quota.tokens, toolCalls: quota.toolCalls, usd: quota.usd, children: quota.children };
35
+ this.wallMs = quota.wallMs;
36
+ this.mem = quota.mem;
37
+ this.maxMailbox = quota.maxMailbox;
38
+ this.bornAt = now();
39
+ }
40
+ rem;
41
+ // own remaining; undefined = unlimited at this level
42
+ spent = { tokens: 0, toolCalls: 0, usd: 0, children: 0 };
43
+ // subtree usage seen here
44
+ wallMs;
45
+ mem;
46
+ maxMailbox;
47
+ bornAt;
48
+ /** Charge n of a meter against this account AND every ancestor (atomic: all-or-nothing). */
49
+ charge(meter, n) {
50
+ if (!Number.isFinite(n) || n <= 0) return;
51
+ for (let a = this; a; a = a.parent) {
52
+ const left = a.rem[meter];
53
+ if (left !== void 0 && n > left) throw new QuotaExceeded(meter);
54
+ }
55
+ for (let a = this; a; a = a.parent) {
56
+ const left = a.rem[meter];
57
+ if (left !== void 0) a.rem[meter] = left - n;
58
+ a.spent[meter] += n;
59
+ }
60
+ }
61
+ /** Throw if wall-clock lifetime exceeded (per-process, not bubbled). */
62
+ checkWall() {
63
+ if (this.wallMs !== void 0 && this.now() - this.bornAt > this.wallMs) throw new QuotaExceeded("wallMs");
64
+ }
65
+ /** Give resources back up the chain (refunds never fail — no pre-check). */
66
+ refund(meter, n) {
67
+ for (let a = this; a; a = a.parent) {
68
+ const left = a.rem[meter];
69
+ if (left !== void 0) a.rem[meter] = left + n;
70
+ a.spent[meter] -= n;
71
+ }
72
+ }
73
+ /** A child account in the tree. Spawning counts 1 against the chain's `children` budget. */
74
+ child(quota) {
75
+ this.charge("children", 1);
76
+ return new _Account(quota, this.now, this);
77
+ }
78
+ /** Called when the owning process exits: free the one `children` slot it occupies in its parent's
79
+ * subtree. `children` is a CONCURRENT cap (cgroup pids.max) — live count, not a lifetime total —
80
+ * so a supervisor that spawns and reaps in a loop keeps working. (tokens/usd stay cumulative.) */
81
+ release() {
82
+ this.parent?.refund("children", 1);
83
+ }
84
+ remaining() {
85
+ const wallLeft = this.wallMs === void 0 ? void 0 : Math.max(0, this.wallMs - (this.now() - this.bornAt));
86
+ return { ...this.rem, wallMs: wallLeft, mem: this.mem };
87
+ }
88
+ /** Aggregate usage of this node and everything below it (charges bubbled up to here). */
89
+ usage() {
90
+ return { ...this.spent };
91
+ }
92
+ };
93
+
94
+ // src/kernel/captable.ts
95
+ var CapError = class extends Error {
96
+ constructor(msg) {
97
+ super(msg);
98
+ this.name = "CapError";
99
+ }
100
+ };
101
+ var CapTable = class {
102
+ // owner → its live caps: O(owned) revoke/scan, not O(all)
103
+ /** `now` is injected (the kernel clock) so caveat expiry is deterministic/replayable, not wall-time. */
104
+ constructor(now = () => Date.now()) {
105
+ this.now = now;
106
+ }
107
+ caps = /* @__PURE__ */ new Map();
108
+ derived = /* @__PURE__ */ new Map();
109
+ // cap → its derivations (transitive revoke)
110
+ parentOf = /* @__PURE__ */ new Map();
111
+ // derivation → its parent: lets revoke unlink upward (no dead-ref pile-up)
112
+ byOwner = /* @__PURE__ */ new Map();
113
+ own(pid, ref) {
114
+ let s = this.byOwner.get(pid);
115
+ if (!s) {
116
+ s = /* @__PURE__ */ new Set();
117
+ this.byOwner.set(pid, s);
118
+ }
119
+ s.add(ref);
120
+ }
121
+ disown(pid, ref) {
122
+ const s = this.byOwner.get(pid);
123
+ if (s) {
124
+ s.delete(ref);
125
+ if (!s.size) this.byOwner.delete(pid);
126
+ }
127
+ }
128
+ /** A cap is live unless revoked OR a caveat is unsatisfied (e.g. it has expired). */
129
+ satisfied(c) {
130
+ if (c.revoked) return false;
131
+ for (const cav of c.caveats) if (cav.kind === "expires" && this.now() > cav.at) return false;
132
+ return true;
133
+ }
134
+ /** Kernel-internal: create authority. Not reachable from userland. */
135
+ mint(owner, resource, rights, caveats = []) {
136
+ const ref = newCapRef();
137
+ this.caps.set(ref, { ref, owner, resource, rights: new Set(rights), caveats, revoked: false });
138
+ this.own(owner, ref);
139
+ return ref;
140
+ }
141
+ /** Userland: weaken a cap you own to a subset of its rights, for least-privilege delegation. */
142
+ derive(owner, parent, rights) {
143
+ return this.attenuate(owner, parent, rights, []);
144
+ }
145
+ /** Userland: weaken a cap you own — a subset of rights AND/OR added caveats (e.g. an expiry). Both only ever
146
+ * RESTRICT: rights must be a subset, and the child inherits ALL the parent's caveats plus the new ones, so a
147
+ * caveat (a TTL, say) can't be stripped by re-deriving. The result is a fresh cap revoked with the parent. */
148
+ attenuate(owner, parent, rights, caveats) {
149
+ const p = this.lookup(parent);
150
+ if (!p || p.owner !== owner) throw new CapError("attenuate: caller does not own this capability");
151
+ const want = rights === void 0 ? new Set(p.rights) : new Set(rights);
152
+ for (const r of want) if (!p.rights.has(r)) throw new CapError(`attenuate: cannot amplify right "${r}"`);
153
+ const child = this.mint(owner, p.resource, want, [...p.caveats, ...caveats]);
154
+ let set = this.derived.get(parent);
155
+ if (!set) {
156
+ set = /* @__PURE__ */ new Set();
157
+ this.derived.set(parent, set);
158
+ }
159
+ set.add(child);
160
+ this.parentOf.set(child, parent);
161
+ return child;
162
+ }
163
+ /** Hand ownership to another process (delegation rides on IPC, like fd-passing). */
164
+ transfer(ref, to) {
165
+ const c = this.caps.get(ref);
166
+ if (c && !c.revoked) {
167
+ this.disown(c.owner, ref);
168
+ c.owner = to;
169
+ this.own(to, ref);
170
+ }
171
+ }
172
+ lookup(ref) {
173
+ const c = this.caps.get(ref);
174
+ return c && this.satisfied(c) ? c : void 0;
175
+ }
176
+ /** Does this process hold any cap over a resource of the given kind? (gates ambient-looking syscalls like spawn) */
177
+ ownsKind(pid, kind) {
178
+ const s = this.byOwner.get(pid);
179
+ if (s) for (const ref of s) {
180
+ const c = this.caps.get(ref);
181
+ if (c && c.resource.kind === kind && this.satisfied(c)) return true;
182
+ }
183
+ return false;
184
+ }
185
+ /** Look up + assert the holder owns it and it grants `right`. The gate every privileged syscall passes. */
186
+ require(holder, ref, right) {
187
+ const c = this.lookup(ref);
188
+ if (!c) throw new CapError("invalid or revoked capability");
189
+ if (c.owner !== holder) throw new CapError("capability not held by caller");
190
+ if (!c.rights.has(right)) throw new CapError(`capability lacks right "${right}"`);
191
+ return c;
192
+ }
193
+ /** Revoke a cap and, transitively, everything derived from it. Frees it from the table (and the
194
+ * owner index) — the flag stays set too, so any lingering Capability reference still reads revoked. */
195
+ revoke(ref) {
196
+ const c = this.caps.get(ref);
197
+ if (!c) return;
198
+ c.revoked = true;
199
+ this.caps.delete(ref);
200
+ this.disown(c.owner, ref);
201
+ const par = this.parentOf.get(ref);
202
+ if (par !== void 0) {
203
+ this.parentOf.delete(ref);
204
+ this.derived.get(par)?.delete(ref);
205
+ }
206
+ const kids = this.derived.get(ref);
207
+ if (kids) {
208
+ this.derived.delete(ref);
209
+ for (const d of kids) this.revoke(d);
210
+ }
211
+ }
212
+ /** On process exit: drop all authority it held — O(caps it owned), not O(caps ever minted). */
213
+ revokeOwnedBy(pid) {
214
+ const s = this.byOwner.get(pid);
215
+ if (s) for (const ref of [...s]) this.revoke(ref);
216
+ }
217
+ };
218
+
219
+ // src/kernel/pcb.ts
220
+ var Mailbox = class {
221
+ q = [];
222
+ head = 0;
223
+ get len() {
224
+ return this.q.length - this.head;
225
+ }
226
+ push(m) {
227
+ this.q.push(m);
228
+ }
229
+ shift() {
230
+ if (this.head >= this.q.length) return void 0;
231
+ const m = this.q[this.head];
232
+ this.head++;
233
+ if (this.head > 64 && this.head * 2 >= this.q.length) {
234
+ this.q = this.q.slice(this.head);
235
+ this.head = 0;
236
+ }
237
+ return m;
238
+ }
239
+ drain() {
240
+ return this.q.slice(this.head);
241
+ }
242
+ // non-destructive view of pending msgs (snapshot/inspect)
243
+ };
244
+ var ExitSignal = class extends Error {
245
+ constructor(reason) {
246
+ super("exit");
247
+ this.reason = reason;
248
+ this.name = "ExitSignal";
249
+ }
250
+ };
251
+ function makePCB(pid, parent, image, args, account, context) {
252
+ return {
253
+ pid,
254
+ name: image.name,
255
+ parent,
256
+ image,
257
+ args,
258
+ account,
259
+ context,
260
+ status: "new",
261
+ stopped: false,
262
+ age: 0,
263
+ reductions: 0,
264
+ op: 0,
265
+ recovered: false,
266
+ priority: 0,
267
+ deadline: null,
268
+ enq: 0,
269
+ cont: null,
270
+ timer: null,
271
+ deadlineTimer: null,
272
+ exitReason: null,
273
+ mailbox: new Mailbox(),
274
+ mailboxWaiters: [],
275
+ links: /* @__PURE__ */ new Set(),
276
+ monitors: /* @__PURE__ */ new Set(),
277
+ handles: /* @__PURE__ */ new Map(),
278
+ cleanups: []
279
+ };
280
+ }
281
+ var ProcTable = class {
282
+ map = /* @__PURE__ */ new Map();
283
+ seq = 0;
284
+ alloc() {
285
+ return ++this.seq;
286
+ }
287
+ // monotone; never reused (no ABA on stale CapRefs)
288
+ add(pcb) {
289
+ this.map.set(pcb.pid, pcb);
290
+ }
291
+ get(pid) {
292
+ return this.map.get(pid);
293
+ }
294
+ remove(pid) {
295
+ this.map.delete(pid);
296
+ }
297
+ all() {
298
+ return this.map.values();
299
+ }
300
+ get count() {
301
+ return this.map.size;
302
+ }
303
+ };
304
+
305
+ // src/kernel/log.ts
306
+ var EventLog = class {
307
+ constructor(cap = 4096, sink, now = Date.now) {
308
+ this.cap = cap;
309
+ this.sink = sink;
310
+ this.now = now;
311
+ }
312
+ buf = [];
313
+ seq = 0;
314
+ byType = {};
315
+ exitsByReason = {};
316
+ charged = {};
317
+ emit(e) {
318
+ const s = { ...e, seq: this.seq++, ts: this.now() };
319
+ this.buf.push(s);
320
+ if (this.buf.length > this.cap) this.buf.shift();
321
+ this.byType[e.t] = (this.byType[e.t] ?? 0) + 1;
322
+ if (e.t === "exit") this.exitsByReason[e.reason.kind] = (this.exitsByReason[e.reason.kind] ?? 0) + 1;
323
+ if (e.t === "charge") this.charged[e.meter] = (this.charged[e.meter] ?? 0) + e.n;
324
+ this.sink?.(s);
325
+ }
326
+ tail(n = 50) {
327
+ return this.buf.slice(-n);
328
+ }
329
+ stats() {
330
+ return { byType: { ...this.byType }, exitsByReason: { ...this.exitsByReason }, charged: { ...this.charged } };
331
+ }
332
+ get count() {
333
+ return this.seq;
334
+ }
335
+ clear() {
336
+ this.buf.length = 0;
337
+ }
338
+ };
339
+
340
+ // src/kernel/memory.ts
341
+ var CJK = /[\u3000-\u9fff\uac00-\ud7af\uf900-\ufaff\uff00-\uffef]/g;
342
+ var estTokens = (body) => {
343
+ const s = JSON.stringify(body ?? "");
344
+ const cjk = (s.match(CJK) || []).length;
345
+ return Math.max(1, Math.ceil(cjk * 1.5 + (s.length - cjk) / 4));
346
+ };
347
+ var DefaultPager = class {
348
+ constructor(summarize) {
349
+ this.summarize = summarize;
350
+ }
351
+ async pageOut(resident, need) {
352
+ const evict = [];
353
+ const victims = [];
354
+ let freed = 0;
355
+ for (const s of resident) {
356
+ if (s.pinned) continue;
357
+ evict.push(s.id);
358
+ victims.push(s);
359
+ freed += s.tokens;
360
+ if (freed >= need) break;
361
+ }
362
+ if (evict.length === 0) return { evict };
363
+ if (victims.every((s) => s.summary)) return { evict };
364
+ const sum = this.summarize ? await this.summarize(victims) : { body: { kind: "summary", of: victims.length }, tokens: Math.min(48, Math.ceil(freed / 4)) };
365
+ return { evict, summary: { body: sum.body, tokens: sum.tokens, pinned: false, summary: true } };
366
+ }
367
+ };
368
+ var segText = (body) => (typeof body === "string" ? body : JSON.stringify(body ?? "")).toLowerCase();
369
+ var terms = (s) => s.split(/[^a-z0-9一-鿿]+/i).filter(Boolean);
370
+ var KeywordRecaller = class {
371
+ text = /* @__PURE__ */ new Map();
372
+ index(id, body) {
373
+ this.text.set(id, segText(body));
374
+ }
375
+ remove(id) {
376
+ this.text.delete(id);
377
+ }
378
+ search(query, candidates, k) {
379
+ const q = terms(query);
380
+ return [...candidates].map((id) => {
381
+ const t = this.text.get(id) ?? "";
382
+ return { id, n: q.reduce((s, w) => s + (t.includes(w) ? 1 : 0), 0) };
383
+ }).filter((x) => x.n > 0).sort((a, b) => b.n - a.n).slice(0, k).map((x) => x.id);
384
+ }
385
+ };
386
+ var Bm25Recaller = class {
387
+ docs = /* @__PURE__ */ new Map();
388
+ df = /* @__PURE__ */ new Map();
389
+ // document frequency per term (over indexed = swapped segs)
390
+ totalLen = 0;
391
+ k1 = 1.5;
392
+ b = 0.75;
393
+ index(id, body) {
394
+ if (this.docs.has(id)) this.remove(id);
395
+ const ts = terms(segText(body));
396
+ const tf = /* @__PURE__ */ new Map();
397
+ for (const t of ts) tf.set(t, (tf.get(t) ?? 0) + 1);
398
+ this.docs.set(id, { tf, len: ts.length });
399
+ this.totalLen += ts.length;
400
+ for (const t of tf.keys()) this.df.set(t, (this.df.get(t) ?? 0) + 1);
401
+ }
402
+ remove(id) {
403
+ const d = this.docs.get(id);
404
+ if (!d) return;
405
+ this.docs.delete(id);
406
+ this.totalLen -= d.len;
407
+ for (const t of d.tf.keys()) {
408
+ const n = (this.df.get(t) ?? 1) - 1;
409
+ if (n <= 0) this.df.delete(t);
410
+ else this.df.set(t, n);
411
+ }
412
+ }
413
+ search(query, candidates, k) {
414
+ const N = this.docs.size || 1;
415
+ const avg = this.totalLen / N || 1;
416
+ const q = [...new Set(terms(query))];
417
+ const scored = [];
418
+ for (const id of candidates) {
419
+ const d = this.docs.get(id);
420
+ if (!d) continue;
421
+ let score = 0;
422
+ for (const t of q) {
423
+ const tf = d.tf.get(t);
424
+ if (!tf) continue;
425
+ const df = this.df.get(t) ?? 1;
426
+ const idf = Math.log(1 + (N - df + 0.5) / (df + 0.5));
427
+ score += idf * (tf * (this.k1 + 1)) / (tf + this.k1 * (1 - this.b + this.b * d.len / avg));
428
+ }
429
+ if (score > 0) scored.push({ id, score });
430
+ }
431
+ return scored.sort((a, b) => b.score - a.score).slice(0, k).map((x) => x.id);
432
+ }
433
+ };
434
+ var VectorRecaller = class {
435
+ constructor(embed) {
436
+ this.embed = embed;
437
+ }
438
+ vecs = /* @__PURE__ */ new Map();
439
+ index(id, body) {
440
+ this.vecs.set(id, this.embed(segText(body)));
441
+ }
442
+ remove(id) {
443
+ this.vecs.delete(id);
444
+ }
445
+ search(query, candidates, k) {
446
+ const q = this.embed(query);
447
+ return [...candidates].map((id) => ({ id, s: cosine(q, this.vecs.get(id) ?? []) })).filter((x) => x.s > 0).sort((a, b) => b.s - a.s).slice(0, k).map((x) => x.id);
448
+ }
449
+ };
450
+ function hashEmbed(dim = 64) {
451
+ const h = (w) => {
452
+ let x = 2166136261;
453
+ for (let i = 0; i < w.length; i++) {
454
+ x ^= w.charCodeAt(i);
455
+ x = Math.imul(x, 16777619);
456
+ }
457
+ return (x >>> 0) % dim;
458
+ };
459
+ return (text) => {
460
+ const v = new Array(dim).fill(0);
461
+ for (const w of terms(text)) v[h(w)] += 1;
462
+ const norm2 = Math.sqrt(v.reduce((s, x) => s + x * x, 0)) || 1;
463
+ return v.map((x) => x / norm2);
464
+ };
465
+ }
466
+ function cosine(a, b) {
467
+ if (a.length !== b.length) return 0;
468
+ let dot = 0;
469
+ for (let i = 0; i < a.length; i++) dot += a[i] * b[i];
470
+ return dot;
471
+ }
472
+ var Context = class _Context {
473
+ constructor(ceiling, pager, recaller = new KeywordRecaller()) {
474
+ this.ceiling = ceiling;
475
+ this.pager = pager;
476
+ this.recaller = recaller;
477
+ }
478
+ resident = /* @__PURE__ */ new Map();
479
+ // insertion ≈ recency
480
+ swap = /* @__PURE__ */ new Map();
481
+ // paged-out ("disk")
482
+ size = 0;
483
+ get watermark() {
484
+ return this.ceiling === void 0 ? void 0 : Math.floor(this.ceiling * 0.8);
485
+ }
486
+ async remember(body, tokens, pinned) {
487
+ const id = newSegId();
488
+ this.resident.set(id, { id, body, tokens, pinned });
489
+ this.size += tokens;
490
+ await this.relieve();
491
+ return id;
492
+ }
493
+ workingSet() {
494
+ return [...this.resident.values()];
495
+ }
496
+ async recall(query, k) {
497
+ const ids = this.recaller.search(query, new Set(this.swap.keys()), k);
498
+ const out = [];
499
+ for (const id of ids) {
500
+ const s = this.swap.get(id);
501
+ if (!s) continue;
502
+ this.swap.delete(id);
503
+ this.recaller.remove(id);
504
+ this.resident.set(id, s);
505
+ this.size += s.tokens;
506
+ out.push(s);
507
+ }
508
+ await this.relieve();
509
+ return out;
510
+ }
511
+ pin(id, on) {
512
+ const s = this.resident.get(id);
513
+ if (s) s.pinned = on;
514
+ }
515
+ forget(id) {
516
+ const r = this.resident.get(id);
517
+ if (r) {
518
+ this.resident.delete(id);
519
+ this.size -= r.tokens;
520
+ } else {
521
+ this.swap.delete(id);
522
+ this.recaller.remove(id);
523
+ }
524
+ }
525
+ stats() {
526
+ return { resident: this.resident.size, swapped: this.swap.size, size: this.size, ceiling: this.ceiling };
527
+ }
528
+ /** Serialize the durable state of the context (for snapshot/migration). */
529
+ dump() {
530
+ return [...this.resident.values(), ...this.swap.values()].map((s) => ({ body: s.body, tokens: s.tokens, pinned: s.pinned }));
531
+ }
532
+ /** Rebuild a context from a dump, then page down under the watermark if over ceiling (e.g.
533
+ * restored on a kernel with a smaller memory cap). Throws QuotaExceeded if the pinned working
534
+ * set alone surpasses the ceiling. */
535
+ static async from(ceiling, pager, segs, recaller) {
536
+ const c = new _Context(ceiling, pager, recaller);
537
+ for (const s of segs) {
538
+ const id = newSegId();
539
+ c.resident.set(id, { id, body: s.body, tokens: s.tokens, pinned: s.pinned });
540
+ c.size += s.tokens;
541
+ }
542
+ await c.relieve();
543
+ return c;
544
+ }
545
+ /** Resident segments coldest-first. The Map's insertion order already tracks recency — remember
546
+ * appends, recall re-inserts (swap→resident) at the end, and no resident key is re-set in place —
547
+ * so iteration is LRU-ascending with no sort. (Keep that invariant if you touch resident.set.) */
548
+ lru() {
549
+ return [...this.resident.values()];
550
+ }
551
+ /** Page out until under the watermark (or only pinned remain); OOM if the pinned set busts the ceiling. */
552
+ async relieve() {
553
+ const wm = this.watermark;
554
+ if (wm === void 0) return;
555
+ while (this.size > wm) {
556
+ const before = this.size;
557
+ const need = this.size - Math.floor(this.ceiling * 0.6);
558
+ const { evict, summary } = await this.pager.pageOut(this.lru(), need);
559
+ if (evict.length === 0) break;
560
+ for (const id of evict) {
561
+ const s = this.resident.get(id);
562
+ if (s) {
563
+ this.resident.delete(id);
564
+ this.size -= s.tokens;
565
+ this.swap.set(id, s);
566
+ this.recaller.index(id, s.body);
567
+ }
568
+ }
569
+ if (summary) {
570
+ const id = newSegId();
571
+ this.resident.set(id, { id, ...summary });
572
+ this.size += summary.tokens;
573
+ }
574
+ if (this.size >= before) break;
575
+ }
576
+ if (this.size > this.ceiling) throw new QuotaExceeded("mem");
577
+ }
578
+ };
579
+
580
+ // src/kernel/journal.ts
581
+ var Journal = class _Journal {
582
+ io = /* @__PURE__ */ new Map();
583
+ seq = [];
584
+ static key(pid, op) {
585
+ return `${pid}:${op}`;
586
+ }
587
+ record(pid, op, result) {
588
+ this.io.set(_Journal.key(pid, op), result);
589
+ this.seq.push({ pid, op, result });
590
+ }
591
+ get(pid, op) {
592
+ return this.io.get(_Journal.key(pid, op));
593
+ }
594
+ get size() {
595
+ return this.seq.length;
596
+ }
597
+ /** Serialize a recorded run so it can be persisted and replayed later. */
598
+ toJSON() {
599
+ return this.seq;
600
+ }
601
+ static fromJSON(seq) {
602
+ const j = new _Journal();
603
+ for (const e of seq) j.record(e.pid, e.op, e.result);
604
+ return j;
605
+ }
606
+ };
607
+
608
+ // src/kernel/clock.ts
609
+ var RealClock = class {
610
+ virtual = false;
611
+ handles = /* @__PURE__ */ new WeakMap();
612
+ now() {
613
+ return Date.now();
614
+ }
615
+ after(ms, cb) {
616
+ const t = { fire: Date.now() + ms, cb };
617
+ this.handles.set(t, setTimeout(() => {
618
+ if (!t.cancelled) cb();
619
+ }, ms));
620
+ return t;
621
+ }
622
+ cancel(t) {
623
+ t.cancelled = true;
624
+ const h = this.handles.get(t);
625
+ if (h) clearTimeout(h);
626
+ }
627
+ advance() {
628
+ return false;
629
+ }
630
+ // wall time advances itself
631
+ };
632
+ var VirtualClock = class {
633
+ virtual = true;
634
+ t = 0;
635
+ pending = [];
636
+ // kept sorted by fire time (earliest first)
637
+ now() {
638
+ return this.t;
639
+ }
640
+ after(ms, cb) {
641
+ const t = { fire: this.t + Math.max(0, ms), cb };
642
+ this.pending.push(t);
643
+ this.pending.sort((a, b) => a.fire - b.fire);
644
+ return t;
645
+ }
646
+ cancel(t) {
647
+ t.cancelled = true;
648
+ this.pending = this.pending.filter((x) => x !== t);
649
+ }
650
+ advance() {
651
+ let next = this.pending.shift();
652
+ while (next && next.cancelled) next = this.pending.shift();
653
+ if (!next) return false;
654
+ this.t = Math.max(this.t, next.fire);
655
+ next.cb();
656
+ return true;
657
+ }
658
+ };
659
+
660
+ // src/kernel/store.ts
661
+ import { appendFileSync, readFileSync, existsSync, writeFileSync } from "node:fs";
662
+ var MemoryStore = class {
663
+ recs = [];
664
+ append(rec) {
665
+ this.recs.push(rec);
666
+ }
667
+ load() {
668
+ return this.recs;
669
+ }
670
+ };
671
+ var FileStore = class {
672
+ constructor(path) {
673
+ this.path = path;
674
+ }
675
+ append(rec) {
676
+ appendFileSync(this.path, JSON.stringify(rec) + "\n");
677
+ }
678
+ load() {
679
+ if (!existsSync(this.path)) return [];
680
+ return readFileSync(this.path, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
681
+ }
682
+ reset() {
683
+ writeFileSync(this.path, "");
684
+ }
685
+ };
686
+ function journalFromStore(records) {
687
+ const j = new Journal();
688
+ for (const r of records) if (r.kind === "io") j.record(r.data.pid, r.data.op, r.data.result);
689
+ return j;
690
+ }
691
+ function eventsFromStore(records) {
692
+ return records.filter((r) => r.kind === "event").map((r) => r.data);
693
+ }
694
+
695
+ // src/kernel/transport.ts
696
+ import { createHmac, timingSafeEqual } from "node:crypto";
697
+ function canon(v) {
698
+ const out = [];
699
+ canonInto(v, out);
700
+ return out.join("");
701
+ }
702
+ function canonInto(v, out) {
703
+ if (v === null || typeof v !== "object") {
704
+ out.push(JSON.stringify(v) ?? "null");
705
+ return;
706
+ }
707
+ if (Array.isArray(v)) {
708
+ out.push("[");
709
+ for (let i = 0; i < v.length; i++) {
710
+ if (i) out.push(",");
711
+ canonInto(v[i], out);
712
+ }
713
+ out.push("]");
714
+ return;
715
+ }
716
+ const o = v;
717
+ const keys = Object.keys(o).sort();
718
+ out.push("{");
719
+ for (let i = 0; i < keys.length; i++) {
720
+ if (i) out.push(",");
721
+ out.push(JSON.stringify(keys[i]), ":");
722
+ canonInto(o[keys[i]], out);
723
+ }
724
+ out.push("}");
725
+ }
726
+ function mac(key, to, nonce, ts, body) {
727
+ return createHmac("sha256", key).update(`${to}\0${nonce}\0${ts}\0${canon(body)}`).digest("hex");
728
+ }
729
+ var _nonceSeq = 0;
730
+ function freshNonce(rand) {
731
+ return `${(_nonceSeq++).toString(36)}.${rand()}`;
732
+ }
733
+ function seal(key, to, body, nonce, ts = Date.now()) {
734
+ return { to, nonce, ts, body, mac: mac(key, to, nonce, ts, body) };
735
+ }
736
+ function verify(key, env) {
737
+ try {
738
+ const want = mac(key, env.to, env.nonce, env.ts, env.body);
739
+ if (typeof env.mac !== "string" || want.length !== env.mac.length) return false;
740
+ return timingSafeEqual(Buffer.from(want), Buffer.from(env.mac));
741
+ } catch {
742
+ return false;
743
+ }
744
+ }
745
+ var NonceGuard = class {
746
+ // nonce → first-seen time (ms)
747
+ constructor(windowMs = 3e5, cap = 1 << 20) {
748
+ this.windowMs = windowMs;
749
+ this.cap = cap;
750
+ }
751
+ seen = /* @__PURE__ */ new Map();
752
+ /** True the first time a nonce is seen within the window; false on replay. `now` is the kernel clock. */
753
+ admit(nonce, now) {
754
+ for (const [n, t] of this.seen) {
755
+ if (now - t > this.windowMs) this.seen.delete(n);
756
+ else break;
757
+ }
758
+ if (this.seen.has(nonce)) return false;
759
+ this.seen.set(nonce, now);
760
+ if (this.seen.size > this.cap) this.seen.delete(this.seen.keys().next().value);
761
+ return true;
762
+ }
763
+ };
764
+
765
+ // src/kernel/kernel.ts
766
+ var QUANTUM = 2e3;
767
+ var AGE_EVERY = 4;
768
+ var AGE_MAX = 8;
769
+ var ageBoost = (age) => Math.min(AGE_MAX, Math.floor(age / AGE_EVERY));
770
+ var KernelError = class extends Error {
771
+ constructor(msg) {
772
+ super(msg);
773
+ this.name = "KernelError";
774
+ }
775
+ };
776
+ var Kernel = class _Kernel {
777
+ proc = new ProcTable();
778
+ caps = new CapTable(() => this.clock.now());
779
+ // caveat expiry uses the kernel clock (deterministic/replayable)
780
+ log;
781
+ drivers = /* @__PURE__ */ new Map();
782
+ onLog;
783
+ pager;
784
+ mode;
785
+ journal;
786
+ clock;
787
+ mkRecaller;
788
+ store;
789
+ transportKey;
790
+ nodeId;
791
+ peers = /* @__PURE__ */ new Map();
792
+ // node id → egress (a dialTcp.send, or any envelope sink)
793
+ egressSeq = 0;
794
+ // node-scoped nonce counter for outbound envelopes
795
+ images = /* @__PURE__ */ new Map();
796
+ // remotely-spawnable images, by name
797
+ pendingSpawns = /* @__PURE__ */ new Map();
798
+ // corr id → spawnRemote resolver
799
+ remoteMonitors = /* @__PURE__ */ new Map();
800
+ // local pid → peers watching it; sent a Down on death
801
+ freshWindowMs = 3e5;
802
+ // ±5 min: cross-node clock-skew tolerance for replay freshness
803
+ nonces = new NonceGuard(this.freshWindowMs);
804
+ /* scheduler state */
805
+ runQueue = [];
806
+ // the ready set; dispatch picks the best (priority → EDF → FIFO)
807
+ qHead = 0;
808
+ // consumed-prefix index → O(1) dequeue on the common FIFO path
809
+ special = 0;
810
+ // # enqueued procs with priority≠0 or a deadline; 0 ⇒ pure FIFO
811
+ enqSeq = 0;
812
+ current = null;
813
+ shutdownFlag = false;
814
+ exitWaiters = /* @__PURE__ */ new Map();
815
+ // kernel-level waitExit (test/orchestration); resolves raw promises
816
+ exitHooks = /* @__PURE__ */ new Map();
817
+ // in-process waitpid wakers (fire inside terminate, then re-dispatch the waiter)
818
+ reaped = /* @__PURE__ */ new Map();
819
+ // recently-dead → reason, so waitpid AFTER death still returns it (bounded)
820
+ registry = /* @__PURE__ */ new Map();
821
+ // service name → owning pid (name service); entries cleared when the owner dies
822
+ initPid = null;
823
+ // pid 1 — orphans reparent here when their parent dies
824
+ constructor(cfg = {}) {
825
+ this.clock = cfg.clock ?? new RealClock();
826
+ this.store = cfg.store;
827
+ this.mode = cfg.mode ?? "live";
828
+ const sink = (e) => {
829
+ cfg.logSink?.(e);
830
+ if (this.mode !== "recover") this.store?.append({ kind: "event", data: e });
831
+ };
832
+ this.log = new EventLog(cfg.logCap, sink, () => this.clock.now());
833
+ this.onLog = cfg.onLog;
834
+ this.pager = cfg.pager ?? new DefaultPager();
835
+ this.journal = cfg.journal ?? new Journal();
836
+ this.mkRecaller = cfg.recaller ?? (() => new Bm25Recaller());
837
+ this.transportKey = cfg.transportKey;
838
+ this.nodeId = cfg.nodeId ?? "";
839
+ }
840
+ /** The I/O journal (record mode populates it; persist via journal.toJSON()). */
841
+ get io() {
842
+ return this.journal;
843
+ }
844
+ /** Run a driver op through record/replay: replay returns the journaled result; record logs the live one. */
845
+ async journaled(pcb, live) {
846
+ const op = pcb.op++;
847
+ if (this.mode === "replay" || this.mode === "recover" && !pcb.recovered) {
848
+ const r = this.journal.get(pcb.pid, op);
849
+ if (r) {
850
+ if ("error" in r) throw new KernelError(r.error);
851
+ return r;
852
+ }
853
+ if (this.mode === "replay") throw new KernelError(`replay: no journal entry for ${pcb.pid}:${op}`);
854
+ pcb.recovered = true;
855
+ }
856
+ let res, caught, threw = false;
857
+ try {
858
+ res = await live();
859
+ } catch (e) {
860
+ caught = e;
861
+ threw = true;
862
+ }
863
+ if (this.mode === "record" || this.mode === "recover") {
864
+ const entry = threw ? { error: caught instanceof Error ? caught.message : String(caught) } : res;
865
+ this.journal.record(pcb.pid, op, entry);
866
+ this.store?.append({ kind: "io", data: { pid: pcb.pid, op, result: entry } });
867
+ }
868
+ if (threw) throw caught;
869
+ return res;
870
+ }
871
+ /* ════════════════════════════════════════════════════════════
872
+ * Public: boot / drivers / introspection
873
+ * ════════════════════════════════════════════════════════════ */
874
+ /** Register a driver (LLM, tools, KV, gateway, WASM, worker) so the kernel
875
+ * can hand its device capabilities to processes at boot/grant time. */
876
+ registerDriver(driver) {
877
+ this.drivers.set(driver.name, driver);
878
+ }
879
+ /** Crash recovery: build a kernel that REPLAYS persisted driver I/O from a store. Re-running the
880
+ * same images reproduces the original run exactly — every read/write returns its journaled result,
881
+ * so no live driver is touched. The event-sourcing log IS the recovery mechanism. */
882
+ static replayFrom(store, cfg = {}) {
883
+ return new _Kernel({ ...cfg, mode: "replay", journal: journalFromStore(store.load()) });
884
+ }
885
+ /** Partial crash recovery: REPLAY the persisted prefix (no live I/O), then — when a process runs past the
886
+ * point the journal reaches (the original run crashed mid-flight) — RESUME it live, appending the new ops to
887
+ * the same store. Unlike replayFrom (strict reproduce; aborts on a missing op), this lets a long-running run
888
+ * pick up where it died. Keeps the store so the resumed tail is durable and a second crash is recoverable too. */
889
+ static recoverFrom(store, cfg = {}) {
890
+ return new _Kernel({ ...cfg, mode: "recover", journal: journalFromStore(store.load()), store });
891
+ }
892
+ /** Boot the system: create pid 1 (init) with the given image, optional root
893
+ * quota, and a set of driver names it may open. Init receives a `BootGrant`
894
+ * as its first message, carrying a `spawn` cap and `devices` record. */
895
+ async boot(init, opts = {}) {
896
+ this.log.emit({ t: "boot" });
897
+ const pid = this.proc.alloc();
898
+ this.initPid = pid;
899
+ const acct = new Account(opts.quota, () => this.clock.now());
900
+ const pcb = makePCB(pid, null, init, opts.args, acct, new Context(acct.mem, this.pager, this.mkRecaller()));
901
+ this.proc.add(pcb);
902
+ const spawn = this.caps.mint(pid, { kind: "spawn" }, ["spawn"]);
903
+ const devices = {};
904
+ for (const name of opts.grant ?? []) {
905
+ if (!this.drivers.has(name)) throw new KernelError(`boot: no such driver "${name}"`);
906
+ devices[name] = this.caps.mint(pid, { kind: "device", driver: drv(name) }, ["open"]);
907
+ }
908
+ pcb.mailbox.push({ id: newMsgId(), from: pid, to: pid, body: { $: "boot", spawn, devices }, caps: [] });
909
+ this.log.emit({ t: "spawn", pid, parent: null, name: pcb.name });
910
+ this.makeRunnable(pcb);
911
+ return pid;
912
+ }
913
+ /** Resolve when a process terminates (test/orchestration helper). */
914
+ waitExit(pid) {
915
+ const dead = this.proc.get(pid);
916
+ if (!dead) return Promise.resolve({ kind: "normal" });
917
+ return new Promise((res) => {
918
+ const arr = this.exitWaiters.get(pid) ?? [];
919
+ arr.push(res);
920
+ this.exitWaiters.set(pid, arr);
921
+ });
922
+ }
923
+ /** Query a snapshot of live processes: pid, name, status, mailbox depth,
924
+ * remaining budget, usage, and memory stats — the userland `ps` analogue. */
925
+ ps() {
926
+ return [...this.proc.all()].map((p) => ({
927
+ pid: p.pid,
928
+ name: p.name,
929
+ parent: p.parent,
930
+ status: p.status,
931
+ stopped: p.stopped,
932
+ mailbox: p.mailbox.len,
933
+ budget: p.account.remaining(),
934
+ used: p.account.usage(),
935
+ mem: p.context.stats()
936
+ }));
937
+ }
938
+ /** Tail the last N stamped events from the kernel event log (audit trail). */
939
+ trace(n = 50) {
940
+ return this.log.tail(n);
941
+ }
942
+ /** Monitoring snapshot: live process count + running aggregates over the whole event history. */
943
+ metrics() {
944
+ const s = this.log.stats();
945
+ return {
946
+ processes: this.proc.count,
947
+ events: this.log.count,
948
+ spawns: s.byType["spawn"] ?? 0,
949
+ exits: s.byType["exit"] ?? 0,
950
+ sends: s.byType["send"] ?? 0,
951
+ scheds: s.byType["sched"] ?? 0,
952
+ faults: s.byType["fault"] ?? 0,
953
+ exitsByReason: s.exitsByReason,
954
+ charged: s.charged
955
+ };
956
+ }
957
+ /** Prometheus text-exposition of the same counters — scrape it straight into a TSDB. */
958
+ metricsText() {
959
+ const m = this.metrics();
960
+ const out = [];
961
+ const line = (name, v, labels = "") => out.push(`agentos_${name}${labels} ${v}`);
962
+ line("processes", m.processes);
963
+ line("events_total", m.events);
964
+ line("spawns_total", m.spawns);
965
+ line("exits_total", m.exits);
966
+ line("sends_total", m.sends);
967
+ line("scheds_total", m.scheds);
968
+ line("faults_total", m.faults);
969
+ for (const [k, v] of Object.entries(m.exitsByReason)) line("exits_total", v, `{reason="${k}"}`);
970
+ for (const [k, v] of Object.entries(m.charged)) line("charged_total", v, `{meter="${k}"}`);
971
+ return out.join("\n") + "\n";
972
+ }
973
+ /** Inspect the resident context (working set) of a live process — the
974
+ * segments currently visible to its LLM. For observability / debugging. */
975
+ inspect(pid) {
976
+ return this.req(pid).context.workingSet();
977
+ }
978
+ /** Transport ingress: inject a message into a local process from OUTSIDE the kernel
979
+ * (a gateway/transport bridging another node). Returns false if no such live process. */
980
+ deliverExternal(pid, body) {
981
+ const p = this.proc.get(pid);
982
+ if (!p || p.status === "dead") return false;
983
+ const lim = p.account.maxMailbox;
984
+ if (lim !== void 0 && p.mailbox.len >= lim) {
985
+ this.log.emit({ t: "fault", pid, error: "ingress: mailbox full \u2014 dropped" });
986
+ return false;
987
+ }
988
+ this.deliver(p, { id: newMsgId(), from: 0, to: pid, body, caps: [] });
989
+ return true;
990
+ }
991
+ /** Register a route to a peer node: `send` will egress envelopes addressed to it (a dialTcp.send, or any
992
+ * sink for tests). The matching transportKey must be shared with the peer, whose ingress() verifies them. */
993
+ connect(node, egress) {
994
+ this.peers.set(node, egress);
995
+ }
996
+ /** Publish an image that PEER nodes may spawn by name (a ProcessImage holds closures → it can't cross the
997
+ * wire; the host already has it, so remote spawn is "spawn the image you registered as N"). `grant` lists
998
+ * the driver devices the spawned process may open. */
999
+ registerImage(name, image, grant) {
1000
+ this.images.set(name, { image, grant });
1001
+ }
1002
+ /** Egress a Down to a peer node's watcher pid (a normal data envelope; ingress delivers it to the mailbox). */
1003
+ sendDown(node, watcher, deadPid, reason) {
1004
+ const e = this.peers.get(node);
1005
+ if (e && this.transportKey) e(seal(this.transportKey, watcher, { $: "down", pid: deadPid, node: this.nodeId, reason }, `${this.nodeId}.${this.egressSeq++}`, this.clock.now()));
1006
+ }
1007
+ /** Control-channel envelope (addressed to pid 0): a remote-spawn request/reply, or a remote-monitor request. */
1008
+ handleControl(body) {
1009
+ const m = body;
1010
+ if (m.$ === "mon-req") {
1011
+ const tp = this.proc.get(m.target);
1012
+ if (!tp || tp.status === "dead") this.sendDown(m.from, m.watcher, m.target, this.reaped.get(m.target) ?? tp?.exitReason ?? { kind: "normal" });
1013
+ else {
1014
+ const arr = this.remoteMonitors.get(m.target) ?? [];
1015
+ arr.push({ node: m.from, watcher: m.watcher });
1016
+ this.remoteMonitors.set(m.target, arr);
1017
+ }
1018
+ return true;
1019
+ }
1020
+ if (m.$ === "spawn-req") {
1021
+ const reply = (r) => {
1022
+ const e = this.peers.get(m.from);
1023
+ if (e && this.transportKey) e(seal(this.transportKey, 0, { $: "spawn-rep", corr: m.corr, ...r }, `${this.nodeId}.${this.egressSeq++}`, this.clock.now()));
1024
+ };
1025
+ const entry = this.images.get(m.name);
1026
+ if (!entry) {
1027
+ reply({ error: `no image "${m.name}"` });
1028
+ return false;
1029
+ }
1030
+ reply({ pid: this.hostRemote(entry, m.args, m.from, m.reply) });
1031
+ return true;
1032
+ }
1033
+ if (m.$ === "spawn-rep") {
1034
+ this.pendingSpawns.get(m.corr)?.({ pid: m.pid, error: m.error });
1035
+ return true;
1036
+ }
1037
+ return false;
1038
+ }
1039
+ /** Spawn a peer-requested image locally, granting it a spawn cap, its allowed devices, and a `reply` proxy
1040
+ * cap back to the requester (so the two can talk). Orphans reparent to init like any process. */
1041
+ hostRemote(entry, args, fromNode, replyPid) {
1042
+ const pid = this.proc.alloc();
1043
+ const acct = new Account({}, () => this.clock.now());
1044
+ const pcb = makePCB(pid, this.initPid, entry.image, args, acct, new Context(acct.mem, this.pager, this.mkRecaller()));
1045
+ this.proc.add(pcb);
1046
+ const spawn = this.caps.mint(pid, { kind: "spawn" }, ["spawn"]);
1047
+ const devices = {};
1048
+ for (const n of entry.grant ?? []) if (this.drivers.has(n)) devices[n] = this.caps.mint(pid, { kind: "device", driver: drv(n) }, ["open"]);
1049
+ const reply = this.caps.mint(pid, { kind: "remote", node: fromNode, pid: replyPid }, ["send"]);
1050
+ pcb.mailbox.push({ id: newMsgId(), from: pid, to: pid, body: { $: "boot", spawn, devices, reply }, caps: [] });
1051
+ this.log.emit({ t: "spawn", pid, parent: this.initPid, name: pcb.name });
1052
+ this.makeRunnable(pcb);
1053
+ return pid;
1054
+ }
1055
+ /** AUTHENTICATED transport ingress: the trust boundary for messages off the wire.
1056
+ * Verifies the HMAC (constant-time) and rejects replays before anything reaches a
1057
+ * process; a bad MAC, a reused nonce, or a dead target are all dropped (→ false).
1058
+ * Capabilities never cross — a verified sender can address a pid, not forge authority. */
1059
+ ingress(env) {
1060
+ if (!this.transportKey) throw new KernelError("ingress: no transportKey configured");
1061
+ if (!verify(this.transportKey, env)) {
1062
+ this.log.emit({ t: "fault", pid: 0, error: "transport: bad MAC (forged/tampered) \u2014 dropped" });
1063
+ return false;
1064
+ }
1065
+ const now = this.clock.now();
1066
+ if (typeof env.ts !== "number" || !Number.isFinite(env.ts) || Math.abs(now - env.ts) > this.freshWindowMs) {
1067
+ this.log.emit({ t: "fault", pid: 0, error: "transport: stale/future envelope \u2014 dropped" });
1068
+ return false;
1069
+ }
1070
+ if (!this.nonces.admit(env.nonce, now)) {
1071
+ this.log.emit({ t: "fault", pid: 0, error: "transport: replayed nonce \u2014 dropped" });
1072
+ return false;
1073
+ }
1074
+ if (env.to === 0) return this.handleControl(env.body);
1075
+ return this.deliverExternal(env.to, env.body);
1076
+ }
1077
+ /**
1078
+ * Snapshot a process's DURABLE state (context + mailbox + budget + sched).
1079
+ * The JS execution stack is not serializable, so restore resumes by re-running
1080
+ * the image against the restored state — sound for context-driven agents whose
1081
+ * state IS their context. Capabilities are kernel-local authority and are NOT
1082
+ * captured; the restorer re-grants them.
1083
+ */
1084
+ snapshot(pid) {
1085
+ const p = this.req(pid);
1086
+ return {
1087
+ abi: ABI_VERSION,
1088
+ image: p.name,
1089
+ args: p.args,
1090
+ priority: p.priority,
1091
+ deadline: p.deadline === null ? null : Math.max(0, p.deadline - this.clock.now()),
1092
+ // remaining ms
1093
+ stopped: p.stopped,
1094
+ account: p.account.remaining(),
1095
+ mailbox: p.mailbox.drain().map((m) => ({ ...m, caps: [] })),
1096
+ // drop kernel-local caps
1097
+ context: p.context.dump()
1098
+ };
1099
+ }
1100
+ /** Restore a snapshot into THIS kernel as a fresh process running `image`. */
1101
+ async restore(snap, image, opts = {}) {
1102
+ if (snap.abi !== ABI_VERSION) throw new KernelError(`restore: ABI mismatch (snapshot v${snap.abi}, kernel v${ABI_VERSION})`);
1103
+ const pid = this.proc.alloc();
1104
+ const acct = new Account(snap.account, () => this.clock.now());
1105
+ const ctx = await Context.from(acct.mem, this.pager, snap.context, this.mkRecaller());
1106
+ const pcb = makePCB(pid, opts.parent ?? null, image, snap.args, acct, ctx);
1107
+ pcb.priority = snap.priority;
1108
+ pcb.stopped = snap.stopped ?? false;
1109
+ for (const m of snap.mailbox) pcb.mailbox.push({ ...m, to: pid });
1110
+ this.proc.add(pcb);
1111
+ if (snap.deadline !== null) this.armDeadline(pcb, snap.deadline);
1112
+ this.log.emit({ t: "spawn", pid, parent: pcb.parent, name: pcb.name });
1113
+ this.makeRunnable(pcb);
1114
+ return pid;
1115
+ }
1116
+ /** Graceful system shutdown: kill every live process with the given reason
1117
+ * (default 'shutdown'), flush the event log, and stop scheduling. */
1118
+ async shutdown(reason = { kind: "shutdown" }) {
1119
+ this.shutdownFlag = true;
1120
+ for (const p of [...this.proc.all()]) this.terminate(p.pid, reason);
1121
+ this.log.emit({ t: "shutdown", reason: reason.kind });
1122
+ }
1123
+ /* ════════════════════════════════════════════════════════════
1124
+ * Scheduler (reductions + run queue)
1125
+ * ════════════════════════════════════════════════════════════ */
1126
+ enqueue(pcb) {
1127
+ pcb.enq = ++this.enqSeq;
1128
+ if (pcb.priority !== 0 || pcb.deadline !== null) this.special++;
1129
+ this.runQueue.push(pcb.pid);
1130
+ }
1131
+ makeRunnable(pcb) {
1132
+ if (pcb.status === "dead" || pcb.status === "running" || pcb.stopped) return;
1133
+ pcb.status = "runnable";
1134
+ this.enqueue(pcb);
1135
+ this.dispatch();
1136
+ }
1137
+ /** SIGSTOP — pause a process: it stays alive but is never dispatched until resumed. Descheduled now if
1138
+ * running; dropped from the ready queue if runnable (re-enqueued on resume); a parked recv just stays parked. */
1139
+ suspendProc(pid) {
1140
+ const pcb = this.proc.get(pid);
1141
+ if (!pcb || pcb.status === "dead" || pcb.stopped) return;
1142
+ pcb.stopped = true;
1143
+ if (this.current === pid) this.current = null;
1144
+ this.log.emit({ t: "signal", pid, sig: "stop" });
1145
+ this.dispatch();
1146
+ }
1147
+ /** SIGCONT — resume a stopped process. Safe even if it has nothing to do: a parked recv re-parks on an
1148
+ * empty mailbox, so a spurious wake is a no-op. */
1149
+ resumeProc(pid) {
1150
+ const pcb = this.proc.get(pid);
1151
+ if (!pcb || !pcb.stopped) return;
1152
+ pcb.stopped = false;
1153
+ this.log.emit({ t: "signal", pid, sig: "cont" });
1154
+ this.makeRunnable(pcb);
1155
+ }
1156
+ /** SIGTERM — a graceful, CATCHABLE stop request: deliver a `{ $: 'term' }` message the process can
1157
+ * handle in its recv loop (clean up, then exit). Unlike kill/signal it never force-terminates; the
1158
+ * handler IS the recv loop. (A stopped target gets it queued; it's seen on resume.) */
1159
+ termProc(pid, from) {
1160
+ const pcb = this.proc.get(pid);
1161
+ if (!pcb || pcb.status === "dead") return;
1162
+ this.log.emit({ t: "signal", pid, sig: "term" });
1163
+ this.deliver(pcb, { id: newMsgId(), from, to: pid, body: { $: "term", from }, caps: [] });
1164
+ }
1165
+ /** Pick the highest-priority runnable; ties broken by earliest deadline (EDF), then FIFO.
1166
+ * Fast path (no priority/deadline in play): a plain FIFO dequeue, O(1) amortized — the
1167
+ * general scan is taken only while a special process is queued. Both yield identical order. */
1168
+ pickNext() {
1169
+ const q = this.runQueue;
1170
+ if (this.special === 0) {
1171
+ while (this.qHead < q.length) {
1172
+ const pcb = this.proc.get(q[this.qHead++]);
1173
+ if (!pcb || pcb.status === "dead" || pcb.stopped) continue;
1174
+ if (this.qHead > 64 && this.qHead * 2 >= q.length) {
1175
+ this.runQueue = q.slice(this.qHead);
1176
+ this.qHead = 0;
1177
+ }
1178
+ return pcb;
1179
+ }
1180
+ this.runQueue = [];
1181
+ this.qHead = 0;
1182
+ return void 0;
1183
+ }
1184
+ let best;
1185
+ const live = [];
1186
+ let spec = 0;
1187
+ for (let i = this.qHead; i < q.length; i++) {
1188
+ const pcb = this.proc.get(q[i]);
1189
+ if (!pcb || pcb.status === "dead" || pcb.stopped) continue;
1190
+ live.push(pcb);
1191
+ if (pcb.priority !== 0 || pcb.deadline !== null) spec++;
1192
+ if (!best || this.higher(pcb, best)) best = pcb;
1193
+ }
1194
+ const next = [];
1195
+ for (const pc of live) {
1196
+ if (pc === best) {
1197
+ pc.age = 0;
1198
+ if (pc.priority !== 0 || pc.deadline !== null) spec--;
1199
+ continue;
1200
+ }
1201
+ pc.age++;
1202
+ next.push(pc.pid);
1203
+ }
1204
+ this.runQueue = next;
1205
+ this.qHead = 0;
1206
+ this.special = spec;
1207
+ return best;
1208
+ }
1209
+ higher(a, b) {
1210
+ const ea = a.priority + ageBoost(a.age), eb = b.priority + ageBoost(b.age);
1211
+ if (ea !== eb) return ea > eb;
1212
+ const da = a.deadline ?? Infinity, db = b.deadline ?? Infinity;
1213
+ if (da !== db) return da < db;
1214
+ return a.enq < b.enq;
1215
+ }
1216
+ dispatch() {
1217
+ if (this.current !== null || this.shutdownFlag) return;
1218
+ const pcb = this.pickNext();
1219
+ if (!pcb) {
1220
+ this.clock.advance();
1221
+ return;
1222
+ }
1223
+ this.current = pcb.pid;
1224
+ pcb.status = "running";
1225
+ pcb.reductions = QUANTUM;
1226
+ this.log.emit({ t: "sched", pid: pcb.pid });
1227
+ const cont = pcb.cont;
1228
+ if (cont) {
1229
+ pcb.cont = null;
1230
+ cont();
1231
+ } else this.startMain(pcb);
1232
+ }
1233
+ /** Arm (or re-arm) a deadline: kill the process if it hasn't finished by then. */
1234
+ armDeadline(pcb, ms) {
1235
+ if (pcb.deadlineTimer) this.clock.cancel(pcb.deadlineTimer);
1236
+ pcb.deadline = this.clock.now() + Math.max(0, ms);
1237
+ pcb.deadlineTimer = this.clock.after(ms, () => {
1238
+ pcb.deadlineTimer = null;
1239
+ if (pcb.status !== "dead") this.terminate(pcb.pid, { kind: "deadline" });
1240
+ });
1241
+ }
1242
+ /** Await an external promise (driver I/O) WITHOUT holding the CPU: the process parks so other
1243
+ * processes run meanwhile; it's rescheduled when the promise settles. Enables true concurrency
1244
+ * during I/O (e.g. a worker-thread offload runs in parallel while the kernel keeps scheduling). */
1245
+ /** The single blocking primitive: park the running process (status→waiting, yield the CPU) until its
1246
+ * `cont` is fired by a wake. recv, send-backpressure and parkOn all build their wait loops on this. */
1247
+ block(pcb) {
1248
+ pcb.status = "waiting";
1249
+ if (this.current === pcb.pid) this.current = null;
1250
+ return new Promise((res) => {
1251
+ pcb.cont = res;
1252
+ this.dispatch();
1253
+ });
1254
+ }
1255
+ parkOn(pcb, promise) {
1256
+ let done = false, ok = false;
1257
+ let value;
1258
+ let error;
1259
+ promise.then((v) => {
1260
+ value = v;
1261
+ ok = true;
1262
+ done = true;
1263
+ this.makeRunnable(pcb);
1264
+ }, (e) => {
1265
+ error = e;
1266
+ done = true;
1267
+ this.makeRunnable(pcb);
1268
+ });
1269
+ return (async () => {
1270
+ while (!done) await this.block(pcb);
1271
+ if (ok) return value;
1272
+ throw error;
1273
+ })();
1274
+ }
1275
+ /** Park the running process and hand control to the scheduler (re-enters the ready set). */
1276
+ reschedule(pcb) {
1277
+ pcb.status = "runnable";
1278
+ this.enqueue(pcb);
1279
+ if (this.current === pcb.pid) this.current = null;
1280
+ const p = new Promise((res) => {
1281
+ pcb.cont = res;
1282
+ });
1283
+ this.dispatch();
1284
+ return p;
1285
+ }
1286
+ /** End-of-syscall fairness point: charge reductions; yield if the quantum is spent. */
1287
+ tick(pcb, cost) {
1288
+ pcb.reductions -= cost;
1289
+ if (pcb.reductions > 0) return Promise.resolve();
1290
+ return this.reschedule(pcb);
1291
+ }
1292
+ runCleanups(pcb) {
1293
+ while (pcb.cleanups.length) {
1294
+ const fn = pcb.cleanups.pop();
1295
+ try {
1296
+ fn();
1297
+ } catch {
1298
+ }
1299
+ }
1300
+ }
1301
+ startMain(pcb) {
1302
+ const sys = this.sysFor(pcb.pid);
1303
+ pcb.image.main(sys, pcb.args).then(
1304
+ () => this.terminate(pcb.pid, { kind: "normal" }),
1305
+ (e) => {
1306
+ if (e instanceof ExitSignal) this.terminate(pcb.pid, e.reason);
1307
+ else if (e instanceof QuotaExceeded) this.terminate(pcb.pid, { kind: "quota", resource: e.resource });
1308
+ else this.terminate(pcb.pid, { kind: "fault", error: e instanceof Error ? e.message : String(e) });
1309
+ }
1310
+ );
1311
+ }
1312
+ /* ════════════════════════════════════════════════════════════
1313
+ * IPC + termination
1314
+ * ════════════════════════════════════════════════════════════ */
1315
+ deliver(pcb, msg) {
1316
+ for (const c of msg.caps) this.caps.transfer(c, pcb.pid);
1317
+ pcb.mailbox.push(msg);
1318
+ this.log.emit({ t: "send", from: msg.from, to: pcb.pid });
1319
+ if (pcb.status === "waiting") this.makeRunnable(pcb);
1320
+ }
1321
+ terminate(pid, reason) {
1322
+ const pcb = this.proc.get(pid);
1323
+ if (!pcb || pcb.status === "dead") return;
1324
+ pcb.status = "dead";
1325
+ pcb.exitReason = reason;
1326
+ pcb.account.release();
1327
+ if (pcb.timer) {
1328
+ this.clock.cancel(pcb.timer);
1329
+ pcb.timer = null;
1330
+ }
1331
+ if (pcb.deadlineTimer) {
1332
+ this.clock.cancel(pcb.deadlineTimer);
1333
+ pcb.deadlineTimer = null;
1334
+ }
1335
+ if (this.current === pid) this.current = null;
1336
+ this.runCleanups(pcb);
1337
+ for (const h of pcb.handles.values()) h.close?.().catch(() => {
1338
+ });
1339
+ this.caps.revokeOwnedBy(pid);
1340
+ for (const [n, p] of this.registry) if (p === pid) this.registry.delete(n);
1341
+ this.log.emit({ t: "exit", pid, reason });
1342
+ if (reason.kind === "fault") this.log.emit({ t: "fault", pid, error: reason.error });
1343
+ for (const w of /* @__PURE__ */ new Set([...pcb.links, ...pcb.monitors])) {
1344
+ const wp = this.proc.get(w);
1345
+ if (wp && wp.status !== "dead") {
1346
+ this.deliver(wp, { id: newMsgId(), from: pid, to: w, body: { $: "down", pid, reason }, caps: [] });
1347
+ }
1348
+ }
1349
+ const rms = this.remoteMonitors.get(pid);
1350
+ if (rms) {
1351
+ this.remoteMonitors.delete(pid);
1352
+ for (const rm of rms) this.sendDown(rm.node, rm.watcher, pid, reason);
1353
+ }
1354
+ if (this.initPid !== null && pid !== this.initPid) {
1355
+ for (const c of this.proc.all()) if (c.parent === pid && c.status !== "dead") c.parent = this.initPid;
1356
+ }
1357
+ this.reaped.set(pid, reason);
1358
+ if (this.reaped.size > 1024) this.reaped.delete(this.reaped.keys().next().value);
1359
+ const waiters = this.exitWaiters.get(pid);
1360
+ if (waiters) {
1361
+ this.exitWaiters.delete(pid);
1362
+ for (const r of waiters) r(reason);
1363
+ }
1364
+ const hooks = this.exitHooks.get(pid);
1365
+ if (hooks) {
1366
+ this.exitHooks.delete(pid);
1367
+ for (const h of hooks) h(reason);
1368
+ }
1369
+ this.proc.remove(pid);
1370
+ while (pcb.mailboxWaiters.length) pcb.mailboxWaiters.shift()();
1371
+ this.dispatch();
1372
+ }
1373
+ chargeDriver(pcb, cost) {
1374
+ if (!cost) return;
1375
+ const charge = (m, n) => {
1376
+ if (n) {
1377
+ pcb.account.charge(m, n);
1378
+ this.log.emit({ t: "charge", pid: pcb.pid, meter: m, n });
1379
+ }
1380
+ };
1381
+ charge("tokens", cost.tokens);
1382
+ charge("toolCalls", cost.toolCalls);
1383
+ charge("usd", cost.usd);
1384
+ }
1385
+ req(pid) {
1386
+ const p = this.proc.get(pid);
1387
+ if (!p) throw new KernelError(`no such process ${pid}`);
1388
+ return p;
1389
+ }
1390
+ /* ════════════════════════════════════════════════════════════
1391
+ * The syscall surface — built per process, closes over its pid.
1392
+ * ════════════════════════════════════════════════════════════ */
1393
+ sysFor(pid) {
1394
+ const k = this;
1395
+ return {
1396
+ self: () => pid,
1397
+ selfCap: () => k.caps.mint(pid, { kind: "process", pid }, ["send", "monitor"]),
1398
+ // send + monitor: holders can reach you AND learn when you die (liveness; no kill)
1399
+ /* ── Service registry (name → process discovery; first-come, ambient — a name grants no authority,
1400
+ * only a SEND-cap to whoever holds it; the service itself decides whether to trust a message) ── */
1401
+ register(name) {
1402
+ const cur = k.registry.get(name);
1403
+ if (cur !== void 0 && cur !== pid && k.proc.get(cur)?.status !== "dead") throw new CapError(`register: name "${name}" already in use`);
1404
+ k.registry.set(name, pid);
1405
+ },
1406
+ unregister(name) {
1407
+ if (k.registry.get(name) === pid) k.registry.delete(name);
1408
+ },
1409
+ async lookup(name) {
1410
+ await k.tick(k.req(pid), 1);
1411
+ const sp = k.registry.get(name);
1412
+ const tp = sp !== void 0 ? k.proc.get(sp) : void 0;
1413
+ if (sp === void 0 || !tp || tp.status === "dead") {
1414
+ if (sp !== void 0) k.registry.delete(name);
1415
+ return void 0;
1416
+ }
1417
+ return k.caps.mint(pid, { kind: "process", pid: sp }, ["send"]);
1418
+ },
1419
+ dial: (node, rpid) => k.caps.mint(pid, { kind: "remote", node, pid: rpid }, ["send", "monitor"]),
1420
+ // proxy to (node, rpid): send egresses, monitor watches its death across the wire
1421
+ async spawnRemote(node, name, args) {
1422
+ const pcb = k.req(pid);
1423
+ if (!k.transportKey) throw new KernelError("spawnRemote: requires a transportKey");
1424
+ const egress = k.peers.get(node);
1425
+ if (!egress) throw new CapError(`spawnRemote: no route to node "${node}"`);
1426
+ const corr = `${k.nodeId}.s${k.egressSeq++}`;
1427
+ const reply = new Promise((res) => k.pendingSpawns.set(corr, res));
1428
+ egress(seal(k.transportKey, 0, { $: "spawn-req", from: k.nodeId, reply: pid, name, args, corr }, `${k.nodeId}.${k.egressSeq++}`, k.clock.now()));
1429
+ const r = await k.parkOn(pcb, reply);
1430
+ k.pendingSpawns.delete(corr);
1431
+ if (r.error || r.pid === void 0) throw new KernelError(`spawnRemote: ${r.error ?? "no pid returned"}`);
1432
+ return k.caps.mint(pid, { kind: "remote", node, pid: r.pid }, ["send", "monitor"]);
1433
+ },
1434
+ async recv() {
1435
+ const pcb = k.req(pid);
1436
+ while (pcb.mailbox.len === 0) await k.block(pcb);
1437
+ const msg = pcb.mailbox.shift();
1438
+ if (pcb.mailboxWaiters.length) pcb.mailboxWaiters.shift()();
1439
+ k.log.emit({ t: "recv", pid });
1440
+ await k.tick(pcb, 1);
1441
+ return msg;
1442
+ },
1443
+ async send(target, body, opts) {
1444
+ const pcb = k.req(pid);
1445
+ pcb.account.checkWall();
1446
+ const cap = k.caps.require(pid, target, "send");
1447
+ if (cap.resource.kind === "remote") {
1448
+ if (opts?.caps?.length) throw new CapError("send: capabilities cannot cross the wire");
1449
+ const egress = k.peers.get(cap.resource.node);
1450
+ if (!egress) throw new CapError(`send: no route to node "${cap.resource.node}"`);
1451
+ if (!k.transportKey) throw new KernelError("send: remote send requires a transportKey");
1452
+ const env = seal(k.transportKey, cap.resource.pid, body, `${k.nodeId}.${k.egressSeq++}`, k.clock.now());
1453
+ await k.tick(pcb, 1);
1454
+ if (!egress(env)) throw new KernelError(`send: remote egress to "${cap.resource.node}" refused (peer down or backlogged)`);
1455
+ return;
1456
+ }
1457
+ if (cap.resource.kind !== "process") throw new CapError("send: capability is not a process");
1458
+ const caps = opts?.caps ?? [];
1459
+ for (const c of caps) {
1460
+ const cc = k.caps.lookup(c);
1461
+ if (!cc || cc.owner !== pid) throw new CapError("send: delegated cap not owned by caller");
1462
+ }
1463
+ const tpid = cap.resource.pid;
1464
+ const lim = k.proc.get(tpid)?.account.maxMailbox;
1465
+ let t = k.proc.get(tpid);
1466
+ while (lim !== void 0 && t && t.mailbox.len >= lim) {
1467
+ t.mailboxWaiters.push(() => k.makeRunnable(pcb));
1468
+ await k.block(pcb);
1469
+ t = k.proc.get(tpid);
1470
+ }
1471
+ const dst = k.proc.get(tpid);
1472
+ if (dst && dst.status !== "dead") k.deliver(dst, { id: newMsgId(), from: pid, to: dst.pid, body, caps, corr: opts?.corr, deadline: opts?.deadline });
1473
+ await k.tick(pcb, 1);
1474
+ },
1475
+ async spawn(image, opts) {
1476
+ const parent = k.req(pid);
1477
+ if (!k.caps.ownsKind(pid, "spawn")) throw new CapError("spawn: caller holds no spawn capability");
1478
+ const acct = parent.account.child(opts?.quota ?? {});
1479
+ const cpid = k.proc.alloc();
1480
+ const child = makePCB(cpid, pid, image, opts?.args, acct, new Context(acct.mem, k.pager, k.mkRecaller()));
1481
+ k.proc.add(child);
1482
+ for (const c of opts?.caps ?? []) {
1483
+ const cc = k.caps.lookup(c);
1484
+ if (!cc || cc.owner !== pid) throw new CapError("spawn: cap not owned by caller");
1485
+ k.caps.transfer(c, cpid);
1486
+ }
1487
+ const cap = k.caps.mint(pid, { kind: "process", pid: cpid }, ["send", "monitor", "kill"]);
1488
+ if (opts?.link) {
1489
+ parent.links.add(cpid);
1490
+ child.links.add(pid);
1491
+ }
1492
+ child.priority = opts?.priority ?? 0;
1493
+ if (opts?.deadline !== void 0) k.armDeadline(child, opts.deadline);
1494
+ k.log.emit({ t: "spawn", pid: cpid, parent: pid, name: child.name });
1495
+ k.makeRunnable(child);
1496
+ await k.tick(parent, 5);
1497
+ return { pid: cpid, cap };
1498
+ },
1499
+ async exit(reason) {
1500
+ throw new ExitSignal(reason ?? { kind: "normal" });
1501
+ },
1502
+ derive(cap, rights) {
1503
+ return k.caps.derive(pid, cap, rights);
1504
+ },
1505
+ attenuate(cap, opts) {
1506
+ const caveats = opts.expiresInMs !== void 0 ? [{ kind: "expires", at: k.clock.now() + Math.max(0, opts.expiresInMs) }] : [];
1507
+ return k.caps.attenuate(pid, cap, opts.rights, caveats);
1508
+ },
1509
+ revoke(cap) {
1510
+ const c = k.caps.lookup(cap);
1511
+ if (c && c.owner === pid) k.caps.revoke(cap);
1512
+ },
1513
+ async open(device) {
1514
+ const pcb = k.req(pid);
1515
+ const cap = k.caps.require(pid, device, "open");
1516
+ if (cap.resource.kind !== "device") throw new CapError("open: capability is not a device");
1517
+ const driver = k.drivers.get(cap.resource.driver);
1518
+ if (!driver) throw new KernelError(`open: no such driver "${cap.resource.driver}"`);
1519
+ const handle = await driver.open(pid);
1520
+ const hcap = k.caps.mint(pid, cap.resource, ["read", "write"]);
1521
+ pcb.handles.set(hcap, handle);
1522
+ await k.tick(pcb, 2);
1523
+ return hcap;
1524
+ },
1525
+ async read(handle, request) {
1526
+ const pcb = k.req(pid);
1527
+ pcb.account.checkWall();
1528
+ k.caps.require(pid, handle, "read");
1529
+ const h = pcb.handles.get(handle);
1530
+ if (!h?.read) throw new KernelError("handle is not readable");
1531
+ const res = await k.parkOn(pcb, k.journaled(pcb, () => h.read(request)));
1532
+ k.chargeDriver(pcb, res.cost);
1533
+ await k.tick(pcb, res.cost?.reductions ?? 1);
1534
+ return res.value;
1535
+ },
1536
+ async readAll(handle, reqs) {
1537
+ const pcb = k.req(pid);
1538
+ pcb.account.checkWall();
1539
+ k.caps.require(pid, handle, "read");
1540
+ const h = pcb.handles.get(handle);
1541
+ if (!h?.read) throw new KernelError("handle is not readable");
1542
+ const settled = await k.parkOn(pcb, Promise.all(reqs.map((r) => k.journaled(pcb, () => h.read(r)))));
1543
+ let red = 0;
1544
+ for (const res of settled) {
1545
+ k.chargeDriver(pcb, res.cost);
1546
+ red += res.cost?.reductions ?? 1;
1547
+ }
1548
+ await k.tick(pcb, red || 1);
1549
+ return settled.map((res) => res.value);
1550
+ },
1551
+ async write(handle, data) {
1552
+ const pcb = k.req(pid);
1553
+ pcb.account.checkWall();
1554
+ k.caps.require(pid, handle, "write");
1555
+ const h = pcb.handles.get(handle);
1556
+ if (!h?.write) throw new KernelError("handle is not writable");
1557
+ const res = await k.parkOn(pcb, k.journaled(pcb, () => h.write(data)));
1558
+ k.chargeDriver(pcb, res.cost);
1559
+ await k.tick(pcb, res.cost?.reductions ?? 1);
1560
+ },
1561
+ async monitor(target) {
1562
+ const pcb = k.req(pid);
1563
+ const cap = k.caps.require(pid, target, "monitor");
1564
+ if (cap.resource.kind === "remote") {
1565
+ if (!k.transportKey) throw new KernelError("monitor: remote monitor requires a transportKey");
1566
+ const egress = k.peers.get(cap.resource.node);
1567
+ if (!egress) throw new CapError(`monitor: no route to node "${cap.resource.node}"`);
1568
+ egress(seal(k.transportKey, 0, { $: "mon-req", from: k.nodeId, watcher: pid, target: cap.resource.pid }, `${k.nodeId}.${k.egressSeq++}`, k.clock.now()));
1569
+ await k.tick(pcb, 1);
1570
+ return;
1571
+ }
1572
+ if (cap.resource.kind !== "process") throw new CapError("monitor: capability is not a process");
1573
+ const tp = k.proc.get(cap.resource.pid);
1574
+ if (!tp || tp.status === "dead") {
1575
+ k.deliver(pcb, { id: newMsgId(), from: cap.resource.pid, to: pid, body: { $: "down", pid: cap.resource.pid, reason: tp?.exitReason ?? { kind: "normal" } }, caps: [] });
1576
+ } else tp.monitors.add(pid);
1577
+ await k.tick(pcb, 1);
1578
+ },
1579
+ async waitpid(target) {
1580
+ const pcb = k.req(pid);
1581
+ const cap = k.caps.require(pid, target, "monitor");
1582
+ if (cap.resource.kind !== "process") throw new CapError("waitpid: capability is not a process");
1583
+ const tpid = cap.resource.pid;
1584
+ const tp = k.proc.get(tpid);
1585
+ if (!tp || tp.status === "dead") {
1586
+ await k.tick(pcb, 1);
1587
+ return k.reaped.get(tpid) ?? tp?.exitReason ?? { kind: "normal" };
1588
+ }
1589
+ let reason = { kind: "normal" };
1590
+ let fired = false;
1591
+ const arr = k.exitHooks.get(tpid) ?? [];
1592
+ arr.push((r) => {
1593
+ reason = r;
1594
+ fired = true;
1595
+ k.makeRunnable(pcb);
1596
+ });
1597
+ k.exitHooks.set(tpid, arr);
1598
+ while (!fired) {
1599
+ pcb.status = "waiting";
1600
+ if (k.current === pid) k.current = null;
1601
+ await new Promise((res) => {
1602
+ pcb.cont = res;
1603
+ k.dispatch();
1604
+ });
1605
+ }
1606
+ await k.tick(pcb, 1);
1607
+ return reason;
1608
+ },
1609
+ async signal(target, reason) {
1610
+ const pcb = k.req(pid);
1611
+ const cap = k.caps.require(pid, target, "kill");
1612
+ if (cap.resource.kind !== "process") throw new CapError("signal: capability is not a process");
1613
+ k.terminate(cap.resource.pid, reason ?? { kind: "killed", by: pid });
1614
+ await k.tick(pcb, 1);
1615
+ },
1616
+ async suspend(target) {
1617
+ const pcb = k.req(pid);
1618
+ const cap = k.caps.require(pid, target, "kill");
1619
+ if (cap.resource.kind !== "process") throw new CapError("suspend: capability is not a process");
1620
+ k.suspendProc(cap.resource.pid);
1621
+ await k.tick(pcb, 1);
1622
+ },
1623
+ async resume(target) {
1624
+ const pcb = k.req(pid);
1625
+ const cap = k.caps.require(pid, target, "kill");
1626
+ if (cap.resource.kind !== "process") throw new CapError("resume: capability is not a process");
1627
+ k.resumeProc(cap.resource.pid);
1628
+ await k.tick(pcb, 1);
1629
+ },
1630
+ async term(target) {
1631
+ const pcb = k.req(pid);
1632
+ const cap = k.caps.require(pid, target, "kill");
1633
+ if (cap.resource.kind !== "process") throw new CapError("term: capability is not a process");
1634
+ k.termProc(cap.resource.pid, pid);
1635
+ await k.tick(pcb, 1);
1636
+ },
1637
+ async sleep(ms) {
1638
+ const pcb = k.req(pid);
1639
+ const until = k.clock.now() + ms;
1640
+ for (let left = ms; left > 0; left = until - k.clock.now()) {
1641
+ pcb.timer = k.clock.after(left, () => {
1642
+ pcb.timer = null;
1643
+ k.makeRunnable(pcb);
1644
+ });
1645
+ await k.block(pcb);
1646
+ if (pcb.timer) {
1647
+ k.clock.cancel(pcb.timer);
1648
+ pcb.timer = null;
1649
+ }
1650
+ }
1651
+ await k.tick(pcb, 1);
1652
+ },
1653
+ async yield() {
1654
+ await k.reschedule(k.req(pid));
1655
+ },
1656
+ sched(opts) {
1657
+ const pcb = k.req(pid);
1658
+ if (opts.priority !== void 0) pcb.priority = opts.priority;
1659
+ if (opts.deadline !== void 0) k.armDeadline(pcb, opts.deadline);
1660
+ },
1661
+ async remember(body, o) {
1662
+ const pcb = k.req(pid);
1663
+ const id = await pcb.context.remember(body, o?.tokens ?? estTokens(body), o?.pin ?? false);
1664
+ await k.tick(pcb, 1);
1665
+ return id;
1666
+ },
1667
+ workingSet() {
1668
+ return k.req(pid).context.workingSet();
1669
+ },
1670
+ async recall(query, n = 4) {
1671
+ const pcb = k.req(pid);
1672
+ const out = await pcb.context.recall(query, n);
1673
+ await k.tick(pcb, 2);
1674
+ return out;
1675
+ },
1676
+ forget(id) {
1677
+ k.req(pid).context.forget(id);
1678
+ },
1679
+ pin(id, on = true) {
1680
+ k.req(pid).context.pin(id, on);
1681
+ },
1682
+ budget() {
1683
+ return k.req(pid).account.remaining();
1684
+ },
1685
+ now() {
1686
+ return k.clock.now();
1687
+ },
1688
+ // kernel clock (virtual under VirtualClock) → deterministic userland timing
1689
+ log(level, msg, fields) {
1690
+ k.onLog?.(pid, level, msg, fields);
1691
+ },
1692
+ onCleanup(fn) {
1693
+ k.proc.get(pid)?.cleanups.push(fn);
1694
+ }
1695
+ // run on terminate (kill included) → release external resources a finally can't
1696
+ };
1697
+ }
1698
+ };
1699
+
1700
+ // src/net/tcp.ts
1701
+ import { createServer, connect } from "node:net";
1702
+ function serveTcp(onEnvelope, opts = {}) {
1703
+ const maxLine = opts.maxLine ?? 16 * 1024 * 1024;
1704
+ const sockets = /* @__PURE__ */ new Set();
1705
+ const server = createServer((sock) => {
1706
+ sockets.add(sock);
1707
+ sock.on("close", () => sockets.delete(sock));
1708
+ sock.on("error", () => sockets.delete(sock));
1709
+ let buf = "";
1710
+ sock.setEncoding("utf8");
1711
+ sock.on("data", (chunk) => {
1712
+ buf += chunk;
1713
+ let nl;
1714
+ while ((nl = buf.indexOf("\n")) >= 0) {
1715
+ const line = buf.slice(0, nl);
1716
+ buf = buf.slice(nl + 1);
1717
+ if (!line) continue;
1718
+ try {
1719
+ onEnvelope(JSON.parse(line));
1720
+ } catch {
1721
+ }
1722
+ }
1723
+ if (buf.length > maxLine) {
1724
+ buf = "";
1725
+ sock.destroy();
1726
+ }
1727
+ });
1728
+ });
1729
+ return new Promise((resolve, reject) => {
1730
+ server.once("error", reject);
1731
+ server.listen(opts.port ?? 0, opts.host ?? "127.0.0.1", () => {
1732
+ const addr = server.address();
1733
+ const port = typeof addr === "object" && addr ? addr.port : 0;
1734
+ resolve({
1735
+ port,
1736
+ close: () => new Promise((res) => {
1737
+ for (const s of sockets) s.destroy();
1738
+ server.close(() => res());
1739
+ })
1740
+ });
1741
+ });
1742
+ });
1743
+ }
1744
+ function dialTcp(host, port, opts = {}) {
1745
+ const maxPending = opts.maxPending ?? 1024;
1746
+ const base = opts.backoffMs ?? 100;
1747
+ const maxBackoff = opts.maxBackoffMs ?? 5e3;
1748
+ const pending = [];
1749
+ let sock = null;
1750
+ let ready = false;
1751
+ let closed = false;
1752
+ let attempt = 0;
1753
+ let timer = null;
1754
+ const dial = () => {
1755
+ if (closed) return;
1756
+ const s = connect(port, host);
1757
+ sock = s;
1758
+ s.setEncoding("utf8");
1759
+ s.on("connect", () => {
1760
+ ready = true;
1761
+ attempt = 0;
1762
+ while (pending.length && sock === s) s.write(pending.shift());
1763
+ });
1764
+ const drop = () => {
1765
+ if (sock !== s) return;
1766
+ ready = false;
1767
+ sock = null;
1768
+ if (closed) return;
1769
+ timer = setTimeout(dial, Math.min(maxBackoff, base * 2 ** attempt++));
1770
+ timer.unref?.();
1771
+ };
1772
+ s.on("error", drop);
1773
+ s.on("close", drop);
1774
+ };
1775
+ dial();
1776
+ return {
1777
+ send(env) {
1778
+ if (closed) return false;
1779
+ const line = JSON.stringify(env) + "\n";
1780
+ if (ready && sock) {
1781
+ sock.write(line);
1782
+ return true;
1783
+ }
1784
+ if (pending.length >= maxPending) return false;
1785
+ pending.push(line);
1786
+ return true;
1787
+ },
1788
+ close() {
1789
+ closed = true;
1790
+ ready = false;
1791
+ if (timer) clearTimeout(timer);
1792
+ sock?.destroy();
1793
+ sock = null;
1794
+ }
1795
+ };
1796
+ }
1797
+
1798
+ // src/userland/supervisor.ts
1799
+ function supervisor(spec) {
1800
+ const max = spec.maxRestarts ?? 5;
1801
+ const period = spec.periodMs ?? 5e3;
1802
+ const backoff = spec.backoffMs ?? 100;
1803
+ const backoffMax = spec.maxBackoffMs ?? 3e4;
1804
+ const order = spec.children.map((c) => c.id);
1805
+ const specOf = (id) => spec.children.find((c) => c.id === id);
1806
+ return { name: "supervisor", async main(sys) {
1807
+ const live = /* @__PURE__ */ new Map();
1808
+ const byPid = /* @__PURE__ */ new Map();
1809
+ const ignore = /* @__PURE__ */ new Set();
1810
+ const stamps = [];
1811
+ const startedAt = /* @__PURE__ */ new Map();
1812
+ const flaps = /* @__PURE__ */ new Map();
1813
+ const start = async (id) => {
1814
+ const c = specOf(id);
1815
+ const { pid, cap } = await sys.spawn(c.image, { args: c.args, quota: c.quota, link: true });
1816
+ live.set(id, { pid, cap });
1817
+ byPid.set(pid, id);
1818
+ startedAt.set(id, sys.now());
1819
+ spec.onStart?.(id, pid);
1820
+ };
1821
+ const stop = async (id, reason) => {
1822
+ const e = live.get(id);
1823
+ if (!e) return;
1824
+ ignore.add(e.pid);
1825
+ try {
1826
+ await sys.signal(e.cap, reason);
1827
+ } catch {
1828
+ }
1829
+ live.delete(id);
1830
+ byPid.delete(e.pid);
1831
+ };
1832
+ for (const id of order) await start(id);
1833
+ while (true) {
1834
+ const down = (await sys.recv()).body;
1835
+ if (down.$ !== "down") continue;
1836
+ const id = byPid.get(down.pid);
1837
+ if (id) {
1838
+ byPid.delete(down.pid);
1839
+ live.delete(id);
1840
+ }
1841
+ if (ignore.delete(down.pid) || !id) continue;
1842
+ const kind = specOf(id).restart ?? "permanent";
1843
+ const abnormal = down.reason.kind !== "normal";
1844
+ if (kind === "temporary" || kind === "transient" && !abnormal) continue;
1845
+ const now = sys.now();
1846
+ stamps.push(now);
1847
+ while (stamps.length && now - stamps[0] > period) stamps.shift();
1848
+ if (stamps.length > max) await sys.exit({ kind: "fault", error: "supervisor: restart intensity exceeded" });
1849
+ const up = now - (startedAt.get(id) ?? now);
1850
+ const flap = up >= period ? 0 : flaps.get(id) ?? 0;
1851
+ flaps.set(id, flap + 1);
1852
+ if (flap > 0) await sys.sleep(Math.min(backoff * 2 ** (flap - 1), backoffMax));
1853
+ const restartable = (oid) => (specOf(oid).restart ?? "permanent") !== "temporary";
1854
+ if (spec.strategy === "one_for_one") {
1855
+ await start(id);
1856
+ } else if (spec.strategy === "one_for_all") {
1857
+ for (const other of order) if (other !== id) await stop(other, { kind: "shutdown" });
1858
+ for (const oid of order) if (restartable(oid)) await start(oid);
1859
+ } else {
1860
+ const after = order.slice(order.indexOf(id) + 1);
1861
+ for (const other of after) await stop(other, { kind: "shutdown" });
1862
+ for (const oid of [id, ...after]) if (restartable(oid)) await start(oid);
1863
+ }
1864
+ }
1865
+ } };
1866
+ }
1867
+
1868
+ // src/userland/nursery.ts
1869
+ var reasonStr = (r) => r.kind === "fault" ? r.error : r.kind === "quota" ? `quota:${r.resource}` : r.kind === "killed" ? `killed by ${r.by}` : r.kind;
1870
+ function nursery(sys) {
1871
+ const kids = [];
1872
+ let joined = false;
1873
+ const cancel = () => {
1874
+ for (const k of kids) void sys.signal(k.cap, { kind: "shutdown" }).catch(() => {
1875
+ });
1876
+ };
1877
+ sys.onCleanup(cancel);
1878
+ return {
1879
+ async spawn(image, opts) {
1880
+ if (joined) throw new Error("nursery: spawn after join");
1881
+ const { pid, cap } = await sys.spawn(image, opts);
1882
+ kids.push({ name: image.name, pid, cap });
1883
+ return cap;
1884
+ },
1885
+ async join() {
1886
+ if (joined) return;
1887
+ joined = true;
1888
+ for (const k of kids) await sys.monitor(k.cap);
1889
+ const failed = [];
1890
+ let remaining = kids.length;
1891
+ while (remaining > 0) {
1892
+ const m = await sys.recv();
1893
+ if (m.body.$ !== "down") continue;
1894
+ const dead = kids.find((k) => k.pid === m.body.pid);
1895
+ if (!dead) continue;
1896
+ remaining--;
1897
+ if (m.body.reason.kind !== "normal" && failed.length === 0) {
1898
+ failed.push({ name: dead.name, reason: m.body.reason });
1899
+ cancel();
1900
+ }
1901
+ }
1902
+ const f = failed[0];
1903
+ if (f) throw new Error(`nursery: child "${f.name}" exited ${reasonStr(f.reason)} \u2014 scope cancelled`);
1904
+ }
1905
+ };
1906
+ }
1907
+
1908
+ // src/userland/nameservice.ts
1909
+ var nameService = { name: "names", async main(sys) {
1910
+ const reg = /* @__PURE__ */ new Map();
1911
+ const owned = /* @__PURE__ */ new Map();
1912
+ const watched = /* @__PURE__ */ new Set();
1913
+ const drop = (name) => {
1914
+ const e = reg.get(name);
1915
+ if (e) {
1916
+ reg.delete(name);
1917
+ owned.get(e.owner)?.delete(name);
1918
+ }
1919
+ };
1920
+ while (true) {
1921
+ const m = await sys.recv();
1922
+ const b = m.body;
1923
+ if (b.$ === "down") {
1924
+ for (const n of owned.get(b.pid) ?? []) if (reg.get(n)?.owner === b.pid) reg.delete(n);
1925
+ owned.delete(b.pid);
1926
+ watched.delete(b.pid);
1927
+ } else if (b.$ === "register") {
1928
+ const cap = m.caps[0];
1929
+ if (!cap) continue;
1930
+ drop(b.name);
1931
+ reg.set(b.name, { cap, owner: m.from });
1932
+ (owned.get(m.from) ?? owned.set(m.from, /* @__PURE__ */ new Set()).get(m.from)).add(b.name);
1933
+ if (!watched.has(m.from)) {
1934
+ watched.add(m.from);
1935
+ try {
1936
+ await sys.monitor(cap);
1937
+ } catch {
1938
+ }
1939
+ }
1940
+ } else if (b.$ === "unregister") drop(b.name);
1941
+ else if (b.$ === "lookup") {
1942
+ const reply = m.caps[0];
1943
+ if (!reply) continue;
1944
+ const target = reg.get(b.name)?.cap;
1945
+ const give = target ? sys.derive(target, ["send"]) : void 0;
1946
+ await sys.send(reply, { $: "resolved", name: b.name, found: !!target }, give ? { caps: [give] } : {});
1947
+ }
1948
+ }
1949
+ } };
1950
+ async function register(sys, names, name) {
1951
+ await sys.send(names, { $: "register", name }, { caps: [sys.selfCap()] });
1952
+ }
1953
+ async function lookup(sys, names, name) {
1954
+ await sys.send(names, { $: "lookup", name }, { caps: [sys.selfCap()] });
1955
+ while (true) {
1956
+ const m = await sys.recv();
1957
+ if (m.body.$ === "resolved" && m.body.name === name) return m.body.found ? m.caps[0] ?? null : null;
1958
+ }
1959
+ }
1960
+
1961
+ // src/userland/agent.ts
1962
+ var DEFAULT_SYSTEM = "You are a capable autonomous agent. Break the task into steps and keep going \u2014 investigate with tools, then act \u2014 until it is fully done; never stop at a plan or a partial result. Verify before answering, then reply concisely.";
1963
+ var HARD_MAX = 384;
1964
+ var MAX_TOOL_CHARS = 8e3;
1965
+ var isMessage = (b) => !!b && typeof b.role === "string";
1966
+ var capResult = (s) => s.length > MAX_TOOL_CHARS ? s.slice(0, MAX_TOOL_CHARS) + `
1967
+ \u2026[truncated ${s.length - MAX_TOOL_CHARS} chars]` : s;
1968
+ function orderForCache(segs) {
1969
+ const lead = [], sums = [], rest = [];
1970
+ for (const s of segs) (s.pinned && !s.summary ? lead : s.summary ? sums : rest).push(s);
1971
+ return [...lead, ...sums, ...rest];
1972
+ }
1973
+ function pairToolCalls(messages) {
1974
+ const resultById = /* @__PURE__ */ new Map();
1975
+ for (const m of messages) if (m.role === "tool" && m.toolCallId) resultById.set(m.toolCallId, m);
1976
+ const out = [];
1977
+ for (const m of messages) {
1978
+ if (m.role === "assistant" && m.toolCalls?.length) {
1979
+ const tc = m.toolCalls.filter((c) => resultById.has(c.id));
1980
+ if (tc.length) {
1981
+ out.push({ ...m, toolCalls: tc });
1982
+ for (const c of tc) out.push(resultById.get(c.id));
1983
+ } else if (m.content?.trim()) out.push({ role: "assistant", content: m.content });
1984
+ } else if (m.role !== "tool") out.push(m);
1985
+ }
1986
+ return out;
1987
+ }
1988
+ function unproductive(out) {
1989
+ if (!out || typeof out !== "object") return false;
1990
+ const o = out;
1991
+ if ("error" in o) return true;
1992
+ const st = o["status"];
1993
+ return typeof st === "number" && (st < 200 || st >= 300);
1994
+ }
1995
+ async function runTurn(sys, llm, tools, spec) {
1996
+ const ceil = Math.max(1, Math.min(spec.maxSteps ?? HARD_MAX, HARD_MAX));
1997
+ const base = Math.max(1, spec.baseSteps ?? 16);
1998
+ const stallMax = Math.max(1, spec.stallLimit ?? 3);
1999
+ let limit = Math.min(base, ceil);
2000
+ const cache = /* @__PURE__ */ new Map();
2001
+ const promptMessages = () => pairToolCalls(orderForCache(sys.workingSet()).map((s) => s.body).filter(isMessage));
2002
+ let answer = "", steps = 0, stall = 0, forced = false;
2003
+ let investigated = 0, steered = false;
2004
+ let revisions = 0;
2005
+ const maxRev = Math.max(0, spec.maxRevisions ?? 2);
2006
+ let continues = 0;
2007
+ const maxCont = Math.max(0, spec.maxContinue ?? 24);
2008
+ for (; steps < limit; steps++) {
2009
+ const noTools = forced || steps >= limit - 1;
2010
+ const del = spec.delegate;
2011
+ const steer = !!del && investigated >= del.after && !noTools;
2012
+ if (steer && !steered) {
2013
+ steered = true;
2014
+ await sys.remember({ role: "user", content: `You've made ${investigated} direct file-read/search calls in a row \u2014 that floods the transcript and your own context. For broad investigation ("where/how/analyze", or scanning several files), call \`${del.via}\` instead: it runs in a sub-agent and returns a distilled summary, keeping your context clean. The read/search tools are withheld this round, so delegate to \`${del.via}\` or act on what you already know. Prefer \`${del.via}\` over shell for searching.` });
2015
+ }
2016
+ const messages = promptMessages();
2017
+ let offer = noTools ? void 0 : spec.tools;
2018
+ if (steer && offer && del) offer = offer.filter((t) => !del.withhold.includes(t.name));
2019
+ const reply = await sys.read(llm, { messages, tools: offer, maxTokens: spec.maxTokens });
2020
+ for (const s of sys.workingSet()) {
2021
+ const b = s.body;
2022
+ if (b && b.images) delete b.images;
2023
+ }
2024
+ if (reply.toolCalls?.length && tools && !noTools) {
2025
+ await sys.remember({ role: "assistant", content: reply.text ?? "", toolCalls: reply.toolCalls });
2026
+ const calls = reply.toolCalls;
2027
+ const keys = calls.map((tc) => tc.name + ":" + JSON.stringify(tc.args ?? {}));
2028
+ const fresh = [];
2029
+ const ran = /* @__PURE__ */ new Set();
2030
+ for (let i = 0; i < calls.length; i++) {
2031
+ const key = keys[i];
2032
+ if (!cache.has(key) && !ran.has(key)) {
2033
+ ran.add(key);
2034
+ fresh.push({ key, req: { tool: calls[i].name, input: calls[i].args } });
2035
+ }
2036
+ }
2037
+ if (fresh.length) {
2038
+ const outs = await sys.readAll(tools, fresh.map((f) => f.req));
2039
+ fresh.forEach((f, j) => cache.set(f.key, outs[j]));
2040
+ }
2041
+ let progressed = false;
2042
+ for (let i = 0; i < calls.length; i++) {
2043
+ const out = cache.get(keys[i]);
2044
+ await sys.remember({ role: "tool", toolCallId: calls[i].id, name: calls[i].name, content: capResult(JSON.stringify(out)) });
2045
+ if (ran.has(keys[i]) && !unproductive(out)) progressed = true;
2046
+ }
2047
+ if (del) {
2048
+ const inv = calls.filter((c) => del.investigate.includes(c.name)).length;
2049
+ if (inv > 0 && inv === calls.length) investigated += inv;
2050
+ else {
2051
+ investigated = 0;
2052
+ steered = false;
2053
+ }
2054
+ }
2055
+ if (progressed) stall = 0;
2056
+ else if (++stall >= stallMax) {
2057
+ forced = true;
2058
+ await sys.remember({ role: "user", content: "Stop searching \u2014 recent tool calls returned no usable data. Answer now, concisely, with what you already know, and state any uncertainty." });
2059
+ }
2060
+ if (!forced && steps >= limit - 2 && limit < ceil) limit = Math.min(ceil, limit + base);
2061
+ continue;
2062
+ }
2063
+ answer = reply.text ?? "";
2064
+ await sys.remember({ role: "assistant", content: answer });
2065
+ if (answer.trim() && spec.reflect && !forced && steps < limit - 1) {
2066
+ const v = await spec.reflect(promptMessages());
2067
+ const r = !v ? null : typeof v === "string" ? { correction: v, kind: "revise" } : v;
2068
+ if (r?.correction) {
2069
+ const cont = r.kind === "continue";
2070
+ if (cont ? continues < maxCont : revisions < maxRev) {
2071
+ if (cont) continues++;
2072
+ else revisions++;
2073
+ limit = Math.min(ceil, limit + base);
2074
+ answer = "";
2075
+ await sys.remember({ role: "user", content: r.correction });
2076
+ continue;
2077
+ }
2078
+ }
2079
+ }
2080
+ if (answer.trim() || steps >= limit - 1) break;
2081
+ await sys.remember({ role: "user", content: "You did not produce a final answer. Take the next concrete step with a tool, or give your final answer now." });
2082
+ }
2083
+ return { answer, steps: Math.min(steps + 1, limit) };
2084
+ }
2085
+ async function openDevices(sys) {
2086
+ const boot = await sys.recv();
2087
+ const [llmDev, toolsDev, parent] = boot.caps;
2088
+ return { llm: await sys.open(llmDev), tools: toolsDev ? await sys.open(toolsDev) : null, parent };
2089
+ }
2090
+ function reactAgent(spec) {
2091
+ return { name: "agent", async main(sys) {
2092
+ const { llm, tools, parent } = await openDevices(sys);
2093
+ await sys.remember({ role: "system", content: spec.system ?? DEFAULT_SYSTEM }, { pin: true });
2094
+ await sys.remember({ role: "user", content: spec.task ?? "" }, { pin: true });
2095
+ const r = await runTurn(sys, llm, tools, spec);
2096
+ if (parent) await sys.send(parent, { $: "result", answer: r.answer, steps: r.steps });
2097
+ } };
2098
+ }
2099
+ function conversationAgent(spec = {}) {
2100
+ return { name: "agent", async main(sys) {
2101
+ const { llm, tools, parent } = await openDevices(sys);
2102
+ if (spec.history?.length) {
2103
+ let pinned = false;
2104
+ for (const m of spec.history) {
2105
+ const pin = m.role === "system" && !pinned;
2106
+ if (pin) pinned = true;
2107
+ await sys.remember(m.images ? { ...m, images: void 0 } : m, { pin });
2108
+ }
2109
+ if (!pinned) await sys.remember({ role: "system", content: spec.system ?? DEFAULT_SYSTEM }, { pin: true });
2110
+ } else {
2111
+ await sys.remember({ role: "system", content: spec.system ?? DEFAULT_SYSTEM }, { pin: true });
2112
+ }
2113
+ for (; ; ) {
2114
+ const msg = await sys.recv();
2115
+ const userTurn = { role: "user", content: msg.body.task, ...msg.body.images?.length ? { images: msg.body.images } : {} };
2116
+ await sys.remember(userTurn);
2117
+ let r;
2118
+ try {
2119
+ r = await runTurn(sys, llm, tools, spec);
2120
+ } catch (e) {
2121
+ r = { answer: "", steps: 0, error: e instanceof Error ? e.message : String(e) };
2122
+ }
2123
+ if (parent) await sys.send(parent, { $: "result", answer: r.answer, steps: r.steps, error: r.error });
2124
+ }
2125
+ } };
2126
+ }
2127
+
2128
+ // src/userland/sandbox.ts
2129
+ import { Worker } from "node:worker_threads";
2130
+ var DEFAULT_ALLOW = ["self", "recv", "send", "remember", "recall", "workingSet", "log", "exit"];
2131
+ var DEFAULT_RESOURCE_LIMITS = { maxOldGenerationSizeMb: 256 };
2132
+ var harness = (code) => `
2133
+ const { parentPort, threadId, workerData } = require('node:worker_threads')
2134
+ const pending = new Map(); let seq = 0
2135
+ function syscall(name, ...args) {
2136
+ return new Promise((res, rej) => { const id = ++seq; pending.set(id, { res, rej }); parentPort.postMessage({ id, name, args }) })
2137
+ }
2138
+ parentPort.on('message', (m) => { const p = pending.get(m.id); if (!p) return; pending.delete(m.id); m.ok ? p.res(m.value) : p.rej(new Error(m.error)) })
2139
+ // The ONLY surface the untrusted code gets \u2014 every method is a mediated RPC, nothing ambient.
2140
+ const sys = new Proxy({}, { get: (_t, name) => (...args) => syscall(String(name), ...args) })
2141
+ const args = workerData
2142
+ ;(async () => { ${code} })().then(
2143
+ (value) => parentPort.postMessage({ done: true, value }),
2144
+ (err) => parentPort.postMessage({ done: true, error: String((err && err.stack) || err) }))
2145
+ `;
2146
+ function sandbox(code, opts = {}) {
2147
+ const allow = new Set(opts.allow ?? DEFAULT_ALLOW);
2148
+ return { name: opts.name ?? "sandbox", async main(sys, args) {
2149
+ const w = new Worker(harness(code), { eval: true, workerData: args, resourceLimits: { ...DEFAULT_RESOURCE_LIMITS, ...opts.resourceLimits } });
2150
+ sys.onCleanup(() => void w.terminate());
2151
+ await new Promise((resolve, reject) => {
2152
+ w.on("message", async (m) => {
2153
+ if (m.done) {
2154
+ await w.terminate();
2155
+ m.error ? reject(new Error(m.error)) : resolve();
2156
+ return;
2157
+ }
2158
+ const name = m.name;
2159
+ if (name === "exit") {
2160
+ await w.terminate();
2161
+ resolve();
2162
+ return;
2163
+ }
2164
+ if (!allow.has(name)) {
2165
+ w.postMessage({ id: m.id, ok: false, error: `sandbox: syscall "${name}" denied` });
2166
+ return;
2167
+ }
2168
+ try {
2169
+ const fn = sys[name];
2170
+ if (typeof fn !== "function") throw new Error(`sandbox: no syscall "${name}"`);
2171
+ const value = await fn.apply(sys, m.args ?? []);
2172
+ w.postMessage({ id: m.id, ok: true, value });
2173
+ } catch (e) {
2174
+ w.postMessage({ id: m.id, ok: false, error: e instanceof Error ? e.message : String(e) });
2175
+ }
2176
+ });
2177
+ w.on("error", reject);
2178
+ });
2179
+ } };
2180
+ }
2181
+
2182
+ // src/userland/longmem.ts
2183
+ var norm = (s) => s.trim().replace(/\s+/g, " ").toLowerCase();
2184
+ var LongMemory = class {
2185
+ recaller;
2186
+ by = /* @__PURE__ */ new Map();
2187
+ seen = /* @__PURE__ */ new Set();
2188
+ // normalized text → dedup (no lesson rot)
2189
+ persist;
2190
+ now;
2191
+ constructor(opts = {}) {
2192
+ this.recaller = opts.recaller ?? new KeywordRecaller();
2193
+ this.persist = opts.persist;
2194
+ this.now = opts.now ?? Date.now;
2195
+ if (this.persist) for (const l of this.persist.load()) this.ingest(l);
2196
+ }
2197
+ ingest(l) {
2198
+ const n = norm(l.text);
2199
+ if (!n || this.seen.has(n)) return false;
2200
+ this.seen.add(n);
2201
+ this.by.set(l.id, l);
2202
+ this.recaller.index(l.id, l.text);
2203
+ return true;
2204
+ }
2205
+ /** Add a distilled lesson (deduped, lossy by design). Returns false if it was a duplicate/empty. */
2206
+ remember(text, tags = []) {
2207
+ const l = { id: newSegId(), text: text.trim(), tags, ts: this.now(), uses: 0 };
2208
+ if (!this.ingest(l)) return false;
2209
+ this.persist?.append(l);
2210
+ return true;
2211
+ }
2212
+ /** Recall the k most relevant lessons for a query (bumps their use count — popularity signal). */
2213
+ recall(query, k = 4) {
2214
+ const out = [];
2215
+ for (const id of this.recaller.search(query, new Set(this.by.keys()), k)) {
2216
+ const l = this.by.get(id);
2217
+ if (l) {
2218
+ l.uses++;
2219
+ out.push(l);
2220
+ }
2221
+ }
2222
+ return out;
2223
+ }
2224
+ all() {
2225
+ return [...this.by.values()];
2226
+ }
2227
+ get size() {
2228
+ return this.by.size;
2229
+ }
2230
+ };
2231
+
2232
+ // src/userland/distill.ts
2233
+ var DISTILL_SYSTEM = "You distill a finished agent session into AT MOST 3 short, REUSABLE lessons \u2014 concrete tactics or gotchas that would help on a similar task next time (NOT a recap of what happened). One per line, imperative, \u2264140 chars, no numbering. If nothing is genuinely reusable, output nothing.";
2234
+ var parseLessons = (text) => text.split("\n").map((s) => s.replace(/^[-*•\d.\s]+/, "").trim()).filter((s) => s.length > 8).slice(0, 3);
2235
+ function llmDistiller(chat, maxTokens = 400) {
2236
+ return async (messages) => {
2237
+ const transcript = messages.filter((m) => m.role !== "system").map((m) => `${m.role}: ${typeof m.content === "string" ? m.content : ""}`).join("\n").slice(-6e3);
2238
+ if (!transcript.trim()) return [];
2239
+ const r = await chat({ messages: [{ role: "system", content: DISTILL_SYSTEM }, { role: "user", content: transcript }], maxTokens });
2240
+ return parseLessons(r.text ?? "");
2241
+ };
2242
+ }
2243
+ async function learn(mem, distill, messages, tags = []) {
2244
+ let n = 0;
2245
+ for (const lesson of await distill(messages)) if (mem.remember(lesson, tags)) n++;
2246
+ return n;
2247
+ }
2248
+
2249
+ // src/userland/persona.ts
2250
+ var PersonaRegistry = class {
2251
+ constructor(persist, now = Date.now) {
2252
+ this.persist = persist;
2253
+ this.now = now;
2254
+ if (persist) for (const p of persist.load()) this.by.set(p.name, p);
2255
+ }
2256
+ by = /* @__PURE__ */ new Map();
2257
+ /** Save (or replace) a persona by name. */
2258
+ save(p) {
2259
+ const full = { ...p, ts: p.ts ?? this.now() };
2260
+ this.by.set(full.name, full);
2261
+ this.persist?.save(full);
2262
+ return full;
2263
+ }
2264
+ get(name) {
2265
+ return this.by.get(name);
2266
+ }
2267
+ list() {
2268
+ return [...this.by.values()];
2269
+ }
2270
+ forDomain(domain) {
2271
+ return this.list().filter((p) => p.domain === domain);
2272
+ }
2273
+ };
2274
+ function instantiate(p, task, opts = {}) {
2275
+ const lessons = opts.memory?.recall(`${p.domain} ${task}`, opts.recall ?? 4) ?? [];
2276
+ const system = lessons.length ? `${p.system}
2277
+
2278
+ Lessons from past work (apply when relevant):
2279
+ ${lessons.map((l) => `- ${l.text}`).join("\n")}` : p.system;
2280
+ return { task, system, tools: p.tools };
2281
+ }
2282
+
2283
+ // src/userland/promptsmith.ts
2284
+ var SMITH_SYSTEM = "You are a world-class prompt engineer. Write a SYSTEM PROMPT for an autonomous, tool-using agent specialized in the given domain. It must: state the persona + quality bar; prescribe a ground\u2192act\u2192verify workflow; be concrete and concise (\u2264200 words); and bake in any supplied lessons. Output ONLY the system prompt text \u2014 no preamble, no quotes.";
2285
+ function brief(b) {
2286
+ const parts = [`Domain: ${b.domain}`];
2287
+ if (b.goal) parts.push(`Goal: ${b.goal}`);
2288
+ if (b.tools?.length) parts.push(`Available tools: ${b.tools.join(", ")}`);
2289
+ if (b.lessons?.length) parts.push(`Lessons to bake in:
2290
+ ${b.lessons.map((l) => `- ${l}`).join("\n")}`);
2291
+ return parts.join("\n");
2292
+ }
2293
+ async function forgePrompt(chat, b, maxTokens = 600) {
2294
+ const r = await chat({ messages: [{ role: "system", content: SMITH_SYSTEM }, { role: "user", content: brief(b) }], maxTokens });
2295
+ return (r.text ?? "").trim();
2296
+ }
2297
+ async function refinePrompt(chat, base, feedback, maxTokens = 600) {
2298
+ const r = await chat({ messages: [
2299
+ { role: "system", content: SMITH_SYSTEM + " You are given a CURRENT prompt and FEEDBACK; return an improved version." },
2300
+ { role: "user", content: `CURRENT PROMPT:
2301
+ ${base}
2302
+
2303
+ FEEDBACK:
2304
+ ${feedback}` }
2305
+ ], maxTokens });
2306
+ return (r.text ?? "").trim();
2307
+ }
2308
+ async function forgeFromMemory(chat, domain, mem, opts = {}) {
2309
+ const lessons = mem.recall(domain + (opts.goal ? " " + opts.goal : ""), opts.k ?? 8).map((l) => l.text);
2310
+ return forgePrompt(chat, { domain, goal: opts.goal, lessons, tools: opts.tools });
2311
+ }
2312
+
2313
+ // src/userland/evolve.ts
2314
+ async function tournament(candidates, score) {
2315
+ const scored = await Promise.all(candidates.map(async (c) => ({ ...c, ...await score(c.prompt) })));
2316
+ return scored.sort((a, b) => b.fitness - a.fitness);
2317
+ }
2318
+ async function evolve(base, variants, score, margin = 0) {
2319
+ const ranked = await tournament([{ prompt: base, label: "baseline" }, ...variants.map((p, i) => ({ prompt: p, label: `v${i + 1}` }))], score);
2320
+ const baseFit = ranked.find((r) => r.label === "baseline").fitness;
2321
+ const top = ranked[0];
2322
+ const improved = top.label !== "baseline" && top.fitness > baseFit + margin;
2323
+ return { winner: improved ? top.prompt : base, improved, ranked };
2324
+ }
2325
+ function meteredFitness(r) {
2326
+ return r.correct * 1e3 - (r.steps ?? 0) * 2 - (r.tokens ?? 0) / 1e3 - (r.usd ?? 0) * 10;
2327
+ }
2328
+
2329
+ // src/userland/expert.ts
2330
+ var PromptExpert = class {
2331
+ constructor(chat, opts = {}) {
2332
+ this.chat = chat;
2333
+ this.memory = new LongMemory({ persist: opts.memory, now: opts.now });
2334
+ this.personas = new PersonaRegistry(opts.personas, opts.now);
2335
+ this.distill = opts.distiller ?? llmDistiller(chat);
2336
+ }
2337
+ memory;
2338
+ personas;
2339
+ distill;
2340
+ /** Reflect on a finished session → fold reusable lessons into long-term memory. Returns added count. */
2341
+ learn(messages, tags) {
2342
+ return learn(this.memory, this.distill, messages, tags);
2343
+ }
2344
+ /** Forge (or refresh) a domain persona from accumulated lessons, then register it. */
2345
+ async specialize(domain, opts = {}) {
2346
+ const system = await forgeFromMemory(this.chat, domain, this.memory, { goal: opts.goal, tools: opts.tools?.map((t) => t.name) });
2347
+ return this.personas.save({ name: opts.name ?? domain, domain, system, tools: opts.tools });
2348
+ }
2349
+ /** Instantiate a persona for a task (enriched with recalled lessons) → ready for reactAgent/conversationAgent. */
2350
+ hire(name, task, recall = 4) {
2351
+ const p = this.personas.get(name);
2352
+ return p ? instantiate(p, task, { memory: this.memory, recall }) : null;
2353
+ }
2354
+ };
2355
+
2356
+ // src/drivers/llm.ts
2357
+ function llmDriver(chat, name = "llm") {
2358
+ return { name, async open() {
2359
+ return { async read(req) {
2360
+ const r = await chat(req);
2361
+ const u = r.usage ?? {};
2362
+ return { value: r, cost: { tokens: (u.promptTokens ?? 0) + (u.completionTokens ?? 0), usd: u.usd } };
2363
+ } };
2364
+ } };
2365
+ }
2366
+
2367
+ // src/drivers/tools.ts
2368
+ function toolDriver(tools, name = "tools") {
2369
+ return { name, async open() {
2370
+ return { async read(req) {
2371
+ const { tool, input } = req;
2372
+ const fn = tools[tool];
2373
+ if (!fn) return { value: { error: `no such tool: ${tool}` }, cost: { toolCalls: 1 } };
2374
+ try {
2375
+ return { value: await fn(input), cost: { toolCalls: 1 } };
2376
+ } catch (e) {
2377
+ return { value: { error: e instanceof Error ? e.message : String(e) }, cost: { toolCalls: 1 } };
2378
+ }
2379
+ } };
2380
+ } };
2381
+ }
2382
+
2383
+ // src/drivers/kv.ts
2384
+ function kvDriver(name = "kv") {
2385
+ const store = /* @__PURE__ */ new Map();
2386
+ return { name, async open() {
2387
+ return {
2388
+ async read(req) {
2389
+ const { get } = req;
2390
+ return { value: store.has(get) ? store.get(get) : null };
2391
+ },
2392
+ async write(data) {
2393
+ const { set } = data;
2394
+ store.set(set.key, set.value);
2395
+ return { value: true };
2396
+ }
2397
+ };
2398
+ } };
2399
+ }
2400
+
2401
+ // src/drivers/gateway.ts
2402
+ import { randomUUID } from "node:crypto";
2403
+ function gatewayDriver(forward, name = "gateway") {
2404
+ return { name, async open() {
2405
+ return { async write(data) {
2406
+ const { to, body } = data;
2407
+ return { value: forward(to, body), cost: { reductions: 1 } };
2408
+ } };
2409
+ } };
2410
+ }
2411
+ function secureGatewayDriver(forward, key, name = "gateway") {
2412
+ return { name, async open() {
2413
+ return { async write(data) {
2414
+ const { to, body } = data;
2415
+ return { value: forward(seal(key, to, body, randomUUID())), cost: { reductions: 1 } };
2416
+ } };
2417
+ } };
2418
+ }
2419
+
2420
+ // src/drivers/worker.ts
2421
+ import { Worker as Worker2 } from "node:worker_threads";
2422
+ var harness2 = (src) => `
2423
+ const { parentPort, workerData, threadId } = require('node:worker_threads')
2424
+ const fn = (input) => { ${src} }
2425
+ Promise.resolve(fn(workerData.input)).then(
2426
+ (value) => parentPort.postMessage({ ok: true, value, tid: threadId }),
2427
+ (err) => parentPort.postMessage({ ok: false, error: String((err && err.stack) || err) }))
2428
+ `;
2429
+ function workerDriver(tasks, name = "worker") {
2430
+ return { name, async open() {
2431
+ return { async read(req) {
2432
+ const { task, input } = req;
2433
+ const src = tasks[task];
2434
+ if (!src) throw new Error(`worker: no such task "${task}"`);
2435
+ const out = await new Promise((resolve, reject) => {
2436
+ const w = new Worker2(harness2(src), { eval: true, workerData: { input } });
2437
+ w.once("message", (m) => {
2438
+ w.terminate();
2439
+ m.ok ? resolve({ value: m.value, tid: m.tid }) : reject(new Error(m.error));
2440
+ });
2441
+ w.once("error", reject);
2442
+ });
2443
+ return { value: out, cost: { toolCalls: 1 } };
2444
+ } };
2445
+ } };
2446
+ }
2447
+
2448
+ // src/drivers/wasm.ts
2449
+ function wasmDriver(modules, name = "wasm") {
2450
+ const compiled = /* @__PURE__ */ new Map();
2451
+ return { name, async open() {
2452
+ return { async read(req) {
2453
+ const { module, fn, args } = req;
2454
+ let mod = compiled.get(module);
2455
+ if (!mod) {
2456
+ const bytes = modules[module];
2457
+ if (!bytes) throw new Error(`wasm: no module "${module}"`);
2458
+ mod = new WebAssembly.Module(bytes);
2459
+ compiled.set(module, mod);
2460
+ }
2461
+ const inst = await WebAssembly.instantiate(mod, {});
2462
+ const f = inst.exports[fn];
2463
+ if (typeof f !== "function") throw new Error(`wasm: no export "${fn}"`);
2464
+ return { value: f(...args ?? []), cost: { toolCalls: 1 } };
2465
+ } };
2466
+ } };
2467
+ }
2468
+ export {
2469
+ ABI_VERSION,
2470
+ Bm25Recaller,
2471
+ CapError,
2472
+ DEFAULT_ALLOW,
2473
+ DefaultPager,
2474
+ EventLog,
2475
+ FileStore,
2476
+ Journal,
2477
+ Kernel,
2478
+ KernelError,
2479
+ KeywordRecaller,
2480
+ LongMemory,
2481
+ MemoryStore,
2482
+ NonceGuard,
2483
+ PersonaRegistry,
2484
+ PromptExpert,
2485
+ QuotaExceeded,
2486
+ RealClock,
2487
+ VectorRecaller,
2488
+ VirtualClock,
2489
+ conversationAgent,
2490
+ dialTcp,
2491
+ drv,
2492
+ estTokens,
2493
+ eventsFromStore,
2494
+ evolve,
2495
+ forgeFromMemory,
2496
+ forgePrompt,
2497
+ freshNonce,
2498
+ gatewayDriver,
2499
+ hasRight,
2500
+ hashEmbed,
2501
+ instantiate,
2502
+ journalFromStore,
2503
+ kvDriver,
2504
+ learn,
2505
+ llmDistiller,
2506
+ llmDriver,
2507
+ lookup,
2508
+ meteredFitness,
2509
+ nameService,
2510
+ newCapRef,
2511
+ newMsgId,
2512
+ nursery,
2513
+ parseLessons,
2514
+ reactAgent,
2515
+ refinePrompt,
2516
+ register,
2517
+ sandbox,
2518
+ seal,
2519
+ secureGatewayDriver,
2520
+ serveTcp,
2521
+ supervisor,
2522
+ toolDriver,
2523
+ tournament,
2524
+ verify,
2525
+ wasmDriver,
2526
+ workerDriver
2527
+ };