@deepstrike/sdk 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +305 -69
  2. package/dist/collaboration/modes/creator-verifier.d.ts +0 -3
  3. package/dist/collaboration/modes/creator-verifier.js +2 -6
  4. package/dist/governance.d.ts +36 -0
  5. package/dist/governance.js +22 -0
  6. package/dist/index.d.ts +14 -6
  7. package/dist/index.js +5 -1
  8. package/dist/kernel.d.ts +43 -0
  9. package/dist/memory/agent.d.ts +66 -0
  10. package/dist/memory/agent.js +151 -0
  11. package/dist/memory/protocols.d.ts +56 -0
  12. package/dist/runtime/execution-plane.d.ts +6 -9
  13. package/dist/runtime/execution-plane.js +53 -25
  14. package/dist/runtime/kernel-event-log.d.ts +26 -0
  15. package/dist/runtime/kernel-event-log.js +220 -0
  16. package/dist/runtime/kernel-primitives-dashboard.d.ts +44 -0
  17. package/dist/runtime/kernel-primitives-dashboard.js +135 -0
  18. package/dist/runtime/kernel-step.d.ts +25 -1
  19. package/dist/runtime/kernel-step.js +12 -3
  20. package/dist/runtime/large-result-spool.d.ts +84 -0
  21. package/dist/runtime/large-result-spool.js +167 -0
  22. package/dist/runtime/os-profile.d.ts +30 -0
  23. package/dist/runtime/os-profile.js +71 -0
  24. package/dist/runtime/os-snapshot.d.ts +35 -0
  25. package/dist/runtime/os-snapshot.js +128 -0
  26. package/dist/runtime/runner.d.ts +79 -9
  27. package/dist/runtime/runner.js +463 -135
  28. package/dist/runtime/session-log.d.ts +118 -4
  29. package/dist/runtime/session-log.js +15 -4
  30. package/dist/runtime/sub-agent-orchestrator.d.ts +2 -2
  31. package/dist/runtime/sub-agent-orchestrator.js +14 -23
  32. package/dist/types/agent.d.ts +12 -3
  33. package/dist/types/agent.js +18 -0
  34. package/dist/types.d.ts +22 -0
  35. package/package.json +2 -2
@@ -1,10 +1,17 @@
1
+ import { memoriesToIndex, selectMemories } from "../memory/agent.js";
2
+ import { resolvePermissionRequest } from "./execution-plane.js";
1
3
  import { getKernel } from "../kernel.js";
2
4
  import { peekProviderReplay, seedProviderReplayFromEvents } from "./provider-replay.js";
3
5
  import { sanitizeReplayText } from "./replay-sanitize.js";
4
6
  import { buildLlmCompletedEvent, buildRunTerminalEvent, repairEventsForRecovery } from "./session-repair.js";
5
- import { capabilityMarker, capabilitySkill, capabilityTool, capabilityCommandMount, capabilityCommandUnmount, kernelAction, kernelApply, forceCompact, messageToKernelMessage, skillMetadataToKernel, taskUpdateToKernel, toolResultToKernel, toolSchemaToKernel, } from "./kernel-step.js";
6
- import { agentRunSpecToKernel, milestoneCheckPass, milestoneCheckResultToKernel, subAgentResultToKernel } from "../types/agent.js";
7
+ import { KernelPrimitivesDashboard } from "./kernel-primitives-dashboard.js";
8
+ import { capabilityMarker, capabilitySkill, capabilityTool, capabilityCommandMount, capabilityCommandUnmount, kernelAction, kernelApply, kernelMaybeAction, forceCompact, messageToKernelMessage, skillMetadataToKernel, taskUpdateToKernel, toolResultToKernel, toolSchemaToKernel, } from "./kernel-step.js";
9
+ import { agentRunSpecToKernel, findSpawnProcessObservation, milestoneCheckPass, milestoneCheckResultToKernel, spawnObservationToManifest, subAgentResultToKernel, } from "../types/agent.js";
7
10
  import { defaultSubAgentOrchestrator } from "./sub-agent-orchestrator.js";
11
+ import { governancePolicyToKernelEvent } from "../governance.js";
12
+ import { kernelObservationToSessionEvent, withCategory } from "./kernel-event-log.js";
13
+ import { assertNativeProfile } from "./os-profile.js";
14
+ import { LargeResultSpool } from "./large-result-spool.js";
8
15
  export class RuntimeRunner {
9
16
  opts;
10
17
  interrupted = false;
@@ -12,13 +19,128 @@ export class RuntimeRunner {
12
19
  pendingObservations = [];
13
20
  currentSessionId = null;
14
21
  nextArchiveStart = 0;
22
+ /** Full tool outputs keyed by call_id until Layer-1 spool observations are logged. */
23
+ pendingSpoolOutputs = new Map();
24
+ /** Local cache of paged-out/archived messages for priority memory retrieval. */
25
+ localPageOutCache = [];
26
+ dashboard = null;
15
27
  constructor(opts) {
16
28
  this.opts = opts;
29
+ if (opts.enableDiagnosticsDashboard) {
30
+ const originalAppend = opts.sessionLog.append.bind(opts.sessionLog);
31
+ opts.sessionLog.append = async (sessionId, event) => {
32
+ const seq = await originalAppend(sessionId, event);
33
+ if (this.dashboard) {
34
+ this.dashboard.ingest(event);
35
+ this.dashboard.print();
36
+ }
37
+ return seq;
38
+ };
39
+ }
17
40
  }
18
41
  /** Host configuration (for coordinator / sub-agent spawn). */
19
42
  get hostOptions() {
20
43
  return this.opts;
21
44
  }
45
+ async writeMemory(memory, opts = {}) {
46
+ const sessionId = opts.sessionId ?? this.currentSessionId;
47
+ const agentId = opts.agentId ?? this.opts.agentId;
48
+ if (!this.opts.dreamStore || !agentId)
49
+ return;
50
+ const observations = [];
51
+ const runtime = this.activeKernel ?? this.createSyscallRuntime();
52
+ kernelApply(runtime, observations, { kind: "write_memory", memory });
53
+ const event = observations.find(o => o.kind === "memory_written");
54
+ if (!event) {
55
+ await this.appendMemorySyscallObservations(sessionId, observations);
56
+ return;
57
+ }
58
+ const existing = await this.opts.dreamStore.loadMemories(agentId);
59
+ await this.opts.dreamStore.commit(agentId, {
60
+ toAdd: [{
61
+ text: memory.content,
62
+ score: 1.0,
63
+ metadata: {
64
+ ...memory.metadata,
65
+ source: "write_memory_syscall",
66
+ },
67
+ }],
68
+ toRemoveIndices: [],
69
+ stats: {
70
+ insightsProcessed: 1,
71
+ duplicatesRemoved: 0,
72
+ conflictsResolved: 0,
73
+ entriesAdded: 1,
74
+ },
75
+ }, existing);
76
+ await this.appendMemorySyscallObservations(sessionId, observations);
77
+ }
78
+ async queryMemory(query, opts = {}) {
79
+ const sessionId = opts.sessionId ?? this.currentSessionId;
80
+ const agentId = opts.agentId ?? this.opts.agentId;
81
+ if (!this.opts.dreamStore || !agentId)
82
+ return [];
83
+ const observations = [];
84
+ const runtime = this.activeKernel ?? this.createSyscallRuntime();
85
+ kernelApply(runtime, observations, { kind: "query_memory", query });
86
+ const allMemories = await this.opts.dreamStore.loadMemories(agentId);
87
+ const retrieval = await selectMemories(query, memoriesToIndex(allMemories));
88
+ let hits;
89
+ if (retrieval.selected_memory_ids.length > 0) {
90
+ const selected = new Set(retrieval.selected_memory_ids);
91
+ hits = allMemories
92
+ .filter(m => selected.has(String(m.metadata?.name ?? "")))
93
+ .slice(0, query.top_k);
94
+ }
95
+ else {
96
+ hits = await this.opts.dreamStore.search(agentId, query.current_context, query.top_k);
97
+ if (hits.length > 0 && retrieval.selection_rationale === "No candidates after filtering") {
98
+ retrieval.selected_memory_ids = hits.map(h => String(h.metadata?.name ?? h.text.slice(0, 32)));
99
+ retrieval.selection_rationale = `DreamStore.search returned ${hits.length} hit(s)`;
100
+ }
101
+ }
102
+ await this.appendMemorySyscallObservations(sessionId, observations);
103
+ await this.logMemoryRetrievalResult(sessionId, runtime, retrieval);
104
+ return hits;
105
+ }
106
+ async logMemoryRetrievalResult(sessionId, runtime, retrieval) {
107
+ if (!sessionId)
108
+ return;
109
+ await this.opts.sessionLog.append(sessionId, {
110
+ kind: "memory_retrieval_result",
111
+ selected_memory_ids: retrieval.selected_memory_ids,
112
+ selection_rationale: retrieval.selection_rationale,
113
+ });
114
+ kernelApply(runtime, [], {
115
+ kind: "memory_retrieval_result",
116
+ retrieval: {
117
+ selected_memory_ids: retrieval.selected_memory_ids,
118
+ selection_rationale: retrieval.selection_rationale,
119
+ },
120
+ });
121
+ }
122
+ createSyscallRuntime() {
123
+ const { KernelRuntime } = getKernel();
124
+ return new KernelRuntime({
125
+ maxTokens: this.opts.maxTokens,
126
+ maxTurns: this.opts.maxTurns,
127
+ timeoutMs: this.opts.timeoutMs !== undefined ? BigInt(this.opts.timeoutMs) : undefined,
128
+ });
129
+ }
130
+ async appendMemorySyscallObservations(sessionId, observations) {
131
+ if (!sessionId)
132
+ return;
133
+ const turn = this.activeKernel?.turn() ?? 0;
134
+ for (const obs of observations) {
135
+ if (obs.kind !== "memory_written"
136
+ && obs.kind !== "memory_queried"
137
+ && obs.kind !== "memory_validation_failed")
138
+ continue;
139
+ const event = kernelObservationToSessionEvent(obs, turn);
140
+ if (event)
141
+ await this.opts.sessionLog.append(sessionId, event);
142
+ }
143
+ }
22
144
  /** Mount a tool capability on the currently-running kernel runtime. No-op if not running. */
23
145
  mountTool(schema) {
24
146
  if (!this.activeKernel)
@@ -43,6 +165,52 @@ export class RuntimeRunner {
43
165
  return;
44
166
  kernelApply(this.activeKernel, this.pendingObservations, capabilityCommandUnmount(kind, id));
45
167
  }
168
+ /** Phase 4: satisfy kernel page-in requests before meta-tool execution. */
169
+ async applyKernelPageIn(runtime, sessionId) {
170
+ const requests = this.pendingObservations.filter((o) => o.kind === "page_in_requested" && typeof o.tool === "string");
171
+ if (requests.length === 0)
172
+ return;
173
+ const entries = [];
174
+ for (const req of requests) {
175
+ const query = typeof req.query === "string" ? req.query : "";
176
+ const topK = typeof req.top_k === "number" ? req.top_k : 5;
177
+ if (req.tool === "memory") {
178
+ // Priority search: Local Page-Out Cache (lexical/keyword filter)
179
+ const localHits = this.localPageOutCache.filter(m => typeof m.content === "string" && m.content.toLowerCase().includes(query.toLowerCase())).slice(0, topK);
180
+ for (const hit of localHits) {
181
+ entries.push({
182
+ content: `[local semantic cache] ${hit.role}: ${hit.content}`,
183
+ source: "semantic_cache",
184
+ });
185
+ }
186
+ // Fall back to dreamStore for the remainder if needed
187
+ const remainingK = topK - entries.length;
188
+ if (remainingK > 0 && this.opts.dreamStore && this.opts.agentId) {
189
+ const hits = await this.opts.dreamStore.search(this.opts.agentId, query, remainingK);
190
+ for (const hit of hits) {
191
+ entries.push({
192
+ content: `[memory score=${hit.score.toFixed(3)}] ${hit.text}`,
193
+ source: "memory",
194
+ });
195
+ }
196
+ }
197
+ }
198
+ else if (req.tool === "knowledge" && this.opts.knowledgeSource) {
199
+ const snippets = await this.opts.knowledgeSource.retrieve(query, topK);
200
+ for (const snippet of snippets) {
201
+ entries.push({ content: snippet, source: "knowledge" });
202
+ }
203
+ }
204
+ }
205
+ if (entries.length === 0)
206
+ return;
207
+ kernelApply(runtime, this.pendingObservations, { kind: "page_in", entries });
208
+ await this.opts.sessionLog.append(sessionId, withCategory({
209
+ kind: "page_in",
210
+ turn: runtime.turn(),
211
+ entry_count: entries.length,
212
+ }));
213
+ }
46
214
  /** Push content into the Knowledge slot (memory retrievals, skill definitions, artifacts). */
47
215
  pushKnowledge(message, tokens) {
48
216
  if (!this.activeKernel)
@@ -69,19 +237,10 @@ export class RuntimeRunner {
69
237
  parent_session_id: parentSessionId,
70
238
  });
71
239
  this.nextArchiveStart = await this.appendObservations(parentSessionId, runtime, this.nextArchiveStart);
72
- const spawned = observations.find((o) => o.kind === "agent_spawned" && typeof o.agent_id === "string");
240
+ const spawned = findSpawnProcessObservation(observations);
73
241
  if (!spawned)
74
- throw new Error("spawn_sub_agent did not emit agent_spawned");
75
- const manifest = {
76
- kind: "agent_spawned",
77
- turn: spawned.turn,
78
- agent_id: spawned.agent_id,
79
- parent_session_id: spawned.parent_session_id ?? parentSessionId,
80
- role: spawned.role ?? spec.role,
81
- isolation: spawned.isolation ?? spec.isolation ?? "shared",
82
- context_inheritance: spawned.context_inheritance ?? "none",
83
- permitted_capability_ids: spawned.permitted_capability_ids ?? [],
84
- };
242
+ throw new Error("spawn_sub_agent did not emit agent_process_changed");
243
+ const manifest = spawnObservationToManifest(spawned, spec, parentSessionId);
85
244
  const orchestrator = this.opts.subAgentOrchestrator ?? defaultSubAgentOrchestrator;
86
245
  const result = await orchestrator.run({
87
246
  parentOpts: this.opts,
@@ -150,7 +309,8 @@ export class RuntimeRunner {
150
309
  if (action1.kind !== "synthesize_insights")
151
310
  throw new Error(`unexpected: ${action1.kind}`);
152
311
  let synthesisText = "";
153
- const providerState = this.opts.provider.createRunState?.();
312
+ const dreamProvider = this.opts.dreamProvider ?? this.opts.provider;
313
+ const providerState = dreamProvider.createRunState?.();
154
314
  const synthMsgs = (action1.messages ?? []);
155
315
  const kernelSystemText = synthMsgs.filter(m => m.role === "system").map(m => m.content).join("\n\n");
156
316
  const synthContext = {
@@ -158,7 +318,7 @@ export class RuntimeRunner {
158
318
  turns: synthMsgs.filter(m => m.role !== "system"),
159
319
  };
160
320
  let totalTokens = 0;
161
- for await (const evt of this.opts.provider.stream(synthContext, [], undefined, providerState)) {
321
+ for await (const evt of dreamProvider.stream(synthContext, [], undefined, providerState)) {
162
322
  if (evt.type === "text_delta") {
163
323
  synthesisText += evt.delta;
164
324
  yield evt;
@@ -194,10 +354,93 @@ export class RuntimeRunner {
194
354
  },
195
355
  };
196
356
  }
357
+ /** Resolve in-kernel AskUser suspend; returns resume lists and stream events to yield. */
358
+ async resolveKernelSuspend(runtime, sessionId) {
359
+ const gated = this.pendingObservations.filter((o) => o.kind === "tool_gated" && typeof o.call_id === "string" && typeof o.tool === "string");
360
+ const approved = [];
361
+ const denied = [];
362
+ const events = [];
363
+ const runCtx = { onPermissionRequest: this.opts.onPermissionRequest };
364
+ for (const g of gated) {
365
+ const request = {
366
+ type: "permission_request",
367
+ callId: g.call_id,
368
+ toolName: g.tool,
369
+ arguments: "{}",
370
+ reason: typeof g.reason === "string" ? g.reason : "",
371
+ };
372
+ events.push(request);
373
+ const decision = await resolvePermissionRequest(request, runCtx);
374
+ events.push({
375
+ type: "permission_resolved",
376
+ callId: g.call_id,
377
+ toolName: g.tool,
378
+ approved: decision.approved,
379
+ responder: decision.responder ?? "host",
380
+ ...(decision.reason ? { reason: decision.reason } : {}),
381
+ });
382
+ await this.opts.sessionLog.append(sessionId, {
383
+ kind: "permission_requested",
384
+ turn: runtime.turn(),
385
+ tool: g.tool,
386
+ arguments: "{}",
387
+ reason: request.reason,
388
+ });
389
+ await this.opts.sessionLog.append(sessionId, {
390
+ kind: "permission_resolved",
391
+ turn: runtime.turn(),
392
+ approved: decision.approved,
393
+ responder: decision.responder ?? "host",
394
+ });
395
+ if (decision.approved) {
396
+ approved.push(g.call_id);
397
+ }
398
+ else {
399
+ denied.push(g.call_id);
400
+ const denyReason = decision.reason ?? "permission denied";
401
+ events.push({
402
+ type: "tool_denied",
403
+ callId: g.call_id,
404
+ toolName: g.tool,
405
+ reason: denyReason,
406
+ });
407
+ events.push({
408
+ type: "tool_result",
409
+ callId: g.call_id,
410
+ name: g.tool,
411
+ content: `permission denied: ${denyReason}`,
412
+ isError: true,
413
+ errorKind: "governance_denied",
414
+ });
415
+ await this.opts.sessionLog.append(sessionId, {
416
+ kind: "tool_denied",
417
+ turn: runtime.turn(),
418
+ call_id: g.call_id,
419
+ tool_name: g.tool,
420
+ reason: denyReason,
421
+ });
422
+ await this.opts.sessionLog.append(sessionId, {
423
+ kind: "tool_completed",
424
+ turn: runtime.turn(),
425
+ results: [{
426
+ call_id: g.call_id,
427
+ output: `permission denied: ${denyReason}`,
428
+ is_error: true,
429
+ error_kind: "governance_denied",
430
+ }],
431
+ });
432
+ }
433
+ }
434
+ return { approved, denied, events };
435
+ }
197
436
  async *execute(sessionId, goal, criteria, extensions, priorEvents, resumeMidRun = false) {
198
437
  this.interrupted = false;
199
438
  this.pendingObservations = [];
439
+ this.pendingSpoolOutputs.clear();
200
440
  this.currentSessionId = sessionId;
441
+ if (this.opts.enableDiagnosticsDashboard) {
442
+ this.dashboard = new KernelPrimitivesDashboard(sessionId);
443
+ }
201
444
  const kernel = getKernel();
202
445
  const ext = { ...this.opts.extensions, ...(extensions ?? {}) };
203
446
  const providerState = this.opts.provider.createRunState?.();
@@ -212,7 +455,6 @@ export class RuntimeRunner {
212
455
  });
213
456
  this.activeKernel = runtime;
214
457
  this.nextArchiveStart = nextCompressedArchiveStart;
215
- const router = new kernel.SignalRouter(256);
216
458
  if (this.opts.tokenizer) {
217
459
  kernelApply(runtime, this.pendingObservations, {
218
460
  kind: "set_tokenizer",
@@ -262,6 +504,20 @@ export class RuntimeRunner {
262
504
  if (this.opts.dreamStore && this.opts.agentId) {
263
505
  kernelApply(runtime, this.pendingObservations, { kind: "set_memory_enabled", enabled: true });
264
506
  }
507
+ // Install optional memory policy. Maps the ergonomic camelCase option onto the kernel's
508
+ // snake_case `set_memory_policy` event; omitted fields fall back to kernel defaults.
509
+ if (this.opts.memoryPolicy) {
510
+ const m = this.opts.memoryPolicy;
511
+ kernelApply(runtime, this.pendingObservations, {
512
+ kind: "set_memory_policy",
513
+ ...(m.memoryPath !== undefined ? { memory_path: m.memoryPath } : {}),
514
+ ...(m.staleWarningDays !== undefined ? { stale_warning_days: m.staleWarningDays } : {}),
515
+ ...(m.retrievalTopK !== undefined ? { retrieval_top_k: m.retrievalTopK } : {}),
516
+ ...(m.validationEnabled !== undefined ? { validation_enabled: m.validationEnabled } : {}),
517
+ ...(m.maxContentBytes !== undefined ? { max_content_bytes: m.maxContentBytes } : {}),
518
+ ...(m.maxNameLength !== undefined ? { max_name_length: m.maxNameLength } : {}),
519
+ });
520
+ }
265
521
  if (this.opts.knowledgeSource) {
266
522
  kernelApply(runtime, this.pendingObservations, { kind: "set_knowledge_enabled", enabled: true });
267
523
  }
@@ -300,11 +556,60 @@ export class RuntimeRunner {
300
556
  if (this.opts.runSpec) {
301
557
  startPayload.run_spec = agentRunSpecToKernel(this.opts.runSpec);
302
558
  }
559
+ const osProfile = assertNativeProfile(this.opts.osProfile ?? "native");
560
+ const attentionPolicy = this.opts.attentionPolicy ?? osProfile.attentionPolicy;
561
+ const governancePolicy = this.opts.governancePolicy ?? osProfile.governancePolicy;
562
+ // Load the declarative governance policy into the kernel before the run starts,
563
+ // so the in-kernel gate enforces deny/veto/rate-limit/param before any tool runs.
564
+ kernelApply(runtime, this.pendingObservations, governancePolicyToKernelEvent(governancePolicy));
565
+ // Enable in-kernel signal routing so the kernel owns disposition + queuing.
566
+ kernelApply(runtime, this.pendingObservations, {
567
+ kind: "set_attention_policy",
568
+ ...(attentionPolicy.maxQueueSize !== undefined
569
+ ? { max_queue_size: attentionPolicy.maxQueueSize }
570
+ : {}),
571
+ });
572
+ // Set optional wall-clock budget override.
573
+ if (this.opts.schedulerBudget) {
574
+ kernelApply(runtime, this.pendingObservations, {
575
+ kind: "set_scheduler_budget",
576
+ ...(this.opts.schedulerBudget.maxWallMs !== undefined
577
+ ? { max_wall_ms: this.opts.schedulerBudget.maxWallMs }
578
+ : {}),
579
+ });
580
+ }
581
+ // Install optional resource quotas at the syscall trap (M2). Maps the ergonomic camelCase
582
+ // option onto the kernel's snake_case quota shape; the write-rate window is the serde tuple
583
+ // `[maxWrites, windowMs]`. Omitting the option leaves spawn / memory writes unbounded.
584
+ if (this.opts.resourceQuota) {
585
+ const q = this.opts.resourceQuota;
586
+ kernelApply(runtime, this.pendingObservations, {
587
+ kind: "set_resource_quota",
588
+ quota: {
589
+ ...(q.maxConcurrentSubagents !== undefined
590
+ ? { max_concurrent_subagents: q.maxConcurrentSubagents }
591
+ : {}),
592
+ ...(q.maxSpawnDepth !== undefined ? { max_spawn_depth: q.maxSpawnDepth } : {}),
593
+ ...(q.memoryWritesPerWindow !== undefined
594
+ ? {
595
+ memory_writes_per_window: [
596
+ q.memoryWritesPerWindow.maxWrites,
597
+ q.memoryWritesPerWindow.windowMs,
598
+ ],
599
+ }
600
+ : {}),
601
+ },
602
+ });
603
+ }
303
604
  let action = resumeMidRun
304
605
  ? kernelAction(runtime, this.pendingObservations, { kind: "resume" })
305
606
  : kernelAction(runtime, this.pendingObservations, startPayload);
306
607
  let hasAttemptedReactiveCompact = false;
307
608
  while (!runtime.isTerminal()) {
609
+ // Page-in must run before appendObservations drains pending kernel observations.
610
+ if (action.kind === "execute_tool") {
611
+ await this.applyKernelPageIn(runtime, sessionId);
612
+ }
308
613
  nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
309
614
  this.nextArchiveStart = nextCompressedArchiveStart;
310
615
  if (this.interrupted) {
@@ -314,30 +619,31 @@ export class RuntimeRunner {
314
619
  if (this.opts.signalSource) {
315
620
  const sig = await this.opts.signalSource.nextSignal();
316
621
  if (sig) {
317
- const kernelSig = {
318
- id: crypto.randomUUID(),
319
- source: sig.source ?? "custom",
320
- signalType: sig.signalType ?? "event",
321
- urgency: sig.urgency ?? "normal",
322
- summary: String(sig.payload?.goal ?? sig.kind ?? "signal"),
323
- payload: JSON.stringify(sig.payload ?? {}),
324
- dedupeKey: sig.dedupeKey,
325
- timestampMs: Date.now(),
326
- };
327
- const disposition = router.ingest(kernelSig, action.kind === "execute_tool");
328
- if (disposition === "interrupt_now") {
329
- action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
330
- break;
331
- }
332
- }
333
- }
334
- let queued = router.next();
335
- while (queued) {
336
- if (queued.urgency === "critical") {
337
- action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
338
- break;
622
+ const id = crypto.randomUUID();
623
+ const source = sig.source ?? "custom";
624
+ const signalType = sig.signalType ?? "event";
625
+ const urgency = sig.urgency ?? "normal";
626
+ const summary = String(sig.payload?.goal ?? sig.kind ?? "signal");
627
+ // Kernel-routed: the kernel decides disposition (dedup/queue/interrupt)
628
+ // and emits `signal_disposed`. An actionable disposition yields a new
629
+ // action to adopt; queued/observed/ignored yields none (kernel buffers).
630
+ // Wire shape is snake_case RuntimeSignal with an object payload.
631
+ const sigAction = kernelMaybeAction(runtime, this.pendingObservations, {
632
+ kind: "signal",
633
+ signal: {
634
+ id,
635
+ source,
636
+ signal_type: signalType,
637
+ urgency,
638
+ summary,
639
+ payload: sig.payload ?? {},
640
+ ...(sig.dedupeKey ? { dedupe_key: sig.dedupeKey } : {}),
641
+ timestamp_ms: Date.now(),
642
+ },
643
+ });
644
+ if (sigAction)
645
+ action = sigAction;
339
646
  }
340
- queued = router.next();
341
647
  }
342
648
  if (runtime.isTerminal())
343
649
  break;
@@ -398,12 +704,25 @@ export class RuntimeRunner {
398
704
  toolCalls: finalToolCalls,
399
705
  tokenCount: turnOutputTokens || turnTokens || undefined,
400
706
  };
401
- action = kernelAction(runtime, this.pendingObservations, {
707
+ const providerEvent = {
402
708
  kind: "provider_result",
403
709
  message: messageToKernelMessage(assistantMessage),
404
710
  ...(turnInputTokens > 0 ? { observed_input_tokens: turnInputTokens } : {}),
405
711
  ...(turnOutputTokens > 0 ? { observed_output_tokens: turnOutputTokens } : {}),
406
- });
712
+ now_ms: Date.now(),
713
+ };
714
+ let nextAction = kernelMaybeAction(runtime, this.pendingObservations, providerEvent);
715
+ if (!nextAction && this.pendingObservations.some(o => o.kind === "suspended")) {
716
+ const resolved = await this.resolveKernelSuspend(runtime, sessionId);
717
+ for (const evt of resolved.events)
718
+ yield evt;
719
+ nextAction = kernelAction(runtime, this.pendingObservations, {
720
+ kind: "resume",
721
+ approved_calls: resolved.approved,
722
+ denied_calls: resolved.denied,
723
+ });
724
+ }
725
+ action = nextAction ?? kernelAction(runtime, this.pendingObservations, providerEvent);
407
726
  const providerReplay = peekProviderReplay(this.opts.provider, finalText, finalToolCalls);
408
727
  await this.opts.sessionLog.append(sessionId, buildLlmCompletedEvent({
409
728
  turn: runtime.turn(),
@@ -421,8 +740,9 @@ export class RuntimeRunner {
421
740
  skillDir: this.opts.skillDir,
422
741
  dreamStore: this.opts.dreamStore,
423
742
  knowledgeSource: this.opts.knowledgeSource,
424
- governance: this.opts.governance,
425
743
  onToolSuspend: this.opts.onToolSuspend,
744
+ onPermissionRequest: this.opts.onPermissionRequest,
745
+ resultSpool: this.opts.resultSpool ?? new LargeResultSpool(),
426
746
  };
427
747
  const toolResults = [];
428
748
  const normalCalls = allCalls.filter(c => c.name !== "update_plan");
@@ -477,21 +797,18 @@ export class RuntimeRunner {
477
797
  kind: "permission_requested",
478
798
  turn,
479
799
  tool: pre.toolName,
480
- arguments: JSON.stringify(pre.arguments),
800
+ arguments: pre.arguments,
481
801
  reason: pre.reason,
482
802
  });
803
+ }
804
+ else if (evt.type === "permission_resolved") {
805
+ const resolved = evt;
806
+ const turn = runtime.turn();
483
807
  await this.opts.sessionLog.append(sessionId, {
484
808
  kind: "permission_resolved",
485
809
  turn,
486
- approved: false,
487
- responder: "policy_gate",
488
- });
489
- await this.opts.sessionLog.append(sessionId, {
490
- kind: "tool_denied",
491
- turn,
492
- call_id: pre.callId,
493
- tool_name: pre.toolName,
494
- reason: `permission denied by policy gate: ${pre.reason}`,
810
+ approved: resolved.approved,
811
+ responder: resolved.responder,
495
812
  });
496
813
  }
497
814
  }
@@ -511,6 +828,12 @@ export class RuntimeRunner {
511
828
  token_count: r.tokenCount,
512
829
  })),
513
830
  });
831
+ for (const call of normalCalls) {
832
+ const result = toolResults.find(r => r.callId === call.id);
833
+ if (result) {
834
+ this.pendingSpoolOutputs.set(call.id, { tool: call.name, output: result.output });
835
+ }
836
+ }
514
837
  action = kernelAction(runtime, this.pendingObservations, {
515
838
  kind: "tool_results",
516
839
  results: toolResults.map(toolResultToKernel),
@@ -590,18 +913,18 @@ export class RuntimeRunner {
590
913
  yield { type: "done", iterations: turnsUsed, totalTokens, status };
591
914
  this.activeKernel = null;
592
915
  this.currentSessionId = null;
916
+ this.dashboard = null;
593
917
  }
594
918
  async appendObservations(sessionId, runtime, nextArchiveStart) {
595
919
  const turn = runtime.turn();
596
920
  const preservedRefs = runtime.preservedRefs();
597
921
  const observations = this.pendingObservations.splice(0);
598
- for (const obs of observations) {
922
+ for (let obs of observations) {
923
+ if (obs.kind === "page_in_requested")
924
+ continue;
925
+ let archiveRef;
926
+ let spoolRef;
599
927
  if (obs.kind === "compressed") {
600
- const latest = await this.opts.sessionLog.latestSeq(sessionId);
601
- if (latest < nextArchiveStart)
602
- continue;
603
- const end = latest;
604
- let archiveRef = undefined;
605
928
  const archived = obs.archived;
606
929
  if (this.opts.compressionStore && archived && archived.length > 0) {
607
930
  try {
@@ -613,91 +936,77 @@ export class RuntimeRunner {
613
936
  // non-fatal
614
937
  }
615
938
  }
616
- const compressedSeq = await this.opts.sessionLog.append(sessionId, {
617
- kind: "compressed",
618
- turn,
619
- archived_seq_range: [nextArchiveStart, end],
620
- action: compressionAction(obs.action),
621
- summary: obs.summary,
622
- summary_tokens: obs.summary ? Math.max(1, Math.ceil(obs.summary.length / 4)) : undefined,
623
- archive_ref: archiveRef,
624
- preserved_refs: preservedRefs,
625
- });
939
+ }
940
+ if (obs.kind === "page_out" && obs.archived) {
941
+ this.localPageOutCache.push(...obs.archived);
942
+ }
943
+ if (obs.kind === "large_result_spooled") {
944
+ const pending = this.pendingSpoolOutputs.get(obs.call_id ?? "");
945
+ if (pending) {
946
+ const spool = this.opts.resultSpool ?? new LargeResultSpool();
947
+ try {
948
+ spoolRef = await spool.persistOutput(obs.call_id ?? "", pending.output);
949
+ }
950
+ catch {
951
+ // non-fatal: preview remains in kernel context; full output still in tool_completed log
952
+ }
953
+ if (!obs.tool && pending.tool) {
954
+ obs = { ...obs, tool: pending.tool };
955
+ }
956
+ this.pendingSpoolOutputs.delete(obs.call_id ?? "");
957
+ }
958
+ }
959
+ const latest = obs.kind === "compressed" ? await this.opts.sessionLog.latestSeq(sessionId) : undefined;
960
+ const event = kernelObservationToSessionEvent(obs, turn, {
961
+ nextArchiveStart,
962
+ latestSeq: latest,
963
+ archiveRef,
964
+ spoolRef,
965
+ preservedRefs,
966
+ compressionAction,
967
+ });
968
+ if (!event)
969
+ continue;
970
+ const compressedSeq = await this.opts.sessionLog.append(sessionId, event);
971
+ if (event.kind === "compressed") {
626
972
  nextArchiveStart = compressedSeq + 1;
973
+ const archived = obs.kind === "compressed" ? obs.archived : undefined;
627
974
  if (this.opts.asyncSummarizer && archived && archived.length > 0) {
628
975
  void this.upgradeCompressedSummary(sessionId, compressedSeq, archived, compressionAction(obs.action) ?? "auto_compact");
629
976
  }
630
977
  }
631
- else if (obs.kind === "rollbacked") {
632
- await this.opts.sessionLog.append(sessionId, {
633
- kind: "rollbacked",
634
- turn: obs.turn ?? turn,
635
- checkpoint_history_len: obs.checkpoint_history_len ?? 0,
636
- reason: obs.reason,
637
- });
638
- }
639
- else if (obs.kind === "capability_changed") {
640
- await this.opts.sessionLog.append(sessionId, {
641
- kind: "capability_changed",
642
- turn: obs.turn ?? turn,
643
- added: obs.added ?? [],
644
- removed: obs.removed ?? [],
645
- ...(obs.change_kind != null && { change_kind: obs.change_kind }),
646
- ...(obs.capability_id != null && { capability_id: obs.capability_id }),
647
- ...(obs.version != null && { version: obs.version }),
648
- ...(obs.mounted_by != null && { mounted_by: obs.mounted_by }),
649
- ...(obs.mount_reason != null && { mount_reason: obs.mount_reason }),
650
- });
651
- }
652
- else if (obs.kind === "milestone_advanced") {
653
- await this.opts.sessionLog.append(sessionId, {
654
- kind: "milestone_advanced",
655
- turn: obs.turn ?? turn,
656
- phase_id: obs.phase_id ?? "",
657
- capabilities_unlocked: obs.capabilities_unlocked ?? [],
658
- });
659
- }
660
- else if (obs.kind === "milestone_blocked") {
661
- await this.opts.sessionLog.append(sessionId, {
662
- kind: "milestone_blocked",
663
- turn: obs.turn ?? turn,
664
- phase_id: obs.phase_id ?? "",
665
- reason: typeof obs.reason === "string" ? obs.reason : "",
666
- });
667
- }
668
- else if (obs.kind === "milestone_evidence") {
669
- await this.opts.sessionLog.append(sessionId, {
670
- kind: "milestone_evidence",
671
- turn: obs.turn ?? turn,
672
- phase_id: obs.phase_id ?? "",
673
- evidence: obs.evidence ?? [],
674
- });
675
- }
676
- else if (obs.kind === "checkpoint_taken") {
677
- await this.opts.sessionLog.append(sessionId, {
678
- kind: "checkpoint_taken",
679
- turn: obs.turn ?? turn,
680
- history_len: obs.history_len ?? 0,
681
- });
682
- }
683
- else if (obs.kind === "agent_spawned") {
684
- await this.opts.sessionLog.append(sessionId, {
685
- kind: "agent_spawned",
686
- turn: obs.turn ?? turn,
687
- agent_id: obs.agent_id ?? "",
688
- parent_session_id: obs.parent_session_id ?? "",
689
- role: obs.role ?? "",
690
- isolation: obs.isolation ?? "",
691
- context_inheritance: obs.context_inheritance ?? "",
692
- permitted_capability_ids: obs.permitted_capability_ids ?? [],
693
- });
694
- }
695
- else if (obs.kind !== "renewed") {
696
- console.warn(`[deepstrike] unhandled KernelObservation kind: ${obs.kind}`);
978
+ if (obs.kind === "page_out"
979
+ && obs.tier_hint === "semantic"
980
+ && Array.isArray(obs.archived)
981
+ && obs.archived.length > 0) {
982
+ void this.archiveSemanticPageOut(obs.archived, compressionAction(obs.action));
697
983
  }
698
984
  }
699
985
  return nextArchiveStart;
700
986
  }
987
+ async archiveSemanticPageOut(archived, action) {
988
+ if (!this.opts.dreamStore || !this.opts.agentId)
989
+ return;
990
+ try {
991
+ const summary = this.opts.dreamSummarizer
992
+ ? await this.opts.dreamSummarizer.summarize(archived, { action })
993
+ : await summarizeForLongTermMemory(this.opts.dreamProvider ?? this.opts.provider, archived, this.opts.dreamSystemPrompt);
994
+ const existing = await this.opts.dreamStore.loadMemories(this.opts.agentId);
995
+ await this.opts.dreamStore.commit(this.opts.agentId, {
996
+ toAdd: [{ text: summary, score: 1.0, metadata: { source: "semantic_page_out", action } }],
997
+ toRemoveIndices: [],
998
+ stats: {
999
+ insightsProcessed: 1,
1000
+ duplicatesRemoved: 0,
1001
+ conflictsResolved: 0,
1002
+ entriesAdded: 1,
1003
+ },
1004
+ }, existing);
1005
+ }
1006
+ catch {
1007
+ // non-fatal: in-context compression summary remains; long-term layer is best-effort
1008
+ }
1009
+ }
701
1010
  async upgradeCompressedSummary(sessionId, compressedSeq, archived, action) {
702
1011
  try {
703
1012
  const summary = await this.opts.asyncSummarizer.summarize(archived, action);
@@ -724,6 +1033,25 @@ function compressionAction(action) {
724
1033
  }
725
1034
  return undefined;
726
1035
  }
1036
+ async function summarizeForLongTermMemory(provider, archived, systemPrompt) {
1037
+ const transcript = archived
1038
+ .map(m => `${m.role}: ${m.content}`)
1039
+ .join("\n");
1040
+ const context = {
1041
+ systemText: [
1042
+ systemPrompt,
1043
+ "Summarize the following conversation for long-term memory. Preserve key facts, decisions, and open questions.",
1044
+ ].filter(Boolean).join("\n\n"),
1045
+ turns: [{ role: "user", content: transcript, toolCalls: [] }],
1046
+ };
1047
+ let text = "";
1048
+ const state = provider.createRunState?.();
1049
+ for await (const evt of provider.stream(context, [], undefined, state)) {
1050
+ if (evt.type === "text_delta" && "delta" in evt)
1051
+ text += evt.delta;
1052
+ }
1053
+ return text.trim() || transcript.slice(0, 2000);
1054
+ }
727
1055
  export function replayMessages(events, maxBytes) {
728
1056
  // Build upgraded-summary index: compressed_seq -> upgraded summary
729
1057
  const upgradedSummaries = new Map();