@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,62 @@
|
|
|
1
|
+
import type { ProcessImage, Segment } from '../abi/index';
|
|
2
|
+
import type { ChatMessage, ToolSchema } from '../drivers/llm';
|
|
3
|
+
export interface AgentSpec {
|
|
4
|
+
readonly task?: string;
|
|
5
|
+
readonly system?: string;
|
|
6
|
+
readonly tools?: readonly ToolSchema[];
|
|
7
|
+
readonly maxSteps?: number;
|
|
8
|
+
readonly baseSteps?: number;
|
|
9
|
+
readonly stallLimit?: number;
|
|
10
|
+
readonly maxTokens?: number;
|
|
11
|
+
readonly history?: readonly ChatMessage[];
|
|
12
|
+
readonly delegate?: DelegatePolicy;
|
|
13
|
+
readonly reflect?: (messages: readonly ChatMessage[]) => Promise<ReflectVerdict> | ReflectVerdict;
|
|
14
|
+
readonly maxRevisions?: number;
|
|
15
|
+
readonly maxContinue?: number;
|
|
16
|
+
}
|
|
17
|
+
/** A reflect verdict, returned just before a turn would finish:
|
|
18
|
+
* - null / '' / undefined → let it finish.
|
|
19
|
+
* - string or {kind:'revise'} → a QUALITY correction (e.g. "verify failed, fix it"); sent back, COUNTED
|
|
20
|
+
* against maxRevisions so it can't loop forever.
|
|
21
|
+
* - {kind:'continue'} → the plan still has work AND is progressing; sent back but NOT counted
|
|
22
|
+
* against maxRevisions (only the generous maxContinue + the maxSteps
|
|
23
|
+
* ceiling bound it). This is what lets a multi-step plan loop to completion
|
|
24
|
+
* instead of being cut off at 2 revisions. Userland MUST stop emitting it
|
|
25
|
+
* once there's no real progress (else it spins to the ceiling). */
|
|
26
|
+
export type ReflectVerdict = string | {
|
|
27
|
+
readonly correction: string;
|
|
28
|
+
readonly kind?: 'revise' | 'continue';
|
|
29
|
+
} | null | undefined;
|
|
30
|
+
/** When the model keeps investigating file-by-file (which floods the transcript + its own context), steer
|
|
31
|
+
* it to delegate to a sub-agent tool instead. Past `after` consecutive investigation calls we withhold
|
|
32
|
+
* the (sub-agent-replaceable) read/search tools for a round and nudge it to call `via`. */
|
|
33
|
+
export interface DelegatePolicy {
|
|
34
|
+
readonly investigate: readonly string[];
|
|
35
|
+
readonly withhold: readonly string[];
|
|
36
|
+
readonly via: string;
|
|
37
|
+
readonly after: number;
|
|
38
|
+
}
|
|
39
|
+
export interface AgentResult {
|
|
40
|
+
readonly $: 'result';
|
|
41
|
+
readonly answer: string;
|
|
42
|
+
readonly steps: number;
|
|
43
|
+
readonly error?: string;
|
|
44
|
+
}
|
|
45
|
+
/** Enforce the provider invariant "an assistant tool_calls turn is followed by a tool result for EACH
|
|
46
|
+
* id" — the MMU can page out one half of a call/result pair, leaving a dangling tool_calls (→ 400) or
|
|
47
|
+
* an orphan tool result. Keep only tool_calls whose result is still resident, drop orphan tool turns,
|
|
48
|
+
* and demote an assistant turn whose calls are all gone to plain narration. Applied just before send. */
|
|
49
|
+
/** Order the working set for maximal provider prompt-cache hits. Caches key on the longest IDENTICAL
|
|
50
|
+
* message PREFIX, so we front-load the STABLE parts: pinned system prompt(s), then the slowly-changing
|
|
51
|
+
* earlier-context summaries, then the live turns in recency order. Between compactions the live turns
|
|
52
|
+
* only grow by append, so the whole prefix stays byte-identical and the provider recomputes just the
|
|
53
|
+
* newest turn. (The pager appends each summary at the END of resident — newest slot — which is both the
|
|
54
|
+
* wrong chronology and cache-hostile; this also restores "summary of old context comes first".) */
|
|
55
|
+
export declare function orderForCache(segs: readonly Segment[]): Segment[];
|
|
56
|
+
export declare function pairToolCalls(messages: readonly ChatMessage[]): ChatMessage[];
|
|
57
|
+
/** One-shot: seed system + task, run a turn, report the answer, exit. */
|
|
58
|
+
export declare function reactAgent(spec: AgentSpec): ProcessImage;
|
|
59
|
+
/** Long-lived conversation: pinned system prompt, then a loop over user turns. Context carries
|
|
60
|
+
* across turns (the MMU pages/compresses it under pressure), so the agent remembers the dialogue.
|
|
61
|
+
* Each turn arrives as `{ task }`; the answer is sent back to the reply cap. */
|
|
62
|
+
export declare function conversationAgent(spec?: AgentSpec): ProcessImage;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ChatFn, ChatMessage } from '../drivers/llm';
|
|
2
|
+
import type { LongMemory } from './longmem';
|
|
3
|
+
export type Distiller = (messages: readonly ChatMessage[]) => Promise<string[]>;
|
|
4
|
+
/** Parse a model reply into clean one-line lessons. */
|
|
5
|
+
export declare const parseLessons: (text: string) => string[];
|
|
6
|
+
/** An LLM-backed distiller: a finished session's messages → reusable lessons. */
|
|
7
|
+
export declare function llmDistiller(chat: ChatFn, maxTokens?: number): Distiller;
|
|
8
|
+
/** Reflect on a finished session: distill lessons and fold them into long-term memory. Returns added count. */
|
|
9
|
+
export declare function learn(mem: LongMemory, distill: Distiller, messages: readonly ChatMessage[], tags?: readonly string[]): Promise<number>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface Candidate {
|
|
2
|
+
prompt: string;
|
|
3
|
+
label?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface Scored extends Candidate {
|
|
6
|
+
fitness: number;
|
|
7
|
+
detail?: unknown;
|
|
8
|
+
}
|
|
9
|
+
export type Scorer = (prompt: string) => Promise<{
|
|
10
|
+
fitness: number;
|
|
11
|
+
detail?: unknown;
|
|
12
|
+
}>;
|
|
13
|
+
/** Score candidates concurrently and rank best-first (higher fitness = better). */
|
|
14
|
+
export declare function tournament(candidates: readonly Candidate[], score: Scorer): Promise<Scored[]>;
|
|
15
|
+
/** Evolve a base prompt: score it against the variants; adopt the winner ONLY if it beats the base
|
|
16
|
+
* by `margin` (else keep the baseline — no regression). Returns the chosen prompt + the ranking. */
|
|
17
|
+
export declare function evolve(base: string, variants: readonly string[], score: Scorer, margin?: number): Promise<{
|
|
18
|
+
winner: string;
|
|
19
|
+
improved: boolean;
|
|
20
|
+
ranked: Scored[];
|
|
21
|
+
}>;
|
|
22
|
+
/** A fitness from a run's outcome: correctness [0..1] dominates; cost (steps/tokens/usd) breaks ties.
|
|
23
|
+
* Tune the weights per domain; this is a sensible default the Scorer can build on. */
|
|
24
|
+
export declare function meteredFitness(r: {
|
|
25
|
+
correct: number;
|
|
26
|
+
steps?: number;
|
|
27
|
+
tokens?: number;
|
|
28
|
+
usd?: number;
|
|
29
|
+
}): number;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ChatFn, ChatMessage } from '../drivers/llm';
|
|
2
|
+
import { LongMemory, type MemoryPersist } from './longmem';
|
|
3
|
+
import { type Distiller } from './distill';
|
|
4
|
+
import { PersonaRegistry, type Persona, type PersonaPersist } from './persona';
|
|
5
|
+
import type { AgentSpec } from './agent';
|
|
6
|
+
export interface ExpertOptions {
|
|
7
|
+
readonly memory?: MemoryPersist;
|
|
8
|
+
readonly personas?: PersonaPersist;
|
|
9
|
+
readonly distiller?: Distiller;
|
|
10
|
+
readonly now?: () => number;
|
|
11
|
+
}
|
|
12
|
+
export declare class PromptExpert {
|
|
13
|
+
private readonly chat;
|
|
14
|
+
readonly memory: LongMemory;
|
|
15
|
+
readonly personas: PersonaRegistry;
|
|
16
|
+
private readonly distill;
|
|
17
|
+
constructor(chat: ChatFn, opts?: ExpertOptions);
|
|
18
|
+
/** Reflect on a finished session → fold reusable lessons into long-term memory. Returns added count. */
|
|
19
|
+
learn(messages: readonly ChatMessage[], tags?: readonly string[]): Promise<number>;
|
|
20
|
+
/** Forge (or refresh) a domain persona from accumulated lessons, then register it. */
|
|
21
|
+
specialize(domain: string, opts?: {
|
|
22
|
+
name?: string;
|
|
23
|
+
goal?: string;
|
|
24
|
+
tools?: Persona['tools'];
|
|
25
|
+
}): Promise<Persona>;
|
|
26
|
+
/** Instantiate a persona for a task (enriched with recalled lessons) → ready for reactAgent/conversationAgent. */
|
|
27
|
+
hire(name: string, task: string, recall?: number): AgentSpec | null;
|
|
28
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Recaller } from '../kernel/memory';
|
|
2
|
+
export interface Lesson {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly text: string;
|
|
5
|
+
readonly tags: readonly string[];
|
|
6
|
+
readonly ts: number;
|
|
7
|
+
uses: number;
|
|
8
|
+
}
|
|
9
|
+
/** Durable backing for lessons — wire it to a file/DB; omit for in-memory only. */
|
|
10
|
+
export interface MemoryPersist {
|
|
11
|
+
load(): readonly Lesson[];
|
|
12
|
+
append(l: Lesson): void;
|
|
13
|
+
}
|
|
14
|
+
export declare class LongMemory {
|
|
15
|
+
private readonly recaller;
|
|
16
|
+
private readonly by;
|
|
17
|
+
private readonly seen;
|
|
18
|
+
private readonly persist?;
|
|
19
|
+
private readonly now;
|
|
20
|
+
constructor(opts?: {
|
|
21
|
+
recaller?: Recaller;
|
|
22
|
+
persist?: MemoryPersist;
|
|
23
|
+
now?: () => number;
|
|
24
|
+
});
|
|
25
|
+
private ingest;
|
|
26
|
+
/** Add a distilled lesson (deduped, lossy by design). Returns false if it was a duplicate/empty. */
|
|
27
|
+
remember(text: string, tags?: readonly string[]): boolean;
|
|
28
|
+
/** Recall the k most relevant lessons for a query (bumps their use count — popularity signal). */
|
|
29
|
+
recall(query: string, k?: number): Lesson[];
|
|
30
|
+
all(): readonly Lesson[];
|
|
31
|
+
get size(): number;
|
|
32
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ProcessImage, Sys, CapRef } from '../abi/index';
|
|
2
|
+
export declare const nameService: ProcessImage;
|
|
3
|
+
/** Publish yourself under `name` so others can find you. */
|
|
4
|
+
export declare function register(sys: Sys, names: CapRef, name: string): Promise<void>;
|
|
5
|
+
/** Resolve `name` → a send-cap (or null). Skips unrelated messages while waiting for the reply. */
|
|
6
|
+
export declare function lookup(sys: Sys, names: CapRef, name: string): Promise<CapRef | null>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Sys, CapRef, ProcessImage, SpawnOptions } from '../abi/index';
|
|
2
|
+
export interface Scope {
|
|
3
|
+
/** Spawn a child INTO the scope; returns its cap (send/monitor/kill). Throws after join(). */
|
|
4
|
+
spawn<A>(image: ProcessImage<A>, opts?: SpawnOptions<A>): Promise<CapRef>;
|
|
5
|
+
/** Wait for ALL children. If any exits abnormally, cancel the rest and throw; else return normally.
|
|
6
|
+
* Either way, on settle every child has terminated. Idempotent. */
|
|
7
|
+
join(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare function nursery(sys: Sys): Scope;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AgentSpec } from './agent';
|
|
2
|
+
import type { ToolSchema } from '../drivers/llm';
|
|
3
|
+
import type { LongMemory } from './longmem';
|
|
4
|
+
export interface Persona {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly domain: string;
|
|
7
|
+
readonly system: string;
|
|
8
|
+
readonly tools?: readonly ToolSchema[];
|
|
9
|
+
readonly notes?: string;
|
|
10
|
+
readonly ts: number;
|
|
11
|
+
}
|
|
12
|
+
/** Durable backing for personas — wire to a file/DB; omit for in-memory only. */
|
|
13
|
+
export interface PersonaPersist {
|
|
14
|
+
load(): readonly Persona[];
|
|
15
|
+
save(p: Persona): void;
|
|
16
|
+
}
|
|
17
|
+
export declare class PersonaRegistry {
|
|
18
|
+
private readonly persist?;
|
|
19
|
+
private readonly now;
|
|
20
|
+
private readonly by;
|
|
21
|
+
constructor(persist?: PersonaPersist | undefined, now?: () => number);
|
|
22
|
+
/** Save (or replace) a persona by name. */
|
|
23
|
+
save(p: Omit<Persona, 'ts'> & {
|
|
24
|
+
ts?: number;
|
|
25
|
+
}): Persona;
|
|
26
|
+
get(name: string): Persona | undefined;
|
|
27
|
+
list(): readonly Persona[];
|
|
28
|
+
forDomain(domain: string): Persona[];
|
|
29
|
+
}
|
|
30
|
+
/** Build an AgentSpec from a persona — folding in the lessons memory recalls for `task`. */
|
|
31
|
+
export declare function instantiate(p: Persona, task: string, opts?: {
|
|
32
|
+
memory?: LongMemory;
|
|
33
|
+
recall?: number;
|
|
34
|
+
}): AgentSpec;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ChatFn } from '../drivers/llm';
|
|
2
|
+
import type { LongMemory } from './longmem';
|
|
3
|
+
export interface ForgeBrief {
|
|
4
|
+
domain: string;
|
|
5
|
+
goal?: string;
|
|
6
|
+
lessons?: readonly string[];
|
|
7
|
+
tools?: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
/** Forge a fresh specialized system prompt for a domain. */
|
|
10
|
+
export declare function forgePrompt(chat: ChatFn, b: ForgeBrief, maxTokens?: number): Promise<string>;
|
|
11
|
+
/** Improve an existing prompt given concrete feedback — fix the problem, keep what worked. */
|
|
12
|
+
export declare function refinePrompt(chat: ChatFn, base: string, feedback: string, maxTokens?: number): Promise<string>;
|
|
13
|
+
/** Forge a domain prompt using the top lessons long-term memory recalls — the learn→specialize loop. */
|
|
14
|
+
export declare function forgeFromMemory(chat: ChatFn, domain: string, mem: LongMemory, opts?: {
|
|
15
|
+
goal?: string;
|
|
16
|
+
k?: number;
|
|
17
|
+
tools?: readonly string[];
|
|
18
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ResourceLimits } from 'node:worker_threads';
|
|
2
|
+
import type { ProcessImage } from '../abi/index';
|
|
3
|
+
/** Syscalls a sandbox may invoke by default — a least-privilege subset (no spawn/open/signal). */
|
|
4
|
+
export declare const DEFAULT_ALLOW: readonly ["self", "recv", "send", "remember", "recall", "workingSet", "log", "exit"];
|
|
5
|
+
/** Default heap bound for the worker. The kernel's `mem` quota bounds CONTEXT segments, not the worker's V8
|
|
6
|
+
* heap — so without this an untrusted sandbox could allocate freely and OOM the HOST. Generous for real agent
|
|
7
|
+
* logic; a breach aborts the worker (→ the process faults and is reaped). Raise via opts.resourceLimits. */
|
|
8
|
+
export declare const DEFAULT_RESOURCE_LIMITS: ResourceLimits;
|
|
9
|
+
/** Run `code` (a function body with `sys`, `args`, `threadId` in scope) isolated in a worker thread. */
|
|
10
|
+
export declare function sandbox(code: string, opts?: {
|
|
11
|
+
allow?: readonly string[];
|
|
12
|
+
name?: string;
|
|
13
|
+
resourceLimits?: ResourceLimits;
|
|
14
|
+
}): ProcessImage;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ProcessImage, Pid, Quota } from '../abi/index';
|
|
2
|
+
export type RestartStrategy = 'one_for_one' | 'one_for_all' | 'rest_for_one';
|
|
3
|
+
export type RestartKind = 'permanent' | 'transient' | 'temporary';
|
|
4
|
+
export interface ChildSpec {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly image: ProcessImage;
|
|
7
|
+
readonly args?: unknown;
|
|
8
|
+
readonly quota?: Quota;
|
|
9
|
+
readonly restart?: RestartKind;
|
|
10
|
+
}
|
|
11
|
+
export interface SupervisorSpec {
|
|
12
|
+
readonly strategy: RestartStrategy;
|
|
13
|
+
readonly children: readonly ChildSpec[];
|
|
14
|
+
readonly maxRestarts?: number;
|
|
15
|
+
readonly periodMs?: number;
|
|
16
|
+
readonly backoffMs?: number;
|
|
17
|
+
readonly maxBackoffMs?: number;
|
|
18
|
+
/** Called after each (re)start — handy for tests/observability. */
|
|
19
|
+
readonly onStart?: (id: string, pid: Pid) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function supervisor(spec: SupervisorSpec): ProcessImage;
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@guangnao/agent-os",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AgentOS Kernel — a lightweight, type-safe agent operating system in TypeScript",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"keywords": [
|
|
20
|
+
"agent", "agents", "llm", "microkernel", "operating-system", "scheduler",
|
|
21
|
+
"capabilities", "object-capability", "sandbox", "wasm", "react-agent", "typescript"
|
|
22
|
+
],
|
|
23
|
+
"author": "guangnao",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "node build.mjs",
|
|
29
|
+
"build:watch": "tsc -p tsconfig.build.json -w",
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
|
+
"typecheck:watch": "tsc --noEmit -w",
|
|
32
|
+
"test": "tsc --noEmit && node --import tsx --test ./test/**/*.test.ts",
|
|
33
|
+
"test:run": "node --import tsx --test ./test/**/*.test.ts",
|
|
34
|
+
"test:watch": "node --import tsx --test --watch ./test/**/*.test.ts",
|
|
35
|
+
"bench": "tsx examples/bench.ts",
|
|
36
|
+
"pack": "node scripts/bundle.mjs",
|
|
37
|
+
"prepublishOnly": "npm test && npm run build"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^26.0.0",
|
|
41
|
+
"tsx": "^4.22.4",
|
|
42
|
+
"typescript": "^5.6.0",
|
|
43
|
+
"esbuild": "^0.28.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
},
|
|
48
|
+
"license": "MIT"
|
|
49
|
+
}
|