@deepstrike/sdk 0.2.4 → 0.2.5

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 (34) hide show
  1. package/README.md +287 -70
  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 +11 -5
  7. package/dist/index.js +5 -1
  8. package/dist/memory/agent.d.ts +111 -0
  9. package/dist/memory/agent.js +151 -0
  10. package/dist/memory/protocols.d.ts +56 -0
  11. package/dist/runtime/execution-plane.d.ts +6 -9
  12. package/dist/runtime/execution-plane.js +53 -25
  13. package/dist/runtime/kernel-event-log.d.ts +26 -0
  14. package/dist/runtime/kernel-event-log.js +220 -0
  15. package/dist/runtime/kernel-primitives-dashboard.d.ts +44 -0
  16. package/dist/runtime/kernel-primitives-dashboard.js +135 -0
  17. package/dist/runtime/kernel-step.d.ts +25 -1
  18. package/dist/runtime/kernel-step.js +12 -3
  19. package/dist/runtime/large-result-spool.d.ts +84 -0
  20. package/dist/runtime/large-result-spool.js +167 -0
  21. package/dist/runtime/os-profile.d.ts +18 -0
  22. package/dist/runtime/os-profile.js +47 -0
  23. package/dist/runtime/os-snapshot.d.ts +35 -0
  24. package/dist/runtime/os-snapshot.js +128 -0
  25. package/dist/runtime/runner.d.ts +62 -9
  26. package/dist/runtime/runner.js +425 -135
  27. package/dist/runtime/session-log.d.ts +118 -4
  28. package/dist/runtime/session-log.js +15 -4
  29. package/dist/runtime/sub-agent-orchestrator.d.ts +2 -2
  30. package/dist/runtime/sub-agent-orchestrator.js +14 -23
  31. package/dist/types/agent.d.ts +12 -3
  32. package/dist/types/agent.js +18 -0
  33. package/dist/types.d.ts +22 -0
  34. 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 { DEFAULT_NATIVE_ATTENTION_POLICY, DEFAULT_NATIVE_GOVERNANCE_POLICY } 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",
@@ -300,11 +542,36 @@ export class RuntimeRunner {
300
542
  if (this.opts.runSpec) {
301
543
  startPayload.run_spec = agentRunSpecToKernel(this.opts.runSpec);
302
544
  }
545
+ const attentionPolicy = this.opts.attentionPolicy ?? DEFAULT_NATIVE_ATTENTION_POLICY;
546
+ const governancePolicy = this.opts.governancePolicy ?? DEFAULT_NATIVE_GOVERNANCE_POLICY;
547
+ // Load the declarative governance policy into the kernel before the run starts,
548
+ // so the in-kernel gate enforces deny/veto/rate-limit/param before any tool runs.
549
+ kernelApply(runtime, this.pendingObservations, governancePolicyToKernelEvent(governancePolicy));
550
+ // Enable in-kernel signal routing so the kernel owns disposition + queuing.
551
+ kernelApply(runtime, this.pendingObservations, {
552
+ kind: "set_attention_policy",
553
+ ...(attentionPolicy.maxQueueSize !== undefined
554
+ ? { max_queue_size: attentionPolicy.maxQueueSize }
555
+ : {}),
556
+ });
557
+ // Set optional wall-clock budget override.
558
+ if (this.opts.schedulerBudget) {
559
+ kernelApply(runtime, this.pendingObservations, {
560
+ kind: "set_scheduler_budget",
561
+ ...(this.opts.schedulerBudget.maxWallMs !== undefined
562
+ ? { max_wall_ms: this.opts.schedulerBudget.maxWallMs }
563
+ : {}),
564
+ });
565
+ }
303
566
  let action = resumeMidRun
304
567
  ? kernelAction(runtime, this.pendingObservations, { kind: "resume" })
305
568
  : kernelAction(runtime, this.pendingObservations, startPayload);
306
569
  let hasAttemptedReactiveCompact = false;
307
570
  while (!runtime.isTerminal()) {
571
+ // Page-in must run before appendObservations drains pending kernel observations.
572
+ if (action.kind === "execute_tool") {
573
+ await this.applyKernelPageIn(runtime, sessionId);
574
+ }
308
575
  nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
309
576
  this.nextArchiveStart = nextCompressedArchiveStart;
310
577
  if (this.interrupted) {
@@ -314,30 +581,31 @@ export class RuntimeRunner {
314
581
  if (this.opts.signalSource) {
315
582
  const sig = await this.opts.signalSource.nextSignal();
316
583
  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;
584
+ const id = crypto.randomUUID();
585
+ const source = sig.source ?? "custom";
586
+ const signalType = sig.signalType ?? "event";
587
+ const urgency = sig.urgency ?? "normal";
588
+ const summary = String(sig.payload?.goal ?? sig.kind ?? "signal");
589
+ // Kernel-routed: the kernel decides disposition (dedup/queue/interrupt)
590
+ // and emits `signal_disposed`. An actionable disposition yields a new
591
+ // action to adopt; queued/observed/ignored yields none (kernel buffers).
592
+ // Wire shape is snake_case RuntimeSignal with an object payload.
593
+ const sigAction = kernelMaybeAction(runtime, this.pendingObservations, {
594
+ kind: "signal",
595
+ signal: {
596
+ id,
597
+ source,
598
+ signal_type: signalType,
599
+ urgency,
600
+ summary,
601
+ payload: sig.payload ?? {},
602
+ ...(sig.dedupeKey ? { dedupe_key: sig.dedupeKey } : {}),
603
+ timestamp_ms: Date.now(),
604
+ },
605
+ });
606
+ if (sigAction)
607
+ action = sigAction;
339
608
  }
340
- queued = router.next();
341
609
  }
342
610
  if (runtime.isTerminal())
343
611
  break;
@@ -398,12 +666,25 @@ export class RuntimeRunner {
398
666
  toolCalls: finalToolCalls,
399
667
  tokenCount: turnOutputTokens || turnTokens || undefined,
400
668
  };
401
- action = kernelAction(runtime, this.pendingObservations, {
669
+ const providerEvent = {
402
670
  kind: "provider_result",
403
671
  message: messageToKernelMessage(assistantMessage),
404
672
  ...(turnInputTokens > 0 ? { observed_input_tokens: turnInputTokens } : {}),
405
673
  ...(turnOutputTokens > 0 ? { observed_output_tokens: turnOutputTokens } : {}),
406
- });
674
+ now_ms: Date.now(),
675
+ };
676
+ let nextAction = kernelMaybeAction(runtime, this.pendingObservations, providerEvent);
677
+ if (!nextAction && this.pendingObservations.some(o => o.kind === "suspended")) {
678
+ const resolved = await this.resolveKernelSuspend(runtime, sessionId);
679
+ for (const evt of resolved.events)
680
+ yield evt;
681
+ nextAction = kernelAction(runtime, this.pendingObservations, {
682
+ kind: "resume",
683
+ approved_calls: resolved.approved,
684
+ denied_calls: resolved.denied,
685
+ });
686
+ }
687
+ action = nextAction ?? kernelAction(runtime, this.pendingObservations, providerEvent);
407
688
  const providerReplay = peekProviderReplay(this.opts.provider, finalText, finalToolCalls);
408
689
  await this.opts.sessionLog.append(sessionId, buildLlmCompletedEvent({
409
690
  turn: runtime.turn(),
@@ -421,8 +702,9 @@ export class RuntimeRunner {
421
702
  skillDir: this.opts.skillDir,
422
703
  dreamStore: this.opts.dreamStore,
423
704
  knowledgeSource: this.opts.knowledgeSource,
424
- governance: this.opts.governance,
425
705
  onToolSuspend: this.opts.onToolSuspend,
706
+ onPermissionRequest: this.opts.onPermissionRequest,
707
+ resultSpool: this.opts.resultSpool ?? new LargeResultSpool(),
426
708
  };
427
709
  const toolResults = [];
428
710
  const normalCalls = allCalls.filter(c => c.name !== "update_plan");
@@ -477,21 +759,18 @@ export class RuntimeRunner {
477
759
  kind: "permission_requested",
478
760
  turn,
479
761
  tool: pre.toolName,
480
- arguments: JSON.stringify(pre.arguments),
762
+ arguments: pre.arguments,
481
763
  reason: pre.reason,
482
764
  });
765
+ }
766
+ else if (evt.type === "permission_resolved") {
767
+ const resolved = evt;
768
+ const turn = runtime.turn();
483
769
  await this.opts.sessionLog.append(sessionId, {
484
770
  kind: "permission_resolved",
485
771
  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}`,
772
+ approved: resolved.approved,
773
+ responder: resolved.responder,
495
774
  });
496
775
  }
497
776
  }
@@ -511,6 +790,12 @@ export class RuntimeRunner {
511
790
  token_count: r.tokenCount,
512
791
  })),
513
792
  });
793
+ for (const call of normalCalls) {
794
+ const result = toolResults.find(r => r.callId === call.id);
795
+ if (result) {
796
+ this.pendingSpoolOutputs.set(call.id, { tool: call.name, output: result.output });
797
+ }
798
+ }
514
799
  action = kernelAction(runtime, this.pendingObservations, {
515
800
  kind: "tool_results",
516
801
  results: toolResults.map(toolResultToKernel),
@@ -590,18 +875,18 @@ export class RuntimeRunner {
590
875
  yield { type: "done", iterations: turnsUsed, totalTokens, status };
591
876
  this.activeKernel = null;
592
877
  this.currentSessionId = null;
878
+ this.dashboard = null;
593
879
  }
594
880
  async appendObservations(sessionId, runtime, nextArchiveStart) {
595
881
  const turn = runtime.turn();
596
882
  const preservedRefs = runtime.preservedRefs();
597
883
  const observations = this.pendingObservations.splice(0);
598
- for (const obs of observations) {
884
+ for (let obs of observations) {
885
+ if (obs.kind === "page_in_requested")
886
+ continue;
887
+ let archiveRef;
888
+ let spoolRef;
599
889
  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
890
  const archived = obs.archived;
606
891
  if (this.opts.compressionStore && archived && archived.length > 0) {
607
892
  try {
@@ -613,91 +898,77 @@ export class RuntimeRunner {
613
898
  // non-fatal
614
899
  }
615
900
  }
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
- });
901
+ }
902
+ if (obs.kind === "page_out" && obs.archived) {
903
+ this.localPageOutCache.push(...obs.archived);
904
+ }
905
+ if (obs.kind === "large_result_spooled") {
906
+ const pending = this.pendingSpoolOutputs.get(obs.call_id ?? "");
907
+ if (pending) {
908
+ const spool = this.opts.resultSpool ?? new LargeResultSpool();
909
+ try {
910
+ spoolRef = await spool.persistOutput(obs.call_id ?? "", pending.output);
911
+ }
912
+ catch {
913
+ // non-fatal: preview remains in kernel context; full output still in tool_completed log
914
+ }
915
+ if (!obs.tool && pending.tool) {
916
+ obs = { ...obs, tool: pending.tool };
917
+ }
918
+ this.pendingSpoolOutputs.delete(obs.call_id ?? "");
919
+ }
920
+ }
921
+ const latest = obs.kind === "compressed" ? await this.opts.sessionLog.latestSeq(sessionId) : undefined;
922
+ const event = kernelObservationToSessionEvent(obs, turn, {
923
+ nextArchiveStart,
924
+ latestSeq: latest,
925
+ archiveRef,
926
+ spoolRef,
927
+ preservedRefs,
928
+ compressionAction,
929
+ });
930
+ if (!event)
931
+ continue;
932
+ const compressedSeq = await this.opts.sessionLog.append(sessionId, event);
933
+ if (event.kind === "compressed") {
626
934
  nextArchiveStart = compressedSeq + 1;
935
+ const archived = obs.kind === "compressed" ? obs.archived : undefined;
627
936
  if (this.opts.asyncSummarizer && archived && archived.length > 0) {
628
937
  void this.upgradeCompressedSummary(sessionId, compressedSeq, archived, compressionAction(obs.action) ?? "auto_compact");
629
938
  }
630
939
  }
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}`);
940
+ if (obs.kind === "page_out"
941
+ && obs.tier_hint === "semantic"
942
+ && Array.isArray(obs.archived)
943
+ && obs.archived.length > 0) {
944
+ void this.archiveSemanticPageOut(obs.archived, compressionAction(obs.action));
697
945
  }
698
946
  }
699
947
  return nextArchiveStart;
700
948
  }
949
+ async archiveSemanticPageOut(archived, action) {
950
+ if (!this.opts.dreamStore || !this.opts.agentId)
951
+ return;
952
+ try {
953
+ const summary = this.opts.dreamSummarizer
954
+ ? await this.opts.dreamSummarizer.summarize(archived, { action })
955
+ : await summarizeForLongTermMemory(this.opts.dreamProvider ?? this.opts.provider, archived, this.opts.dreamSystemPrompt);
956
+ const existing = await this.opts.dreamStore.loadMemories(this.opts.agentId);
957
+ await this.opts.dreamStore.commit(this.opts.agentId, {
958
+ toAdd: [{ text: summary, score: 1.0, metadata: { source: "semantic_page_out", action } }],
959
+ toRemoveIndices: [],
960
+ stats: {
961
+ insightsProcessed: 1,
962
+ duplicatesRemoved: 0,
963
+ conflictsResolved: 0,
964
+ entriesAdded: 1,
965
+ },
966
+ }, existing);
967
+ }
968
+ catch {
969
+ // non-fatal: in-context compression summary remains; long-term layer is best-effort
970
+ }
971
+ }
701
972
  async upgradeCompressedSummary(sessionId, compressedSeq, archived, action) {
702
973
  try {
703
974
  const summary = await this.opts.asyncSummarizer.summarize(archived, action);
@@ -724,6 +995,25 @@ function compressionAction(action) {
724
995
  }
725
996
  return undefined;
726
997
  }
998
+ async function summarizeForLongTermMemory(provider, archived, systemPrompt) {
999
+ const transcript = archived
1000
+ .map(m => `${m.role}: ${m.content}`)
1001
+ .join("\n");
1002
+ const context = {
1003
+ systemText: [
1004
+ systemPrompt,
1005
+ "Summarize the following conversation for long-term memory. Preserve key facts, decisions, and open questions.",
1006
+ ].filter(Boolean).join("\n\n"),
1007
+ turns: [{ role: "user", content: transcript, toolCalls: [] }],
1008
+ };
1009
+ let text = "";
1010
+ const state = provider.createRunState?.();
1011
+ for await (const evt of provider.stream(context, [], undefined, state)) {
1012
+ if (evt.type === "text_delta" && "delta" in evt)
1013
+ text += evt.delta;
1014
+ }
1015
+ return text.trim() || transcript.slice(0, 2000);
1016
+ }
727
1017
  export function replayMessages(events, maxBytes) {
728
1018
  // Build upgraded-summary index: compressed_seq -> upgraded summary
729
1019
  const upgradedSummaries = new Map();