@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.
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/abi/capability.d.ts +45 -0
- package/dist/abi/driver.d.ts +23 -0
- package/dist/abi/ids.d.ts +20 -0
- package/dist/abi/index.d.ts +11 -0
- package/dist/abi/memory.d.ts +20 -0
- package/dist/abi/message.d.ts +18 -0
- package/dist/abi/syscall.d.ts +158 -0
- package/dist/drivers/gateway.d.ts +8 -0
- package/dist/drivers/index.d.ts +8 -0
- package/dist/drivers/kv.d.ts +2 -0
- package/dist/drivers/llm.d.ts +46 -0
- package/dist/drivers/tools.d.ts +7 -0
- package/dist/drivers/wasm.d.ts +2 -0
- package/dist/drivers/worker.d.ts +3 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +2527 -0
- package/dist/kernel/account.d.ts +34 -0
- package/dist/kernel/captable.d.ts +38 -0
- package/dist/kernel/clock.d.ts +30 -0
- package/dist/kernel/journal.d.ts +25 -0
- package/dist/kernel/kernel.d.ts +227 -0
- package/dist/kernel/log.d.ts +71 -0
- package/dist/kernel/memory.d.ts +97 -0
- package/dist/kernel/pcb.d.ts +60 -0
- package/dist/kernel/store.d.ts +36 -0
- package/dist/kernel/transport.d.ts +26 -0
- package/dist/net/tcp.d.ts +22 -0
- package/dist/userland/agent.d.ts +62 -0
- package/dist/userland/distill.d.ts +9 -0
- package/dist/userland/evolve.d.ts +29 -0
- package/dist/userland/expert.d.ts +28 -0
- package/dist/userland/longmem.d.ts +32 -0
- package/dist/userland/nameservice.d.ts +6 -0
- package/dist/userland/nursery.d.ts +9 -0
- package/dist/userland/persona.d.ts +34 -0
- package/dist/userland/promptsmith.d.ts +18 -0
- package/dist/userland/sandbox.d.ts +14 -0
- package/dist/userland/supervisor.d.ts +21 -0
- package/package.json +49 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Quota } from '../abi/index';
|
|
2
|
+
export type Meter = 'tokens' | 'toolCalls' | 'usd' | 'children';
|
|
3
|
+
declare const METERS: readonly Meter[];
|
|
4
|
+
export declare class QuotaExceeded extends Error {
|
|
5
|
+
readonly resource: keyof Quota;
|
|
6
|
+
constructor(resource: keyof Quota);
|
|
7
|
+
}
|
|
8
|
+
export declare class Account {
|
|
9
|
+
private readonly now;
|
|
10
|
+
private readonly parent;
|
|
11
|
+
private readonly rem;
|
|
12
|
+
private readonly spent;
|
|
13
|
+
private readonly wallMs?;
|
|
14
|
+
readonly mem?: number;
|
|
15
|
+
readonly maxMailbox?: number;
|
|
16
|
+
readonly bornAt: number;
|
|
17
|
+
constructor(quota?: Quota, now?: () => number, parent?: Account | null);
|
|
18
|
+
/** Charge n of a meter against this account AND every ancestor (atomic: all-or-nothing). */
|
|
19
|
+
charge(meter: Meter, n: number): void;
|
|
20
|
+
/** Throw if wall-clock lifetime exceeded (per-process, not bubbled). */
|
|
21
|
+
checkWall(): void;
|
|
22
|
+
/** Give resources back up the chain (refunds never fail — no pre-check). */
|
|
23
|
+
private refund;
|
|
24
|
+
/** A child account in the tree. Spawning counts 1 against the chain's `children` budget. */
|
|
25
|
+
child(quota: Quota): Account;
|
|
26
|
+
/** Called when the owning process exits: free the one `children` slot it occupies in its parent's
|
|
27
|
+
* subtree. `children` is a CONCURRENT cap (cgroup pids.max) — live count, not a lifetime total —
|
|
28
|
+
* so a supervisor that spawns and reaps in a loop keeps working. (tokens/usd stay cumulative.) */
|
|
29
|
+
release(): void;
|
|
30
|
+
remaining(): Quota;
|
|
31
|
+
/** Aggregate usage of this node and everything below it (charges bubbled up to here). */
|
|
32
|
+
usage(): Record<Meter, number>;
|
|
33
|
+
}
|
|
34
|
+
export { METERS };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Pid, CapRef } from '../abi/index';
|
|
2
|
+
import type { Resource, Right, Capability, Caveat } from '../abi/capability';
|
|
3
|
+
export declare class CapError extends Error {
|
|
4
|
+
constructor(msg: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class CapTable {
|
|
7
|
+
private readonly now;
|
|
8
|
+
private readonly caps;
|
|
9
|
+
private readonly derived;
|
|
10
|
+
private readonly parentOf;
|
|
11
|
+
private readonly byOwner;
|
|
12
|
+
/** `now` is injected (the kernel clock) so caveat expiry is deterministic/replayable, not wall-time. */
|
|
13
|
+
constructor(now?: () => number);
|
|
14
|
+
private own;
|
|
15
|
+
private disown;
|
|
16
|
+
/** A cap is live unless revoked OR a caveat is unsatisfied (e.g. it has expired). */
|
|
17
|
+
private satisfied;
|
|
18
|
+
/** Kernel-internal: create authority. Not reachable from userland. */
|
|
19
|
+
mint(owner: Pid, resource: Resource, rights: Iterable<Right>, caveats?: readonly Caveat[]): CapRef;
|
|
20
|
+
/** Userland: weaken a cap you own to a subset of its rights, for least-privilege delegation. */
|
|
21
|
+
derive(owner: Pid, parent: CapRef, rights: Iterable<Right>): CapRef;
|
|
22
|
+
/** Userland: weaken a cap you own — a subset of rights AND/OR added caveats (e.g. an expiry). Both only ever
|
|
23
|
+
* RESTRICT: rights must be a subset, and the child inherits ALL the parent's caveats plus the new ones, so a
|
|
24
|
+
* caveat (a TTL, say) can't be stripped by re-deriving. The result is a fresh cap revoked with the parent. */
|
|
25
|
+
attenuate(owner: Pid, parent: CapRef, rights: Iterable<Right> | undefined, caveats: readonly Caveat[]): CapRef;
|
|
26
|
+
/** Hand ownership to another process (delegation rides on IPC, like fd-passing). */
|
|
27
|
+
transfer(ref: CapRef, to: Pid): void;
|
|
28
|
+
lookup(ref: CapRef): Capability | undefined;
|
|
29
|
+
/** Does this process hold any cap over a resource of the given kind? (gates ambient-looking syscalls like spawn) */
|
|
30
|
+
ownsKind(pid: Pid, kind: Resource['kind']): boolean;
|
|
31
|
+
/** Look up + assert the holder owns it and it grants `right`. The gate every privileged syscall passes. */
|
|
32
|
+
require(holder: Pid, ref: CapRef, right: Right): Capability;
|
|
33
|
+
/** Revoke a cap and, transitively, everything derived from it. Frees it from the table (and the
|
|
34
|
+
* owner index) — the flag stays set too, so any lingering Capability reference still reads revoked. */
|
|
35
|
+
revoke(ref: CapRef): void;
|
|
36
|
+
/** On process exit: drop all authority it held — O(caps it owned), not O(caps ever minted). */
|
|
37
|
+
revokeOwnedBy(pid: Pid): void;
|
|
38
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface Timer {
|
|
2
|
+
fire: number;
|
|
3
|
+
readonly cb: () => void;
|
|
4
|
+
cancelled?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface Clock {
|
|
7
|
+
readonly virtual: boolean;
|
|
8
|
+
now(): number;
|
|
9
|
+
after(ms: number, cb: () => void): Timer;
|
|
10
|
+
cancel(t: Timer): void;
|
|
11
|
+
/** Advance to the next pending event when idle; fires it. Returns false if nothing is pending. */
|
|
12
|
+
advance(): boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare class RealClock implements Clock {
|
|
15
|
+
readonly virtual = false;
|
|
16
|
+
private readonly handles;
|
|
17
|
+
now(): number;
|
|
18
|
+
after(ms: number, cb: () => void): Timer;
|
|
19
|
+
cancel(t: Timer): void;
|
|
20
|
+
advance(): boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare class VirtualClock implements Clock {
|
|
23
|
+
readonly virtual = true;
|
|
24
|
+
private t;
|
|
25
|
+
private pending;
|
|
26
|
+
now(): number;
|
|
27
|
+
after(ms: number, cb: () => void): Timer;
|
|
28
|
+
cancel(t: Timer): void;
|
|
29
|
+
advance(): boolean;
|
|
30
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Pid, DriverResult } from '../abi/index';
|
|
2
|
+
export type KernelMode = 'live' | 'record' | 'replay' | 'recover';
|
|
3
|
+
/** A journaled driver op: either its result, OR the error it threw. Recording the FAILURE too is what lets a
|
|
4
|
+
* run that hit a driver error (e.g. an LLM HTTP failure) replay faithfully — otherwise replay would miss the
|
|
5
|
+
* op entirely and abort with "no journal entry" instead of reproducing the original throw. */
|
|
6
|
+
export type Journaled = DriverResult | {
|
|
7
|
+
readonly error: string;
|
|
8
|
+
};
|
|
9
|
+
interface Entry {
|
|
10
|
+
readonly pid: Pid;
|
|
11
|
+
readonly op: number;
|
|
12
|
+
readonly result: Journaled;
|
|
13
|
+
}
|
|
14
|
+
export declare class Journal {
|
|
15
|
+
private readonly io;
|
|
16
|
+
private readonly seq;
|
|
17
|
+
private static key;
|
|
18
|
+
record(pid: Pid, op: number, result: Journaled): void;
|
|
19
|
+
get(pid: Pid, op: number): Journaled | undefined;
|
|
20
|
+
get size(): number;
|
|
21
|
+
/** Serialize a recorded run so it can be persisted and replayed later. */
|
|
22
|
+
toJSON(): readonly Entry[];
|
|
23
|
+
static fromJSON(seq: readonly Entry[]): Journal;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import type { Pid, CapRef, Segment, Pager, Message, ProcessImage, Quota, ExitReason, LogLevel, Driver } from '../abi/index';
|
|
2
|
+
import { type Meter } from './account';
|
|
3
|
+
import { type StampedEvent } from './log';
|
|
4
|
+
import { type Recaller } from './memory';
|
|
5
|
+
import { Journal, type KernelMode } from './journal';
|
|
6
|
+
import { type Clock } from './clock';
|
|
7
|
+
import { type Store } from './store';
|
|
8
|
+
import { type SealedEnvelope } from './transport';
|
|
9
|
+
export declare class KernelError extends Error {
|
|
10
|
+
constructor(msg: string);
|
|
11
|
+
}
|
|
12
|
+
export interface KernelConfig {
|
|
13
|
+
readonly logCap?: number;
|
|
14
|
+
readonly logSink?: (e: StampedEvent) => void;
|
|
15
|
+
readonly onLog?: (pid: Pid, level: LogLevel, msg: string, fields?: Record<string, unknown>) => void;
|
|
16
|
+
/** Paging policy for context memory. Default evicts LRU + leaves a placeholder; inject one with an LLM summarizer. */
|
|
17
|
+
readonly pager?: Pager;
|
|
18
|
+
/** Record/replay driver I/O for reproducible runs. 'live' (default) = no journaling. */
|
|
19
|
+
readonly mode?: KernelMode;
|
|
20
|
+
readonly journal?: Journal;
|
|
21
|
+
/** Time source. Default RealClock; pass a VirtualClock for instant, fully-deterministic runs. */
|
|
22
|
+
readonly clock?: Clock;
|
|
23
|
+
/** Per-process recall strategy for paged-out context. Default keyword; pass `() => new VectorRecaller(embed)`. */
|
|
24
|
+
readonly recaller?: () => Recaller;
|
|
25
|
+
/** Append-only store for event-sourcing: events (audit) + driver I/O (replay) are written here. */
|
|
26
|
+
readonly store?: Store;
|
|
27
|
+
/** Shared secret for authenticated cross-node ingress. Set it to accept sealed envelopes via `ingress()`. */
|
|
28
|
+
readonly transportKey?: string;
|
|
29
|
+
/** This node's identity. Scopes egress nonces so two nodes never collide; required to dial/send remotely. */
|
|
30
|
+
readonly nodeId?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface BootOptions<A = unknown> {
|
|
33
|
+
readonly args?: A;
|
|
34
|
+
readonly quota?: Quota;
|
|
35
|
+
readonly grant?: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
/** A process's durable, serializable state (everything but the JS stack + kernel-local caps). */
|
|
38
|
+
export interface ProcessSnapshot {
|
|
39
|
+
readonly abi: number;
|
|
40
|
+
readonly image: string;
|
|
41
|
+
readonly args: unknown;
|
|
42
|
+
readonly priority: number;
|
|
43
|
+
readonly deadline: number | null;
|
|
44
|
+
readonly stopped?: boolean;
|
|
45
|
+
readonly account: Quota;
|
|
46
|
+
readonly mailbox: readonly Message[];
|
|
47
|
+
readonly context: readonly {
|
|
48
|
+
body: unknown;
|
|
49
|
+
tokens: number;
|
|
50
|
+
pinned: boolean;
|
|
51
|
+
}[];
|
|
52
|
+
}
|
|
53
|
+
/** The boot message init (pid 1) receives first, carrying its initial capabilities. */
|
|
54
|
+
export interface BootGrant {
|
|
55
|
+
readonly $: 'boot';
|
|
56
|
+
readonly spawn: CapRef;
|
|
57
|
+
readonly devices: Readonly<Record<string, CapRef>>;
|
|
58
|
+
readonly reply?: CapRef;
|
|
59
|
+
}
|
|
60
|
+
export declare class Kernel {
|
|
61
|
+
private readonly proc;
|
|
62
|
+
private readonly caps;
|
|
63
|
+
private readonly log;
|
|
64
|
+
private readonly drivers;
|
|
65
|
+
private readonly onLog?;
|
|
66
|
+
private readonly pager;
|
|
67
|
+
private readonly mode;
|
|
68
|
+
private readonly journal;
|
|
69
|
+
private readonly clock;
|
|
70
|
+
private readonly mkRecaller;
|
|
71
|
+
private readonly store?;
|
|
72
|
+
private readonly transportKey?;
|
|
73
|
+
private readonly nodeId;
|
|
74
|
+
private readonly peers;
|
|
75
|
+
private egressSeq;
|
|
76
|
+
private readonly images;
|
|
77
|
+
private readonly pendingSpawns;
|
|
78
|
+
private readonly remoteMonitors;
|
|
79
|
+
private readonly freshWindowMs;
|
|
80
|
+
private readonly nonces;
|
|
81
|
+
private runQueue;
|
|
82
|
+
private qHead;
|
|
83
|
+
private special;
|
|
84
|
+
private enqSeq;
|
|
85
|
+
private current;
|
|
86
|
+
private shutdownFlag;
|
|
87
|
+
private readonly exitWaiters;
|
|
88
|
+
private readonly exitHooks;
|
|
89
|
+
private readonly reaped;
|
|
90
|
+
private readonly registry;
|
|
91
|
+
private initPid;
|
|
92
|
+
constructor(cfg?: KernelConfig);
|
|
93
|
+
/** The I/O journal (record mode populates it; persist via journal.toJSON()). */
|
|
94
|
+
get io(): Journal;
|
|
95
|
+
/** Run a driver op through record/replay: replay returns the journaled result; record logs the live one. */
|
|
96
|
+
private journaled;
|
|
97
|
+
/** Register a driver (LLM, tools, KV, gateway, WASM, worker) so the kernel
|
|
98
|
+
* can hand its device capabilities to processes at boot/grant time. */
|
|
99
|
+
registerDriver(driver: Driver): void;
|
|
100
|
+
/** Crash recovery: build a kernel that REPLAYS persisted driver I/O from a store. Re-running the
|
|
101
|
+
* same images reproduces the original run exactly — every read/write returns its journaled result,
|
|
102
|
+
* so no live driver is touched. The event-sourcing log IS the recovery mechanism. */
|
|
103
|
+
static replayFrom(store: Store, cfg?: Omit<KernelConfig, 'mode' | 'journal'>): Kernel;
|
|
104
|
+
/** Partial crash recovery: REPLAY the persisted prefix (no live I/O), then — when a process runs past the
|
|
105
|
+
* point the journal reaches (the original run crashed mid-flight) — RESUME it live, appending the new ops to
|
|
106
|
+
* the same store. Unlike replayFrom (strict reproduce; aborts on a missing op), this lets a long-running run
|
|
107
|
+
* pick up where it died. Keeps the store so the resumed tail is durable and a second crash is recoverable too. */
|
|
108
|
+
static recoverFrom(store: Store, cfg?: Omit<KernelConfig, 'mode' | 'journal' | 'store'>): Kernel;
|
|
109
|
+
/** Boot the system: create pid 1 (init) with the given image, optional root
|
|
110
|
+
* quota, and a set of driver names it may open. Init receives a `BootGrant`
|
|
111
|
+
* as its first message, carrying a `spawn` cap and `devices` record. */
|
|
112
|
+
boot<A>(init: ProcessImage<A>, opts?: BootOptions<A>): Promise<Pid>;
|
|
113
|
+
/** Resolve when a process terminates (test/orchestration helper). */
|
|
114
|
+
waitExit(pid: Pid): Promise<ExitReason>;
|
|
115
|
+
/** Query a snapshot of live processes: pid, name, status, mailbox depth,
|
|
116
|
+
* remaining budget, usage, and memory stats — the userland `ps` analogue. */
|
|
117
|
+
ps(): {
|
|
118
|
+
pid: Pid;
|
|
119
|
+
name: string;
|
|
120
|
+
parent: Pid | null;
|
|
121
|
+
status: import("./pcb").ProcStatus;
|
|
122
|
+
stopped: boolean;
|
|
123
|
+
mailbox: number;
|
|
124
|
+
budget: Quota;
|
|
125
|
+
used: Record<Meter, number>;
|
|
126
|
+
mem: {
|
|
127
|
+
resident: number;
|
|
128
|
+
swapped: number;
|
|
129
|
+
size: number;
|
|
130
|
+
ceiling: number | undefined;
|
|
131
|
+
};
|
|
132
|
+
}[];
|
|
133
|
+
/** Tail the last N stamped events from the kernel event log (audit trail). */
|
|
134
|
+
trace(n?: number): readonly StampedEvent[];
|
|
135
|
+
/** Monitoring snapshot: live process count + running aggregates over the whole event history. */
|
|
136
|
+
metrics(): {
|
|
137
|
+
processes: number;
|
|
138
|
+
events: number;
|
|
139
|
+
spawns: number;
|
|
140
|
+
exits: number;
|
|
141
|
+
sends: number;
|
|
142
|
+
scheds: number;
|
|
143
|
+
faults: number;
|
|
144
|
+
exitsByReason: Record<string, number>;
|
|
145
|
+
charged: Record<string, number>;
|
|
146
|
+
};
|
|
147
|
+
/** Prometheus text-exposition of the same counters — scrape it straight into a TSDB. */
|
|
148
|
+
metricsText(): string;
|
|
149
|
+
/** Inspect the resident context (working set) of a live process — the
|
|
150
|
+
* segments currently visible to its LLM. For observability / debugging. */
|
|
151
|
+
inspect(pid: Pid): readonly Segment[];
|
|
152
|
+
/** Transport ingress: inject a message into a local process from OUTSIDE the kernel
|
|
153
|
+
* (a gateway/transport bridging another node). Returns false if no such live process. */
|
|
154
|
+
deliverExternal(pid: Pid, body: unknown): boolean;
|
|
155
|
+
/** Register a route to a peer node: `send` will egress envelopes addressed to it (a dialTcp.send, or any
|
|
156
|
+
* sink for tests). The matching transportKey must be shared with the peer, whose ingress() verifies them. */
|
|
157
|
+
connect(node: string, egress: (env: SealedEnvelope) => boolean): void;
|
|
158
|
+
/** Publish an image that PEER nodes may spawn by name (a ProcessImage holds closures → it can't cross the
|
|
159
|
+
* wire; the host already has it, so remote spawn is "spawn the image you registered as N"). `grant` lists
|
|
160
|
+
* the driver devices the spawned process may open. */
|
|
161
|
+
registerImage(name: string, image: ProcessImage, grant?: readonly string[]): void;
|
|
162
|
+
/** Egress a Down to a peer node's watcher pid (a normal data envelope; ingress delivers it to the mailbox). */
|
|
163
|
+
private sendDown;
|
|
164
|
+
/** Control-channel envelope (addressed to pid 0): a remote-spawn request/reply, or a remote-monitor request. */
|
|
165
|
+
private handleControl;
|
|
166
|
+
/** Spawn a peer-requested image locally, granting it a spawn cap, its allowed devices, and a `reply` proxy
|
|
167
|
+
* cap back to the requester (so the two can talk). Orphans reparent to init like any process. */
|
|
168
|
+
private hostRemote;
|
|
169
|
+
/** AUTHENTICATED transport ingress: the trust boundary for messages off the wire.
|
|
170
|
+
* Verifies the HMAC (constant-time) and rejects replays before anything reaches a
|
|
171
|
+
* process; a bad MAC, a reused nonce, or a dead target are all dropped (→ false).
|
|
172
|
+
* Capabilities never cross — a verified sender can address a pid, not forge authority. */
|
|
173
|
+
ingress(env: SealedEnvelope): boolean;
|
|
174
|
+
/**
|
|
175
|
+
* Snapshot a process's DURABLE state (context + mailbox + budget + sched).
|
|
176
|
+
* The JS execution stack is not serializable, so restore resumes by re-running
|
|
177
|
+
* the image against the restored state — sound for context-driven agents whose
|
|
178
|
+
* state IS their context. Capabilities are kernel-local authority and are NOT
|
|
179
|
+
* captured; the restorer re-grants them.
|
|
180
|
+
*/
|
|
181
|
+
snapshot(pid: Pid): ProcessSnapshot;
|
|
182
|
+
/** Restore a snapshot into THIS kernel as a fresh process running `image`. */
|
|
183
|
+
restore(snap: ProcessSnapshot, image: ProcessImage, opts?: {
|
|
184
|
+
parent?: Pid;
|
|
185
|
+
}): Promise<Pid>;
|
|
186
|
+
/** Graceful system shutdown: kill every live process with the given reason
|
|
187
|
+
* (default 'shutdown'), flush the event log, and stop scheduling. */
|
|
188
|
+
shutdown(reason?: ExitReason): Promise<void>;
|
|
189
|
+
private enqueue;
|
|
190
|
+
private makeRunnable;
|
|
191
|
+
/** SIGSTOP — pause a process: it stays alive but is never dispatched until resumed. Descheduled now if
|
|
192
|
+
* running; dropped from the ready queue if runnable (re-enqueued on resume); a parked recv just stays parked. */
|
|
193
|
+
private suspendProc;
|
|
194
|
+
/** SIGCONT — resume a stopped process. Safe even if it has nothing to do: a parked recv re-parks on an
|
|
195
|
+
* empty mailbox, so a spurious wake is a no-op. */
|
|
196
|
+
private resumeProc;
|
|
197
|
+
/** SIGTERM — a graceful, CATCHABLE stop request: deliver a `{ $: 'term' }` message the process can
|
|
198
|
+
* handle in its recv loop (clean up, then exit). Unlike kill/signal it never force-terminates; the
|
|
199
|
+
* handler IS the recv loop. (A stopped target gets it queued; it's seen on resume.) */
|
|
200
|
+
private termProc;
|
|
201
|
+
/** Pick the highest-priority runnable; ties broken by earliest deadline (EDF), then FIFO.
|
|
202
|
+
* Fast path (no priority/deadline in play): a plain FIFO dequeue, O(1) amortized — the
|
|
203
|
+
* general scan is taken only while a special process is queued. Both yield identical order. */
|
|
204
|
+
private pickNext;
|
|
205
|
+
private higher;
|
|
206
|
+
private dispatch;
|
|
207
|
+
/** Arm (or re-arm) a deadline: kill the process if it hasn't finished by then. */
|
|
208
|
+
private armDeadline;
|
|
209
|
+
/** Await an external promise (driver I/O) WITHOUT holding the CPU: the process parks so other
|
|
210
|
+
* processes run meanwhile; it's rescheduled when the promise settles. Enables true concurrency
|
|
211
|
+
* during I/O (e.g. a worker-thread offload runs in parallel while the kernel keeps scheduling). */
|
|
212
|
+
/** The single blocking primitive: park the running process (status→waiting, yield the CPU) until its
|
|
213
|
+
* `cont` is fired by a wake. recv, send-backpressure and parkOn all build their wait loops on this. */
|
|
214
|
+
private block;
|
|
215
|
+
private parkOn;
|
|
216
|
+
/** Park the running process and hand control to the scheduler (re-enters the ready set). */
|
|
217
|
+
private reschedule;
|
|
218
|
+
/** End-of-syscall fairness point: charge reductions; yield if the quantum is spent. */
|
|
219
|
+
private tick;
|
|
220
|
+
private runCleanups;
|
|
221
|
+
private startMain;
|
|
222
|
+
private deliver;
|
|
223
|
+
private terminate;
|
|
224
|
+
private chargeDriver;
|
|
225
|
+
private req;
|
|
226
|
+
private sysFor;
|
|
227
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Pid, ExitReason } from '../abi/index';
|
|
2
|
+
import type { Meter } from './account';
|
|
3
|
+
export type KEvent = {
|
|
4
|
+
readonly t: 'boot';
|
|
5
|
+
} | {
|
|
6
|
+
readonly t: 'shutdown';
|
|
7
|
+
readonly reason: string;
|
|
8
|
+
} | {
|
|
9
|
+
readonly t: 'spawn';
|
|
10
|
+
readonly pid: Pid;
|
|
11
|
+
readonly parent: Pid | null;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
} | {
|
|
14
|
+
readonly t: 'exit';
|
|
15
|
+
readonly pid: Pid;
|
|
16
|
+
readonly reason: ExitReason;
|
|
17
|
+
} | {
|
|
18
|
+
readonly t: 'send';
|
|
19
|
+
readonly from: Pid;
|
|
20
|
+
readonly to: Pid;
|
|
21
|
+
} | {
|
|
22
|
+
readonly t: 'recv';
|
|
23
|
+
readonly pid: Pid;
|
|
24
|
+
} | {
|
|
25
|
+
readonly t: 'sched';
|
|
26
|
+
readonly pid: Pid;
|
|
27
|
+
} | {
|
|
28
|
+
readonly t: 'charge';
|
|
29
|
+
readonly pid: Pid;
|
|
30
|
+
readonly meter: Meter;
|
|
31
|
+
readonly n: number;
|
|
32
|
+
} | {
|
|
33
|
+
readonly t: 'fault';
|
|
34
|
+
readonly pid: Pid;
|
|
35
|
+
readonly error: string;
|
|
36
|
+
} | {
|
|
37
|
+
readonly t: 'restart';
|
|
38
|
+
readonly pid: Pid;
|
|
39
|
+
readonly by: Pid;
|
|
40
|
+
} | {
|
|
41
|
+
readonly t: 'signal';
|
|
42
|
+
readonly pid: Pid;
|
|
43
|
+
readonly sig: 'stop' | 'cont' | 'term';
|
|
44
|
+
};
|
|
45
|
+
export type StampedEvent = KEvent & {
|
|
46
|
+
readonly seq: number;
|
|
47
|
+
readonly ts: number;
|
|
48
|
+
};
|
|
49
|
+
/** Running aggregates over the WHOLE event history (the ring is bounded, so counts can't be
|
|
50
|
+
* recomputed from it — they're tallied at emit time). The seed for metrics()/monitoring. */
|
|
51
|
+
export interface LogStats {
|
|
52
|
+
readonly byType: Record<string, number>;
|
|
53
|
+
readonly exitsByReason: Record<string, number>;
|
|
54
|
+
readonly charged: Record<string, number>;
|
|
55
|
+
}
|
|
56
|
+
export declare class EventLog {
|
|
57
|
+
private readonly cap;
|
|
58
|
+
private readonly sink?;
|
|
59
|
+
private readonly now;
|
|
60
|
+
private readonly buf;
|
|
61
|
+
private seq;
|
|
62
|
+
private readonly byType;
|
|
63
|
+
private readonly exitsByReason;
|
|
64
|
+
private readonly charged;
|
|
65
|
+
constructor(cap?: number, sink?: ((e: StampedEvent) => void) | undefined, now?: () => number);
|
|
66
|
+
emit(e: KEvent): void;
|
|
67
|
+
tail(n?: number): readonly StampedEvent[];
|
|
68
|
+
stats(): LogStats;
|
|
69
|
+
get count(): number;
|
|
70
|
+
clear(): void;
|
|
71
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { SegId, Segment, Pager } from '../abi/index';
|
|
2
|
+
/** Deterministic token estimate: ≈1.5 tokens per CJK char, ≈4 latin chars per token. Inject a real
|
|
3
|
+
* tokenizer via the pager if you need exactness; this only needs to not under-count (→ no surprise OOM). */
|
|
4
|
+
export declare const estTokens: (body: unknown) => number;
|
|
5
|
+
/** Default pager: evict LRU unpinned segments to free `need` tokens, leaving one summary in their place.
|
|
6
|
+
* Pass a `summarize` (e.g. an LLM call) for real gists; the default leaves a deterministic placeholder. */
|
|
7
|
+
export declare class DefaultPager implements Pager {
|
|
8
|
+
private readonly summarize?;
|
|
9
|
+
constructor(summarize?: ((segs: readonly Segment[]) => Promise<{
|
|
10
|
+
body: unknown;
|
|
11
|
+
tokens: number;
|
|
12
|
+
}>) | undefined);
|
|
13
|
+
pageOut(resident: readonly Segment[], need: number): Promise<{
|
|
14
|
+
evict: readonly SegId[];
|
|
15
|
+
summary?: Omit<Segment, 'id'>;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
/** Ranks swapped segments against a query. Indexes on page-out, drops on page-in/forget. */
|
|
19
|
+
export interface Recaller {
|
|
20
|
+
index(id: SegId, body: unknown): void;
|
|
21
|
+
remove(id: SegId): void;
|
|
22
|
+
search(query: string, candidates: ReadonlySet<SegId>, k: number): SegId[];
|
|
23
|
+
}
|
|
24
|
+
/** Default recaller: keyword (term-overlap) match — no model, deterministic. */
|
|
25
|
+
export declare class KeywordRecaller implements Recaller {
|
|
26
|
+
private readonly text;
|
|
27
|
+
index(id: SegId, body: unknown): void;
|
|
28
|
+
remove(id: SegId): void;
|
|
29
|
+
search(query: string, candidates: ReadonlySet<SegId>, k: number): SegId[];
|
|
30
|
+
}
|
|
31
|
+
/** BM25 recaller: ranks swapped segments by Okapi BM25 (term frequency · inverse document frequency ·
|
|
32
|
+
* length normalization) — far better than KeywordRecaller's raw term-overlap count, yet still
|
|
33
|
+
* deterministic and dependency-free (no embedding model). The default for context page-in. */
|
|
34
|
+
export declare class Bm25Recaller implements Recaller {
|
|
35
|
+
private readonly docs;
|
|
36
|
+
private readonly df;
|
|
37
|
+
private totalLen;
|
|
38
|
+
private readonly k1;
|
|
39
|
+
private readonly b;
|
|
40
|
+
index(id: SegId, body: unknown): void;
|
|
41
|
+
remove(id: SegId): void;
|
|
42
|
+
search(query: string, candidates: ReadonlySet<SegId>, k: number): SegId[];
|
|
43
|
+
}
|
|
44
|
+
export type EmbedFn = (text: string) => readonly number[];
|
|
45
|
+
/** Vector recaller: embed each segment, rank by cosine similarity. Pass a real embedding model;
|
|
46
|
+
* `hashEmbed()` gives a deterministic, dependency-free default for tests/demos. */
|
|
47
|
+
export declare class VectorRecaller implements Recaller {
|
|
48
|
+
private readonly embed;
|
|
49
|
+
private readonly vecs;
|
|
50
|
+
constructor(embed: EmbedFn);
|
|
51
|
+
index(id: SegId, body: unknown): void;
|
|
52
|
+
remove(id: SegId): void;
|
|
53
|
+
search(query: string, candidates: ReadonlySet<SegId>, k: number): SegId[];
|
|
54
|
+
}
|
|
55
|
+
/** Deterministic hashed bag-of-words embedding, L2-normalized. No dependencies. */
|
|
56
|
+
export declare function hashEmbed(dim?: number): EmbedFn;
|
|
57
|
+
export declare class Context {
|
|
58
|
+
private readonly ceiling;
|
|
59
|
+
private readonly pager;
|
|
60
|
+
private readonly recaller;
|
|
61
|
+
private readonly resident;
|
|
62
|
+
private readonly swap;
|
|
63
|
+
size: number;
|
|
64
|
+
constructor(ceiling: number | undefined, pager: Pager, recaller?: Recaller);
|
|
65
|
+
private get watermark();
|
|
66
|
+
remember(body: unknown, tokens: number, pinned: boolean): Promise<SegId>;
|
|
67
|
+
workingSet(): readonly Segment[];
|
|
68
|
+
recall(query: string, k: number): Promise<readonly Segment[]>;
|
|
69
|
+
pin(id: SegId, on: boolean): void;
|
|
70
|
+
forget(id: SegId): void;
|
|
71
|
+
stats(): {
|
|
72
|
+
resident: number;
|
|
73
|
+
swapped: number;
|
|
74
|
+
size: number;
|
|
75
|
+
ceiling: number | undefined;
|
|
76
|
+
};
|
|
77
|
+
/** Serialize the durable state of the context (for snapshot/migration). */
|
|
78
|
+
dump(): {
|
|
79
|
+
body: unknown;
|
|
80
|
+
tokens: number;
|
|
81
|
+
pinned: boolean;
|
|
82
|
+
}[];
|
|
83
|
+
/** Rebuild a context from a dump, then page down under the watermark if over ceiling (e.g.
|
|
84
|
+
* restored on a kernel with a smaller memory cap). Throws QuotaExceeded if the pinned working
|
|
85
|
+
* set alone surpasses the ceiling. */
|
|
86
|
+
static from(ceiling: number | undefined, pager: Pager, segs: readonly {
|
|
87
|
+
body: unknown;
|
|
88
|
+
tokens: number;
|
|
89
|
+
pinned: boolean;
|
|
90
|
+
}[], recaller?: Recaller): Promise<Context>;
|
|
91
|
+
/** Resident segments coldest-first. The Map's insertion order already tracks recency — remember
|
|
92
|
+
* appends, recall re-inserts (swap→resident) at the end, and no resident key is re-set in place —
|
|
93
|
+
* so iteration is LRU-ascending with no sort. (Keep that invariant if you touch resident.set.) */
|
|
94
|
+
private lru;
|
|
95
|
+
/** Page out until under the watermark (or only pinned remain); OOM if the pinned set busts the ceiling. */
|
|
96
|
+
private relieve;
|
|
97
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Pid, CapRef, Message, ProcessImage, ExitReason, DriverHandle } from '../abi/index';
|
|
2
|
+
import { Account } from './account';
|
|
3
|
+
import type { Context } from './memory';
|
|
4
|
+
import type { Timer } from './clock';
|
|
5
|
+
export type ProcStatus = 'new' | 'runnable' | 'running' | 'waiting' | 'dead';
|
|
6
|
+
/** FIFO inbox with O(1) amortized dequeue: a head index + lazy prefix compaction, so draining a
|
|
7
|
+
* deep mailbox (fan-in / aggregation) stays linear instead of Array.shift()'s O(n)-per-pop. */
|
|
8
|
+
export declare class Mailbox<T> {
|
|
9
|
+
private q;
|
|
10
|
+
private head;
|
|
11
|
+
get len(): number;
|
|
12
|
+
push(m: T): void;
|
|
13
|
+
shift(): T | undefined;
|
|
14
|
+
drain(): T[];
|
|
15
|
+
}
|
|
16
|
+
/** Thrown by sys.exit() to unwind the process body cleanly (not a fault). */
|
|
17
|
+
export declare class ExitSignal extends Error {
|
|
18
|
+
readonly reason: ExitReason;
|
|
19
|
+
constructor(reason: ExitReason);
|
|
20
|
+
}
|
|
21
|
+
export interface PCB {
|
|
22
|
+
readonly pid: Pid;
|
|
23
|
+
readonly name: string;
|
|
24
|
+
parent: Pid | null;
|
|
25
|
+
readonly image: ProcessImage<unknown>;
|
|
26
|
+
readonly args: unknown;
|
|
27
|
+
readonly account: Account;
|
|
28
|
+
readonly context: Context;
|
|
29
|
+
status: ProcStatus;
|
|
30
|
+
stopped: boolean;
|
|
31
|
+
age: number;
|
|
32
|
+
reductions: number;
|
|
33
|
+
op: number;
|
|
34
|
+
recovered: boolean;
|
|
35
|
+
priority: number;
|
|
36
|
+
deadline: number | null;
|
|
37
|
+
enq: number;
|
|
38
|
+
/** Resolver of the promise the process is currently parked on (yield / recv-block / sleep). */
|
|
39
|
+
cont: (() => void) | null;
|
|
40
|
+
timer: Timer | null;
|
|
41
|
+
deadlineTimer: Timer | null;
|
|
42
|
+
exitReason: ExitReason | null;
|
|
43
|
+
readonly mailbox: Mailbox<Message>;
|
|
44
|
+
readonly mailboxWaiters: Array<() => void>;
|
|
45
|
+
readonly links: Set<Pid>;
|
|
46
|
+
readonly monitors: Set<Pid>;
|
|
47
|
+
readonly handles: Map<CapRef, DriverHandle>;
|
|
48
|
+
readonly cleanups: Array<() => void>;
|
|
49
|
+
}
|
|
50
|
+
export declare function makePCB(pid: Pid, parent: Pid | null, image: ProcessImage<unknown>, args: unknown, account: Account, context: Context): PCB;
|
|
51
|
+
export declare class ProcTable {
|
|
52
|
+
private readonly map;
|
|
53
|
+
private seq;
|
|
54
|
+
alloc(): Pid;
|
|
55
|
+
add(pcb: PCB): void;
|
|
56
|
+
get(pid: Pid): PCB | undefined;
|
|
57
|
+
remove(pid: Pid): void;
|
|
58
|
+
all(): IterableIterator<PCB>;
|
|
59
|
+
get count(): number;
|
|
60
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Pid } from '../abi/index';
|
|
2
|
+
import type { StampedEvent } from './log';
|
|
3
|
+
import { Journal, type Journaled } from './journal';
|
|
4
|
+
export type StoreRecord = {
|
|
5
|
+
readonly kind: 'event';
|
|
6
|
+
readonly data: StampedEvent;
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: 'io';
|
|
9
|
+
readonly data: {
|
|
10
|
+
readonly pid: Pid;
|
|
11
|
+
readonly op: number;
|
|
12
|
+
readonly result: Journaled;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export interface Store {
|
|
16
|
+
append(rec: StoreRecord): void;
|
|
17
|
+
load(): readonly StoreRecord[];
|
|
18
|
+
}
|
|
19
|
+
/** In-memory store (tests / ephemeral). */
|
|
20
|
+
export declare class MemoryStore implements Store {
|
|
21
|
+
private readonly recs;
|
|
22
|
+
append(rec: StoreRecord): void;
|
|
23
|
+
load(): readonly StoreRecord[];
|
|
24
|
+
}
|
|
25
|
+
/** Append-only JSONL file store — survives process restarts. */
|
|
26
|
+
export declare class FileStore implements Store {
|
|
27
|
+
private readonly path;
|
|
28
|
+
constructor(path: string);
|
|
29
|
+
append(rec: StoreRecord): void;
|
|
30
|
+
load(): readonly StoreRecord[];
|
|
31
|
+
reset(): void;
|
|
32
|
+
}
|
|
33
|
+
/** Rebuild a replay journal from persisted I/O records. */
|
|
34
|
+
export declare function journalFromStore(records: readonly StoreRecord[]): Journal;
|
|
35
|
+
/** Read just the audit trail (events) back from a store. */
|
|
36
|
+
export declare function eventsFromStore(records: readonly StoreRecord[]): StampedEvent[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** A message as it crosses the trust boundary: addressing + payload + the MAC that authenticates all of it. */
|
|
2
|
+
export interface SealedEnvelope {
|
|
3
|
+
readonly to: number;
|
|
4
|
+
readonly nonce: string;
|
|
5
|
+
readonly ts: number;
|
|
6
|
+
readonly body: unknown;
|
|
7
|
+
readonly mac: string;
|
|
8
|
+
}
|
|
9
|
+
/** A fresh nonce: monotone counter + entropy, so two sends never collide even within a tick. */
|
|
10
|
+
export declare function freshNonce(rand: () => string): string;
|
|
11
|
+
/** Seal a message for transport. The returned envelope is safe to ship over a hostile wire. `ts` (seal
|
|
12
|
+
* time) is stamped now by default and folded into the MAC, so the receiver can reject stale replays. */
|
|
13
|
+
export declare function seal(key: string, to: number, body: unknown, nonce: string, ts?: number): SealedEnvelope;
|
|
14
|
+
/** Verify an envelope's MAC in constant time. False ⇒ forged or tampered — drop it. */
|
|
15
|
+
export declare function verify(key: string, env: SealedEnvelope): boolean;
|
|
16
|
+
/** Replay guard scoped to a freshness WINDOW: a nonce is accepted at most once while it's fresh. Entries
|
|
17
|
+
* past the window are pruned — a replay that old is already caught by ingress's timestamp check, so the
|
|
18
|
+
* cache only has to cover the window (no count-eviction hole the old bounded set had). cap is a flood backstop. */
|
|
19
|
+
export declare class NonceGuard {
|
|
20
|
+
private readonly windowMs;
|
|
21
|
+
private readonly cap;
|
|
22
|
+
private readonly seen;
|
|
23
|
+
constructor(windowMs?: number, cap?: number);
|
|
24
|
+
/** True the first time a nonce is seen within the window; false on replay. `now` is the kernel clock. */
|
|
25
|
+
admit(nonce: string, now: number): boolean;
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SealedEnvelope } from '../kernel/transport';
|
|
2
|
+
/** Listen for sealed envelopes; hand each verified-by-the-handler line to `onEnvelope`. */
|
|
3
|
+
export declare function serveTcp(onEnvelope: (env: SealedEnvelope) => boolean, opts?: {
|
|
4
|
+
port?: number;
|
|
5
|
+
host?: string;
|
|
6
|
+
maxLine?: number;
|
|
7
|
+
}): Promise<{
|
|
8
|
+
port: number;
|
|
9
|
+
close: () => Promise<void>;
|
|
10
|
+
}>;
|
|
11
|
+
/** Dial a peer with a SELF-HEALING socket: it reconnects (capped exponential backoff) after a drop and
|
|
12
|
+
* flushes a bounded backlog on (re)connect. `send` is HONEST — true means accepted (sent or queued),
|
|
13
|
+
* false means refused: the dialer is closed, or the backlog is full (so it never grows unbounded and
|
|
14
|
+
* never claims success it can't keep). */
|
|
15
|
+
export declare function dialTcp(host: string, port: number, opts?: {
|
|
16
|
+
maxPending?: number;
|
|
17
|
+
backoffMs?: number;
|
|
18
|
+
maxBackoffMs?: number;
|
|
19
|
+
}): {
|
|
20
|
+
send: (env: SealedEnvelope) => boolean;
|
|
21
|
+
close: () => void;
|
|
22
|
+
};
|