@deepstrike/wasm 0.2.2 → 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.
@@ -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 { DEFAULT_NATIVE_ATTENTION_POLICY, DEFAULT_NATIVE_GOVERNANCE_POLICY } 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,6 @@ 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);
134
262
  kernelApply(runtime, this.pendingObservations, {
135
263
  kind: "set_tools",
136
264
  tools: this.opts.executionPlane.schemas().map(toolSchemaToKernel),
@@ -199,11 +327,23 @@ export class RuntimeRunner {
199
327
  if (this.opts.runSpec) {
200
328
  startPayload.run_spec = agentRunSpecToKernel(this.opts.runSpec);
201
329
  }
330
+ const attentionPolicy = this.opts.attentionPolicy ?? DEFAULT_NATIVE_ATTENTION_POLICY;
331
+ const governancePolicy = this.opts.governancePolicy ?? DEFAULT_NATIVE_GOVERNANCE_POLICY;
332
+ kernelApply(runtime, this.pendingObservations, governancePolicyToKernelEvent(governancePolicy));
333
+ kernelApply(runtime, this.pendingObservations, {
334
+ kind: "set_attention_policy",
335
+ ...(attentionPolicy.maxQueueSize !== undefined
336
+ ? { max_queue_size: attentionPolicy.maxQueueSize }
337
+ : {}),
338
+ });
202
339
  let action = resumeMidRun
203
340
  ? kernelAction(runtime, this.pendingObservations, { kind: "resume" })
204
341
  : kernelAction(runtime, this.pendingObservations, startPayload);
205
342
  let hasAttemptedReactiveCompact = false;
206
343
  while (!runtime.isTerminal()) {
344
+ if (action.kind === "execute_tool") {
345
+ await this.applyKernelPageIn(runtime, sessionId);
346
+ }
207
347
  nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
208
348
  if (this.interrupted) {
209
349
  action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
@@ -212,30 +352,27 @@ export class RuntimeRunner {
212
352
  if (this.opts.signalSource) {
213
353
  const sig = await this.opts.signalSource.nextSignal();
214
354
  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;
355
+ const id = crypto.randomUUID();
356
+ const source = sig.source ?? "custom";
357
+ const signalType = sig.signalType ?? "event";
358
+ const urgency = sig.urgency ?? "normal";
359
+ const summary = String(sig.payload?.goal ?? "signal");
360
+ const sigAction = kernelMaybeAction(runtime, this.pendingObservations, {
361
+ kind: "signal",
362
+ signal: {
363
+ id,
364
+ source,
365
+ signal_type: signalType,
366
+ urgency,
367
+ summary,
368
+ payload: sig.payload ?? {},
369
+ ...(sig.dedupeKey ? { dedupe_key: sig.dedupeKey } : {}),
370
+ timestamp_ms: Date.now(),
371
+ },
372
+ });
373
+ if (sigAction)
374
+ action = sigAction;
237
375
  }
238
- queued = router.next();
239
376
  }
240
377
  if (runtime.isTerminal())
241
378
  break;
@@ -245,11 +382,16 @@ export class RuntimeRunner {
245
382
  const context = action.context;
246
383
  const tools = action.tools;
247
384
  let turnTokens = 0;
385
+ let turnInputTokens = 0;
386
+ let turnOutputTokens = 0;
248
387
  let shouldRetry = false;
249
388
  try {
250
389
  for await (const evt of this.opts.provider.stream(context, tools, Object.keys(ext).length ? ext : undefined, providerState)) {
251
390
  if (evt.type === "usage") {
252
- turnTokens = evt.totalTokens;
391
+ const usageEvt = evt;
392
+ turnTokens = usageEvt.totalTokens;
393
+ turnInputTokens = usageEvt.inputTokens ?? 0;
394
+ turnOutputTokens = usageEvt.outputTokens ?? 0;
253
395
  continue;
254
396
  }
255
397
  yield evt;
@@ -289,17 +431,33 @@ export class RuntimeRunner {
289
431
  role: "assistant",
290
432
  content: finalText,
291
433
  toolCalls: finalToolCalls,
292
- tokenCount: turnTokens || undefined,
434
+ tokenCount: turnOutputTokens || turnTokens || undefined,
293
435
  };
294
- action = kernelAction(runtime, this.pendingObservations, {
436
+ const providerEvent = {
295
437
  kind: "provider_result",
296
438
  message: messageToKernelMessage(assistantMessage),
297
- });
439
+ ...(turnInputTokens > 0 ? { observed_input_tokens: turnInputTokens } : {}),
440
+ ...(turnOutputTokens > 0 ? { observed_output_tokens: turnOutputTokens } : {}),
441
+ now_ms: Date.now(),
442
+ };
443
+ let nextAction = kernelMaybeAction(runtime, this.pendingObservations, providerEvent);
444
+ const hasSuspended = this.pendingObservations.some(o => o.kind === "suspended");
445
+ if (!nextAction && hasSuspended) {
446
+ const resolved = await this.resolveKernelSuspend(runtime, sessionId);
447
+ for (const evt of resolved.events)
448
+ yield evt;
449
+ nextAction = kernelAction(runtime, this.pendingObservations, {
450
+ kind: "resume",
451
+ approved_calls: resolved.approved,
452
+ denied_calls: resolved.denied,
453
+ });
454
+ }
455
+ action = nextAction ?? kernelAction(runtime, this.pendingObservations, providerEvent);
298
456
  const providerReplay = peekProviderReplay(this.opts.provider, finalText, finalToolCalls);
299
457
  await this.opts.sessionLog.append(sessionId, buildLlmCompletedEvent({
300
458
  turn: runtime.turn(),
301
459
  content: finalText,
302
- tokenCount: turnTokens || undefined,
460
+ tokenCount: turnOutputTokens || turnTokens || undefined,
303
461
  toolCalls: finalToolCalls,
304
462
  providerReplay,
305
463
  }));
@@ -312,8 +470,8 @@ export class RuntimeRunner {
312
470
  skillContentMap: this.opts.skillContentMap,
313
471
  dreamStore: this.opts.dreamStore,
314
472
  knowledgeSource: this.opts.knowledgeSource,
315
- governance: this.opts.governance,
316
473
  onToolSuspend: this.opts.onToolSuspend,
474
+ onPermissionRequest: this.opts.onPermissionRequest,
317
475
  };
318
476
  const toolResults = [];
319
477
  for await (const evt of this.opts.executionPlane.executeAll(allCalls, runCtx)) {
@@ -358,18 +516,15 @@ export class RuntimeRunner {
358
516
  arguments: typeof pre.arguments === "string" ? pre.arguments : JSON.stringify(pre.arguments),
359
517
  reason: pre.reason,
360
518
  });
519
+ }
520
+ else if (evt.type === "permission_resolved") {
521
+ const resolved = evt;
522
+ const turn = runtime.turn();
361
523
  await this.opts.sessionLog.append(sessionId, {
362
524
  kind: "permission_resolved",
363
525
  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}`,
526
+ approved: resolved.approved,
527
+ responder: resolved.responder,
373
528
  });
374
529
  }
375
530
  }
@@ -383,6 +538,12 @@ export class RuntimeRunner {
383
538
  token_count: r.tokenCount,
384
539
  })),
385
540
  });
541
+ for (const call of allCalls) {
542
+ const result = toolResults.find(r => r.callId === call.id);
543
+ if (result) {
544
+ this.pendingSpoolOutputs.set(call.id, { tool: call.name, output: result.output });
545
+ }
546
+ }
386
547
  action = kernelAction(runtime, this.pendingObservations, {
387
548
  kind: "tool_results",
388
549
  results: toolResults.map(toolResultToKernel),
@@ -474,19 +635,10 @@ export class RuntimeRunner {
474
635
  parent_session_id: parentSessionId,
475
636
  });
476
637
  this.nextArchiveStart = await this.appendObservations(parentSessionId, runtime, this.nextArchiveStart);
477
- const spawned = observations.find(o => o.kind === "agent_spawned" && typeof o.agent_id === "string");
638
+ const spawned = findSpawnProcessObservation(observations);
478
639
  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
- };
640
+ throw new Error("spawn_sub_agent did not emit agent_process_changed");
641
+ const manifest = spawnObservationToManifest(spawned, spec, parentSessionId);
490
642
  const orchestrator = this.opts.subAgentOrchestrator ?? defaultSubAgentOrchestrator;
491
643
  const result = await orchestrator.run({
492
644
  parentOpts: this.opts,
@@ -505,95 +657,94 @@ export class RuntimeRunner {
505
657
  const turn = runtime.turn();
506
658
  const preservedRefs = runtime.preservedRefs();
507
659
  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
- });
660
+ for (let obs of observations) {
661
+ if (obs.kind === "page_in_requested")
662
+ continue;
663
+ let spoolRef;
664
+ if (obs.kind === "large_result_spooled") {
665
+ const pending = this.pendingSpoolOutputs.get(obs.call_id ?? "");
666
+ if (pending) {
667
+ const spool = this.opts.resultSpool ?? new LargeResultSpool();
668
+ try {
669
+ spoolRef = await spool.persistOutput(obs.call_id ?? "", pending.output);
670
+ }
671
+ catch {
672
+ // non-fatal
673
+ }
674
+ if (!obs.tool && pending.tool) {
675
+ obs = { ...obs, tool: pending.tool };
676
+ }
677
+ this.pendingSpoolOutputs.delete(obs.call_id ?? "");
678
+ }
571
679
  }
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
- });
680
+ const latest = obs.kind === "compressed" ? await this.opts.sessionLog.latestSeq(sessionId) : undefined;
681
+ const event = kernelObservationToSessionEvent(obs, turn, {
682
+ nextArchiveStart,
683
+ latestSeq: latest,
684
+ preservedRefs,
685
+ spoolRef,
686
+ compressionAction,
687
+ });
688
+ if (!event)
689
+ continue;
690
+ if (obs.kind === "page_out" && obs.archived) {
691
+ this.localPageOutCache.push(...obs.archived);
578
692
  }
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
- });
693
+ const compressedSeq = await this.opts.sessionLog.append(sessionId, event);
694
+ if (event.kind === "compressed") {
695
+ nextArchiveStart = compressedSeq + 1;
590
696
  }
591
- else if (obs.kind !== "renewed") {
592
- console.warn(`[deepstrike] unhandled KernelObservation kind: ${obs.kind}`);
697
+ if (obs.kind === "page_out"
698
+ && obs.tier_hint === "semantic"
699
+ && Array.isArray(obs.archived)
700
+ && obs.archived.length > 0) {
701
+ void this.archiveSemanticPageOut(obs.archived, compressionAction(obs.action));
593
702
  }
594
703
  }
595
704
  return nextArchiveStart;
596
705
  }
706
+ async archiveSemanticPageOut(archived, action) {
707
+ if (!this.opts.dreamStore || !this.opts.agentId)
708
+ return;
709
+ try {
710
+ const summary = this.opts.dreamSummarizer
711
+ ? await this.opts.dreamSummarizer.summarize(archived, { action })
712
+ : await summarizeForLongTermMemory(this.opts.dreamProvider ?? this.opts.provider, archived, this.opts.dreamSystemPrompt);
713
+ const existing = await this.opts.dreamStore.loadMemories(this.opts.agentId);
714
+ await this.opts.dreamStore.commit(this.opts.agentId, {
715
+ toAdd: [{ text: summary, score: 1.0, metadata: { source: "semantic_page_out", action } }],
716
+ toRemoveIndices: [],
717
+ stats: {
718
+ insightsProcessed: 1,
719
+ duplicatesRemoved: 0,
720
+ conflictsResolved: 0,
721
+ entriesAdded: 1,
722
+ },
723
+ }, existing);
724
+ }
725
+ catch {
726
+ // non-fatal
727
+ }
728
+ }
729
+ }
730
+ async function summarizeForLongTermMemory(provider, archived, systemPrompt) {
731
+ const transcript = archived
732
+ .map(m => `${m.role}: ${m.content}`)
733
+ .join("\n");
734
+ const context = {
735
+ systemText: [
736
+ systemPrompt,
737
+ "Summarize the following conversation for long-term memory. Preserve key facts, decisions, and open questions.",
738
+ ].filter(Boolean).join("\n\n"),
739
+ turns: [{ role: "user", content: transcript, toolCalls: [] }],
740
+ };
741
+ let text = "";
742
+ const state = provider.createRunState?.();
743
+ for await (const evt of provider.stream(context, [], undefined, state)) {
744
+ if (evt.type === "text_delta")
745
+ text += evt.delta;
746
+ }
747
+ return text.trim() || transcript.slice(0, 2000);
597
748
  }
598
749
  function isMidRun(events) {
599
750
  return events.length > 0 && !events.some(e => e.event.kind === "run_terminal");
@@ -608,8 +759,14 @@ function compressionAction(action) {
608
759
  return undefined;
609
760
  }
610
761
  function replayMessages(events, maxBytes) {
611
- const messages = [];
762
+ // Build upgraded-summary index: compressed_seq -> upgraded summary
763
+ const upgradedSummaries = new Map();
612
764
  for (const { event: e } of events) {
765
+ if (e.kind === "summary_upgraded")
766
+ upgradedSummaries.set(e.compressed_seq, e.summary);
767
+ }
768
+ const messages = [];
769
+ for (const { seq, event: e } of events) {
613
770
  if (e.kind === "run_started") {
614
771
  const userText = e.criteria.length
615
772
  ? `${e.goal}\n\nCriteria:\n${e.criteria.map((c, i) => `${i + 1}. ${c}`).join("\n")}`
@@ -622,8 +779,9 @@ function replayMessages(events, maxBytes) {
622
779
  });
623
780
  }
624
781
  else if (e.kind === "compressed") {
625
- if (e.summary) {
626
- const systemText = `[Compressed context: turn ${e.turn}]\n${e.summary}`;
782
+ const summary = upgradedSummaries.get(seq) ?? e.summary;
783
+ if (summary) {
784
+ const systemText = `[Compressed context: turn ${e.turn}]\n${summary}`;
627
785
  messages.push({
628
786
  role: "system",
629
787
  content: systemText,