@deepstrike/sdk 0.2.49 → 0.2.51

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 (41) hide show
  1. package/README.md +83 -60
  2. package/dist/harness/manifest.d.ts +1 -1
  3. package/dist/harness/manifest.js +43 -29
  4. package/dist/index.d.ts +5 -7
  5. package/dist/index.js +3 -3
  6. package/dist/kernel.d.ts +61 -31
  7. package/dist/runtime/canonical-kernel-step.d.ts +143 -0
  8. package/dist/runtime/canonical-kernel-step.js +1444 -0
  9. package/dist/runtime/execution-plane.d.ts +0 -3
  10. package/dist/runtime/execution-plane.js +0 -24
  11. package/dist/runtime/facade.js +3 -0
  12. package/dist/runtime/kernel-event-log.js +7 -13
  13. package/dist/runtime/kernel-journal.d.ts +264 -0
  14. package/dist/runtime/kernel-journal.js +741 -0
  15. package/dist/runtime/kernel-primitives-dashboard.d.ts +0 -2
  16. package/dist/runtime/kernel-primitives-dashboard.js +1 -8
  17. package/dist/runtime/kernel-step.d.ts +29 -109
  18. package/dist/runtime/kernel-step.js +47 -317
  19. package/dist/runtime/os-snapshot.d.ts +2 -2
  20. package/dist/runtime/os-snapshot.js +2 -6
  21. package/dist/runtime/payload-store.d.ts +16 -0
  22. package/dist/runtime/payload-store.js +80 -0
  23. package/dist/runtime/runner.d.ts +80 -119
  24. package/dist/runtime/runner.js +706 -779
  25. package/dist/runtime/session-log.d.ts +34 -32
  26. package/dist/runtime/session-log.js +21 -131
  27. package/dist/runtime/session-repair.d.ts +2 -36
  28. package/dist/runtime/session-repair.js +2 -47
  29. package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
  30. package/dist/runtime/sub-agent-orchestrator.js +42 -40
  31. package/dist/types/agent.d.ts +31 -19
  32. package/dist/types/agent.js +31 -42
  33. package/dist/workflow/public.d.ts +1 -1
  34. package/dist/workflow/public.js +1 -1
  35. package/package.json +2 -2
  36. package/dist/runtime/kernel-rebuild.d.ts +0 -13
  37. package/dist/runtime/kernel-rebuild.js +0 -75
  38. package/dist/runtime/kernel-transaction-log.d.ts +0 -61
  39. package/dist/runtime/kernel-transaction-log.js +0 -149
  40. package/dist/runtime/large-result-spool.d.ts +0 -93
  41. package/dist/runtime/large-result-spool.js +0 -214
package/README.md CHANGED
@@ -82,7 +82,7 @@ const reply = await collectText(runner.run({ sessionId: "chat-1", goal: "What is
82
82
 
83
83
  Use `InMemorySessionLog` for process-local sessions or `FileSessionLog` when replay should survive restarts. `wake(sessionId)` resumes from the event log without inserting a duplicate `run_started` event.
84
84
 
85
- ### Package layout (v0.2.30)
85
+ ### Package layout (v0.2.50)
86
86
 
87
87
  The root export is the **intent layer** — what you reach for to run an agent, run a workflow, author a tool, or pick a provider (~30 symbols). Advanced machinery lives behind subpaths, so the common surface stays small and tree-shakeable:
88
88
 
@@ -96,7 +96,7 @@ The root export is the **intent layer** — what you reach for to run an agent,
96
96
  | `@deepstrike/sdk/harness` | `AttemptLoop`, body/judge/carry policies, `judge` |
97
97
  | `@deepstrike/sdk/os` | profiles, `KernelPrimitivesDashboard`, `primitiveForKind` / `KernelPrimitive`, signals, `PermissionManager`, replay-testing utilities |
98
98
 
99
- > **Migration from 0.2.x:** the kernel-lowering converters (`*ToKernel`), low-level prompt/eval builders, and the `OpenAIChatProvider` alias are no longer exported from root; backend providers, planes, memory, harness, and OS utilities moved to the subpaths above. See [`MIGRATION-v0.2.300.md`](./MIGRATION-v0.2.300.md).
99
+ > **Migration from 0.2.x:** the kernel-lowering converters (`*ToKernel`), low-level prompt/eval builders, and the `OpenAIChatProvider` alias are no longer exported from root; backend providers, planes, memory, harness, and OS utilities moved to the subpaths above. See [`MIGRATION-v0.2.30.md`](./MIGRATION-v0.2.30.md).
100
100
 
101
101
  ### Recipes — the canonical entry points
102
102
 
@@ -163,19 +163,19 @@ for await (const event of runner.run({ sessionId: "readme-1", goal: "Summarize R
163
163
  │ RuntimeRunner (Layer 1.5) │
164
164
  │ LLMProvider · ExecutionPlane · SessionLog · DreamStore │
165
165
  └───────────────────────────┬─────────────────────────────┘
166
- step(JSON event) actions / observations
166
+ durable prepare / append / commit
167
167
  ┌───────────────────────────▼─────────────────────────────┐
168
- │ @deepstrike/core KernelRuntime
168
+ │ @deepstrike/core Canonical Kernel ABI
169
169
  │ P1 Syscall · P2 Sched · P3 MM · Proc · IPC │
170
170
  └─────────────────────────────────────────────────────────┘
171
171
  ```
172
172
 
173
- The runner drives a single loop:
173
+ The runner drives one durable operation loop:
174
174
 
175
- 1. Kernel returns an **action** `call_provider`, `execute_tool`, `evaluate_milestone`, or `done`.
176
- 2. SDK executes the action (stream LLM, run tools, call milestone verifier).
177
- 3. SDK feeds the result back as a kernel **event** (`provider_result`, `tool_results`, …).
178
- 4. Kernel **observations** (compression, page-out, spool, signals, …) are drained into `SessionLog`.
175
+ 1. The host prepares one canonical envelope and durably appends the exact core-produced record.
176
+ 2. After commit, the kernel publishes typed **effects** such as provider, tool, or task work.
177
+ 3. The SDK executes those effects and returns each outcome through the single `resolve_effect` input.
178
+ 4. Typed observations and the terminal disposition are projected into `SessionLog` and the public stream.
179
179
 
180
180
  Kernel session events carry an optional `category` tag (`syscall` · `sched` · `mm` · `proc` · `ipc`) for diagnostics and OS snapshot rebuilds.
181
181
 
@@ -186,26 +186,26 @@ The mechanisms above are not internal refactors — they change what you can bui
186
186
  **Kernel-mediated runtime (M0–M4)**
187
187
  Tool calls, spawns, compression, and signals pass through one kernel gate with an explicit lifecycle (Ready / Running / Blocked / Suspended). You implement I/O; the kernel decides *when* and *whether*. Node, Python, and Rust share the same decision path, so `wake(sessionId)` and cross-language tooling see consistent behavior.
188
188
 
189
- **Longer, sturdier sessions (Layer-1 spool + semantic page-out)**
190
- Oversized tool results (> 50 KB) stay in context as a preview plus a `.spool/` reference the model reads the full payload on demand via ordinary file tools. When pressure triggers semantic eviction, the SDK summarizes archived content into `DreamStore`. Long tasks survive token pressure instead of failing mid-run.
189
+ **Longer, sturdier sessions (external payloads + semantic page-out)**
190
+ The host atomically persists oversized tool results before submitting an `External` result. Core journals only the opaque locator, digest, size, and preview; `read_result` becomes a correlated `LoadPayload` effect. When pressure triggers semantic eviction, the SDK summarizes archived content into `DreamStore`.
191
191
 
192
192
  **Safety and governance by default (OS native profile)**
193
193
  Every run loads declarative `governancePolicy` (deny / ask_user / rate-limit / param rules) and in-kernel signal routing (`signalPolicy`, default queue 64). Dangerous tools, external interrupts, and approval flows are policy — not ad-hoc `if` checks in your handlers.
194
194
 
195
195
  **Long-term memory as syscalls (Phase-7)**
196
- `writeMemory` and `queryMemory` run outside the main tool loop: kernel validation before `DreamStore.commit`, search `selectMemories` `memory_retrieval_result` on query. Failed writes emit `memory_validation_failed` for audit; good memory is durable without polluting history.
196
+ `writeMemory` and `queryMemory` run outside the main tool loop: kernel validation happens before `DreamStore.upsert`, while queries call `DreamStore.search` and journal `memory_retrieval_result`. Failed writes emit `memory_validation_failed` for audit; good memory is durable without polluting history.
197
197
 
198
198
  **Multi-agent and multi-signal orchestration**
199
199
  Sub-agents register in the kernel process table (`agent_process_changed`); parent runs suspend explicitly until `sub_agent_completed`. Signals get disposition (Interrupt / Queue / Observe / Dropped) in-kernel, so gateways, cron, and heartbeats compose with the main loop instead of racing it.
200
200
 
201
201
  **Observable like an OS log**
202
- Spool, page-out, signals, processes, budgets, and memory events land in `SessionLog` with categories. Rebuild an OS snapshot (`pageOutCount`, `spoolCount`, `processByAgent`, memory counters) from one event stream replay still strips audit events when reconstructing LLM messages.
202
+ Page-out, signals, processes, budgets, and memory events land in `SessionLog` with categories. Rebuild an OS snapshot (`pageOutCount`, `processByAgent`, memory counters) from one event stream; payload residency stays in the canonical journal.
203
203
 
204
204
  | You need… | Use… |
205
205
  |---|---|
206
206
  | Policy before tools run | `governancePolicy` (default: allow-all native profile) |
207
207
  | External interrupts | `signalSource` + in-kernel `signalPolicy` |
208
- | Huge tool output | Automatic Layer-1 spool; optional custom `resultSpool` |
208
+ | Huge tool output | Canonical external payload; optional custom `payloadStore` |
209
209
  | Durable recall across runs | `DreamStore` + semantic `page_out` via `dreamSummarizer` |
210
210
  | Programmatic memory I/O | `runner.writeMemory()` / `runner.queryMemory()` |
211
211
  | Debug / compliance | `SessionLog` events + OS snapshot helpers |
@@ -228,27 +228,56 @@ const outcome = await runner.runWorkflow({
228
228
  { task: "Skeptic: which flags are real violations?", role: "verify", dependsOn: [0, 1, 2] },
229
229
  ],
230
230
  })
231
- // → { completed: ["wf-node0", ], failed: [], outputs: { "wf-node3": "…" } }
231
+ const completed = outcome.nodeOutcomes.filter(node => node.status === "completed")
232
+ const partial = outcome.nodeOutcomes.filter(node => node.status === "completed_partial")
233
+ const failed = outcome.nodeOutcomes.filter(node => node.status === "failed")
234
+ // outcome.outputs["wf-node3"] contains the skeptic's final text.
235
+ // outcome.rejection is present only when the whole workflow was rejected before any node ran.
232
236
  ```
233
237
 
234
- `runWorkflow` works **standalone** — call it on a freshly-constructed runner (e.g. inside a stateless HTTP handler) and it auto-bootstraps a kernel that owns the DAG, drives it under the same governance/quota/attention policies a full `run()` gets, and tears it down on completion. Called *during* a `run()`, it instead drives the workflow on the active kernel. Either way every node's final text comes back in `outputs`, keyed by node agent-id. To resume an interrupted standalone run, pass the prior session id: `runner.resumeWorkflow(spec, { sessionId })`.
238
+ `runWorkflow` works **standalone** — call it on a freshly-constructed runner (e.g. inside a stateless HTTP handler) and it auto-bootstraps a kernel that owns the DAG, drives it under the same governance/quota/attention policies a full `run()` gets, and tears it down on completion. Called *during* a `run()`, it instead drives the workflow on the active kernel. Either way every node's final text comes back in `outputs`, keyed by node agent-id.
235
239
 
236
- A node's `kind` selects the control-flow shape; the same executor drives them all, every spawn passing the syscall gate:
240
+ A workflow node has no public `kind` field. Its control-flow shape is selected by one of four
241
+ mutually exclusive fields; omit all four for a normal spawn. The same executor drives every shape,
242
+ and every spawn passes the syscall gate:
237
243
 
238
- | Node `kind` | Behavior |
244
+ | Public `WorkflowNodeSpec` field | Behavior |
239
245
  |---|---|
240
- | `{ type: "spawn" }` (default) | Run the node's agent once |
241
- | `{ type: "loop", maxIters }` | Re-run until the agent signals it's done, capped at `maxIters` |
242
- | `{ type: "classify", branches }` | The classifier's result selects one branch; the rest are pruned |
243
- | `{ type: "tournament", entrants }` | Generate N entrants, then a pairwise-judge bracket to one winner |
244
- | `{ type: "reduce", reducer }` | **Tokenless host-compute** — a pure function (`dedupe_lines` / `merge_json_arrays` / `concat` / `count`, or your own via the `reducers` runner option) over the node's dependency outputs |
246
+ | none (default) | Run the node's agent once |
247
+ | `loop: { maxIters }` | Re-run until the agent signals it's done, capped at `maxIters` |
248
+ | `classify: { branches }` | The classifier's result selects one branch; the rest are pruned |
249
+ | `tournament: { entrants }` | Generate N entrants, then a pairwise-judge bracket to one winner |
250
+ | `reducer: "concat"` | **Tokenless host-compute** — a pure function (`dedupe_lines` / `merge_json_arrays` / `concat` / `count`, or your own via the `reducers` runner option) over the node's `dependsOn` outputs |
245
251
 
246
- ### 0.2.11 capabilities
252
+ Dependencies use `dependsOn: number[]`, where each number is a node index, and `depPolicy` controls
253
+ how upstream terminal states gate the node (`all_success` by default, plus `accept_partial`,
254
+ `all_terminal`, and `optional`).
247
255
 
248
- - **Runtime fan-out** — give a node the `submitWorkflowNodesTool` and its agent can append nodes to the live DAG mid-run (true loop-until-done; one verifier per claim it discovers). Recorded and replayed on `resumeWorkflow`. Governance rejection fails the submitting node instead of acknowledging work that was never appended.
256
+ ### Workflow capabilities (v0.2.50)
257
+
258
+ - **Runtime fan-out** — register `submitWorkflowNodesTool` on the parent execution plane and a trusted node can append nodes to the live DAG mid-run (true loop-until-done; one verifier per discovered claim). The tool schema is exported from `@deepstrike/sdk/workflow`, not the package root. Submission events remain audit projections; checkpoint state owns recovery. Governance rejection fails the submitting node instead of acknowledging work that was never appended.
249
259
  - **Quarantine, no escape** — set `trust: "quarantined"` on a node that reads untrusted content; it's denied write-capable isolation in-kernel, and any nodes it submits are coerced to quarantined too (no privilege escalation).
250
260
  - **Structured output** — set `outputSchema` on a node; the runner instructs the agent, validates the result against the JSON-Schema subset, and re-runs once with the errors on mismatch. A node that never conforms fails (its dependents starve).
251
- - **Budget as signal** — with a `maxWorkflowNodes` / `maxConcurrentSubagents` quota installed, each spawned node's goal carries its remaining headroom so a coordinator can size its fan-out to fit.
261
+ - **Budget as signal** — set `resourceQuota.maxWorkflowNodes` and/or `resourceQuota.maxConcurrentSubagents`; each spawned node's goal carries its remaining headroom so a coordinator can size its fan-out to fit.
262
+
263
+ `submitWorkflowNodesTool` is a `ToolSchema`, while execution planes register `RegisteredTool`
264
+ instances. Adapt it once on the parent plane; the runner intercepts the call before the placeholder
265
+ handler executes:
266
+
267
+ ```typescript
268
+ import { tool } from "@deepstrike/sdk"
269
+ import { submitWorkflowNodesTool } from "@deepstrike/sdk/workflow"
270
+
271
+ plane.register(tool(
272
+ submitWorkflowNodesTool.name,
273
+ submitWorkflowNodesTool.description,
274
+ JSON.parse(submitWorkflowNodesTool.parameters),
275
+ async () => "", // intercepted by RuntimeRunner
276
+ ))
277
+ ```
278
+
279
+ Trusted workflow nodes inherit the parent plane, so the tool is visible to all of them. There is no
280
+ per-node tool allowlist on `WorkflowNodeSpec`; quarantined nodes use a filtered, deny-all plane.
252
281
 
253
282
  ---
254
283
 
@@ -337,7 +366,7 @@ Full reference: [docs/concepts/context-slots-compression.md](../docs/concepts/co
337
366
  import {
338
367
  DEFAULT_NATIVE_GOVERNANCE_POLICY,
339
368
  DEFAULT_NATIVE_SIGNAL_POLICY,
340
- } from "@deepstrike/sdk"
369
+ } from "@deepstrike/sdk/os"
341
370
 
342
371
  const runner = new RuntimeRunner({
343
372
  provider,
@@ -359,14 +388,15 @@ const runner = new RuntimeRunner({
359
388
  // Resource quotas (M2) — enforced at the kernel syscall trap. Opt-in; omit for unbounded.
360
389
  resourceQuota: {
361
390
  maxConcurrentSubagents: 4, // deny spawn while at cap
391
+ maxTotalSubagents: 20, // cumulative cap across the RunGroup
362
392
  maxSpawnDepth: 2, // deny spawn past nesting depth
393
+ maxWorkflowNodes: 32, // cap one live DAG, including submitted nodes
363
394
  memoryWritesPerWindow: { maxWrites: 20, windowMs: 60_000 }, // rate-limit writeMemory
364
395
  },
365
396
 
366
- // Long-term memory policy (set_memory_policy) — opt-in, kernel-enforced; omit for defaults.
397
+ // Canonical long-term memory policy — opt-in, kernel-enforced; omit for defaults.
367
398
  memoryPolicy: {
368
- memoryPath: "./.memory", // where the SDK persists/scans memories (SDK-consumed)
369
- staleWarningDays: 30, // flag recalled memories older than this (SDK-consumed)
399
+ staleWarningDays: 30, // flag recalled memories older than this
370
400
  retrievalTopK: 5, // kernel caps query_memory requested_k to this
371
401
  validationEnabled: true, // false → admit writes without validation
372
402
  maxContentBytes: 10_000, // override write_memory content-size limit
@@ -414,13 +444,14 @@ const runner = new RuntimeRunner({
414
444
 
415
445
  | Option | Purpose |
416
446
  |--------|---------|
417
- | `governancePolicy` | Declarative deny / ask_user / rate-limit / param rules loaded into the kernel before `start_run` |
447
+ | `governancePolicy` | Declarative deny / ask_user / rate-limit / param rules installed before canonical root start |
418
448
  | `signalPolicy` | Versioned in-kernel signal queue/TTL policy (default queue 64) |
419
449
  | `promptBudget` | Provider-envelope overhead, output reserve, and safety margin deducted from the context window |
420
- | `resourceQuota` | M2 declarative limits — `maxConcurrentSubagents` / `maxSpawnDepth` / `memoryWritesPerWindow` — enforced at the kernel syscall trap (`set_resource_quota`); over-quota spawns roll back, over-rate writes surface as `memory_validation_failed` |
421
- | `memoryPolicy` | Long-term memory config sent as `set_memory_policy` and **kernel-enforced**: `validationEnabled: false` admits writes without validation, `maxContentBytes` / `maxNameLength` override validation limits, `retrievalTopK` caps `query_memory` breadth; `memoryPath` / `staleWarningDays` are SDK-consumed (requires `dreamStore` + `agentId` to enable memory) |
450
+ | `resourceQuota` | M2 declarative limits — `maxConcurrentSubagents` / `maxTotalSubagents` / `maxSpawnDepth` / `maxWorkflowNodes` / `memoryWritesPerWindow` — enforced at the kernel syscall trap (`set_resource_quota`); over-quota spawns roll back, over-rate writes surface as `memory_validation_failed` |
451
+ | `memoryPolicy` | Canonical long-term memory policy: `validationEnabled: false` admits writes without validation, `maxContentBytes` / `maxNameLength` override validation limits, `retrievalTopK` caps `query_memory` breadth, and `staleWarningDays` controls stale recall policy. Storage belongs to the configured `dreamStore`. |
422
452
  | `onPermissionRequest` | Resolves `tool_gated` + `suspended` → kernel `resume` with approved/denied call IDs |
423
453
  | `compressionStore` | Writes archived messages on `compressed` observations |
454
+ | `payloadStore` | Resolves canonical opaque payload locators (default: `.payloads/`) |
424
455
  | `asyncSummarizer` | Background LLM summary after compression; stored as `summary_upgraded` |
425
456
  | `dreamSummarizer` | Summarizes `page_out { tier_hint: "semantic" }` into `DreamStore` during a run |
426
457
  | `dreamProvider` | Separate LLM for `dream()` idle consolidation (falls back to `provider`) |
@@ -428,37 +459,33 @@ const runner = new RuntimeRunner({
428
459
  Rebuild an OS diagnostics snapshot from session events:
429
460
 
430
461
  ```typescript
431
- import { rebuildOsSnapshotFromSessionEvents } from "@deepstrike/sdk"
462
+ import { rebuildOsSnapshotFromSessionEvents } from "@deepstrike/sdk/os"
432
463
 
433
464
  const events = (await sessionLog.read(sessionId)).map(e => e.event)
434
465
  const snap = rebuildOsSnapshotFromSessionEvents(events)
435
- // snap.pageOutCount, snap.spoolCount, snap.signals, snap.processByAgent, …
466
+ // snap.pageOutCount, snap.signals, snap.processByAgent, …
436
467
  ```
437
468
 
438
469
  ---
439
470
 
440
- ## Large result spool (Layer 1)
471
+ ## External tool payloads
441
472
 
442
- When a single tool result exceeds **50 KB**, the kernel keeps a short preview in context and emits `large_result_spooled`. The SDK writes the full payload to `.spool/` under the process cwd (SHA-256 keyed files) and logs `spool_ref` in the session.
473
+ When a tool result exceeds the configured inline threshold, the SDK persists the full body before sending the canonical `External` result. The kernel receives only `payload_ref`, `digest`, `original_size`, and a bounded preview.
443
474
 
444
- The model can retrieve full content via ordinary read tools `LocalExecutionPlane` transparently resolves paths under `.spool/`:
475
+ The locator is opaque and never passed to ordinary file tools. The model calls `read_result`; core authorizes the reachable handle and emits `LoadPayload`, which the runner resolves through `PayloadStore`.
445
476
 
446
- ```typescript
447
- // Kernel context shows a preview + spool reference.
448
- // LLM calls read_file({ path: ".spool/abc123…" }) → full content returned.
449
- ```
450
-
451
- No configuration is required; customize the directory by passing a `resultSpool` instance when constructing `RuntimeRunner` (see tests under `tests/runtime/large-result-spool.test.ts`).
477
+ No configuration is required. Pass a `PayloadStore` through `RuntimeOptions.payloadStore` to use a different filesystem root or storage adapter.
452
478
 
453
479
  ---
454
480
 
455
481
  ## Tools
456
482
 
457
483
  ```typescript
458
- import { tool, readFile } from "@deepstrike/sdk"
484
+ import { tool } from "@deepstrike/sdk"
485
+ import { readFile } from "@deepstrike/sdk/workflow"
459
486
 
460
487
  plane.register(tool("search", "Search.", schema, async (args) => ...))
461
- plane.register(readFile) // built-in: read files from disk (also resolves .spool/ refs)
488
+ plane.register(readFile) // built-in: read files explicitly named by the caller
462
489
  plane.unregister("search")
463
490
  ```
464
491
 
@@ -553,35 +580,31 @@ mem.clear()
553
580
  import type { DreamStore } from "@deepstrike/sdk/memory"
554
581
 
555
582
  class MyStore implements DreamStore {
556
- async loadSessions(agentId) { ... }
557
- async loadMemories(agentId) { ... }
558
- async commit(agentId, result, existing) { ... }
559
- async search(agentId, query) { ... } // Promise<MemoryRecall[]>
583
+ async upsert(agentId, record) { ... } // the only durable memory mutation
584
+ async search(agentId, query) { ... } // Promise<MemoryRecall[]>
585
+ async saveSession(session) { ... } // completed transcript for extraction
560
586
  }
561
587
 
588
+ const memoryScope = { tenant_id: "acme", namespace: "assistant" }
562
589
  const runner = new RuntimeRunner({
563
590
  provider,
564
591
  executionPlane: plane,
565
592
  sessionLog: new FileSessionLog(".deepstrike/sessions"),
566
593
  maxTokens: 4096,
567
594
  dreamStore: new MyStore(),
568
- agentId: "my-agent", // enables `memory` meta-tool + semantic page-out archival
595
+ agentId: "my-agent",
596
+ memoryScope, // scopes run-start recall, queries, extraction, and semantic page-out
569
597
  })
570
598
  ```
571
599
 
572
- Three memory paths:
600
+ Four memory paths:
573
601
 
574
602
  | Path | When | What happens |
575
603
  |------|------|--------------|
576
604
  | In-session `memory(query)` | LLM calls meta-tool | `DreamStore.search()` → history tool result |
577
605
  | `initialMemory` | Run start | Injected into Slot 2 (`systemKnowledge`) |
578
- | Semantic `page_out` | Kernel evicts with `tier_hint: "semantic"` | SDK summarizes via `dreamSummarizer` / `dreamProvider` → `DreamStore.commit()` |
579
- | `dream(agentId)` | Explicit idle call | `IdlePipeline` batch-consolidates past sessions |
580
-
581
- ```typescript
582
- // Post-session batch consolidation
583
- const result = await runner.dream("my-agent", Date.now())
584
- ```
606
+ | `writeMemory(record)` | Host writes a durable record | Kernel validation / quota / dedup → `DreamStore.upsert()` |
607
+ | Semantic `page_out` | Kernel evicts with `tier_hint: "semantic"` | SDK summarizes via `dreamSummarizer` / `dreamProvider` gated `writeMemory()` |
585
608
 
586
609
  ### Phase-7 memory syscalls (`writeMemory` / `queryMemory`)
587
610
 
@@ -682,7 +705,7 @@ Inbound signals are routed by the in-kernel attention policy (default queue size
682
705
  | queue full | `dropped` |
683
706
 
684
707
  ```typescript
685
- import { SignalGateway, ScheduledPrompt } from "@deepstrike/sdk"
708
+ import { SignalGateway, ScheduledPrompt } from "@deepstrike/sdk/os"
686
709
 
687
710
  const gw = new SignalGateway()
688
711
  gw.schedule(new ScheduledPrompt("standup", Date.now() + 3600_000))
@@ -22,7 +22,7 @@ export declare function composeSystemPrompt(base: string | undefined, instructio
22
22
  * The exact `RuntimeOptions` fields a manifest may drive. Derived via `Pick` so field names and types
23
23
  * track `RuntimeOptions` verbatim; anything outside this set is rejected by `applyManifest`/`applyPatch`.
24
24
  */
25
- export type HarnessRuntimePatch = Pick<RuntimeOptions, "maxTurns" | "maxTotalTokens" | "criteriaGate" | "repeatFuse" | "entropyWatch" | "knowledgeBudgetRatio" | "skillLeaseTurns" | "allowedToolIds" | "stableCoreToolIds" | "enablePlanTool" | "skillFilter"> & Pick<MemoryPolicy, "retrievalTopK" | "promotionRecallThreshold">;
25
+ export type HarnessRuntimePatch = Pick<RuntimeOptions, "maxTurns" | "maxTotalTokens" | "criteriaGate" | "repeatFuse" | "entropyWatch" | "knowledgeBudgetRatio" | "skillLeaseTurns" | "allowedToolIds" | "baselineToolIds" | "stableCoreToolIds" | "enablePlanTool" | "skillFilter"> & Pick<MemoryPolicy, "retrievalTopK" | "promotionRecallThreshold">;
26
26
  /**
27
27
  * The promotion tier of an editable surface — the SECOND axis of the safety boundary (the whitelist is
28
28
  * the first). Even a whitelisted surface may need a heavier gate than "typed validation passed".
@@ -11,12 +11,16 @@
11
11
  * absent, so a proposer can never rewrite them (spec design principle: conservative promotion).
12
12
  *
13
13
  * Tool/skill surfaces add the SECOND safety invariant (spec design principle A — the capability
14
- * ceiling): `allowedToolIds`, `stableCoreToolIds`, and `skillFilter` fold onto the host baseline by
15
- * INTERSECTION, never assignment. A manifest can only NARROW the tools/skills the host already
16
- * exposes — never widen. Capability expansion (naming a tool the host does not expose) is therefore
17
- * structurally inexpressible, and the whole security audit stays O(1): read the whitelist, check the
18
- * one invariant. (`enablePlanTool` is exempt — it toggles a kernel-owned meta-tool, attention-shaping
19
- * not capability-granting, so it folds by plain assignment.)
14
+ * ceiling): `allowedToolIds`, `baselineToolIds`, `stableCoreToolIds`, and `skillFilter` fold onto the
15
+ * host baseline by INTERSECTION, never assignment. A manifest can only NARROW the tools/skills the
16
+ * host already exposes — never widen. Capability expansion (naming a tool the host does not expose) is
17
+ * therefore structurally inexpressible, and the whole security audit stays O(1): read the whitelist,
18
+ * check the one invariant. (`enablePlanTool` is exempt — it toggles a kernel-owned meta-tool,
19
+ * attention-shaping not capability-granting, so it folds by plain assignment.)
20
+ *
21
+ * `toolDispatchGate` is deliberately ABSENT from the whitelist and must stay so: it selects whether
22
+ * the kernel enforces the exposure surface at dispatch. A proposer that could set it to `"registered"`
23
+ * would disable the enforcement half of the ceiling it is otherwise structurally unable to widen.
20
24
  */
21
25
  import { createHash } from "node:crypto";
22
26
  import { validateNudgeRules } from "./nudge.js";
@@ -44,7 +48,12 @@ export function composeSystemPrompt(base, instructions) {
44
48
  }
45
49
  const MEMORY_POLICY_PATCH_KEYS = ["retrievalTopK", "promotionRecallThreshold"];
46
50
  /** Tool/skill surfaces whose fold is intersection-with-baseline (capability ceiling), not assignment. */
47
- const INTERSECTION_PATCH_KEYS = ["allowedToolIds", "stableCoreToolIds", "skillFilter"];
51
+ const INTERSECTION_PATCH_KEYS = [
52
+ "allowedToolIds",
53
+ "baselineToolIds",
54
+ "stableCoreToolIds",
55
+ "skillFilter",
56
+ ];
48
57
  const RUNTIME_PATCH_KEYS = [
49
58
  "maxTurns",
50
59
  "maxTotalTokens",
@@ -54,12 +63,13 @@ const RUNTIME_PATCH_KEYS = [
54
63
  "knowledgeBudgetRatio",
55
64
  "skillLeaseTurns",
56
65
  "allowedToolIds",
66
+ "baselineToolIds",
57
67
  "stableCoreToolIds",
58
68
  "enablePlanTool",
59
69
  "skillFilter",
60
70
  ...MEMORY_POLICY_PATCH_KEYS,
61
71
  ];
62
- /** Bounds for the id-list surfaces (allowedToolIds / stableCoreToolIds / skillFilter). */
72
+ /** Bounds for the id-list surfaces (allowedToolIds / baselineToolIds / stableCoreToolIds / skillFilter). */
63
73
  const MAX_TOOL_ID_CHARS = 128;
64
74
  const MAX_TOOL_LIST_ENTRIES = 128;
65
75
  /**
@@ -165,23 +175,19 @@ function validateRuntimePatch(runtime) {
165
175
  }
166
176
  }
167
177
  }
168
- /**
169
- * Validate an id-list surface: array of unique, non-empty strings (each ≤128 chars), ≤128 entries.
170
- * `allowEmpty` is the load-bearing asymmetry. For the tool-id arrays it is FALSE: the runner reads an
171
- * empty/absent `allowedToolIds` as "no gating — expose ALL registered tools", so an empty array would
172
- * WIDEN exposure to everything if it reached the runner (and a zero-tool run is the v0.2.46 pathology).
173
- * For `skillFilter` it is TRUE: the runner's no-gating sentinel is ONLY `undefined`, and an empty array
174
- * legitimately means "no skills available" (a proposer may find skills are a distraction) — a narrowing.
175
- */
176
- function validateIdList(key, value, allowEmpty) {
178
+ /** Validate an id-list surface: array of unique, non-empty strings (each ≤128 chars), ≤128 entries. */
179
+ function validateIdList(key, value, emptyPolicy) {
177
180
  if (!Array.isArray(value))
178
181
  throw new TypeError(`runtime.${key} must be a string[]`);
179
182
  if (value.length > MAX_TOOL_LIST_ENTRIES) {
180
183
  throw new RangeError(`runtime.${key} exceeds ${MAX_TOOL_LIST_ENTRIES} entries`);
181
184
  }
182
- if (!allowEmpty && value.length === 0) {
185
+ if (emptyPolicy === "reject:widens" && value.length === 0) {
183
186
  throw new RangeError(`runtime.${key} must be a non-empty list — an empty array is read by the runner as "no gating" (expose all registered tools), which WIDENS exposure`);
184
187
  }
188
+ if (emptyPolicy === "reject:drastic" && value.length === 0) {
189
+ throw new RangeError(`runtime.${key} must be a non-empty list — an empty baseline collapses the pre-activation surface to meta-tools only, which stays a human/host decision`);
190
+ }
185
191
  const seen = new Set();
186
192
  for (const entry of value) {
187
193
  if (typeof entry !== "string" || entry.length === 0) {
@@ -219,10 +225,13 @@ function validateRuntimeValue(key, value) {
219
225
  return;
220
226
  case "allowedToolIds":
221
227
  case "stableCoreToolIds":
222
- validateIdList(key, value, /* allowEmpty */ false);
228
+ validateIdList(key, value, "reject:widens");
229
+ return;
230
+ case "baselineToolIds":
231
+ validateIdList(key, value, "reject:drastic");
223
232
  return;
224
233
  case "skillFilter":
225
- validateIdList(key, value, /* allowEmpty */ true);
234
+ validateIdList(key, value, "allow");
226
235
  return;
227
236
  case "knowledgeBudgetRatio":
228
237
  if (typeof value !== "number" || !(value > 0 && value <= 1)) {
@@ -335,7 +344,9 @@ export function applyManifest(manifest, base) {
335
344
  }
336
345
  /**
337
346
  * Fold one intersection surface (capability ceiling): effective = manifest ∩ host-baseline, so a
338
- * manifest can only NARROW. The empty-baseline meaning is surface-specific and load-bearing:
347
+ * manifest can only NARROW. The empty-baseline meaning is surface-specific and load-bearing — it
348
+ * tracks whatever the RUNNER's own no-gating sentinel is for that option, not whether the option
349
+ * happens to hold tool ids:
339
350
  *
340
351
  * - allowedToolIds / stableCoreToolIds — the runner reads an empty OR absent baseline as
341
352
  * "no gating = all registered tools" (the universe), so a non-array/empty baseline yields the
@@ -343,19 +354,22 @@ export function applyManifest(manifest, base) {
343
354
  * empty intersection THROWS: a zero-tool run reprises the v0.2.46 pathology AND the runner would
344
355
  * silently reinterpret the empty result as "no gating" (full exposure) — so we turn the candidate
345
356
  * into a discardable error instead.
346
- * - skillFilter — the runner's no-gating sentinel is ONLY `undefined`; an empty-array baseline is a
347
- * genuine, maximally-tight ceiling (no skills). So ANY present array (even `[]`) is intersected,
348
- * and an empty result is FINE (= no skills). This mirrors the validation asymmetry exactly.
357
+ * - skillFilter / baselineToolIds — the runner's no-gating sentinel is ONLY `undefined`; an
358
+ * empty-array baseline is a genuine, maximally-tight ceiling (no skills / the minimal meta-only
359
+ * tool surface). So ANY present array (even `[]`) is intersected, and an empty result is FINE —
360
+ * it reaches the runner as exactly that maximally-tight value, never as "no gating". This mirrors
361
+ * the validation asymmetry exactly. `baselineToolIds` sits on THIS side despite being a tool-id
362
+ * list: `[]` is its documented minimal surface (kernel `Some([])`), distinct from absent.
349
363
  */
350
364
  function foldIntersection(key, manifestList, baseList) {
351
- const skillLike = key === "skillFilter";
352
- // Is the host baseline a real constraining set? Tool ids: non-empty array only (empty == universe).
353
- // skillFilter: any array (empty == the empty set).
354
- const constrained = Array.isArray(baseList) && (skillLike || baseList.length > 0);
365
+ // Is an EMPTY host baseline a maximally-tight ceiling (intersect it, empty result fine) or the
366
+ // universe (ignore it, empty result is a bug)?
367
+ const emptyBaselineIsTight = key === "skillFilter" || key === "baselineToolIds";
368
+ const constrained = Array.isArray(baseList) && (emptyBaselineIsTight || baseList.length > 0);
355
369
  const effective = constrained
356
370
  ? manifestList.filter(id => baseList.includes(id)) // manifest order → deterministic
357
371
  : manifestList;
358
- if (!skillLike && effective.length === 0) {
372
+ if (!emptyBaselineIsTight && effective.length === 0) {
359
373
  throw new RangeError(`applyManifest: runtime.${key} intersection is empty — manifest [${manifestList.join(", ")}] ∩ ` +
360
374
  `host [${(baseList ?? []).join(", ")}] names no shared tool. A zero-tool run is rejected (it ` +
361
375
  `reprises the v0.2.46 pathology and the runner would read empty as "no gating" = full exposure).`);
package/dist/index.d.ts CHANGED
@@ -4,20 +4,18 @@ export type { LoopSpec, LoopOutcome } from "./runtime/loop-driver.js";
4
4
  export type { RunAgentOptions, RunFanoutOptions } from "./runtime/facade.js";
5
5
  export { RuntimeRunner, collectText } from "./runtime/runner.js";
6
6
  export type { RuntimeOptions, KernelReliabilityOptions, OperationCancellationReason, PromptBudget, SchedulerPolicy } from "./runtime/runner.js";
7
+ export { PayloadStore } from "./runtime/payload-store.js";
8
+ export type { PayloadStoreConfig } from "./runtime/payload-store.js";
7
9
  export type { InstructionProfile, NudgeRule, NudgeTrigger } from "./harness/public.js";
8
10
  export type { SignalPolicy } from "./runtime/os-profile.js";
9
- export { readKernelDiagnostics, restoreKernelRuntime, snapshotKernelRuntime } from "./runtime/kernel-step.js";
10
- export type { KernelDiagnostics, KernelSnapshot } from "./runtime/kernel-step.js";
11
- export { rebuildKernelRuntime } from "./runtime/kernel-rebuild.js";
12
- export type { KernelRebuildResult } from "./runtime/kernel-rebuild.js";
13
11
  export { CONTEXT_POLICY_VERSION, DEFAULT_CONTEXT_POLICY_V1, PPM_SCALE, contextPolicyV1, normalizeContextPolicyV1, ratioToPpm, } from "./runtime/context-policy.js";
14
12
  export type { ContextPolicyOverridesV1, ContextPolicyV1, ContextPolicyWireV1, ContextPressureThresholdsV1, } from "./runtime/context-policy.js";
15
13
  export { LocalExecutionPlane } from "./runtime/execution-plane.js";
16
14
  export type { ExecutionPlane, RunContext } from "./runtime/execution-plane.js";
17
15
  export { InMemorySessionLog, FileSessionLog } from "./runtime/session-log.js";
18
- export type { KernelTransactionEntry, SessionLog, SessionEvent } from "./runtime/session-log.js";
19
- export { KERNEL_LOG_RECORD_VERSION, KernelLogConflictError, KernelLogIntegrityError, canonicalKernelJson, createKernelOperationGenesis, createKernelTransaction, kernelRecordDigest, verifyKernelOperationGenesis, verifyKernelTransaction, verifyKernelTransactionStream, verifyKernelTransactionSuccessor, } from "./runtime/kernel-transaction-log.js";
20
- export type { DurableAppendReceipt, KernelGenesisReceipt, KernelOperationCursor, KernelOperationGenesis, KernelOperationGenesisBody, KernelTransaction, KernelTransactionBody, } from "./runtime/kernel-transaction-log.js";
16
+ export type { SessionLog, SessionEvent } from "./runtime/session-log.js";
17
+ export { FileKernelJournal, InMemoryKernelJournal, JournalCasConflictError, JournalIntegrityError, JournalIoError, } from "./runtime/kernel-journal.js";
18
+ export type { CheckpointCandidate, InstalledCheckpoint, JournalAppendReceipt, JournalEntry, JournalHead, JournalPruneReceipt, JournalRecordInput, KernelJournal, } from "./runtime/kernel-journal.js";
21
19
  export { InMemoryGroupBudgetStore, GroupBudgetScope } from "./runtime/run-group.js";
22
20
  export type { RunGroup, GroupBudgetStore, GroupLedger, GroupCharge, GroupMember, GroupBudgetRequest, GroupBudgetReservation, } from "./runtime/run-group.js";
23
21
  export { InMemoryEventStream, isVisibleTo } from "./runtime/event-stream.js";
package/dist/index.js CHANGED
@@ -15,13 +15,13 @@ export { runAgent, runFanout } from "./runtime/facade.js";
15
15
  // ③ dynamic loop agents: self-pacing rounds over the kernel pacing trap.
16
16
  export { runLoop, LoopDriver, foldLoopState } from "./runtime/loop-driver.js";
17
17
  export { RuntimeRunner, collectText } from "./runtime/runner.js";
18
- export { readKernelDiagnostics, restoreKernelRuntime, snapshotKernelRuntime } from "./runtime/kernel-step.js";
19
- export { rebuildKernelRuntime } from "./runtime/kernel-rebuild.js";
18
+ export { PayloadStore } from "./runtime/payload-store.js";
20
19
  export { CONTEXT_POLICY_VERSION, DEFAULT_CONTEXT_POLICY_V1, PPM_SCALE, contextPolicyV1, normalizeContextPolicyV1, ratioToPpm, } from "./runtime/context-policy.js";
21
20
  // ── Execution plane + session log (the defaults) ────────────────────────────
22
21
  export { LocalExecutionPlane } from "./runtime/execution-plane.js";
23
22
  export { InMemorySessionLog, FileSessionLog } from "./runtime/session-log.js";
24
- export { KERNEL_LOG_RECORD_VERSION, KernelLogConflictError, KernelLogIntegrityError, canonicalKernelJson, createKernelOperationGenesis, createKernelTransaction, kernelRecordDigest, verifyKernelOperationGenesis, verifyKernelTransaction, verifyKernelTransactionStream, verifyKernelTransactionSuccessor, } from "./runtime/kernel-transaction-log.js";
23
+ // ── Durable transaction capability (Canonical Kernel ABI §9.1) ──────────────
24
+ export { FileKernelJournal, InMemoryKernelJournal, JournalCasConflictError, JournalIntegrityError, JournalIoError, } from "./runtime/kernel-journal.js";
25
25
  export { InMemoryGroupBudgetStore, GroupBudgetScope } from "./runtime/run-group.js";
26
26
  export { InMemoryEventStream, isVisibleTo } from "./runtime/event-stream.js";
27
27
  export { ManagedTaskScope, operationAbortSignal } from "./runtime/reliability.js";
package/dist/kernel.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Message, RenderedContext } from "./types.js";
1
+ import type { Message } from "./types.js";
2
2
  export interface GovernanceVerdict {
3
3
  kind: "allow" | "deny" | "rate_limited" | "ask_user";
4
4
  reason?: string;
@@ -33,16 +33,12 @@ export interface ResourceQuota {
33
33
  /**
34
34
  * Long-term memory policy — declarative knobs for the kernel's memory subsystem.
35
35
  *
36
- * Installed through the versioned JSON event ABI (`set_memory_policy`), the same channel as
37
- * governance / scheduler / quota config, so memory configuration is replayable and
38
- * session-loggable rather than a side-channel setter. Installing the policy is opt-in and
39
- * kernel-enforced; omitted fields fall back to the kernel defaults (empty path, 2-day stale
40
- * warning, top-5 retrieval, validation on). Enabling memory is still `dreamStore` + `agentId`.
36
+ * Included in canonical operation configuration, so memory policy is replayable and
37
+ * kernel-enforced. Omitted fields retain the canonical defaults. Host storage belongs to the
38
+ * configured `DreamStore`, never to this contract.
41
39
  */
42
40
  export interface MemoryPolicy {
43
- /** Filesystem root the SDK uses to persist/scan memories; carried for SDK recall I/O. */
44
- memoryPath?: string;
45
- /** Age after which a recalled memory is flagged stale (days); consumed SDK-side. */
41
+ /** Age after which a recalled memory is flagged stale (days). */
46
42
  staleWarningDays?: number;
47
43
  /** Upper bound on retrieval breadth: the kernel clamps `query_memory` top-k to this. */
48
44
  retrievalTopK?: number;
@@ -108,31 +104,65 @@ export interface Verdict {
108
104
  content: string;
109
105
  };
110
106
  }
111
- export interface KernelRuntimeInstance {
112
- step(inputJson: string): string;
113
- prepareStep(inputJson: string): string;
114
- commitPrepared(prepareToken: string): string;
115
- abortPrepared(prepareToken: string): void;
116
- snapshot(): string;
117
- restore(snapshotJson: string): void;
118
- diagnostics(): string;
119
- isTerminal(): boolean;
120
- turn(): number;
121
- /** L1 (RunGroup): cumulative sub-agent spawns this run, for charging the group ledger at run end. */
122
- localSubagentsSpawned(): number;
123
- recoveryContentBytes(): number;
124
- render(): RenderedContext;
125
- drainNewMessages(): Message[];
126
- preservedRefs(): string[];
107
+ export interface CanonicalPrepared {
108
+ status: "prepared";
109
+ prepareToken: string;
110
+ stepSeq: string;
111
+ expectedHead?: string;
112
+ recordDigest: string;
113
+ recordBytes: Buffer;
114
+ plannedStepJson: string;
115
+ }
116
+ export interface CanonicalReplayed {
117
+ status: "replayed";
118
+ stepSeq: string;
119
+ expectedHead?: string;
120
+ recordDigest: string;
121
+ recordBytes?: Buffer;
122
+ plannedStepJson?: string;
123
+ }
124
+ export interface CanonicalRejected {
125
+ status: "rejected";
126
+ faultJson: string;
127
+ }
128
+ /** Closed §7.13 result; only `prepared` carries a token that may be committed or aborted. */
129
+ export type CanonicalPreparation = CanonicalPrepared | CanonicalReplayed | CanonicalRejected;
130
+ export interface CanonicalCommit {
131
+ stepSeq: string;
132
+ recordDigest: string;
133
+ plannedStepJson: string;
134
+ checkpointAdviceJson?: string;
135
+ }
136
+ export interface CanonicalCheckpoint {
137
+ checkpointBytes: Buffer;
138
+ throughStepSeq: string;
139
+ coveredHead: string;
140
+ stateDigest: string;
141
+ ackToken: string;
142
+ }
143
+ export interface CanonicalRestoreCost {
144
+ recordsBeforeCheckpoint: string;
145
+ tailInputsReplayed: string;
146
+ recordsAfterCheckpoint: string;
147
+ bytesRead: string;
148
+ }
149
+ export interface CanonicalKernelInstance {
150
+ prepare(inputJson: string): CanonicalPreparation;
151
+ commit(prepareToken: string, appendedHead: string): CanonicalCommit;
152
+ abort(prepareToken: string): void;
153
+ checkpointCandidate(): CanonicalCheckpoint;
154
+ checkpointRebase(checkpointBytes: Buffer): CanonicalCheckpoint;
155
+ ackCheckpoint(throughStepSeq: string, coveredHead: string): void;
156
+ /** Replaces native state in place; the JavaScript handle retains its identity. */
157
+ restore(checkpointBytes: Buffer | undefined, recordBytes: Buffer[]): CanonicalRestoreCost;
158
+ lifecycle(): "created" | "configured" | "running" | "suspended" | "completed" | "cancelled" | "failed";
159
+ pendingEffectsJson(): string;
160
+ terminalJson(): string | undefined;
127
161
  }
128
162
  interface KernelModule {
129
163
  Governance: new (defaultAction?: "allow" | "deny" | "ask_user") => GovernanceInstance;
130
- KernelRuntime: new (policy: {
131
- maxTokens: number;
132
- maxTurns?: number;
133
- maxTotalTokens?: bigint;
134
- timeoutMs?: bigint;
135
- }) => KernelRuntimeInstance;
164
+ CanonicalKernel: new () => CanonicalKernelInstance;
165
+ kernelAbiVersion(): number;
136
166
  SignalRouter: new (maxQueueSize: number) => SignalRouterInstance;
137
167
  buildEvalMessages(goal: string, criteria: NativeCriterion[], result: string, attempt: number, extractSkillOnPass: boolean): Message[];
138
168
  parseVerdict(content: string): Verdict;