@deepstrike/wasm 0.2.2 → 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.
@@ -1,10 +1,15 @@
1
+ import { resolvePermissionRequest } from "./execution-plane.js";
2
+ import { governancePolicyToKernelEvent } from "../governance.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 { forceCompact, kernelAction, kernelApply, messageToKernelMessage, skillMetadataToKernel, toolResultToKernel, toolSchemaToKernel, } from "./kernel-step.js";
6
- import { agentRunSpecToKernel, subAgentResultToKernel, milestoneCheckPass, milestoneCheckResultToKernel } from "./types/agent.js";
7
+ import { forceCompact, kernelAction, kernelApply, kernelMaybeAction, messageToKernelMessage, skillMetadataToKernel, toolResultToKernel, toolSchemaToKernel, } from "./kernel-step.js";
8
+ import { agentRunSpecToKernel, findSpawnProcessObservation, milestoneCheckPass, milestoneCheckResultToKernel, spawnObservationToManifest, subAgentResultToKernel, } from "./types/agent.js";
7
9
  import { defaultSubAgentOrchestrator } from "./sub-agent-orchestrator.js";
10
+ import { kernelObservationToSessionEvent, withCategory } from "./kernel-event-log.js";
11
+ import { assertNativeProfile } from "./os-profile.js";
12
+ import { LargeResultSpool } from "./large-result-spool.js";
8
13
  export class RuntimeRunner {
9
14
  opts;
10
15
  interrupted = false;
@@ -12,6 +17,8 @@ export class RuntimeRunner {
12
17
  activeKernel = null;
13
18
  currentSessionId = null;
14
19
  nextArchiveStart = 0;
20
+ localPageOutCache = [];
21
+ pendingSpoolOutputs = new Map();
15
22
  constructor(opts) {
16
23
  this.opts = opts;
17
24
  }
@@ -42,21 +49,142 @@ export class RuntimeRunner {
42
49
  const start = startEntry.event;
43
50
  yield* this.execute(sessionId, start.goal, start.criteria, extensions, events, true);
44
51
  }
45
- /** Push a large artifact into the kernel artifacts partition (not inlined in history). */
46
- pushArtifact(message, tokens) {
52
+ /** Push content into Slot 2 (system_knowledge) via add_knowledge_message. */
53
+ pushKnowledge(message, tokens) {
47
54
  if (!this.activeKernel)
48
55
  return;
49
56
  kernelApply(this.activeKernel, this.pendingObservations, {
50
- kind: "push_artifact",
51
- message: messageToKernelMessage(message),
52
- ...(tokens !== undefined ? { tokens } : {}),
57
+ kind: "add_knowledge_message",
58
+ content: message.content ?? "",
59
+ tokens: tokens ?? Math.max(1, Math.ceil((message.content?.length ?? 0) / 4)),
53
60
  });
54
61
  }
62
+ /** Phase 4: satisfy kernel page-in requests before meta-tool execution. */
63
+ async applyKernelPageIn(runtime, sessionId) {
64
+ const requests = this.pendingObservations.filter((o) => o.kind === "page_in_requested" && typeof o.tool === "string");
65
+ if (requests.length === 0)
66
+ return;
67
+ const entries = [];
68
+ for (const req of requests) {
69
+ const query = typeof req.query === "string" ? req.query : "";
70
+ const topK = typeof req.top_k === "number" ? req.top_k : 5;
71
+ if (req.tool === "memory") {
72
+ const localHits = this.localPageOutCache.filter(m => typeof m.content === "string" && m.content.toLowerCase().includes(query.toLowerCase())).slice(0, topK);
73
+ for (const hit of localHits) {
74
+ entries.push({
75
+ content: `[local semantic cache] ${hit.role}: ${hit.content}`,
76
+ source: "semantic_cache",
77
+ });
78
+ }
79
+ const remainingK = topK - entries.length;
80
+ if (remainingK > 0 && this.opts.dreamStore && this.opts.agentId) {
81
+ const hits = await this.opts.dreamStore.search(this.opts.agentId, query, remainingK);
82
+ for (const hit of hits) {
83
+ entries.push({
84
+ content: `[memory score=${hit.score.toFixed(3)}] ${hit.text}`,
85
+ source: "memory",
86
+ });
87
+ }
88
+ }
89
+ }
90
+ else if (req.tool === "knowledge" && this.opts.knowledgeSource) {
91
+ const snippets = await this.opts.knowledgeSource.retrieve(query, topK);
92
+ for (const snippet of snippets) {
93
+ entries.push({ content: snippet, source: "knowledge" });
94
+ }
95
+ }
96
+ }
97
+ if (entries.length === 0)
98
+ return;
99
+ kernelApply(runtime, this.pendingObservations, { kind: "page_in", entries });
100
+ await this.opts.sessionLog.append(sessionId, withCategory({
101
+ kind: "page_in",
102
+ turn: runtime.turn(),
103
+ entry_count: entries.length,
104
+ }));
105
+ }
106
+ async resolveKernelSuspend(runtime, sessionId) {
107
+ const gated = this.pendingObservations.filter((o) => o.kind === "tool_gated" && typeof o.call_id === "string" && typeof o.tool === "string");
108
+ const approved = [];
109
+ const denied = [];
110
+ const events = [];
111
+ const runCtx = { onPermissionRequest: this.opts.onPermissionRequest };
112
+ for (const g of gated) {
113
+ const request = {
114
+ type: "permission_request",
115
+ callId: g.call_id,
116
+ toolName: g.tool,
117
+ arguments: "{}",
118
+ reason: typeof g.reason === "string" ? g.reason : "",
119
+ };
120
+ events.push(request);
121
+ const decision = await resolvePermissionRequest(request, runCtx);
122
+ events.push({
123
+ type: "permission_resolved",
124
+ callId: g.call_id,
125
+ toolName: g.tool,
126
+ approved: decision.approved,
127
+ responder: decision.responder ?? "host",
128
+ ...(decision.reason ? { reason: decision.reason } : {}),
129
+ });
130
+ await this.opts.sessionLog.append(sessionId, {
131
+ kind: "permission_requested",
132
+ turn: runtime.turn(),
133
+ tool: g.tool,
134
+ arguments: "{}",
135
+ reason: request.reason,
136
+ });
137
+ await this.opts.sessionLog.append(sessionId, {
138
+ kind: "permission_resolved",
139
+ turn: runtime.turn(),
140
+ approved: decision.approved,
141
+ responder: decision.responder ?? "host",
142
+ });
143
+ if (decision.approved) {
144
+ approved.push(g.call_id);
145
+ }
146
+ else {
147
+ denied.push(g.call_id);
148
+ const denyReason = decision.reason ?? "permission denied";
149
+ events.push({
150
+ type: "tool_denied",
151
+ callId: g.call_id,
152
+ toolName: g.tool,
153
+ reason: denyReason,
154
+ });
155
+ events.push({
156
+ type: "tool_result",
157
+ callId: g.call_id,
158
+ name: g.tool,
159
+ content: `permission denied: ${denyReason}`,
160
+ isError: true,
161
+ errorKind: "governance_denied",
162
+ });
163
+ await this.opts.sessionLog.append(sessionId, {
164
+ kind: "tool_denied",
165
+ turn: runtime.turn(),
166
+ call_id: g.call_id,
167
+ tool_name: g.tool,
168
+ reason: denyReason,
169
+ });
170
+ await this.opts.sessionLog.append(sessionId, {
171
+ kind: "tool_completed",
172
+ turn: runtime.turn(),
173
+ results: [{
174
+ call_id: g.call_id,
175
+ output: `permission denied: ${denyReason}`,
176
+ is_error: true,
177
+ error_kind: "governance_denied",
178
+ }],
179
+ });
180
+ }
181
+ }
182
+ return { approved, denied, events };
183
+ }
55
184
  async dream(agentId, nowMs = Date.now()) {
56
185
  if (!this.opts.dreamStore)
57
186
  throw new Error("dreamStore not configured");
58
187
  const kernel = await getKernel();
59
- this.opts.governance?._attach(kernel);
60
188
  const sessions = await this.opts.dreamStore.loadSessions(agentId);
61
189
  const existingMemories = await this.opts.dreamStore.loadMemories(agentId);
62
190
  if (!sessions.length)
@@ -77,13 +205,14 @@ export class RuntimeRunner {
77
205
  if (action1.kind !== "synthesize_insights")
78
206
  throw new Error(`unexpected: ${action1.kind}`);
79
207
  let synthesisText = "";
80
- const providerState = this.opts.provider.createRunState?.();
208
+ const dreamProvider = this.opts.dreamProvider ?? this.opts.provider;
209
+ const providerState = dreamProvider.createRunState?.();
81
210
  const synthMsgs = (action1.messages ?? []);
82
211
  const synthContext = {
83
212
  systemText: synthMsgs.filter(m => m.role === "system").map(m => m.content).join("\n\n"),
84
213
  turns: synthMsgs.filter(m => m.role !== "system"),
85
214
  };
86
- for await (const evt of this.opts.provider.stream(synthContext, [], undefined, providerState)) {
215
+ for await (const evt of dreamProvider.stream(synthContext, [], undefined, providerState)) {
87
216
  if (evt.type === "text_delta")
88
217
  synthesisText += evt.delta;
89
218
  }
@@ -115,9 +244,9 @@ export class RuntimeRunner {
115
244
  async *execute(sessionId, goal, criteria, extensions, priorEvents, resumeMidRun = false) {
116
245
  this.interrupted = false;
117
246
  this.pendingObservations = [];
247
+ this.pendingSpoolOutputs.clear();
118
248
  this.currentSessionId = sessionId;
119
249
  const kernel = await getKernel();
120
- this.opts.governance?._attach(kernel);
121
250
  const ext = { ...this.opts.extensions, ...(extensions ?? {}) };
122
251
  const providerState = this.opts.provider.createRunState?.();
123
252
  let nextCompressedArchiveStart = nextArchivedSeqStart(priorEvents);
@@ -130,7 +259,18 @@ export class RuntimeRunner {
130
259
  timeoutMs: effectiveTimeoutMs !== undefined ? BigInt(effectiveTimeoutMs) : undefined,
131
260
  });
132
261
  this.activeKernel = runtime;
133
- const router = new kernel.SignalRouter(256);
262
+ if (this.opts.tokenizer) {
263
+ kernelApply(runtime, this.pendingObservations, {
264
+ kind: "set_tokenizer",
265
+ name: this.opts.tokenizer,
266
+ });
267
+ }
268
+ if (this.opts.enablePlanTool !== undefined) {
269
+ kernelApply(runtime, this.pendingObservations, {
270
+ kind: "set_plan_tool_enabled",
271
+ enabled: this.opts.enablePlanTool,
272
+ });
273
+ }
134
274
  kernelApply(runtime, this.pendingObservations, {
135
275
  kind: "set_tools",
136
276
  tools: this.opts.executionPlane.schemas().map(toolSchemaToKernel),
@@ -145,7 +285,7 @@ export class RuntimeRunner {
145
285
  if (this.opts.initialMemory) {
146
286
  for (const mem of this.opts.initialMemory) {
147
287
  kernelApply(runtime, this.pendingObservations, {
148
- kind: "add_memory_message",
288
+ kind: "add_knowledge_message",
149
289
  content: mem,
150
290
  tokens: Math.max(1, Math.ceil(mem.length / 4)),
151
291
  });
@@ -199,11 +339,64 @@ export class RuntimeRunner {
199
339
  if (this.opts.runSpec) {
200
340
  startPayload.run_spec = agentRunSpecToKernel(this.opts.runSpec);
201
341
  }
342
+ const osProfile = assertNativeProfile(this.opts.osProfile ?? "native");
343
+ const attentionPolicy = this.opts.attentionPolicy ?? osProfile.attentionPolicy;
344
+ const governancePolicy = this.opts.governancePolicy ?? osProfile.governancePolicy;
345
+ kernelApply(runtime, this.pendingObservations, governancePolicyToKernelEvent(governancePolicy));
346
+ kernelApply(runtime, this.pendingObservations, {
347
+ kind: "set_attention_policy",
348
+ ...(attentionPolicy.maxQueueSize !== undefined
349
+ ? { max_queue_size: attentionPolicy.maxQueueSize }
350
+ : {}),
351
+ });
352
+ if (this.opts.schedulerBudget) {
353
+ kernelApply(runtime, this.pendingObservations, {
354
+ kind: "set_scheduler_budget",
355
+ ...(this.opts.schedulerBudget.maxWallMs !== undefined
356
+ ? { max_wall_ms: this.opts.schedulerBudget.maxWallMs }
357
+ : {}),
358
+ });
359
+ }
360
+ if (this.opts.resourceQuota) {
361
+ const q = this.opts.resourceQuota;
362
+ kernelApply(runtime, this.pendingObservations, {
363
+ kind: "set_resource_quota",
364
+ quota: {
365
+ ...(q.maxConcurrentSubagents !== undefined
366
+ ? { max_concurrent_subagents: q.maxConcurrentSubagents }
367
+ : {}),
368
+ ...(q.maxSpawnDepth !== undefined ? { max_spawn_depth: q.maxSpawnDepth } : {}),
369
+ ...(q.memoryWritesPerWindow !== undefined
370
+ ? {
371
+ memory_writes_per_window: [
372
+ q.memoryWritesPerWindow.maxWrites,
373
+ q.memoryWritesPerWindow.windowMs,
374
+ ],
375
+ }
376
+ : {}),
377
+ },
378
+ });
379
+ }
380
+ if (this.opts.memoryPolicy) {
381
+ const m = this.opts.memoryPolicy;
382
+ kernelApply(runtime, this.pendingObservations, {
383
+ kind: "set_memory_policy",
384
+ ...(m.memoryPath !== undefined ? { memory_path: m.memoryPath } : {}),
385
+ ...(m.staleWarningDays !== undefined ? { stale_warning_days: m.staleWarningDays } : {}),
386
+ ...(m.retrievalTopK !== undefined ? { retrieval_top_k: m.retrievalTopK } : {}),
387
+ ...(m.validationEnabled !== undefined ? { validation_enabled: m.validationEnabled } : {}),
388
+ ...(m.maxContentBytes !== undefined ? { max_content_bytes: m.maxContentBytes } : {}),
389
+ ...(m.maxNameLength !== undefined ? { max_name_length: m.maxNameLength } : {}),
390
+ });
391
+ }
202
392
  let action = resumeMidRun
203
393
  ? kernelAction(runtime, this.pendingObservations, { kind: "resume" })
204
394
  : kernelAction(runtime, this.pendingObservations, startPayload);
205
395
  let hasAttemptedReactiveCompact = false;
206
396
  while (!runtime.isTerminal()) {
397
+ if (action.kind === "execute_tool") {
398
+ await this.applyKernelPageIn(runtime, sessionId);
399
+ }
207
400
  nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
208
401
  if (this.interrupted) {
209
402
  action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
@@ -212,30 +405,27 @@ export class RuntimeRunner {
212
405
  if (this.opts.signalSource) {
213
406
  const sig = await this.opts.signalSource.nextSignal();
214
407
  if (sig) {
215
- const kernelSig = {
216
- id: crypto.randomUUID(),
217
- source: sig.source ?? "custom",
218
- signalType: sig.signalType ?? "event",
219
- urgency: sig.urgency ?? "normal",
220
- summary: String(sig.payload?.goal ?? "signal"),
221
- payload: JSON.stringify(sig.payload ?? {}),
222
- dedupeKey: sig.dedupeKey,
223
- timestampMs: Date.now(),
224
- };
225
- const disposition = router.ingest(kernelSig, action.kind === "execute_tool");
226
- if (disposition === "interrupt_now") {
227
- action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
228
- break;
229
- }
230
- }
231
- }
232
- let queued = router.next();
233
- while (queued) {
234
- if (queued.urgency === "critical") {
235
- action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
236
- break;
408
+ const id = crypto.randomUUID();
409
+ const source = sig.source ?? "custom";
410
+ const signalType = sig.signalType ?? "event";
411
+ const urgency = sig.urgency ?? "normal";
412
+ const summary = String(sig.payload?.goal ?? "signal");
413
+ const sigAction = kernelMaybeAction(runtime, this.pendingObservations, {
414
+ kind: "signal",
415
+ signal: {
416
+ id,
417
+ source,
418
+ signal_type: signalType,
419
+ urgency,
420
+ summary,
421
+ payload: sig.payload ?? {},
422
+ ...(sig.dedupeKey ? { dedupe_key: sig.dedupeKey } : {}),
423
+ timestamp_ms: Date.now(),
424
+ },
425
+ });
426
+ if (sigAction)
427
+ action = sigAction;
237
428
  }
238
- queued = router.next();
239
429
  }
240
430
  if (runtime.isTerminal())
241
431
  break;
@@ -245,11 +435,16 @@ export class RuntimeRunner {
245
435
  const context = action.context;
246
436
  const tools = action.tools;
247
437
  let turnTokens = 0;
438
+ let turnInputTokens = 0;
439
+ let turnOutputTokens = 0;
248
440
  let shouldRetry = false;
249
441
  try {
250
442
  for await (const evt of this.opts.provider.stream(context, tools, Object.keys(ext).length ? ext : undefined, providerState)) {
251
443
  if (evt.type === "usage") {
252
- turnTokens = evt.totalTokens;
444
+ const usageEvt = evt;
445
+ turnTokens = usageEvt.totalTokens;
446
+ turnInputTokens = usageEvt.inputTokens ?? 0;
447
+ turnOutputTokens = usageEvt.outputTokens ?? 0;
253
448
  continue;
254
449
  }
255
450
  yield evt;
@@ -289,17 +484,33 @@ export class RuntimeRunner {
289
484
  role: "assistant",
290
485
  content: finalText,
291
486
  toolCalls: finalToolCalls,
292
- tokenCount: turnTokens || undefined,
487
+ tokenCount: turnOutputTokens || turnTokens || undefined,
293
488
  };
294
- action = kernelAction(runtime, this.pendingObservations, {
489
+ const providerEvent = {
295
490
  kind: "provider_result",
296
491
  message: messageToKernelMessage(assistantMessage),
297
- });
492
+ ...(turnInputTokens > 0 ? { observed_input_tokens: turnInputTokens } : {}),
493
+ ...(turnOutputTokens > 0 ? { observed_output_tokens: turnOutputTokens } : {}),
494
+ now_ms: Date.now(),
495
+ };
496
+ let nextAction = kernelMaybeAction(runtime, this.pendingObservations, providerEvent);
497
+ const hasSuspended = this.pendingObservations.some(o => o.kind === "suspended");
498
+ if (!nextAction && hasSuspended) {
499
+ const resolved = await this.resolveKernelSuspend(runtime, sessionId);
500
+ for (const evt of resolved.events)
501
+ yield evt;
502
+ nextAction = kernelAction(runtime, this.pendingObservations, {
503
+ kind: "resume",
504
+ approved_calls: resolved.approved,
505
+ denied_calls: resolved.denied,
506
+ });
507
+ }
508
+ action = nextAction ?? kernelAction(runtime, this.pendingObservations, providerEvent);
298
509
  const providerReplay = peekProviderReplay(this.opts.provider, finalText, finalToolCalls);
299
510
  await this.opts.sessionLog.append(sessionId, buildLlmCompletedEvent({
300
511
  turn: runtime.turn(),
301
512
  content: finalText,
302
- tokenCount: turnTokens || undefined,
513
+ tokenCount: turnOutputTokens || turnTokens || undefined,
303
514
  toolCalls: finalToolCalls,
304
515
  providerReplay,
305
516
  }));
@@ -312,8 +523,8 @@ export class RuntimeRunner {
312
523
  skillContentMap: this.opts.skillContentMap,
313
524
  dreamStore: this.opts.dreamStore,
314
525
  knowledgeSource: this.opts.knowledgeSource,
315
- governance: this.opts.governance,
316
526
  onToolSuspend: this.opts.onToolSuspend,
527
+ onPermissionRequest: this.opts.onPermissionRequest,
317
528
  };
318
529
  const toolResults = [];
319
530
  for await (const evt of this.opts.executionPlane.executeAll(allCalls, runCtx)) {
@@ -358,18 +569,15 @@ export class RuntimeRunner {
358
569
  arguments: typeof pre.arguments === "string" ? pre.arguments : JSON.stringify(pre.arguments),
359
570
  reason: pre.reason,
360
571
  });
572
+ }
573
+ else if (evt.type === "permission_resolved") {
574
+ const resolved = evt;
575
+ const turn = runtime.turn();
361
576
  await this.opts.sessionLog.append(sessionId, {
362
577
  kind: "permission_resolved",
363
578
  turn,
364
- approved: false,
365
- responder: "policy_gate",
366
- });
367
- await this.opts.sessionLog.append(sessionId, {
368
- kind: "tool_denied",
369
- turn,
370
- call_id: pre.callId,
371
- tool_name: pre.toolName,
372
- reason: `permission denied by policy gate: ${pre.reason}`,
579
+ approved: resolved.approved,
580
+ responder: resolved.responder,
373
581
  });
374
582
  }
375
583
  }
@@ -383,6 +591,12 @@ export class RuntimeRunner {
383
591
  token_count: r.tokenCount,
384
592
  })),
385
593
  });
594
+ for (const call of allCalls) {
595
+ const result = toolResults.find(r => r.callId === call.id);
596
+ if (result) {
597
+ this.pendingSpoolOutputs.set(call.id, { tool: call.name, output: result.output });
598
+ }
599
+ }
386
600
  action = kernelAction(runtime, this.pendingObservations, {
387
601
  kind: "tool_results",
388
602
  results: toolResults.map(toolResultToKernel),
@@ -474,19 +688,10 @@ export class RuntimeRunner {
474
688
  parent_session_id: parentSessionId,
475
689
  });
476
690
  this.nextArchiveStart = await this.appendObservations(parentSessionId, runtime, this.nextArchiveStart);
477
- const spawned = observations.find(o => o.kind === "agent_spawned" && typeof o.agent_id === "string");
691
+ const spawned = findSpawnProcessObservation(observations);
478
692
  if (!spawned)
479
- throw new Error("spawn_sub_agent did not emit agent_spawned");
480
- const manifest = {
481
- kind: "agent_spawned",
482
- turn: spawned.turn,
483
- agent_id: spawned.agent_id,
484
- parent_session_id: spawned.parent_session_id ?? parentSessionId,
485
- role: spawned.role ?? spec.role,
486
- isolation: spawned.isolation ?? spec.isolation ?? "shared",
487
- context_inheritance: spawned.context_inheritance ?? "none",
488
- permitted_capability_ids: spawned.permitted_capability_ids ?? [],
489
- };
693
+ throw new Error("spawn_sub_agent did not emit agent_process_changed");
694
+ const manifest = spawnObservationToManifest(spawned, spec, parentSessionId);
490
695
  const orchestrator = this.opts.subAgentOrchestrator ?? defaultSubAgentOrchestrator;
491
696
  const result = await orchestrator.run({
492
697
  parentOpts: this.opts,
@@ -505,95 +710,94 @@ export class RuntimeRunner {
505
710
  const turn = runtime.turn();
506
711
  const preservedRefs = runtime.preservedRefs();
507
712
  const observations = this.pendingObservations.splice(0);
508
- for (const obs of observations) {
509
- if (obs.kind === "compressed") {
510
- const latest = await this.opts.sessionLog.latestSeq(sessionId);
511
- if (latest < nextArchiveStart)
512
- continue;
513
- const end = latest;
514
- const compressedSeq = await this.opts.sessionLog.append(sessionId, {
515
- kind: "compressed",
516
- turn,
517
- archived_seq_range: [nextArchiveStart, end],
518
- action: compressionAction(obs.action),
519
- summary: obs.summary,
520
- summary_tokens: obs.summary
521
- ? Math.max(1, Math.ceil(obs.summary.length / 4))
522
- : undefined,
523
- preserved_refs: preservedRefs,
524
- });
525
- nextArchiveStart = compressedSeq + 1;
526
- }
527
- else if (obs.kind === "rollbacked") {
528
- await this.opts.sessionLog.append(sessionId, {
529
- kind: "rollbacked",
530
- turn: obs.turn ?? turn,
531
- checkpoint_history_len: obs.checkpoint_history_len ?? 0,
532
- reason: obs.reason,
533
- });
534
- }
535
- else if (obs.kind === "capability_changed") {
536
- await this.opts.sessionLog.append(sessionId, {
537
- kind: "capability_changed",
538
- turn: obs.turn ?? turn,
539
- added: obs.added ?? [],
540
- removed: obs.removed ?? [],
541
- ...(obs.change_kind != null && { change_kind: obs.change_kind }),
542
- ...(obs.capability_id != null && { capability_id: obs.capability_id }),
543
- ...(obs.version != null && { version: obs.version }),
544
- ...(obs.mounted_by != null && { mounted_by: obs.mounted_by }),
545
- ...(obs.mount_reason != null && { mount_reason: obs.mount_reason }),
546
- });
547
- }
548
- else if (obs.kind === "milestone_advanced") {
549
- await this.opts.sessionLog.append(sessionId, {
550
- kind: "milestone_advanced",
551
- turn: obs.turn ?? turn,
552
- phase_id: obs.phase_id ?? "",
553
- capabilities_unlocked: obs.capabilities_unlocked ?? [],
554
- });
555
- }
556
- else if (obs.kind === "milestone_blocked") {
557
- await this.opts.sessionLog.append(sessionId, {
558
- kind: "milestone_blocked",
559
- turn: obs.turn ?? turn,
560
- phase_id: obs.phase_id ?? "",
561
- reason: typeof obs.reason === "string" ? obs.reason : "",
562
- });
563
- }
564
- else if (obs.kind === "milestone_evidence") {
565
- await this.opts.sessionLog.append(sessionId, {
566
- kind: "milestone_evidence",
567
- turn: obs.turn ?? turn,
568
- phase_id: obs.phase_id ?? "",
569
- evidence: obs.evidence ?? [],
570
- });
713
+ for (let obs of observations) {
714
+ if (obs.kind === "page_in_requested")
715
+ continue;
716
+ let spoolRef;
717
+ if (obs.kind === "large_result_spooled") {
718
+ const pending = this.pendingSpoolOutputs.get(obs.call_id ?? "");
719
+ if (pending) {
720
+ const spool = this.opts.resultSpool ?? new LargeResultSpool();
721
+ try {
722
+ spoolRef = await spool.persistOutput(obs.call_id ?? "", pending.output);
723
+ }
724
+ catch {
725
+ // non-fatal
726
+ }
727
+ if (!obs.tool && pending.tool) {
728
+ obs = { ...obs, tool: pending.tool };
729
+ }
730
+ this.pendingSpoolOutputs.delete(obs.call_id ?? "");
731
+ }
571
732
  }
572
- else if (obs.kind === "checkpoint_taken") {
573
- await this.opts.sessionLog.append(sessionId, {
574
- kind: "checkpoint_taken",
575
- turn: obs.turn ?? turn,
576
- history_len: obs.history_len ?? 0,
577
- });
733
+ const latest = obs.kind === "compressed" ? await this.opts.sessionLog.latestSeq(sessionId) : undefined;
734
+ const event = kernelObservationToSessionEvent(obs, turn, {
735
+ nextArchiveStart,
736
+ latestSeq: latest,
737
+ preservedRefs,
738
+ spoolRef,
739
+ compressionAction,
740
+ });
741
+ if (!event)
742
+ continue;
743
+ if (obs.kind === "page_out" && obs.archived) {
744
+ this.localPageOutCache.push(...obs.archived);
578
745
  }
579
- else if (obs.kind === "agent_spawned") {
580
- await this.opts.sessionLog.append(sessionId, {
581
- kind: "agent_spawned",
582
- turn: obs.turn ?? turn,
583
- agent_id: obs.agent_id ?? "",
584
- parent_session_id: obs.parent_session_id ?? "",
585
- role: obs.role ?? "",
586
- isolation: obs.isolation ?? "",
587
- context_inheritance: obs.context_inheritance ?? "",
588
- permitted_capability_ids: obs.permitted_capability_ids ?? [],
589
- });
746
+ const compressedSeq = await this.opts.sessionLog.append(sessionId, event);
747
+ if (event.kind === "compressed") {
748
+ nextArchiveStart = compressedSeq + 1;
590
749
  }
591
- else if (obs.kind !== "renewed") {
592
- console.warn(`[deepstrike] unhandled KernelObservation kind: ${obs.kind}`);
750
+ if (obs.kind === "page_out"
751
+ && obs.tier_hint === "semantic"
752
+ && Array.isArray(obs.archived)
753
+ && obs.archived.length > 0) {
754
+ void this.archiveSemanticPageOut(obs.archived, compressionAction(obs.action));
593
755
  }
594
756
  }
595
757
  return nextArchiveStart;
596
758
  }
759
+ async archiveSemanticPageOut(archived, action) {
760
+ if (!this.opts.dreamStore || !this.opts.agentId)
761
+ return;
762
+ try {
763
+ const summary = this.opts.dreamSummarizer
764
+ ? await this.opts.dreamSummarizer.summarize(archived, { action })
765
+ : await summarizeForLongTermMemory(this.opts.dreamProvider ?? this.opts.provider, archived, this.opts.dreamSystemPrompt);
766
+ const existing = await this.opts.dreamStore.loadMemories(this.opts.agentId);
767
+ await this.opts.dreamStore.commit(this.opts.agentId, {
768
+ toAdd: [{ text: summary, score: 1.0, metadata: { source: "semantic_page_out", action } }],
769
+ toRemoveIndices: [],
770
+ stats: {
771
+ insightsProcessed: 1,
772
+ duplicatesRemoved: 0,
773
+ conflictsResolved: 0,
774
+ entriesAdded: 1,
775
+ },
776
+ }, existing);
777
+ }
778
+ catch {
779
+ // non-fatal
780
+ }
781
+ }
782
+ }
783
+ async function summarizeForLongTermMemory(provider, archived, systemPrompt) {
784
+ const transcript = archived
785
+ .map(m => `${m.role}: ${m.content}`)
786
+ .join("\n");
787
+ const context = {
788
+ systemText: [
789
+ systemPrompt,
790
+ "Summarize the following conversation for long-term memory. Preserve key facts, decisions, and open questions.",
791
+ ].filter(Boolean).join("\n\n"),
792
+ turns: [{ role: "user", content: transcript, toolCalls: [] }],
793
+ };
794
+ let text = "";
795
+ const state = provider.createRunState?.();
796
+ for await (const evt of provider.stream(context, [], undefined, state)) {
797
+ if (evt.type === "text_delta")
798
+ text += evt.delta;
799
+ }
800
+ return text.trim() || transcript.slice(0, 2000);
597
801
  }
598
802
  function isMidRun(events) {
599
803
  return events.length > 0 && !events.some(e => e.event.kind === "run_terminal");
@@ -608,8 +812,14 @@ function compressionAction(action) {
608
812
  return undefined;
609
813
  }
610
814
  function replayMessages(events, maxBytes) {
611
- const messages = [];
815
+ // Build upgraded-summary index: compressed_seq -> upgraded summary
816
+ const upgradedSummaries = new Map();
612
817
  for (const { event: e } of events) {
818
+ if (e.kind === "summary_upgraded")
819
+ upgradedSummaries.set(e.compressed_seq, e.summary);
820
+ }
821
+ const messages = [];
822
+ for (const { seq, event: e } of events) {
613
823
  if (e.kind === "run_started") {
614
824
  const userText = e.criteria.length
615
825
  ? `${e.goal}\n\nCriteria:\n${e.criteria.map((c, i) => `${i + 1}. ${c}`).join("\n")}`
@@ -622,8 +832,9 @@ function replayMessages(events, maxBytes) {
622
832
  });
623
833
  }
624
834
  else if (e.kind === "compressed") {
625
- if (e.summary) {
626
- const systemText = `[Compressed context: turn ${e.turn}]\n${e.summary}`;
835
+ const summary = upgradedSummaries.get(seq) ?? e.summary;
836
+ if (summary) {
837
+ const systemText = `[Compressed context: turn ${e.turn}]\n${summary}`;
627
838
  messages.push({
628
839
  role: "system",
629
840
  content: systemText,