@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 guangnao
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# @guangnao/agent-os
|
|
2
|
+
|
|
3
|
+
A **microkernel operating system for LLM agents** — not a framework, not a library.
|
|
4
|
+
|
|
5
|
+
The distinguishing line: the kernel **owns execution and resources**, and a process
|
|
6
|
+
(an "agent") affects the world **only through capability-mediated syscalls**. That
|
|
7
|
+
boundary — not the vocabulary — is what makes this a *system*.
|
|
8
|
+
|
|
9
|
+
- **Metered by construction** — every LLM/tool call is charged to a per-process budget (tokens, $, tool-calls, wall-clock, memory). Over budget → the kernel kills it.
|
|
10
|
+
- **Least privilege** — a process holds no ambient authority, only unforgeable capabilities you delegate. Attenuate and pass them like file descriptors.
|
|
11
|
+
- **Let it crash** — link/monitor + OTP-style supervisors restart flaky agents.
|
|
12
|
+
- **Deterministic** — record/replay driver I/O and a virtual clock reproduce a run byte-for-byte.
|
|
13
|
+
- **Batteries included** — LLM (OpenAI-compatible), tools, KV, cross-node gateway, worker-thread offload, and a WASM sandbox drivers; a ready-made ReAct agent.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @guangnao/agent-os
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
ESM-only, types bundled. Node ≥ 18.
|
|
22
|
+
|
|
23
|
+
## Quick start — a real LLM agent in ~30 lines
|
|
24
|
+
|
|
25
|
+
A ReAct agent that thinks → calls a tool → observes → answers, with every LLM and
|
|
26
|
+
tool call metered by the kernel. Point it at any OpenAI-compatible endpoint
|
|
27
|
+
(DeepSeek below).
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { Kernel, llmDriver, toolDriver, openaiChat, reactAgent } from '@guangnao/agent-os'
|
|
31
|
+
import type { ProcessImage, ToolSchema, AgentResult, BootGrant } from '@guangnao/agent-os'
|
|
32
|
+
|
|
33
|
+
// 1. Tools are plain functions; the agent reaches them through a metered device.
|
|
34
|
+
const tools = {
|
|
35
|
+
calc: ({ op, a, b }: { op: string; a: number; b: number }) =>
|
|
36
|
+
({ result: op === 'mul' ? a * b : op === 'add' ? a + b : a - b }),
|
|
37
|
+
}
|
|
38
|
+
const schemas: ToolSchema[] = [{
|
|
39
|
+
name: 'calc', description: 'arithmetic on two numbers',
|
|
40
|
+
parameters: { type: 'object', required: ['op', 'a', 'b'], properties: {
|
|
41
|
+
op: { type: 'string', enum: ['add', 'sub', 'mul'] }, a: { type: 'number' }, b: { type: 'number' },
|
|
42
|
+
} },
|
|
43
|
+
}]
|
|
44
|
+
|
|
45
|
+
// 2. Boot a kernel; register the drivers (the only way out to the world).
|
|
46
|
+
const k = new Kernel()
|
|
47
|
+
k.registerDriver(llmDriver(openaiChat({
|
|
48
|
+
apiKey: process.env.DEEPSEEK_API_KEY!,
|
|
49
|
+
baseUrl: 'https://api.deepseek.com/v1',
|
|
50
|
+
model: 'deepseek-chat',
|
|
51
|
+
})))
|
|
52
|
+
k.registerDriver(toolDriver(tools))
|
|
53
|
+
|
|
54
|
+
// 3. init delegates the llm + tools devices to a ReAct agent and awaits its answer.
|
|
55
|
+
const init: ProcessImage = { name: 'init', async main(sys) {
|
|
56
|
+
const g = (await sys.recv<BootGrant>()).body
|
|
57
|
+
const { cap } = await sys.spawn(
|
|
58
|
+
reactAgent({ task: 'What is 23 * 19? Use calc, then give just the number.', tools: schemas }),
|
|
59
|
+
{ link: true, quota: { tokens: 200_000, usd: 1, toolCalls: 20, mem: 100_000 } },
|
|
60
|
+
)
|
|
61
|
+
await sys.send(cap, {}, { caps: [ // delegate caps like fds
|
|
62
|
+
sys.derive(g.devices['llm']!, ['open']),
|
|
63
|
+
sys.derive(g.devices['tools']!, ['open']),
|
|
64
|
+
sys.selfCap(),
|
|
65
|
+
] })
|
|
66
|
+
const r = (await sys.recv<AgentResult>()).body
|
|
67
|
+
console.log(r.answer, `(${r.steps} steps)`) // → 437 (2 steps)
|
|
68
|
+
} }
|
|
69
|
+
|
|
70
|
+
await k.boot(init, { grant: ['llm', 'tools'], quota: { tokens: 2_000_000, usd: 10, children: 4 } })
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
A process is just `{ name, async main(sys, args) }`. It holds no kernel object — only
|
|
74
|
+
`sys`, the syscall surface. Everything it can do (spawn, send, open a device, remember,
|
|
75
|
+
read an LLM) is a syscall, charged and capability-checked.
|
|
76
|
+
|
|
77
|
+
## Architecture (microkernel, 3 rings)
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Ring 2 · Userland — agents + services: pure main(sys) bodies, syscalls only
|
|
81
|
+
──────────────────── the syscall ABI (the only boundary)
|
|
82
|
+
Ring 1 · Services — drivers (LLM/tools/memory), supervisors, name service
|
|
83
|
+
────────────────────
|
|
84
|
+
Ring 0 · Kernel — PIDs · syscall dispatch · scheduler · accounting · cap table
|
|
85
|
+
· IPC switch · supervision mechanism · event log
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| Subsystem | What it is | Agent twist |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| **Process** | kernel-owned PCB + PID; the kernel runs/parks/kills it | not an object you `new` |
|
|
91
|
+
| **Scheduler** | cooperative, by **reductions** (syscall budget per quantum) + priority/EDF deadlines | schedules **token/tool cost**, not CPU time |
|
|
92
|
+
| **Accounting** | hierarchical (cgroup) quotas: tokens, tool calls, $, wall-clock, memory, children | parent's budget bounds its whole subtree |
|
|
93
|
+
| **Capabilities** | unforgeable handles; attenuate + delegate; no ambient authority | least-privilege, confused-deputy-proof |
|
|
94
|
+
| **IPC** | mailboxes + a switch; caps ride on messages (fd-passing) | the only way agents share data |
|
|
95
|
+
| **Memory (MMU)** | context window = paged address space; watermark evict → lossy summary → recall; OOM | pluggable keyword/vector recall |
|
|
96
|
+
| **Supervision** | link/monitor → `Down` (kernel) + restart strategies (userland) | OTP "let it crash" |
|
|
97
|
+
| **Drivers** | LLM/tools/memory/net behind one `open/read/write` | swap a vendor = swap a driver; metered at the chokepoint |
|
|
98
|
+
| **Event log** | every syscall/charge/fault, stamped | `ps`/`top`, audit, deterministic replay |
|
|
99
|
+
|
|
100
|
+
## Driver standard library
|
|
101
|
+
|
|
102
|
+
| Driver | Purpose |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `llmDriver(chat)` | LLM device; wrap any `ChatFn` |
|
|
105
|
+
| `openaiChat(cfg)` | a `ChatFn` for any OpenAI-compatible API (OpenAI, DeepSeek, …); pass `onDelta` on a request for SSE token streaming |
|
|
106
|
+
| `toolDriver(tools)` | expose plain functions as a metered tool device |
|
|
107
|
+
| `kvDriver()` | a shared key-value store device |
|
|
108
|
+
| `gatewayDriver(forward)` | cross-node IPC over a **trusted** channel (in-process / private link) |
|
|
109
|
+
| `secureGatewayDriver(forward, key)` | cross-node IPC over a **hostile wire** — HMAC-sealed, replay-protected |
|
|
110
|
+
| `workerDriver(tasks)` | offload CPU tasks to real `worker_threads` (isolated heap, another core) |
|
|
111
|
+
| `wasmDriver(modules)` | run untrusted **compute** in a zero-import WebAssembly sandbox |
|
|
112
|
+
|
|
113
|
+
## Beyond the basics
|
|
114
|
+
|
|
115
|
+
- **Deterministic record/replay** — journal driver I/O; replay reproduces a run exactly.
|
|
116
|
+
- **Virtual clock** — discrete-event time; sleeps cost zero real time, traces are byte-identical.
|
|
117
|
+
- **Snapshot / migration** — `kernel.snapshot(pid)` → durable state (context + mailbox + budget); `kernel.restore(snap, image)` resumes it on any kernel (live migration across nodes).
|
|
118
|
+
- **Event-sourcing store** — `MemoryStore` / `FileStore` persist every event + I/O; rebuild a replayable journal from disk.
|
|
119
|
+
- **Vector recall** — context page-in via `KeywordRecaller` (default) or `VectorRecaller(embed)`.
|
|
120
|
+
- **Supervisors & name service** — `supervisor({ strategy, children })` (`one_for_one` / `one_for_all` / `rest_for_one`) and a capability broker.
|
|
121
|
+
|
|
122
|
+
## Security & operations
|
|
123
|
+
|
|
124
|
+
- **Authenticated transport** — off-machine messages are HMAC-SHA256 sealed over a canonical `(to, nonce, body)` encoding; `kernel.ingress()` verifies in constant time and drops replays. **Capabilities never cross the wire** — a verified peer can address a pid, never forge authority over one.
|
|
125
|
+
- **Crash recovery** — `Kernel.replayFrom(store)` rebuilds a kernel that replays persisted driver I/O, reproducing a run exactly with no live calls. The event-sourcing log *is* the recovery mechanism.
|
|
126
|
+
- **Backpressure** — `quota.maxMailbox` bounds an inbox; a full one parks senders until the consumer drains it (no drops, order preserved).
|
|
127
|
+
- **Metrics** — `kernel.metrics()` and `kernel.metricsText()` (Prometheus exposition) over the whole event history.
|
|
128
|
+
- **ABI versioning** — `ABI_VERSION` is stamped into snapshots; `restore` refuses an incompatible version rather than silently resuming mismatched state.
|
|
129
|
+
- **Tested for robustness** — property tests (accounting conservation/atomicity, capability attenuation), adversarial tests (malicious-driver quota-kill), and chaos tests (spawn/kill churn) run on a seeded PRNG.
|
|
130
|
+
|
|
131
|
+
## Performance
|
|
132
|
+
|
|
133
|
+
Single-core, cooperative kernel — these are one-core ceilings. Reproduce with `npm run bench`:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
IPC round-trips ~530k /s send → recv → send → recv
|
|
137
|
+
Process spawn+exit ~135k /s spawn → notify → exit (O(caps-owned) revoke — flat under load)
|
|
138
|
+
Context switches ~2.1M /s sys.yield (O(1) dispatch — flat to thousands of procs)
|
|
139
|
+
Fan-in recv ~1.2M /s 32 senders → 1 deep mailbox (O(1) dequeue — flat under load)
|
|
140
|
+
Driver reads ~840k /s syscall + driver dispatch + accounting
|
|
141
|
+
Memory remember ~2.2M /s context write
|
|
142
|
+
Memory recall ~1.6M /s keyword page-in
|
|
143
|
+
Memory under paging ~900k /s write at the mem ceiling (O(1) amortized — flat vs resident size)
|
|
144
|
+
|
|
145
|
+
Parallel I/O ~16× 16 latency-bound reads: sys.readAll vs sequential
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`sys.readAll(handle, reqs)` fires every request concurrently under a single park —
|
|
149
|
+
a ReAct turn's N tool calls become one network-bound wait, not N — while staying
|
|
150
|
+
fully metered and replay-deterministic (op ids are assigned in order).
|
|
151
|
+
|
|
152
|
+
*(Apple M-series, Node 25; indicative, not a controlled benchmark.)*
|
|
153
|
+
|
|
154
|
+
## Real network + true isolation
|
|
155
|
+
|
|
156
|
+
- **TCP transport** — `serveTcp(env => kernel.ingress(env))` + `dialTcp(host, port)` move HMAC-sealed envelopes over real sockets between kernels on different ports/machines. Authentic frames are delivered; tampered/forged ones are dropped at ingress.
|
|
157
|
+
- **Sandboxed agents** — `sandbox(code)` runs untrusted agent logic in a real worker thread (separate V8 isolate + heap). It holds no reference to `sys`, the cap table, or kernel memory; its only authority is a mediated syscall RPC through a trusted reference-monitor shim (allow-listed syscalls + the usual capability/quota checks). It can't read kernel memory, forge a capability, or call a syscall it wasn't granted.
|
|
158
|
+
|
|
159
|
+
### Honest scope
|
|
160
|
+
|
|
161
|
+
Production-shaped but young — naming it straight:
|
|
162
|
+
|
|
163
|
+
- **Distributed** has authenticated envelopes + replay guard + a real TCP transport, but no built-in service discovery, failure detector, or partition handling yet.
|
|
164
|
+
- **WASM sandbox** confines *compute*; the agent-process isolation story is `sandbox()` (worker-thread), not WASM-of-the-whole-agent.
|
|
165
|
+
- **Migration** serializes a process's context + mailbox + budget, not its JS stack — so it migrates message-loop state machines, not arbitrary suspended call stacks.
|
|
166
|
+
|
|
167
|
+
## Requirements
|
|
168
|
+
|
|
169
|
+
- Node ≥ 18, ESM (`"type": "module"` or `.mjs`).
|
|
170
|
+
- TypeScript optional but recommended — full types ship with the package.
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Pid, CapRef, DriverName } from './ids';
|
|
2
|
+
/** What a capability governs. */
|
|
3
|
+
export type Resource = {
|
|
4
|
+
readonly kind: 'process';
|
|
5
|
+
readonly pid: Pid;
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: 'remote';
|
|
8
|
+
readonly node: string;
|
|
9
|
+
readonly pid: Pid;
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: 'device';
|
|
12
|
+
readonly driver: DriverName;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: 'spawn';
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: 'budget';
|
|
17
|
+
readonly grant: BudgetGrant;
|
|
18
|
+
};
|
|
19
|
+
/** A transferable resource grant a parent carves off its own account for a child. */
|
|
20
|
+
export interface BudgetGrant {
|
|
21
|
+
readonly tokens?: number;
|
|
22
|
+
readonly toolCalls?: number;
|
|
23
|
+
readonly wallMs?: number;
|
|
24
|
+
readonly usd?: number;
|
|
25
|
+
readonly children?: number;
|
|
26
|
+
}
|
|
27
|
+
/** Fine-grained operations a capability may permit. Attenuation = dropping rights. */
|
|
28
|
+
export type Right = 'send' | 'monitor' | 'open' | 'read' | 'write' | 'spawn' | 'kill';
|
|
29
|
+
/** A constraint carried by a capability and checked on every use. Caveats only ATTENUATE: they ride down the
|
|
30
|
+
* derivation chain (a child inherits its parent's), so a holder can't `derive` one away. `expires` = the cap
|
|
31
|
+
* is invalid once the kernel clock passes `at` — a TTL / temporary delegation. */
|
|
32
|
+
export type Caveat = {
|
|
33
|
+
readonly kind: 'expires';
|
|
34
|
+
readonly at: number;
|
|
35
|
+
};
|
|
36
|
+
/** Kernel-side capability record. Holders never see this — only the CapRef handle. */
|
|
37
|
+
export interface Capability {
|
|
38
|
+
readonly ref: CapRef;
|
|
39
|
+
owner: Pid;
|
|
40
|
+
readonly resource: Resource;
|
|
41
|
+
readonly rights: ReadonlySet<Right>;
|
|
42
|
+
readonly caveats: readonly Caveat[];
|
|
43
|
+
revoked: boolean;
|
|
44
|
+
}
|
|
45
|
+
export declare const hasRight: (cap: Capability, r: Right) => boolean;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Pid } from './ids';
|
|
2
|
+
/** Cost a driver op incurs against the caller's account (the kernel charges it). */
|
|
3
|
+
export interface DriverCost {
|
|
4
|
+
readonly tokens?: number;
|
|
5
|
+
readonly toolCalls?: number;
|
|
6
|
+
readonly usd?: number;
|
|
7
|
+
readonly reductions?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface DriverResult<T = unknown> {
|
|
10
|
+
readonly value: T;
|
|
11
|
+
readonly cost?: DriverCost;
|
|
12
|
+
}
|
|
13
|
+
/** A driver instance, opened per process. */
|
|
14
|
+
export interface DriverHandle {
|
|
15
|
+
read?(req: unknown): Promise<DriverResult>;
|
|
16
|
+
write?(data: unknown): Promise<DriverResult>;
|
|
17
|
+
close?(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export interface Driver {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
/** Open a per-process handle. `owner` lets the driver scope/meter per caller. */
|
|
22
|
+
open(owner: Pid, params?: unknown): Promise<DriverHandle>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type Pid = number & {
|
|
2
|
+
readonly __pid: unique symbol;
|
|
3
|
+
};
|
|
4
|
+
export type CapRef = string & {
|
|
5
|
+
readonly __cap: unique symbol;
|
|
6
|
+
};
|
|
7
|
+
export type MsgId = string & {
|
|
8
|
+
readonly __msg: unique symbol;
|
|
9
|
+
};
|
|
10
|
+
export type SegId = string & {
|
|
11
|
+
readonly __seg: unique symbol;
|
|
12
|
+
};
|
|
13
|
+
export type DriverName = string & {
|
|
14
|
+
readonly __drv: unique symbol;
|
|
15
|
+
};
|
|
16
|
+
export declare function newSegId(): SegId;
|
|
17
|
+
/** Mint a fresh, hard-to-guess capability handle (holder presents it; authority lives kernel-side). */
|
|
18
|
+
export declare function newCapRef(): CapRef;
|
|
19
|
+
export declare function newMsgId(): MsgId;
|
|
20
|
+
export declare const drv: (name: string) => DriverName;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { Pid, CapRef, MsgId, SegId, DriverName } from './ids';
|
|
2
|
+
export { newCapRef, newMsgId, newSegId, drv } from './ids';
|
|
3
|
+
/** The syscall-ABI contract version. Snapshots carry it; restore rejects a mismatch, so a process
|
|
4
|
+
* state serialized under one ABI can't be silently resumed under an incompatible one. */
|
|
5
|
+
export declare const ABI_VERSION = 1;
|
|
6
|
+
export type { Segment, Pager } from './memory';
|
|
7
|
+
export type { Resource, Right, Capability, BudgetGrant } from './capability';
|
|
8
|
+
export { hasRight } from './capability';
|
|
9
|
+
export type { Message, SendOptions } from './message';
|
|
10
|
+
export type { Sys, ProcessImage, SpawnOptions, Quota, ExitReason, LogLevel, Down, Term, } from './syscall';
|
|
11
|
+
export type { Driver, DriverHandle, DriverResult, DriverCost } from './driver';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SegId } from './ids';
|
|
2
|
+
export interface Segment {
|
|
3
|
+
readonly id: SegId;
|
|
4
|
+
readonly body: unknown;
|
|
5
|
+
readonly tokens: number;
|
|
6
|
+
readonly pinned: boolean;
|
|
7
|
+
readonly summary?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Pluggable paging policy. The kernel calls it under memory pressure; an
|
|
10
|
+
* implementation may summarize via an LLM driver (async) or just drop. */
|
|
11
|
+
export interface Pager {
|
|
12
|
+
/**
|
|
13
|
+
* Free at least `need` tokens from `resident` (unpinned, oldest-first is typical).
|
|
14
|
+
* Return the segments to evict, and optionally one summary segment to leave behind.
|
|
15
|
+
*/
|
|
16
|
+
pageOut(resident: readonly Segment[], need: number): Promise<{
|
|
17
|
+
readonly evict: readonly SegId[];
|
|
18
|
+
readonly summary?: Omit<Segment, 'id'>;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Pid, MsgId, CapRef } from './ids';
|
|
2
|
+
export interface Message<T = unknown> {
|
|
3
|
+
readonly id: MsgId;
|
|
4
|
+
readonly from: Pid;
|
|
5
|
+
readonly to: Pid;
|
|
6
|
+
readonly body: T;
|
|
7
|
+
/** Capabilities handed to the receiver (fd-passing). Empty for most messages. */
|
|
8
|
+
readonly caps: readonly CapRef[];
|
|
9
|
+
/** Correlates a reply to a request (RPC). */
|
|
10
|
+
readonly corr?: MsgId;
|
|
11
|
+
/** Wall-clock deadline; the kernel drops the message if delivered late. */
|
|
12
|
+
readonly deadline?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface SendOptions {
|
|
15
|
+
readonly caps?: readonly CapRef[];
|
|
16
|
+
readonly corr?: MsgId;
|
|
17
|
+
readonly deadline?: number;
|
|
18
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { Pid, CapRef, SegId } from './ids';
|
|
2
|
+
import type { Right, Resource } from './capability';
|
|
3
|
+
import type { Message, SendOptions } from './message';
|
|
4
|
+
import type { Segment } from './memory';
|
|
5
|
+
/** Lifetime resource limits enforced by the kernel at syscall time. */
|
|
6
|
+
export interface Quota {
|
|
7
|
+
readonly tokens?: number;
|
|
8
|
+
readonly toolCalls?: number;
|
|
9
|
+
readonly wallMs?: number;
|
|
10
|
+
readonly usd?: number;
|
|
11
|
+
readonly children?: number;
|
|
12
|
+
readonly mem?: number;
|
|
13
|
+
readonly maxMailbox?: number;
|
|
14
|
+
}
|
|
15
|
+
export type ExitReason = {
|
|
16
|
+
readonly kind: 'normal';
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: 'killed';
|
|
19
|
+
readonly by: Pid;
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: 'quota';
|
|
22
|
+
readonly resource: keyof Quota;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: 'fault';
|
|
25
|
+
readonly error: string;
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: 'deadline';
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: 'shutdown';
|
|
30
|
+
};
|
|
31
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
32
|
+
/** Kernel→process notification that a monitored/linked process died. */
|
|
33
|
+
export interface Down {
|
|
34
|
+
readonly $: 'down';
|
|
35
|
+
readonly pid: Pid;
|
|
36
|
+
readonly reason: ExitReason;
|
|
37
|
+
}
|
|
38
|
+
/** Kernel→process graceful-stop request (SIGTERM): handle it in your recv loop (clean up, then exit). */
|
|
39
|
+
export interface Term {
|
|
40
|
+
readonly $: 'term';
|
|
41
|
+
readonly from: Pid;
|
|
42
|
+
}
|
|
43
|
+
export interface SpawnOptions<Args = unknown> {
|
|
44
|
+
readonly args?: Args;
|
|
45
|
+
/** Caps handed to the child at birth (subset/derivations of yours — no ambient authority). */
|
|
46
|
+
readonly caps?: readonly CapRef[];
|
|
47
|
+
/** Budget carved from YOUR account for the child (parent pays for children). */
|
|
48
|
+
readonly quota?: Quota;
|
|
49
|
+
/** If true, the parent is linked: child death sends a Down to the parent. Default false. */
|
|
50
|
+
readonly link?: boolean;
|
|
51
|
+
/** Scheduling priority — higher runs sooner. Default 0. */
|
|
52
|
+
readonly priority?: number;
|
|
53
|
+
/** Soft deadline: ms from spawn by which it must finish, else it's killed (reason 'deadline'). */
|
|
54
|
+
readonly deadline?: number;
|
|
55
|
+
}
|
|
56
|
+
export interface ProcessImage<Args = unknown> {
|
|
57
|
+
readonly name: string;
|
|
58
|
+
/** Process body. Runs until it returns (→ normal exit) or calls sys.exit(). */
|
|
59
|
+
main(sys: Sys, args: Args): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
export interface Sys {
|
|
62
|
+
/** Own pid. */
|
|
63
|
+
self(): Pid;
|
|
64
|
+
/** A fresh cap to yourself — your "return address" — with send + monitor rights: the holder can reply
|
|
65
|
+
* AND be notified (a Down) when you die, so brokers like the name service can drop stale entries. No kill. */
|
|
66
|
+
selfCap(): CapRef;
|
|
67
|
+
/** Block until a message arrives (process goes 'waiting', descheduled, costs nothing while blocked). */
|
|
68
|
+
recv<T = unknown>(): Promise<Message<T>>;
|
|
69
|
+
/** Send to a process/handle you hold a 'send'/'write' cap for. May delegate caps (fd-passing). */
|
|
70
|
+
send(target: CapRef, body: unknown, opts?: SendOptions): Promise<void>;
|
|
71
|
+
/** Publish THIS process under a well-known name so peers can find it without knowing its pid. First-come:
|
|
72
|
+
* throws if a LIVE process already holds the name. Auto-cleared when this process exits. */
|
|
73
|
+
register(name: string): void;
|
|
74
|
+
/** Withdraw a name you registered (no-op if you don't hold it). */
|
|
75
|
+
unregister(name: string): void;
|
|
76
|
+
/** Resolve a name to a SEND-only cap for the service (least privilege — no kill/monitor), or undefined if
|
|
77
|
+
* no live process holds it. */
|
|
78
|
+
lookup(name: string): Promise<CapRef | undefined>;
|
|
79
|
+
/** Address a process on ANOTHER node: returns a SEND-only proxy cap for (node, pid). send() to it egresses a
|
|
80
|
+
* sealed envelope to that node's ingress. Addressing only — authority never crosses the wire; the remote
|
|
81
|
+
* node still enforces its own local checks. Requires a route (kernel.connect) and a transportKey. */
|
|
82
|
+
dial(node: string, pid: Pid): CapRef;
|
|
83
|
+
/** Spawn an image the peer node published (registerImage) by NAME, and get a SEND proxy to the new remote
|
|
84
|
+
* process. The spawned process boots with a `reply` proxy back to you, so the two can converse over the wire. */
|
|
85
|
+
spawnRemote(node: string, name: string, args?: unknown): Promise<CapRef>;
|
|
86
|
+
/** Create a child with exactly the caps + budget you give it. Returns its pid + a cap to talk to it. */
|
|
87
|
+
spawn<A>(image: ProcessImage<A>, opts?: SpawnOptions<A>): Promise<{
|
|
88
|
+
readonly pid: Pid;
|
|
89
|
+
readonly cap: CapRef;
|
|
90
|
+
}>;
|
|
91
|
+
/** Terminate self. Never returns. */
|
|
92
|
+
exit(reason?: ExitReason): Promise<never>;
|
|
93
|
+
/** Weaken a cap you hold to a subset of rights; send the result to delegate least privilege. */
|
|
94
|
+
derive(cap: CapRef, rights: readonly Right[]): CapRef;
|
|
95
|
+
/** Weaken a cap further with rights AND/OR a caveat (e.g. expiresInMs → a TTL cap). Restrictions only:
|
|
96
|
+
* the caveat is inherited by any later derivation, so a delegate can't strip the expiry. */
|
|
97
|
+
attenuate(cap: CapRef, opts: {
|
|
98
|
+
readonly rights?: readonly Right[];
|
|
99
|
+
readonly expiresInMs?: number;
|
|
100
|
+
}): CapRef;
|
|
101
|
+
/** Invalidate a cap you minted (and any derivations) — revocation is transitive. */
|
|
102
|
+
revoke(cap: CapRef): void;
|
|
103
|
+
/** Open a device cap → a handle cap with read/write rights. */
|
|
104
|
+
open(device: CapRef): Promise<CapRef>;
|
|
105
|
+
read<T = unknown>(handle: CapRef, req?: unknown): Promise<T>;
|
|
106
|
+
/** Batch-read one handle: issue every request CONCURRENTLY (one park), each metered + journaled
|
|
107
|
+
* in order. The parallel-I/O primitive — N tool calls become one network-bound wait, not N. */
|
|
108
|
+
readAll<T = unknown>(handle: CapRef, reqs: readonly unknown[]): Promise<T[]>;
|
|
109
|
+
write(handle: CapRef, data: unknown): Promise<void>;
|
|
110
|
+
/** Watch a process; receive a Down message when it dies (one-way, no restart — that's the supervisor's job). */
|
|
111
|
+
monitor(target: CapRef): Promise<void>;
|
|
112
|
+
/** Block until the target (you hold a 'monitor' cap for) exits; return its ExitReason. The ergonomic
|
|
113
|
+
* reaper — wait for a specific child and learn why it died (vs monitor()'s async Down). Returns at once
|
|
114
|
+
* if it already exited. */
|
|
115
|
+
waitpid(target: CapRef): Promise<ExitReason>;
|
|
116
|
+
/** Kill a process you hold a 'kill' cap for (supervision policy — one_for_all/rest_for_one). SIGKILL. */
|
|
117
|
+
signal(target: CapRef, reason?: ExitReason): Promise<void>;
|
|
118
|
+
/** SIGSTOP — pause a process (needs a 'kill' cap): it stays alive but isn't scheduled until resume(). */
|
|
119
|
+
suspend(target: CapRef): Promise<void>;
|
|
120
|
+
/** SIGCONT — resume a process you suspended. */
|
|
121
|
+
resume(target: CapRef): Promise<void>;
|
|
122
|
+
/** SIGTERM — ask a process to stop GRACEFULLY: delivers a { $: 'term' } message it handles in recv then exits. */
|
|
123
|
+
term(target: CapRef): Promise<void>;
|
|
124
|
+
/** Write a segment into your context. Pinned segments survive paging. May page out / OOM-kill. */
|
|
125
|
+
remember(body: unknown, opts?: {
|
|
126
|
+
tokens?: number;
|
|
127
|
+
pin?: boolean;
|
|
128
|
+
}): Promise<SegId>;
|
|
129
|
+
/** The resident working set — what you'd feed the LLM right now. */
|
|
130
|
+
workingSet(): readonly Segment[];
|
|
131
|
+
/** Page matching segments back in from swap (a page fault resolved from long-term memory). */
|
|
132
|
+
recall(query: string, k?: number): Promise<readonly Segment[]>;
|
|
133
|
+
/** Free a segment (resident or swapped). */
|
|
134
|
+
forget(id: SegId): void;
|
|
135
|
+
/** Pin/unpin a segment against eviction. */
|
|
136
|
+
pin(id: SegId, on?: boolean): void;
|
|
137
|
+
sleep(ms: number): Promise<void>;
|
|
138
|
+
/** Voluntarily yield the rest of this quantum (cooperative preemption point). */
|
|
139
|
+
yield(): Promise<void>;
|
|
140
|
+
/** Adjust own scheduling: change priority and/or (re)set a relative deadline (ms from now). */
|
|
141
|
+
sched(opts: {
|
|
142
|
+
readonly priority?: number;
|
|
143
|
+
readonly deadline?: number;
|
|
144
|
+
}): void;
|
|
145
|
+
/** Remaining lifetime budget. */
|
|
146
|
+
budget(): Readonly<Quota>;
|
|
147
|
+
/** Current kernel time (ms). The kernel's clock — virtual under a VirtualClock — so userland timing
|
|
148
|
+
* (e.g. a supervisor's restart-intensity window) stays deterministic and replayable. Use this, not
|
|
149
|
+
* Date.now(). */
|
|
150
|
+
now(): number;
|
|
151
|
+
log(level: LogLevel, msg: string, fields?: Record<string, unknown>): void;
|
|
152
|
+
/** Register a callback to run when THIS process terminates (any reason — normal, fault, kill, deadline).
|
|
153
|
+
* For releasing EXTERNAL resources the kernel can't see (worker threads, child processes, timers): a
|
|
154
|
+
* process that's killed mid-`main` never returns, so a `finally` won't fire — this hook still does. */
|
|
155
|
+
onCleanup(fn: () => void): void;
|
|
156
|
+
}
|
|
157
|
+
/** Re-export the resource shape so userland can describe caps it derives. */
|
|
158
|
+
export type { Resource };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Driver } from '../abi/index';
|
|
2
|
+
import { type SealedEnvelope } from '../kernel/transport';
|
|
3
|
+
/** `forward(toPid, body)` ships a message to the peer node; returns whether it was delivered.
|
|
4
|
+
* TRUSTED transport: no authentication — use only in-process or over a private channel. */
|
|
5
|
+
export declare function gatewayDriver(forward: (to: number, body: unknown) => boolean, name?: string): Driver;
|
|
6
|
+
/** AUTHENTICATED transport: seals each message (HMAC + nonce) before it hits the wire, so a
|
|
7
|
+
* hostile network can't forge, tamper, or replay it. The peer kernel verifies via `ingress()`. */
|
|
8
|
+
export declare function secureGatewayDriver(forward: (env: SealedEnvelope) => boolean, key: string, name?: string): Driver;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { llmDriver } from './llm';
|
|
2
|
+
export type { ChatFn, LlmRequest, LlmReply, ChatMessage, ToolCallReq, ToolSchema } from './llm';
|
|
3
|
+
export { toolDriver } from './tools';
|
|
4
|
+
export type { ToolFn, ToolCall } from './tools';
|
|
5
|
+
export { kvDriver } from './kv';
|
|
6
|
+
export { gatewayDriver, secureGatewayDriver } from './gateway';
|
|
7
|
+
export { workerDriver } from './worker';
|
|
8
|
+
export { wasmDriver } from './wasm';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Driver } from '../abi/index';
|
|
2
|
+
export interface ChatMessage {
|
|
3
|
+
readonly role: 'system' | 'user' | 'assistant' | 'tool';
|
|
4
|
+
readonly content: string;
|
|
5
|
+
readonly toolCalls?: readonly ToolCallReq[];
|
|
6
|
+
readonly toolCallId?: string;
|
|
7
|
+
readonly name?: string;
|
|
8
|
+
/** Multimodal side-channel (additive): image URLs (http or `data:` base64) attached to a user turn.
|
|
9
|
+
* The kernel/context treat a message as its text `content`; only the LLM adapter reads `images` and
|
|
10
|
+
* emits provider image parts (vision). Text-only backends ignore it. Rides verbatim on the stored
|
|
11
|
+
* segment, so it survives paging and follow-up turns. */
|
|
12
|
+
readonly images?: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export interface ToolCallReq {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly args: unknown;
|
|
18
|
+
}
|
|
19
|
+
export interface ToolSchema {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly description?: string;
|
|
22
|
+
readonly parameters: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export interface LlmRequest {
|
|
25
|
+
readonly messages: readonly ChatMessage[];
|
|
26
|
+
readonly tools?: readonly ToolSchema[];
|
|
27
|
+
readonly maxTokens?: number;
|
|
28
|
+
/** Optional side-channel: a streaming backend calls this with each text delta as it arrives.
|
|
29
|
+
* Purely observational (live UI) — the syscall still resolves once with the full reply, so
|
|
30
|
+
* metering, journaling and replay are unchanged. Non-streaming backends ignore it. */
|
|
31
|
+
readonly onDelta?: (text: string) => void;
|
|
32
|
+
/** Optional abort signal: a backend passes it to its HTTP call so an in-flight request can be
|
|
33
|
+
* cancelled (e.g. the user interrupts). Side-channel; non-HTTP backends ignore it. */
|
|
34
|
+
readonly signal?: AbortSignal;
|
|
35
|
+
}
|
|
36
|
+
export interface LlmReply {
|
|
37
|
+
readonly text?: string;
|
|
38
|
+
readonly toolCalls?: readonly ToolCallReq[];
|
|
39
|
+
readonly usage?: {
|
|
40
|
+
readonly promptTokens?: number;
|
|
41
|
+
readonly completionTokens?: number;
|
|
42
|
+
readonly usd?: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export type ChatFn = (req: LlmRequest) => Promise<LlmReply>;
|
|
46
|
+
export declare function llmDriver(chat: ChatFn, name?: string): Driver;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Driver } from '../abi/index';
|
|
2
|
+
export type ToolFn = (input: unknown) => unknown | Promise<unknown>;
|
|
3
|
+
export interface ToolCall {
|
|
4
|
+
readonly tool: string;
|
|
5
|
+
readonly input?: unknown;
|
|
6
|
+
}
|
|
7
|
+
export declare function toolDriver(tools: Record<string, ToolFn>, name?: string): Driver;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export { Kernel, KernelError } from './kernel/kernel';
|
|
2
|
+
export type { KernelConfig, BootOptions, BootGrant, ProcessSnapshot } from './kernel/kernel';
|
|
3
|
+
export { EventLog } from './kernel/log';
|
|
4
|
+
export type { KEvent, StampedEvent } from './kernel/log';
|
|
5
|
+
export { QuotaExceeded } from './kernel/account';
|
|
6
|
+
export { CapError } from './kernel/captable';
|
|
7
|
+
export { DefaultPager, estTokens, KeywordRecaller, Bm25Recaller, VectorRecaller, hashEmbed } from './kernel/memory';
|
|
8
|
+
export type { Recaller, EmbedFn } from './kernel/memory';
|
|
9
|
+
export { Journal } from './kernel/journal';
|
|
10
|
+
export type { KernelMode } from './kernel/journal';
|
|
11
|
+
export { MemoryStore, FileStore, journalFromStore, eventsFromStore } from './kernel/store';
|
|
12
|
+
export type { Store, StoreRecord } from './kernel/store';
|
|
13
|
+
export { seal, verify, freshNonce, NonceGuard } from './kernel/transport';
|
|
14
|
+
export type { SealedEnvelope } from './kernel/transport';
|
|
15
|
+
export { serveTcp, dialTcp } from './net/tcp';
|
|
16
|
+
export { RealClock, VirtualClock } from './kernel/clock';
|
|
17
|
+
export type { Clock, Timer } from './kernel/clock';
|
|
18
|
+
export type { Pid, CapRef, MsgId, SegId, DriverName, Sys, ProcessImage, SpawnOptions, Quota, ExitReason, LogLevel, Down, Message, SendOptions, Resource, Right, Capability, BudgetGrant, Driver, DriverHandle, DriverResult, DriverCost, Segment, Pager, } from './abi/index';
|
|
19
|
+
export { newCapRef, newMsgId, drv, hasRight, ABI_VERSION } from './abi/index';
|
|
20
|
+
export { supervisor } from './userland/supervisor';
|
|
21
|
+
export type { SupervisorSpec, ChildSpec, RestartStrategy, RestartKind } from './userland/supervisor';
|
|
22
|
+
export { nursery } from './userland/nursery';
|
|
23
|
+
export type { Scope } from './userland/nursery';
|
|
24
|
+
export { nameService, register, lookup } from './userland/nameservice';
|
|
25
|
+
export { reactAgent, conversationAgent } from './userland/agent';
|
|
26
|
+
export type { AgentSpec, AgentResult, ReflectVerdict } from './userland/agent';
|
|
27
|
+
export { sandbox, DEFAULT_ALLOW } from './userland/sandbox';
|
|
28
|
+
export { LongMemory } from './userland/longmem';
|
|
29
|
+
export type { Lesson, MemoryPersist } from './userland/longmem';
|
|
30
|
+
export { llmDistiller, parseLessons, learn } from './userland/distill';
|
|
31
|
+
export type { Distiller } from './userland/distill';
|
|
32
|
+
export { PersonaRegistry, instantiate } from './userland/persona';
|
|
33
|
+
export type { Persona, PersonaPersist } from './userland/persona';
|
|
34
|
+
export { forgePrompt, refinePrompt, forgeFromMemory } from './userland/promptsmith';
|
|
35
|
+
export type { ForgeBrief } from './userland/promptsmith';
|
|
36
|
+
export { tournament, evolve, meteredFitness } from './userland/evolve';
|
|
37
|
+
export type { Candidate, Scored, Scorer } from './userland/evolve';
|
|
38
|
+
export { PromptExpert } from './userland/expert';
|
|
39
|
+
export type { ExpertOptions } from './userland/expert';
|
|
40
|
+
export { llmDriver, toolDriver, kvDriver, gatewayDriver, secureGatewayDriver, workerDriver, wasmDriver } from './drivers/index';
|
|
41
|
+
export type { ChatFn, LlmRequest, LlmReply, ChatMessage, ToolCallReq, ToolSchema, ToolFn, ToolCall, } from './drivers/index';
|