@deepstrike/sdk 0.1.1
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/README.md +296 -0
- package/dist/agent.d.ts +62 -0
- package/dist/agent.js +321 -0
- package/dist/harness/harness.d.ts +40 -0
- package/dist/harness/harness.js +100 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +12 -0
- package/dist/knowledge/source.d.ts +3 -0
- package/dist/knowledge/source.js +1 -0
- package/dist/memory/protocols.d.ts +48 -0
- package/dist/memory/protocols.js +2 -0
- package/dist/memory/working.d.ts +8 -0
- package/dist/memory/working.js +8 -0
- package/dist/providers/anthropic.d.ts +15 -0
- package/dist/providers/anthropic.js +102 -0
- package/dist/providers/base.d.ts +15 -0
- package/dist/providers/base.js +46 -0
- package/dist/providers/ollama.d.ts +9 -0
- package/dist/providers/ollama.js +58 -0
- package/dist/providers/openai.d.ts +44 -0
- package/dist/providers/openai.js +144 -0
- package/dist/safety/permissions.d.ts +17 -0
- package/dist/safety/permissions.js +32 -0
- package/dist/signals/gateway.d.ts +30 -0
- package/dist/signals/gateway.js +71 -0
- package/dist/signals/scheduled.d.ts +9 -0
- package/dist/signals/scheduled.js +18 -0
- package/dist/signals/types.d.ts +8 -0
- package/dist/signals/types.js +1 -0
- package/dist/skills/loader.d.ts +11 -0
- package/dist/skills/loader.js +43 -0
- package/dist/tools/index.d.ts +12 -0
- package/dist/tools/index.js +25 -0
- package/dist/types.d.ts +73 -0
- package/dist/types.js +1 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# DeepStrike Node.js SDK
|
|
2
|
+
|
|
3
|
+
Agent framework built on a Rust kernel. The kernel handles loop control, context compression, skill selection, and termination — the SDK handles all I/O.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @deepstrike/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 18+. The Rust kernel is distributed as a pre-built native addon (`@deepstrike/core`).
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { Agent, AnthropicProvider, tool } from "@deepstrike/sdk"
|
|
19
|
+
|
|
20
|
+
const add = tool("add", "Add two numbers.", {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: { x: { type: "number" }, y: { type: "number" } },
|
|
23
|
+
required: ["x", "y"],
|
|
24
|
+
}, async ({ x, y }) => String((x as number) + (y as number)))
|
|
25
|
+
|
|
26
|
+
const agent = new Agent(
|
|
27
|
+
new AnthropicProvider("sk-..."),
|
|
28
|
+
{ maxTokens: 32_000, maxTurns: 10 },
|
|
29
|
+
)
|
|
30
|
+
agent.register(add)
|
|
31
|
+
|
|
32
|
+
await agent.run("What is 2 + 3?")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Streaming:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
for await (const event of agent.runStreaming("Summarize README.md")) {
|
|
39
|
+
if (event.type === "text_delta") process.stdout.write(event.delta)
|
|
40
|
+
else if (event.type === "tool_call") console.log(`\n[→ ${event.name}]`)
|
|
41
|
+
else if (event.type === "done") console.log(`\ndone in ${event.iterations} turns (${event.status})`)
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Architecture
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
src/
|
|
51
|
+
├── index.ts # Public exports
|
|
52
|
+
├── agent.ts # Agent — top-level entry point
|
|
53
|
+
├── types.ts # Shared type definitions
|
|
54
|
+
├── providers/ # LLM adapters (HTTP + streaming)
|
|
55
|
+
├── tools/ # tool() helper, executeTools, built-ins
|
|
56
|
+
├── skills/ # SkillLoader — .md file loading
|
|
57
|
+
├── memory/ # WorkingMemory + MemorySource/Extractor interfaces
|
|
58
|
+
├── knowledge/ # KnowledgeSource interface
|
|
59
|
+
├── harness/ # SinglePassHarness, EvalLoopHarness, QualityGate
|
|
60
|
+
├── signals/ # RuntimeSignal, SignalSource, ScheduledPrompt
|
|
61
|
+
└── safety/ # PermissionManager
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The kernel (`@deepstrike/core`, Rust/NAPI) owns:
|
|
65
|
+
- `LoopStateMachine` — drives `call_llm → execute_tools → load_skills → done`
|
|
66
|
+
- `ContextEngine` — 5-partition context with pressure-based compression
|
|
67
|
+
- `Governance` — tool veto authority
|
|
68
|
+
- `SignalRouter` — external interrupt queue
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Providers
|
|
73
|
+
|
|
74
|
+
| Class | Backend | Notes |
|
|
75
|
+
|-------|---------|-------|
|
|
76
|
+
| `AnthropicProvider` | Anthropic API | Native SSE, `ThinkingDelta` support |
|
|
77
|
+
| `OpenAIProvider` | OpenAI API | SSE tool-call accumulation |
|
|
78
|
+
| `QwenProvider` | DashScope | `enable_thinking` via extensions |
|
|
79
|
+
| `DeepSeekProvider` | DeepSeek API | Reasoner models strip tools automatically |
|
|
80
|
+
| `MiniMaxProvider` | MiniMax API | M1 reasoning via `expose_reasoning` |
|
|
81
|
+
| `OllamaProvider` | Local Ollama | `http://localhost:11434` default |
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { AnthropicProvider } from "@deepstrike/sdk"
|
|
85
|
+
|
|
86
|
+
const provider = new AnthropicProvider("sk-...", "claude-opus-4-7", {
|
|
87
|
+
maxRetries: 5,
|
|
88
|
+
baseDelay: 2000,
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Thinking / reasoning:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
for await (const event of agent.runStreaming("...", undefined, { enable_thinking: true })) {
|
|
96
|
+
if (event.type === "thinking_delta") process.stdout.write(event.delta)
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Tools
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { tool, Agent } from "@deepstrike/sdk"
|
|
106
|
+
|
|
107
|
+
const search = tool("search", "Search the knowledge base.", {
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: { query: { type: "string" }, topK: { type: "number" } },
|
|
110
|
+
required: ["query"],
|
|
111
|
+
}, async ({ query, topK }) => mySearch(query as string, topK as number))
|
|
112
|
+
|
|
113
|
+
agent.register(search)
|
|
114
|
+
agent.unregister("search")
|
|
115
|
+
agent.blockTool("bash") // governance veto — permanent for this agent instance
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Built-in tools: `readFile`.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Skills
|
|
123
|
+
|
|
124
|
+
Skills are `.md` files with YAML frontmatter. The kernel selects and injects them automatically.
|
|
125
|
+
|
|
126
|
+
```markdown
|
|
127
|
+
---
|
|
128
|
+
name: debug
|
|
129
|
+
description: Step-by-step debugging guide
|
|
130
|
+
when_to_use: error, traceback, exception
|
|
131
|
+
effort: 2
|
|
132
|
+
estimated_tokens: 800
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Debug protocol
|
|
136
|
+
1. Read the traceback carefully ...
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { Agent, SkillLoader } from "@deepstrike/sdk"
|
|
141
|
+
|
|
142
|
+
const loader = new SkillLoader("~/.deepstrike/skills")
|
|
143
|
+
const agent = new Agent(provider, { maxTokens: 32_000, maxTurns: 10, skillLoader: loader })
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Memory
|
|
149
|
+
|
|
150
|
+
Implement `MemorySource` to inject persistent context before a run, and `MemoryExtractor` to persist what was learned after.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import type { MemorySource, MemoryExtractor } from "@deepstrike/sdk"
|
|
154
|
+
|
|
155
|
+
class MyMemorySource implements MemorySource {
|
|
156
|
+
async load(goal: string): Promise<string[]> {
|
|
157
|
+
return db.query(goal)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
class MyMemoryExtractor implements MemoryExtractor {
|
|
162
|
+
async extract(goal: string, finalText: string, turns: number): Promise<void> {
|
|
163
|
+
await db.save(goal, finalText)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const agent = new Agent(provider, {
|
|
168
|
+
maxTokens: 32_000,
|
|
169
|
+
maxTurns: 10,
|
|
170
|
+
memorySource: new MyMemorySource(),
|
|
171
|
+
memoryExtractor: new MyMemoryExtractor(),
|
|
172
|
+
})
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`WorkingMemory` is an in-process scratch pad for within-run state:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { WorkingMemory } from "@deepstrike/sdk"
|
|
179
|
+
|
|
180
|
+
const mem = new WorkingMemory()
|
|
181
|
+
mem.set("step", 1)
|
|
182
|
+
mem.get("step") // 1
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Knowledge
|
|
188
|
+
|
|
189
|
+
Inject run-scoped evidence (RAG results, API responses) without polluting long-term memory:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import type { KnowledgeSource } from "@deepstrike/sdk"
|
|
193
|
+
|
|
194
|
+
class VectorSearch implements KnowledgeSource {
|
|
195
|
+
async retrieve(goal: string, topK = 5): Promise<string[]> {
|
|
196
|
+
return vectorDb.search(goal, topK)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const agent = new Agent(provider, {
|
|
201
|
+
maxTokens: 32_000,
|
|
202
|
+
maxTurns: 10,
|
|
203
|
+
knowledgeSource: new VectorSearch(),
|
|
204
|
+
})
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Snippets are prepended as a system message before the first LLM call.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Harness
|
|
212
|
+
|
|
213
|
+
Control how runs are attempted:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { Agent, SinglePassHarness, EvalLoopHarness, HarnessRequest } from "@deepstrike/sdk"
|
|
217
|
+
import type { QualityGate, HarnessOutcome } from "@deepstrike/sdk"
|
|
218
|
+
|
|
219
|
+
// Single pass
|
|
220
|
+
const harness = new SinglePassHarness(agent)
|
|
221
|
+
const outcome = await harness.run({ goal: "Write a haiku" })
|
|
222
|
+
|
|
223
|
+
// Eval loop — retry until QualityGate passes (max 3 attempts)
|
|
224
|
+
class LengthGate implements QualityGate {
|
|
225
|
+
async evaluate(request: HarnessRequest, outcome: HarnessOutcome): Promise<boolean> {
|
|
226
|
+
return outcome.result.length > 50
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const evalHarness = new EvalLoopHarness(agent, new LengthGate(), 3)
|
|
231
|
+
const result = await evalHarness.run({ goal: "Write a haiku" })
|
|
232
|
+
console.log(result.passed, result.iterations)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Signals & interrupts
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { ScheduledPrompt } from "@deepstrike/sdk"
|
|
241
|
+
import type { SignalSource, RuntimeSignal } from "@deepstrike/sdk"
|
|
242
|
+
|
|
243
|
+
// Interrupt a running agent
|
|
244
|
+
setTimeout(() => agent.interrupt(), 30_000)
|
|
245
|
+
|
|
246
|
+
// Convert a scheduled prompt to a RuntimeSignal
|
|
247
|
+
const prompt = new ScheduledPrompt("Daily standup summary", 1_700_000_000_000)
|
|
248
|
+
const signal = prompt.toSignal()
|
|
249
|
+
// signal.kind === "scheduled"
|
|
250
|
+
// signal.payload === { goal: "Daily standup summary", criteria: [], runAtMs: ... }
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Implement `SignalSource` to feed signals from any external source:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
class WebhookSource implements SignalSource {
|
|
257
|
+
async nextSignal(): Promise<RuntimeSignal | null> {
|
|
258
|
+
const event = await webhookQueue.get()
|
|
259
|
+
return { kind: "external", payload: event }
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Permissions
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { PermissionManager, PermissionMode } from "@deepstrike/sdk"
|
|
270
|
+
|
|
271
|
+
const pm = new PermissionManager(PermissionMode.DEFAULT)
|
|
272
|
+
pm.grant("bash", "execute")
|
|
273
|
+
pm.grant("fs", "*") // wildcard: all actions on fs
|
|
274
|
+
pm.revoke("bash", "execute")
|
|
275
|
+
|
|
276
|
+
const decision = pm.evaluate("bash", "execute")
|
|
277
|
+
decision.allowed // boolean
|
|
278
|
+
decision.reason // string
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Modes: `DEFAULT` (evaluate grants), `PLAN` (block all), `AUTO` (allow all).
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Stream events
|
|
286
|
+
|
|
287
|
+
| Event type | Fields |
|
|
288
|
+
|------------|--------|
|
|
289
|
+
| `text_delta` | `delta: string` |
|
|
290
|
+
| `thinking_delta` | `delta: string` |
|
|
291
|
+
| `tool_call` | `id, name, arguments` |
|
|
292
|
+
| `tool_result` | `callId, name, content, isError` |
|
|
293
|
+
| `done` | `iterations, totalTokens, status` |
|
|
294
|
+
| `error` | `message: string` |
|
|
295
|
+
|
|
296
|
+
`status` mirrors the kernel termination reason: `completed` / `max_turns` / `token_budget` / `timeout` / `user_abort` / `error`.
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { LLMProvider, StreamEvent } from "./types.js";
|
|
2
|
+
import type { RegisteredTool } from "./tools/index.js";
|
|
3
|
+
import type { DreamStore, DreamResult } from "./memory/protocols.js";
|
|
4
|
+
import type { KnowledgeSource } from "./knowledge/source.js";
|
|
5
|
+
import type { SignalSource } from "./signals/types.js";
|
|
6
|
+
export interface AgentOptions {
|
|
7
|
+
maxTokens: number;
|
|
8
|
+
maxTurns?: number;
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
extensions?: Record<string, unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* Directory containing skill `.md` files. The kernel will auto-inject a
|
|
13
|
+
* `skill` meta-tool so the model can load any skill by name on demand.
|
|
14
|
+
*/
|
|
15
|
+
skillDir?: string;
|
|
16
|
+
knowledgeSource?: KnowledgeSource;
|
|
17
|
+
signalSource?: SignalSource;
|
|
18
|
+
/** Backing store for the idle dreaming pipeline. Required to call `Agent.dream()`. */
|
|
19
|
+
dreamStore?: DreamStore;
|
|
20
|
+
/**
|
|
21
|
+
* Stable identifier for this agent. Required to enable in-session memory retrieval
|
|
22
|
+
* when `dreamStore` is configured — the kernel injects a `memory` meta-tool and
|
|
23
|
+
* the SDK calls `dreamStore.search(agentId, query)` on demand.
|
|
24
|
+
*/
|
|
25
|
+
agentId?: string;
|
|
26
|
+
/** Governance instance from @deepstrike/core for permission checks on tool calls. */
|
|
27
|
+
governance?: {
|
|
28
|
+
evaluate(toolName: string, args: string): {
|
|
29
|
+
kind: string;
|
|
30
|
+
reason?: string;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export declare class Agent {
|
|
35
|
+
private readonly provider;
|
|
36
|
+
private readonly options;
|
|
37
|
+
private tools;
|
|
38
|
+
private blockedTools;
|
|
39
|
+
private extensions;
|
|
40
|
+
private skillDir?;
|
|
41
|
+
private knowledgeSource?;
|
|
42
|
+
private signalSource?;
|
|
43
|
+
private dreamStore?;
|
|
44
|
+
private interrupted;
|
|
45
|
+
private pendingInterrupt;
|
|
46
|
+
constructor(provider: LLMProvider, options: AgentOptions);
|
|
47
|
+
interrupt(): void;
|
|
48
|
+
register(...tools: RegisteredTool[]): this;
|
|
49
|
+
unregister(name: string): this;
|
|
50
|
+
blockTool(name: string): this;
|
|
51
|
+
run(goal: string, criteria?: string[], extensions?: Record<string, unknown>): Promise<string>;
|
|
52
|
+
runStreaming(goal: string, criteria?: string[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
53
|
+
/**
|
|
54
|
+
* Trigger an idle dreaming cycle for the given agent.
|
|
55
|
+
*
|
|
56
|
+
* Phase 1 — kernel rule-based analysis + LLM prompt assembly (kernel, pure computation)
|
|
57
|
+
* Phase 2 — LLM synthesis call (SDK, I/O here)
|
|
58
|
+
* Phase 3 — kernel parses + curates results (kernel, pure computation)
|
|
59
|
+
* Phase 4 — commit delta to DreamStore (SDK, I/O here)
|
|
60
|
+
*/
|
|
61
|
+
dream(agentId: string, nowMs?: number): Promise<DreamResult>;
|
|
62
|
+
}
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { executeTools } from "./tools/index.js";
|
|
2
|
+
import { readSkillFile, scanSkillDir } from "./skills/loader.js";
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
async function loadKernel() {
|
|
5
|
+
return import("@deepstrike/core");
|
|
6
|
+
}
|
|
7
|
+
export class Agent {
|
|
8
|
+
provider;
|
|
9
|
+
options;
|
|
10
|
+
tools = new Map();
|
|
11
|
+
blockedTools = new Set();
|
|
12
|
+
extensions;
|
|
13
|
+
skillDir;
|
|
14
|
+
knowledgeSource;
|
|
15
|
+
signalSource;
|
|
16
|
+
dreamStore;
|
|
17
|
+
interrupted = false;
|
|
18
|
+
pendingInterrupt = false;
|
|
19
|
+
constructor(provider, options) {
|
|
20
|
+
this.provider = provider;
|
|
21
|
+
this.options = options;
|
|
22
|
+
this.extensions = options.extensions ?? {};
|
|
23
|
+
this.skillDir = options.skillDir;
|
|
24
|
+
this.knowledgeSource = options.knowledgeSource;
|
|
25
|
+
this.signalSource = options.signalSource;
|
|
26
|
+
this.dreamStore = options.dreamStore;
|
|
27
|
+
}
|
|
28
|
+
interrupt() {
|
|
29
|
+
this.interrupted = true;
|
|
30
|
+
}
|
|
31
|
+
register(...tools) {
|
|
32
|
+
for (const t of tools)
|
|
33
|
+
this.tools.set(t.schema.name, t);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
unregister(name) {
|
|
37
|
+
this.tools.delete(name);
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
blockTool(name) {
|
|
41
|
+
this.blockedTools.add(name);
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
async run(goal, criteria, extensions) {
|
|
45
|
+
let result;
|
|
46
|
+
for await (const evt of this.runStreaming(goal, criteria, extensions)) {
|
|
47
|
+
if (evt.type === "done")
|
|
48
|
+
result = evt;
|
|
49
|
+
}
|
|
50
|
+
return result ? `done in ${result.iterations} turns (${result.status})` : "done";
|
|
51
|
+
}
|
|
52
|
+
async *runStreaming(goal, criteria, extensions) {
|
|
53
|
+
this.interrupted = false;
|
|
54
|
+
this.pendingInterrupt = false;
|
|
55
|
+
const kernel = await loadKernel();
|
|
56
|
+
const ext = { ...this.extensions, ...(extensions ?? {}) };
|
|
57
|
+
const sm = new kernel.LoopStateMachine({
|
|
58
|
+
maxTokens: this.options.maxTokens,
|
|
59
|
+
maxTurns: this.options.maxTurns ?? 25,
|
|
60
|
+
timeoutMs: this.options.timeoutMs,
|
|
61
|
+
});
|
|
62
|
+
// Create a per-run SignalRouter so dedup state doesn't leak across runs.
|
|
63
|
+
const router = new kernel.SignalRouter(256);
|
|
64
|
+
const toolSchemas = Array.from(this.tools.values()).map(t => t.schema);
|
|
65
|
+
sm.setTools(toolSchemas);
|
|
66
|
+
// Scan skill directory and register metadata with the kernel.
|
|
67
|
+
// The kernel will auto-inject the `skill` meta-tool into every CallLLM action.
|
|
68
|
+
if (this.skillDir) {
|
|
69
|
+
const skillMetas = await scanSkillDir(this.skillDir);
|
|
70
|
+
sm.setAvailableSkills(skillMetas.map(m => ({
|
|
71
|
+
name: m.name,
|
|
72
|
+
description: m.description,
|
|
73
|
+
whenToUse: m.whenToUse,
|
|
74
|
+
effort: m.effort,
|
|
75
|
+
estimatedTokens: m.estimatedTokens ?? 0,
|
|
76
|
+
})));
|
|
77
|
+
}
|
|
78
|
+
// Enable the memory meta-tool when both dreamStore and agentId are provided.
|
|
79
|
+
if (this.dreamStore && this.options.agentId) {
|
|
80
|
+
sm.setMemoryEnabled(true);
|
|
81
|
+
}
|
|
82
|
+
// Enable the knowledge meta-tool when a KnowledgeSource is configured.
|
|
83
|
+
if (this.knowledgeSource) {
|
|
84
|
+
sm.setKnowledgeEnabled(true);
|
|
85
|
+
}
|
|
86
|
+
let action = sm.start({ goal, criteria: criteria ?? [] });
|
|
87
|
+
let finalText = "";
|
|
88
|
+
while (!sm.isTerminal()) {
|
|
89
|
+
if (this.interrupted) {
|
|
90
|
+
action = sm.feedTimeout();
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
if (this.pendingInterrupt) {
|
|
94
|
+
this.pendingInterrupt = false;
|
|
95
|
+
action = sm.feedTimeout();
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
// Poll signal source and route through kernel SignalRouter
|
|
99
|
+
if (this.signalSource) {
|
|
100
|
+
const sig = await this.signalSource.nextSignal();
|
|
101
|
+
if (sig) {
|
|
102
|
+
const kernelSig = {
|
|
103
|
+
id: crypto.randomUUID(),
|
|
104
|
+
source: sig.source ?? "custom",
|
|
105
|
+
signalType: sig.signalType ?? "event",
|
|
106
|
+
urgency: sig.urgency ?? (sig.kind === "interrupt" ? "critical" : "normal"),
|
|
107
|
+
summary: String(sig.payload?.goal ?? sig.kind),
|
|
108
|
+
payload: JSON.stringify(sig.payload ?? {}),
|
|
109
|
+
dedupeKey: sig.dedupeKey ?? null,
|
|
110
|
+
timestampMs: Date.now(),
|
|
111
|
+
};
|
|
112
|
+
const disposition = router.ingest(kernelSig, action.kind === "execute_tools");
|
|
113
|
+
if (disposition === "interrupt_now") {
|
|
114
|
+
action = sm.feedTimeout();
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
if (disposition === "interrupt") {
|
|
118
|
+
this.pendingInterrupt = true;
|
|
119
|
+
}
|
|
120
|
+
// "queue" → buffered; "observe" / "ignore" / "dropped" → no action
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
sm.takeObservations();
|
|
124
|
+
if (action.kind === "call_llm") {
|
|
125
|
+
finalText = "";
|
|
126
|
+
const finalToolCalls = [];
|
|
127
|
+
const messages = (action.messages ?? []);
|
|
128
|
+
const tools = (action.tools ?? []);
|
|
129
|
+
try {
|
|
130
|
+
for await (const evt of this.provider.stream(messages, tools, Object.keys(ext).length ? ext : undefined)) {
|
|
131
|
+
yield evt;
|
|
132
|
+
if (evt.type === "text_delta")
|
|
133
|
+
finalText += evt.delta;
|
|
134
|
+
else if (evt.type === "tool_call") {
|
|
135
|
+
const tc = evt;
|
|
136
|
+
finalToolCalls.push({ id: tc.id, name: tc.name, arguments: JSON.stringify(tc.arguments) });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
yield { type: "error", message: String(err) };
|
|
142
|
+
action = sm.feedTimeout();
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
action = sm.feedLlmResponse({ role: "assistant", content: finalText, toolCalls: finalToolCalls });
|
|
146
|
+
}
|
|
147
|
+
else if (action.kind === "execute_tools") {
|
|
148
|
+
const allCalls = action.calls ?? [];
|
|
149
|
+
// Governance check: blocked tools + GovernancePipeline
|
|
150
|
+
const permittedCalls = [];
|
|
151
|
+
for (const c of allCalls) {
|
|
152
|
+
if (this.blockedTools.has(c.name)) {
|
|
153
|
+
yield { type: "error", message: `tool blocked: ${c.name}` };
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (this.options.governance) {
|
|
157
|
+
const verdict = this.options.governance.evaluate(c.name, c.arguments);
|
|
158
|
+
if (verdict.kind === "deny") {
|
|
159
|
+
yield { type: "error", message: `permission denied: ${c.name} — ${verdict.reason}` };
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (verdict.kind === "ask_user") {
|
|
163
|
+
yield { type: "permission_request", callId: c.id, toolName: c.name, arguments: c.arguments, reason: verdict.reason };
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
permittedCalls.push(c);
|
|
168
|
+
}
|
|
169
|
+
const calls = permittedCalls;
|
|
170
|
+
// Intercept `skill` meta-tool calls: read file, return content as tool result.
|
|
171
|
+
const skillCalls = calls.filter((c) => c.name === "skill");
|
|
172
|
+
// Intercept `memory` meta-tool calls: search DreamStore, return entries as tool result.
|
|
173
|
+
const memoryCalls = calls.filter((c) => c.name === "memory");
|
|
174
|
+
// Intercept `knowledge` meta-tool calls: retrieve from KnowledgeSource.
|
|
175
|
+
const knowledgeCalls = calls.filter((c) => c.name === "knowledge");
|
|
176
|
+
const regularCalls = calls.filter((c) => c.name !== "skill" && c.name !== "memory" && c.name !== "knowledge");
|
|
177
|
+
const skillResults = this.skillDir
|
|
178
|
+
? await Promise.all(skillCalls.map(async (c) => {
|
|
179
|
+
const args = tryParseJson(c.arguments);
|
|
180
|
+
const name = String(args?.name ?? "");
|
|
181
|
+
const content = await readSkillFile(this.skillDir, name);
|
|
182
|
+
const output = content ?? `Skill "${name}" not found.`;
|
|
183
|
+
return { callId: c.id, name: c.name, output, isError: !content };
|
|
184
|
+
}))
|
|
185
|
+
: skillCalls.map((c) => ({ callId: c.id, name: c.name, output: "No skill directory configured.", isError: true }));
|
|
186
|
+
const memoryResults = (this.dreamStore && this.options.agentId)
|
|
187
|
+
? await Promise.all(memoryCalls.map(async (c) => {
|
|
188
|
+
const args = tryParseJson(c.arguments);
|
|
189
|
+
const query = String(args?.query ?? "");
|
|
190
|
+
const topK = typeof args?.top_k === "number" ? args.top_k : 5;
|
|
191
|
+
const entries = await this.dreamStore.search(this.options.agentId, query, topK);
|
|
192
|
+
const output = entries.length
|
|
193
|
+
? entries.map(e => `[score=${e.score.toFixed(3)}] ${e.text}`).join("\n---\n")
|
|
194
|
+
: "No relevant memories found.";
|
|
195
|
+
return { callId: c.id, name: c.name, output, isError: false };
|
|
196
|
+
}))
|
|
197
|
+
: memoryCalls.map((c) => ({ callId: c.id, name: c.name, output: "Memory retrieval not configured.", isError: true }));
|
|
198
|
+
const knowledgeResults = this.knowledgeSource
|
|
199
|
+
? await Promise.all(knowledgeCalls.map(async (c) => {
|
|
200
|
+
const args = tryParseJson(c.arguments);
|
|
201
|
+
const query = String(args?.query ?? "");
|
|
202
|
+
const topK = typeof args?.top_k === "number" ? args.top_k : 5;
|
|
203
|
+
const snippets = await this.knowledgeSource.retrieve(query, topK);
|
|
204
|
+
const output = snippets.length ? snippets.join("\n---\n") : "No relevant knowledge found.";
|
|
205
|
+
return { callId: c.id, name: c.name, output, isError: false };
|
|
206
|
+
}))
|
|
207
|
+
: knowledgeCalls.map((c) => ({ callId: c.id, name: c.name, output: "Knowledge source not configured.", isError: true }));
|
|
208
|
+
// Yield all meta-tool results
|
|
209
|
+
for (const r of [...skillResults, ...memoryResults, ...knowledgeResults])
|
|
210
|
+
yield { type: "tool_result", callId: r.callId, name: r.name, content: r.output, isError: r.isError };
|
|
211
|
+
const results = await executeTools(regularCalls, this.tools);
|
|
212
|
+
for (const r of results) {
|
|
213
|
+
const name = regularCalls.find(c => c.id === r.callId)?.name ?? "";
|
|
214
|
+
yield { type: "tool_result", callId: r.callId, name, content: r.output, isError: r.isError };
|
|
215
|
+
}
|
|
216
|
+
action = sm.feedToolResults([
|
|
217
|
+
...skillResults,
|
|
218
|
+
...memoryResults,
|
|
219
|
+
...knowledgeResults,
|
|
220
|
+
...results.map(r => ({ callId: r.callId, output: r.output, isError: r.isError })),
|
|
221
|
+
]);
|
|
222
|
+
}
|
|
223
|
+
else if (action.kind === "done") {
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const result = action.result;
|
|
228
|
+
yield {
|
|
229
|
+
type: "done",
|
|
230
|
+
iterations: result?.turnsUsed ?? 0,
|
|
231
|
+
totalTokens: Number(result?.totalTokensUsed ?? 0),
|
|
232
|
+
status: result?.termination ?? "error",
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Trigger an idle dreaming cycle for the given agent.
|
|
237
|
+
*
|
|
238
|
+
* Phase 1 — kernel rule-based analysis + LLM prompt assembly (kernel, pure computation)
|
|
239
|
+
* Phase 2 — LLM synthesis call (SDK, I/O here)
|
|
240
|
+
* Phase 3 — kernel parses + curates results (kernel, pure computation)
|
|
241
|
+
* Phase 4 — commit delta to DreamStore (SDK, I/O here)
|
|
242
|
+
*/
|
|
243
|
+
async dream(agentId, nowMs = Date.now()) {
|
|
244
|
+
if (!this.dreamStore)
|
|
245
|
+
throw new Error("dreamStore not configured on AgentOptions");
|
|
246
|
+
const kernel = await loadKernel();
|
|
247
|
+
const sessions = await this.dreamStore.loadSessions(agentId);
|
|
248
|
+
const existingMemories = await this.dreamStore.loadMemories(agentId);
|
|
249
|
+
if (!sessions.length) {
|
|
250
|
+
return { sessionsProcessed: 0, insightsExtracted: 0, entriesAdded: 0, entriesRemoved: 0 };
|
|
251
|
+
}
|
|
252
|
+
const kernelSessions = sessions.map(s => ({
|
|
253
|
+
sessionId: s.sessionId,
|
|
254
|
+
agentId: s.agentId,
|
|
255
|
+
messages: s.messages.map(m => ({
|
|
256
|
+
role: m.role,
|
|
257
|
+
content: m.content,
|
|
258
|
+
tokenCount: m.tokenCount,
|
|
259
|
+
toolCalls: (m.toolCalls ?? []).map(tc => ({
|
|
260
|
+
id: tc.id, name: tc.name, arguments: tc.arguments,
|
|
261
|
+
})),
|
|
262
|
+
})),
|
|
263
|
+
metadata: JSON.stringify(s.metadata ?? null),
|
|
264
|
+
createdAtMs: s.createdAtMs,
|
|
265
|
+
updatedAtMs: s.updatedAtMs,
|
|
266
|
+
}));
|
|
267
|
+
const kernelMemories = existingMemories.map(e => ({
|
|
268
|
+
text: e.text,
|
|
269
|
+
score: e.score,
|
|
270
|
+
metadata: JSON.stringify(e.metadata ?? null),
|
|
271
|
+
}));
|
|
272
|
+
const pipeline = new kernel.IdlePipeline(agentId);
|
|
273
|
+
const action1 = pipeline.feedTrigger(kernelSessions, kernelMemories, nowMs);
|
|
274
|
+
if (action1.kind === "noop") {
|
|
275
|
+
return { sessionsProcessed: 0, insightsExtracted: 0, entriesAdded: 0, entriesRemoved: 0 };
|
|
276
|
+
}
|
|
277
|
+
if (action1.kind !== "synthesize_insights") {
|
|
278
|
+
throw new Error(`unexpected action after feedTrigger: ${action1.kind}`);
|
|
279
|
+
}
|
|
280
|
+
let synthesisText = "";
|
|
281
|
+
for await (const evt of this.provider.stream((action1.messages ?? []), [], undefined)) {
|
|
282
|
+
if (evt.type === "text_delta")
|
|
283
|
+
synthesisText += evt.delta;
|
|
284
|
+
}
|
|
285
|
+
const action2 = pipeline.feedSynthesisResult(synthesisText);
|
|
286
|
+
if (action2.kind !== "commit_memories") {
|
|
287
|
+
throw new Error(`unexpected action after feedSynthesisResult: ${action2.kind}`);
|
|
288
|
+
}
|
|
289
|
+
const cr = action2.curationResult;
|
|
290
|
+
const rr = action2.runResult;
|
|
291
|
+
const dsResult = {
|
|
292
|
+
toAdd: (cr.toAdd ?? []).map((e) => ({
|
|
293
|
+
text: e.text,
|
|
294
|
+
score: e.score,
|
|
295
|
+
metadata: tryParseJson(e.metadata),
|
|
296
|
+
})),
|
|
297
|
+
toRemoveIndices: (cr.toRemoveIndices ?? []).map(Number),
|
|
298
|
+
stats: {
|
|
299
|
+
insightsProcessed: cr.stats?.insightsProcessed ?? 0,
|
|
300
|
+
duplicatesRemoved: cr.stats?.duplicatesRemoved ?? 0,
|
|
301
|
+
conflictsResolved: cr.stats?.conflictsResolved ?? 0,
|
|
302
|
+
entriesAdded: cr.stats?.entriesAdded ?? 0,
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
await this.dreamStore.commit(agentId, dsResult, existingMemories);
|
|
306
|
+
return {
|
|
307
|
+
sessionsProcessed: rr.sessionsProcessed,
|
|
308
|
+
insightsExtracted: rr.insightsExtracted,
|
|
309
|
+
entriesAdded: cr.stats?.entriesAdded ?? 0,
|
|
310
|
+
entriesRemoved: (cr.toRemoveIndices ?? []).length,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function tryParseJson(s) {
|
|
315
|
+
try {
|
|
316
|
+
return JSON.parse(s);
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|