@deepstrike/wasm 0.2.76 → 0.2.78

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 (62) hide show
  1. package/dist/agent.d.ts +6 -2
  2. package/dist/agent.js +59 -19
  3. package/dist/index.d.ts +7 -9
  4. package/dist/index.js +3 -5
  5. package/dist/memory/attestation.d.ts +38 -0
  6. package/dist/memory/attestation.js +43 -0
  7. package/dist/memory/authority.d.ts +2 -0
  8. package/dist/memory/authority.js +3 -1
  9. package/dist/memory/candidate-sink.d.ts +66 -0
  10. package/dist/memory/candidate-sink.js +82 -0
  11. package/dist/memory/durable.d.ts +4 -2
  12. package/dist/memory/durable.js +2 -0
  13. package/dist/memory/index.d.ts +9 -1
  14. package/dist/memory/index.js +5 -0
  15. package/dist/memory/lifecycle.d.ts +82 -0
  16. package/dist/memory/lifecycle.js +46 -0
  17. package/dist/memory/recall-projection.d.ts +5 -0
  18. package/dist/memory/recall-projection.js +18 -0
  19. package/dist/memory/runtime-policy.d.ts +15 -0
  20. package/dist/memory/runtime-policy.js +26 -0
  21. package/dist/memory/writer.d.ts +22 -0
  22. package/dist/memory/writer.js +41 -0
  23. package/dist/providers/anthropic-compaction.d.ts +84 -0
  24. package/dist/providers/anthropic-compaction.js +222 -0
  25. package/dist/providers/anthropic.d.ts +41 -1
  26. package/dist/providers/anthropic.js +187 -18
  27. package/dist/providers/base.js +11 -1
  28. package/dist/runtime/canonical-kernel-step.d.ts +1 -2
  29. package/dist/runtime/canonical-kernel-step.js +74 -8
  30. package/dist/runtime/execution-plane.js +1 -1
  31. package/dist/runtime/index.d.ts +0 -2
  32. package/dist/runtime/index.js +0 -1
  33. package/dist/runtime/kernel-event-log.d.ts +8 -0
  34. package/dist/runtime/kernel-event-log.js +61 -1
  35. package/dist/runtime/kernel-step.d.ts +13 -2
  36. package/dist/runtime/os-snapshot.js +1 -0
  37. package/dist/runtime/runner.d.ts +40 -25
  38. package/dist/runtime/runner.js +196 -240
  39. package/dist/runtime/session-event-registry.generated.d.ts +212 -0
  40. package/dist/runtime/session-event-registry.generated.js +112 -0
  41. package/dist/runtime/session-log.d.ts +69 -16
  42. package/dist/runtime/session-log.js +10 -58
  43. package/dist/runtime/session-repair.d.ts +0 -3
  44. package/dist/runtime/session-repair.js +0 -3
  45. package/dist/runtime/sub-agent-orchestrator.d.ts +2 -0
  46. package/dist/runtime/sub-agent-orchestrator.js +2 -0
  47. package/dist/runtime/types/agent.d.ts +9 -52
  48. package/dist/runtime/types/agent.js +8 -45
  49. package/dist/types.d.ts +10 -5
  50. package/package.json +3 -3
  51. package/dist/memory/extraction.d.ts +0 -4
  52. package/dist/memory/extraction.js +0 -48
  53. package/dist/runtime/context.d.ts +0 -28
  54. package/dist/runtime/context.js +0 -16
  55. package/dist/runtime/evolution.d.ts +0 -189
  56. package/dist/runtime/evolution.js +0 -25
  57. package/dist/runtime/reducers.d.ts +0 -15
  58. package/dist/runtime/reducers.js +0 -57
  59. package/dist/runtime/verifiable-report.d.ts +0 -59
  60. package/dist/runtime/verifiable-report.js +0 -64
  61. package/dist/runtime/workflow-control-flow.d.ts +0 -21
  62. package/dist/runtime/workflow-control-flow.js +0 -83
@@ -1,6 +1,7 @@
1
1
  import { requestSnapshot } from "./prepared-request.js";
2
2
  import { assistantReplayKey, collectStreamMessage, toAnthropicMessages } from "./base.js";
3
3
  import { malformedToolArguments, toolArgumentsText } from "../runtime/tool-arguments.js";
4
+ import { OnDemandCompactor, THRESHOLD_COMPACTION_BETA, compactionBlock, resolveCompaction, resumeAfterCompactionPause, withoutCompactionPause, } from "./anthropic-compaction.js";
4
5
  /** Anthropic accepts at most this many cache_control breakpoints per request. */
5
6
  const MAX_CACHE_BREAKPOINTS = 4;
6
7
  /** Rolling cache breakpoints reserved for the message history (system uses ≤2). */
@@ -117,15 +118,53 @@ function assertCacheBudget(system, toolCount) {
117
118
  throw new Error(`Anthropic cache_control budget exceeded: ${systemBreakpoints} system + ${toolBreakpoints} tool + ${MESSAGE_CACHE_BREAKPOINTS} message > ${MAX_CACHE_BREAKPOINTS}`);
118
119
  }
119
120
  }
121
+ const CONTEXT_MANAGEMENT_BETA = "context-management-2025-06-27";
122
+ const PROMPT_CACHING_BETA = "prompt-caching-2024-07-31";
123
+ /** The leading wire messages that are history; the state turn follows them. */
124
+ function historyCount(wireTurns) {
125
+ return wireTurns.filter(index => index >= 0).length;
126
+ }
127
+ function clearToolUsesEdit(config) {
128
+ const c = config === true ? {} : config;
129
+ return {
130
+ type: "clear_tool_uses_20250919",
131
+ ...(c.triggerInputTokens !== undefined ? { trigger: { type: "input_tokens", value: c.triggerInputTokens } } : {}),
132
+ ...(c.keepToolUses !== undefined ? { keep: { type: "tool_uses", value: c.keepToolUses } } : {}),
133
+ ...(c.clearAtLeastInputTokens !== undefined ? { clear_at_least: { type: "input_tokens", value: c.clearAtLeastInputTokens } } : {}),
134
+ ...(c.excludeTools?.length ? { exclude_tools: [...c.excludeTools] } : {}),
135
+ ...(c.clearToolInputs !== undefined ? { clear_tool_inputs: c.clearToolInputs } : {}),
136
+ };
137
+ }
120
138
  export class AnthropicProvider {
121
139
  apiKey;
122
140
  model;
123
141
  maxTokens;
124
142
  nativeAssistantBlocks = new Map();
125
- constructor(apiKey, model = "claude-sonnet-4-6", maxTokens = 8096) {
143
+ /** Server-side context edits and the beta each needs. */
144
+ nativeContextEdits = [];
145
+ /** Threshold compaction with a pause: how many history messages stay verbatim behind the block. */
146
+ compactionPauseKeep;
147
+ onDemandCompactor;
148
+ constructor(apiKey, model = "claude-sonnet-4-6", maxTokens = 8096, options = {}) {
126
149
  this.apiKey = apiKey;
127
150
  this.model = model;
128
151
  this.maxTokens = maxTokens;
152
+ // Clearing runs first so the compaction trigger reads the input after tool results are cleared.
153
+ if (options.contextEditing)
154
+ this.nativeContextEdits.push({ beta: CONTEXT_MANAGEMENT_BETA, edit: clearToolUsesEdit(options.contextEditing) });
155
+ const compaction = options.compaction ? resolveCompaction(options.compaction) : undefined;
156
+ if (compaction?.mode === "threshold") {
157
+ this.nativeContextEdits.push({ beta: THRESHOLD_COMPACTION_BETA, edit: compaction.edit });
158
+ if (compaction.edit.pause_after_compaction)
159
+ this.compactionPauseKeep = compaction.keepRecentMessages ?? 0;
160
+ }
161
+ else if (compaction?.mode === "on_demand") {
162
+ this.onDemandCompactor = new OnDemandCompactor(compaction.config, body => this.createMessage(body));
163
+ }
164
+ }
165
+ /** On-demand compaction in flight settles; for tests and orderly shutdown. */
166
+ compactionIdle() {
167
+ return this.onDemandCompactor?.idle() ?? Promise.resolve();
129
168
  }
130
169
  descriptor() {
131
170
  return {
@@ -141,6 +180,12 @@ export class AnthropicProvider {
141
180
  supported: true,
142
181
  requiresStrictPairing: true,
143
182
  },
183
+ ...(this.nativeContextEdits.length || this.onDemandCompactor ? {
184
+ nativeContext: {
185
+ ...(this.nativeContextEdits.some(native => native.edit.type === "clear_tool_uses_20250919") ? { toolResultClearing: true } : {}),
186
+ ...(this.onDemandCompactor || this.nativeContextEdits.some(native => native.edit.type === "compact_20260112") ? { compaction: true } : {}),
187
+ },
188
+ } : {}),
144
189
  };
145
190
  }
146
191
  requestPlanIdentity() {
@@ -174,16 +219,37 @@ export class AnthropicProvider {
174
219
  systemBlocks.push({ type: "text", text: context.systemKnowledge, ...(emitOnSystemBlocks ? { cache_control: cc } : {}) });
175
220
  }
176
221
  const system = systemBlocks.length ? systemBlocks : (context.systemText || undefined);
177
- const msgs = toAnthropicMessages(context, message => this.nativeAssistantBlocks.get(assistantReplayKey(message)));
178
- applyMessageCacheControl(msgs, context.frozenPrefixLen, strategy);
222
+ const replay = (message) => this.nativeAssistantBlocks.get(assistantReplayKey(message));
223
+ let msgs = [];
224
+ // For each wire message, the index of the rendered turn it came from; the state turn is -1.
225
+ let wireTurns = [];
226
+ context.turns.forEach((turn, index) => {
227
+ for (const message of toAnthropicMessages({ systemText: "", turns: [turn] }, replay)) {
228
+ msgs.push(message);
229
+ wireTurns.push(index);
230
+ }
231
+ });
232
+ // The API ignores everything before a compaction block, so it is not sent.
233
+ let head = -1;
234
+ for (let index = msgs.length - 1; index >= 0 && head < 0; index--) {
235
+ const content = msgs[index].content;
236
+ if (Array.isArray(content) && content.some(block => block.type === "compaction"))
237
+ head = index;
238
+ }
239
+ if (head > 0) {
240
+ msgs = msgs.slice(head);
241
+ wireTurns = wireTurns.slice(head);
242
+ }
243
+ applyMessageCacheControl(msgs, typeof context.frozenPrefixLen === "number" && head > 0 ? context.frozenPrefixLen - head : context.frozenPrefixLen, strategy);
179
244
  // Append the volatile State turn AFTER the cache breakpoints (uncached tail);
180
245
  // absent on un-rebuilt bindings, where the state is already inside `turns`.
181
246
  // Render through toAnthropicMessages so assistant tool_use blocks are
182
247
  // serialized correctly — raw content would silently drop toolCalls.
183
248
  if (context.stateTurn) {
184
249
  const stateCtx = { systemText: "", turns: [context.stateTurn] };
185
- const stateMsgs = toAnthropicMessages(stateCtx, message => this.nativeAssistantBlocks.get(assistantReplayKey(message)));
250
+ const stateMsgs = toAnthropicMessages(stateCtx, replay);
186
251
  msgs.push(...stateMsgs);
252
+ wireTurns.push(...stateMsgs.map(() => -1));
187
253
  }
188
254
  assertCacheBudget(system, tools.length);
189
255
  const builtTools = tools.length ? buildAnthropicTools(tools, !Array.isArray(system), strategy) : undefined;
@@ -200,25 +266,113 @@ export class AnthropicProvider {
200
266
  if (extensions?.enable_thinking) {
201
267
  body.thinking = { type: "enabled", budget_tokens: 8000 };
202
268
  }
269
+ // The kernel stopped running these tiers on the strength of the edits, so they ride on every
270
+ // request beside any betas or edits the caller passed.
271
+ if (this.nativeContextEdits.length) {
272
+ const betas = Array.isArray(extensions?.betas) ? extensions.betas.filter((value) => typeof value === "string") : [];
273
+ const callerManagement = extensions?.context_management && typeof extensions.context_management === "object"
274
+ ? extensions.context_management
275
+ : {};
276
+ const edits = Array.isArray(callerManagement.edits) ? [...callerManagement.edits] : [];
277
+ for (const native of this.nativeContextEdits) {
278
+ if (!betas.includes(native.beta))
279
+ betas.push(native.beta);
280
+ if (!edits.some(edit => edit?.type === native.edit.type))
281
+ edits.push(native.edit);
282
+ }
283
+ body.betas = betas;
284
+ body.context_management = { ...callerManagement, edits };
285
+ }
286
+ this.onDemandCompactor?.apply(body);
203
287
  const frozenBody = requestSnapshot(body);
204
288
  return {
205
289
  scope: "encoded_body", request: requestSnapshot(frozenBody), state: requestSnapshot(state ?? null),
206
- stream: signal => this.streamBody(frozenBody, slotBp, signal),
290
+ stream: signal => this.streamTurn(context, frozenBody, wireTurns, slotBp, signal),
207
291
  };
208
292
  }
293
+ /** One turn: the response's events, or — when a compaction pause stops the response at the
294
+ * summary — the continuation's. With a pause configured, events are held until the response
295
+ * shows it is not a pause (any non-usage event) or ends. */
296
+ async *streamTurn(context, body, wireTurns, slotBp, signal) {
297
+ const first = this.streamBody(body, slotBp, signal)[Symbol.asyncIterator]();
298
+ let held = this.compactionPauseKeep !== undefined ? [] : undefined;
299
+ let outcome;
300
+ for (;;) {
301
+ const next = await first.next();
302
+ if (next.done) {
303
+ outcome = next.value;
304
+ break;
305
+ }
306
+ if (held && next.value.type !== "usage") {
307
+ yield* held;
308
+ held = undefined;
309
+ }
310
+ if (held)
311
+ held.push(next.value);
312
+ else
313
+ yield next.value;
314
+ }
315
+ const paused = held && outcome.stopReason === "compaction" ? compactionBlock(outcome.nativeBlocks) : undefined;
316
+ let unanchoredBlock;
317
+ if (paused) {
318
+ const resumed = resumeAfterCompactionPause(body.messages, historyCount(wireTurns), paused, this.compactionPauseKeep);
319
+ if (resumed.anchor) {
320
+ const turn = context.turns[wireTurns[resumed.anchor.wireIndex]];
321
+ if (turn)
322
+ this.nativeAssistantBlocks.set(assistantReplayKey(turn), resumed.anchor.content);
323
+ }
324
+ else {
325
+ unanchoredBlock = paused;
326
+ }
327
+ const continuation = this.streamBody({ ...withoutCompactionPause(body), messages: resumed.messages }, slotBp, signal)[Symbol.asyncIterator]();
328
+ for (;;) {
329
+ const next = await continuation.next();
330
+ if (next.done) {
331
+ outcome = next.value;
332
+ break;
333
+ }
334
+ yield next.value;
335
+ }
336
+ }
337
+ else if (held) {
338
+ yield* held;
339
+ }
340
+ this.rememberNativeBlocks({ content: outcome.finalText, toolCalls: outcome.finalToolCalls }, [...(unanchoredBlock ? [unanchoredBlock] : []), ...outcome.nativeBlocks]);
341
+ this.onDemandCompactor?.observe(body, historyCount(wireTurns), outcome.totalTokens);
342
+ }
343
+ headers(body) {
344
+ return {
345
+ "x-api-key": this.apiKey,
346
+ "anthropic-version": "2023-06-01",
347
+ "content-type": "application/json",
348
+ "anthropic-beta": [
349
+ PROMPT_CACHING_BETA,
350
+ ...(Array.isArray(body.betas) ? body.betas.filter((value) => typeof value === "string" && value !== PROMPT_CACHING_BETA) : []),
351
+ ].filter((value, index, values) => values.indexOf(value) === index).join(","),
352
+ };
353
+ }
354
+ /** A non-streaming request; the on-demand compaction request goes this way. */
355
+ async createMessage(body) {
356
+ const resp = await fetch("https://api.anthropic.com/v1/messages", {
357
+ method: "POST",
358
+ headers: this.headers(body),
359
+ // Betas travel in the header; the Messages API refuses them as a body field.
360
+ body: JSON.stringify({ ...body, stream: undefined, betas: undefined }),
361
+ });
362
+ if (!resp.ok) {
363
+ throw Object.assign(new Error(`Anthropic ${resp.status}: ${await resp.text()}`), { status: resp.status });
364
+ }
365
+ return await resp.json();
366
+ }
209
367
  async *stream(context, tools, extensions, state, signal) {
210
368
  yield* this.prepareRequest(context, tools, extensions, state).stream(signal);
211
369
  }
212
370
  async *streamBody(body, slotBp, signal) {
213
371
  const resp = await fetch("https://api.anthropic.com/v1/messages", {
214
372
  method: "POST",
215
- headers: {
216
- "x-api-key": this.apiKey,
217
- "anthropic-version": "2023-06-01",
218
- "content-type": "application/json",
219
- "anthropic-beta": "prompt-caching-2024-07-31",
220
- },
221
- body: JSON.stringify(body),
373
+ headers: this.headers(body),
374
+ // Betas travel in the header; the Messages API refuses them as a body field.
375
+ body: JSON.stringify({ ...body, betas: undefined }),
222
376
  ...(signal ? { signal } : {}), // #2-B-ii: a preempt aborts the in-flight request at the socket.
223
377
  });
224
378
  if (!resp.ok) {
@@ -235,6 +389,14 @@ export class AnthropicProvider {
235
389
  let cacheReadTokens = 0;
236
390
  let cacheCreationTokens = 0;
237
391
  let outputTokens = 0;
392
+ let stopReason;
393
+ const outcome = () => ({
394
+ nativeBlocks: Object.keys(nativeBlocks).map(Number).sort((a, b) => a - b).map(index => nativeBlocks[index]),
395
+ finalText,
396
+ finalToolCalls,
397
+ ...(stopReason ? { stopReason } : {}),
398
+ totalTokens: uncachedInput + cacheReadTokens + cacheCreationTokens + outputTokens,
399
+ });
238
400
  while (true) {
239
401
  const { done, value } = await reader.read();
240
402
  if (done)
@@ -247,10 +409,15 @@ export class AnthropicProvider {
247
409
  continue;
248
410
  const data = line.slice(6).trim();
249
411
  if (data === "[DONE]")
250
- return;
412
+ return outcome();
251
413
  try {
252
414
  const evt = JSON.parse(data);
253
415
  if (evt.type === "message_start" || evt.type === "message_delta") {
416
+ // stop_reason rides on message_delta; "max_tokens" drives output-cap recovery and
417
+ // "compaction" marks a paused threshold compaction.
418
+ const eventStopReason = evt.delta?.stop_reason ?? undefined;
419
+ if (eventStopReason)
420
+ stopReason = eventStopReason;
254
421
  const usage = (evt.usage ?? evt.message?.usage);
255
422
  if (usage) {
256
423
  // input + cache counts are pinned at message_start; a later
@@ -264,8 +431,6 @@ export class AnthropicProvider {
264
431
  const inputTokens = uncachedInput + cacheReadTokens + cacheCreationTokens;
265
432
  if (inputTokens > 0 || outputTokens > 0) {
266
433
  const bySlot = estimateCacheReadBySlot(cacheReadTokens, slotBp);
267
- // stop_reason rides on message_delta; "max_tokens" drives output-cap recovery.
268
- const stopReason = evt.delta?.stop_reason;
269
434
  yield {
270
435
  type: "usage",
271
436
  totalTokens: inputTokens + outputTokens,
@@ -274,7 +439,7 @@ export class AnthropicProvider {
274
439
  cacheReadInputTokens: cacheReadTokens,
275
440
  cacheCreationInputTokens: cacheCreationTokens,
276
441
  ...(bySlot ? { cacheReadInputTokensBySlot: bySlot } : {}),
277
- ...(stopReason ? { stopReason } : {}),
442
+ ...(eventStopReason ? { stopReason: eventStopReason } : {}),
278
443
  };
279
444
  }
280
445
  }
@@ -298,6 +463,10 @@ export class AnthropicProvider {
298
463
  nativeBlocks[idx] = { ...nativeBlocks[idx], thinking: String(nativeBlocks[idx]?.thinking ?? "") + d.thinking };
299
464
  yield { type: "thinking_delta", delta: d.thinking };
300
465
  }
466
+ else if (d.type === "compaction_delta") {
467
+ // The summary arrives whole in one delta; it is replayed, never shown.
468
+ nativeBlocks[idx] = { ...nativeBlocks[idx], content: String(nativeBlocks[idx]?.content ?? "") + String(d.content ?? "") };
469
+ }
301
470
  else if (d.type === "signature_delta") {
302
471
  nativeBlocks[idx] = { ...nativeBlocks[idx], signature: String(nativeBlocks[idx]?.signature ?? "") + d.signature };
303
472
  }
@@ -326,12 +495,12 @@ export class AnthropicProvider {
326
495
  catch { /* skip malformed */ }
327
496
  }
328
497
  }
329
- this.rememberNativeBlocks({ content: finalText, toolCalls: finalToolCalls }, Object.keys(nativeBlocks).map(Number).sort((a, b) => a - b).map(index => nativeBlocks[index]));
498
+ return outcome();
330
499
  }
331
500
  rememberNativeBlocks(message, blocks) {
332
501
  if (!blocks.length)
333
502
  return;
334
- if (!message.toolCalls?.length && !blocks.some(b => b.type === "thinking"))
503
+ if (!message.toolCalls?.length && !blocks.some(b => b.type === "thinking" || b.type === "compaction"))
335
504
  return;
336
505
  this.nativeAssistantBlocks.set(assistantReplayKey(message), blocks);
337
506
  }
@@ -110,10 +110,20 @@ export function toAnthropicMessages(context, nativeReplay) {
110
110
  }
111
111
  continue;
112
112
  }
113
+ if (msg.role === "assistant" && !msg.toolCalls?.length) {
114
+ // A turn that carries a server compaction block goes back as the API sent it: the block is
115
+ // what tells the API to drop everything before it.
116
+ const replay = nativeReplay?.(msg);
117
+ if (replay?.some(block => block.type === "compaction")) {
118
+ // Copies: request-time annotations such as `cache_control` must not land on the replay.
119
+ result.push({ role: "assistant", content: replay.map(block => ({ ...block })) });
120
+ continue;
121
+ }
122
+ }
113
123
  if (msg.role === "assistant" && msg.toolCalls?.length) {
114
124
  const replay = nativeReplay?.(msg);
115
125
  if (replay) {
116
- result.push({ role: "assistant", content: replay });
126
+ result.push({ role: "assistant", content: replay.map(block => ({ ...block })) });
117
127
  continue;
118
128
  }
119
129
  const blocks = [];
@@ -38,7 +38,7 @@ export interface CanonicalTransition {
38
38
  }
39
39
  export declare function canonicalUnsupportedEffectResolution(effectId: string, effectKind: string): CanonicalKernelInput;
40
40
  /** Adapt the core-owned KernelProjection JSON into the WASM action surface. */
41
- export declare function canonicalActionFromProjectionJson(raw: string): KernelRunnerAction | null;
41
+ export declare function canonicalActionFromProjectionJson(raw: string, observations?: readonly KernelObservation[]): KernelRunnerAction | null;
42
42
  export declare class CanonicalKernelRejectedError extends Error {
43
43
  readonly fault: Record<string, unknown>;
44
44
  constructor(faultJson: string);
@@ -98,7 +98,6 @@ export interface CanonicalRunnerRuntimeOptions {
98
98
  maxTurns?: number;
99
99
  maxTotalTokens?: number;
100
100
  maxWallMs?: number;
101
- artifactSetDigest?: string;
102
101
  memoryBindingId?: string;
103
102
  persistPayload?: (callId: string, content: string, previewBytes: number) => Promise<{
104
103
  payloadRef: string;
@@ -1,13 +1,23 @@
1
1
  import { normalizeProviderStopReason } from "../providers/stop-reason.js";
2
2
  import { sha256Hex } from "./sha256.js";
3
3
  import { JournalCasConflictError, MAX_CHAIN_POSITION as JOURNAL_MAX_CHAIN_POSITION, } from "./kernel-journal.js";
4
- import { encodeCanonicalContentParts, kernelMessageToSdk, renderedContextToSdk, } from "./kernel-step.js";
4
+ import { archivePresentationFromObservations, encodeCanonicalContentParts, kernelMessageToSdk, renderedContextToSdk, } from "./kernel-step.js";
5
5
  import { toolArgumentsFromWire } from "./tool-arguments.js";
6
6
  export const MAX_CHAIN_POSITION = JOURNAL_MAX_CHAIN_POSITION;
7
7
  const utf8ByteLength = (value) => new TextEncoder().encode(value).byteLength;
8
8
  function asObject(value) {
9
9
  return value && typeof value === "object" ? value : {};
10
10
  }
11
+ // Keep structured candidate errors compact and deterministic across SDKs.
12
+ function memoryCandidateErrorJson(value) {
13
+ if (Array.isArray(value))
14
+ return `[${value.map(memoryCandidateErrorJson).join(",")}]`;
15
+ if (value !== null && typeof value === "object") {
16
+ const object = value;
17
+ return `{${Object.keys(object).sort().map(key => `${JSON.stringify(key)}:${memoryCandidateErrorJson(object[key])}`).join(",")}}`;
18
+ }
19
+ return JSON.stringify(value);
20
+ }
11
21
  function totalUsageTokens(terminal) {
12
22
  const usage = asObject(terminal.usage);
13
23
  const input = Number(usage.input_tokens ?? 0);
@@ -68,7 +78,7 @@ function canonicalDoneFromTerminal(terminal) {
68
78
  return { kind: "done", effectId: "", result: { termination, turnsUsed, totalTokensUsed: totalUsageTokens(terminal) } };
69
79
  }
70
80
  /** Adapt the core-owned KernelProjection JSON into the WASM action surface. */
71
- export function canonicalActionFromProjectionJson(raw) {
81
+ export function canonicalActionFromProjectionJson(raw, observations = []) {
72
82
  const projection = asObject(JSON.parse(raw));
73
83
  const state = String(projection.state ?? "idle");
74
84
  if (state === "idle")
@@ -107,7 +117,6 @@ export function canonicalActionFromProjectionJson(raw) {
107
117
  kind: "call_provider",
108
118
  effectId,
109
119
  context: renderedContextToSdk(context),
110
- contextEffect: payload,
111
120
  tools: (Array.isArray(payload.tools) ? payload.tools : []).map(raw => {
112
121
  const tool = asObject(raw);
113
122
  return {
@@ -193,6 +202,7 @@ export function canonicalActionFromProjectionJson(raw) {
193
202
  }
194
203
  return {
195
204
  kind: "archive_page_out",
205
+ ...archivePresentationFromObservations(observations),
196
206
  effectId,
197
207
  handleId: String(payload.handle_id ?? ""),
198
208
  payload: {
@@ -781,9 +791,6 @@ export class CanonicalRunnerRuntime {
781
791
  this.host = new CanonicalKernelHost(kernel, journal, operationId);
782
792
  this.memoryBindingId = options.memoryBindingId ?? "wasm-memory";
783
793
  this.config = {
784
- ...(options.artifactSetDigest
785
- ? { artifact_set_binding: { artifact_set_digest: options.artifactSetDigest } }
786
- : {}),
787
794
  execution_policy: {
788
795
  max_context_tokens: options.maxContextTokens,
789
796
  ...(options.maxTurns !== undefined ? { max_turns: options.maxTurns } : {}),
@@ -1142,6 +1149,46 @@ export class CanonicalRunnerRuntime {
1142
1149
  },
1143
1150
  });
1144
1151
  break;
1152
+ case "memory_candidate_result":
1153
+ // M2 · the host's answer for a write it holds as a candidate. `pending` is terminal for the
1154
+ // effect and carries no record: nothing is persisted, and the kernel must not present it as
1155
+ // remembered. `accepted` names the record the store now holds, as `memory_persist_result`
1156
+ // does. Malformed receipts and rejections fail non-retryably; a non-null error takes
1157
+ // precedence and fails retryably (the same candidate id is redelivered).
1158
+ if (event.error !== undefined && event.error !== null) {
1159
+ input = this.failedEffect(event, "storage_unavailable", typeof event.error === "string" ? event.error : memoryCandidateErrorJson(event.error), true);
1160
+ break;
1161
+ }
1162
+ if (event.status === "pending") {
1163
+ input = typeof event.candidate_id !== "string" || !event.candidate_id
1164
+ || typeof event.receipt_id !== "string" || !event.receipt_id
1165
+ ? this.failedEffect(event, "storage_unavailable", "memory candidate receipt names no candidate_id/receipt_id", false)
1166
+ : this.succeededEffect(event, {
1167
+ kind: "memory_persisted",
1168
+ candidate: {
1169
+ binding_id: this.memoryBindingId,
1170
+ candidate_id: event.candidate_id,
1171
+ receipt_id: event.receipt_id,
1172
+ },
1173
+ });
1174
+ break;
1175
+ }
1176
+ if (event.status === "accepted") {
1177
+ input = typeof event.record_ref !== "string" || !event.record_ref
1178
+ || typeof event.digest !== "string" || !event.digest
1179
+ ? this.failedEffect(event, "storage_unavailable", "memory candidate accepted without record_ref/digest", false)
1180
+ : this.succeededEffect(event, {
1181
+ kind: "memory_persisted",
1182
+ receipt: {
1183
+ binding_id: this.memoryBindingId,
1184
+ record_ref: event.record_ref,
1185
+ digest: event.digest,
1186
+ },
1187
+ });
1188
+ break;
1189
+ }
1190
+ input = this.failedEffect(event, "storage_unavailable", `memory candidate rejected: ${typeof event.reason_code === "string" && event.reason_code ? event.reason_code : "unspecified"}`, false);
1191
+ break;
1145
1192
  case "memory_query_result":
1146
1193
  input = event.error
1147
1194
  ? this.failedEffect(event, "storage_unavailable", String(event.error), true)
@@ -1157,6 +1204,10 @@ export class CanonicalRunnerRuntime {
1157
1204
  name: String(record.name ?? ""),
1158
1205
  kind: String(record.kind ?? "reference"),
1159
1206
  content: String(record.content ?? ""),
1207
+ ...(typeof record.description === "string" && record.description.trim()
1208
+ ? { description: record.description } : {}),
1209
+ ...(typeof asObject(record.provenance).trust === "string"
1210
+ ? { trust: String(asObject(record.provenance).trust) } : {}),
1160
1211
  ...(typeof hit.score === "number" ? { score: hit.score } : {}),
1161
1212
  // M3: the store reports the count it holds; the kernel derives the next one.
1162
1213
  ...(Number(record.recall_count) > 0
@@ -1342,7 +1393,7 @@ export class CanonicalRunnerRuntime {
1342
1393
  }
1343
1394
  if (transition) {
1344
1395
  this.absorbTransition(transition);
1345
- this.lastAction = canonicalActionFromProjectionJson(this.host.kernel.projectPlannedStepJson(JSON.stringify(transition.plannedStep)));
1396
+ this.lastAction = canonicalActionFromProjectionJson(this.host.kernel.projectPlannedStepJson(JSON.stringify(transition.plannedStep)), transition.plannedStep.observations);
1346
1397
  }
1347
1398
  if (this.lastAction?.kind !== "unsupported_effect")
1348
1399
  return this.lastAction;
@@ -1506,6 +1557,12 @@ export class CanonicalRunnerRuntime {
1506
1557
  ...(event.tokens !== undefined ? { tokens: Number(event.tokens) } : {}),
1507
1558
  });
1508
1559
  return true;
1560
+ case "add_dependency_output":
1561
+ (this.initialContext.dependencies ??= []).push({
1562
+ agent_id: String(event.agent_id ?? ""),
1563
+ content: String(event.content ?? ""),
1564
+ });
1565
+ return true;
1509
1566
  case "add_knowledge_message":
1510
1567
  this.initialContext.knowledge.push({
1511
1568
  content: String(event.content ?? ""),
@@ -1522,10 +1579,18 @@ export class CanonicalRunnerRuntime {
1522
1579
  return true;
1523
1580
  case "set_memory_enabled":
1524
1581
  this.featurePolicy().memory_enabled = Boolean(event.enabled);
1582
+ // Only sent when on, so kernels that predate the field still accept memory-enabled runs.
1583
+ if (event.propose_memory)
1584
+ this.featurePolicy().propose_memory_enabled = true;
1585
+ else
1586
+ delete this.featurePolicy().propose_memory_enabled;
1587
+ if (event.enabled && event.recall) {
1588
+ this.config.memory_policy = { ...asObject(this.config.memory_policy), recall: true };
1589
+ }
1525
1590
  if (event.enabled) {
1526
1591
  this.config.memory_access = {
1527
1592
  binding_id: this.memoryBindingId,
1528
- capabilities: { read: true, write: true },
1593
+ capabilities: { read: event.read !== false, write: event.write !== false },
1529
1594
  };
1530
1595
  }
1531
1596
  return true;
@@ -1534,6 +1599,7 @@ export class CanonicalRunnerRuntime {
1534
1599
  return true;
1535
1600
  case "set_memory_policy":
1536
1601
  this.config.memory_policy = {
1602
+ ...(asObject(this.config.memory_policy).recall ? { recall: true } : {}),
1537
1603
  ...(event.stale_warning_days !== undefined ? { stale_warning_days: event.stale_warning_days } : {}),
1538
1604
  ...(event.retrieval_top_k !== undefined ? { retrieval_top_k: event.retrieval_top_k } : {}),
1539
1605
  ...(event.validation_enabled !== undefined ? { validation_enabled: event.validation_enabled } : {}),
@@ -49,7 +49,7 @@ export class LocalExecutionPlane {
49
49
  ? await ctx.memoryStore.search(ctx.agentId, { scope: ctx.memoryScope, query: String(args?.query ?? ""), top_k: topK, kinds: [] })
50
50
  : [];
51
51
  const content = entries.length
52
- ? entries.map(e => `[memory record_id=${e.record.record_id} score=${e.score.toFixed(3)}] ${e.record.content}`).join("\n---\n")
52
+ ? entries.map(e => `[memory record_id=${e.record.record_id} trust=${e.record.provenance.trust} score=${e.score.toFixed(3)}] ${e.record.content}`).join("\n---\n")
53
53
  : "No relevant memories found.";
54
54
  yield { type: "tool_result", callId: c.id, name: c.name, content, isError: false };
55
55
  }
@@ -16,8 +16,6 @@ export { runAgent, runFanout } from "./facade.js";
16
16
  export type { RunAgentOptions, RunFanoutOptions } from "./facade.js";
17
17
  export { McpExecutionPlane } from "./mcp-transport.js";
18
18
  export type { McpConnection, McpConnectionFactory, McpCredentialVault, McpServerConfig } from "./mcp-transport.js";
19
- export { builtinReducers, resolveReducer } from "./reducers.js";
20
- export type { Reducer, ReducerRegistry, ReducerInput } from "./reducers.js";
21
19
  export { getKernel } from "./kernel.js";
22
20
  export { ReplayProvider } from "./replay-provider.js";
23
21
  export type { ReplayProviderOpts } from "./replay-provider.js";
@@ -10,7 +10,6 @@ export { MemoryPayloadDriver, PayloadStore } from "./payload-store.js";
10
10
  export { CanonicalKernelHost, CanonicalKernelRejectedError, CanonicalKernelRebuildRequiredError, CanonicalRunnerRuntime, sha256, } from "./canonical-kernel-step.js";
11
11
  export { runAgent, runFanout } from "./facade.js";
12
12
  export { McpExecutionPlane } from "./mcp-transport.js";
13
- export { builtinReducers, resolveReducer } from "./reducers.js";
14
13
  export { getKernel } from "./kernel.js";
15
14
  export { ReplayProvider } from "./replay-provider.js";
16
15
  export { extractRecordedMessages } from "./replay-fixture.js";
@@ -6,6 +6,14 @@ export declare function categoryForKind(kind: string): KernelEventCategory;
6
6
  type CompressionAction = Extract<SessionEvent, {
7
7
  kind: "compressed";
8
8
  }>["action"];
9
+ /** The kernel's pressure actions that name a session-level compression action; any other
10
+ * action (e.g. `none`) is omitted from the event. Shared rule across all four SDKs. */
11
+ export declare function canonicalCompressionAction(action?: string): CompressionAction;
12
+ /**
13
+ * Project one kernel observation onto its session event (null = not persisted). Pinned across
14
+ * SDKs by the `kernel_observation_projection` conformance fixture: an absent optional field is
15
+ * omitted, never written as `null`.
16
+ */
9
17
  export declare function kernelObservationToSessionEvent(obs: KernelObservation, turn: number, opts?: {
10
18
  nextArchiveStart?: number;
11
19
  latestSeq?: number;