@deepstrike/sdk 0.2.41 → 0.2.42
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.
- package/README.md +2 -2
- package/dist/governance.d.ts +4 -5
- package/dist/kernel.d.ts +5 -0
- package/dist/runtime/kernel-step.d.ts +3 -0
- package/dist/runtime/runner.d.ts +28 -1
- package/dist/runtime/runner.js +182 -75
- package/dist/types/agent.d.ts +8 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -245,7 +245,7 @@ A node's `kind` selects the control-flow shape; the same executor drives them al
|
|
|
245
245
|
|
|
246
246
|
### 0.2.11 capabilities
|
|
247
247
|
|
|
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`.
|
|
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.
|
|
249
249
|
- **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
250
|
- **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
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.
|
|
@@ -719,7 +719,7 @@ for await (const evt of runner.spawnSubAgent({
|
|
|
719
719
|
}
|
|
720
720
|
```
|
|
721
721
|
|
|
722
|
-
Requires an active parent run (`run()` / `wake()` in progress). The kernel emits `agent_process_changed`; the default `SubAgentOrchestrator` runs the child with a filtered execution plane and feeds `sub_agent_completed` back.
|
|
722
|
+
Requires an active parent run (`run()` / `wake()` in progress). The kernel emits `agent_process_changed`; the default `SubAgentOrchestrator` runs the child with a filtered execution plane and feeds `sub_agent_completed` back. If governance rejects the spawn before execution, the iterator yields one `error` event containing the denial reason; the parent turn is not rolled back.
|
|
723
723
|
|
|
724
724
|
---
|
|
725
725
|
|
package/dist/governance.d.ts
CHANGED
|
@@ -30,11 +30,10 @@ export interface GovernancePolicy {
|
|
|
30
30
|
}[];
|
|
31
31
|
constraints?: GovernanceConstraint[];
|
|
32
32
|
/** I5: when true (default), the runner pre-filters denied tools out of the schema passed to the
|
|
33
|
-
* provider — the model never sees them and never tries to call them
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* agent should learn the denial via a real attempt). */
|
|
33
|
+
* provider — the model never sees them and never tries to call them. The denied tool names are
|
|
34
|
+
* also surfaced as a single line on the system slot so the model knows not to plan around them.
|
|
35
|
+
* Set to false when the agent should learn the denial through a real attempted call and its
|
|
36
|
+
* visible error tool result. */
|
|
38
37
|
surfaceDeniedInSystem?: boolean;
|
|
39
38
|
}
|
|
40
39
|
/** I5: walk the tool list and bucket each tool into `allowed` / `denied` based on a declarative
|
package/dist/kernel.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface ResourceQuota {
|
|
|
25
25
|
maxTotalSubagents?: number;
|
|
26
26
|
/** Max sub-agent nesting depth (direct children of the root loop are depth 1). */
|
|
27
27
|
maxSpawnDepth?: number;
|
|
28
|
+
/** Max nodes in one in-kernel workflow DAG, including dynamically submitted nodes. */
|
|
29
|
+
maxWorkflowNodes?: number;
|
|
28
30
|
/** Rolling-window memory-write rate limit: at most `maxWrites` per any `windowMs` span. */
|
|
29
31
|
memoryWritesPerWindow?: MemoryWriteRateLimit;
|
|
30
32
|
}
|
|
@@ -50,6 +52,9 @@ export interface MemoryPolicy {
|
|
|
50
52
|
maxContentBytes?: number;
|
|
51
53
|
/** Override the kernel's `write_memory` name-length limit. */
|
|
52
54
|
maxNameLength?: number;
|
|
55
|
+
/** M4: recall count at which the kernel emits an advisory (edge-triggered)
|
|
56
|
+
* `promotion_suggested` for a recalled record. Omitted = suggestions disabled. */
|
|
57
|
+
promotionRecallThreshold?: number;
|
|
53
58
|
}
|
|
54
59
|
export interface GovernanceInstance {
|
|
55
60
|
setIdentity(agentId: string, sessionId: string): void;
|
|
@@ -157,6 +157,9 @@ export type KernelRunnerAction = {
|
|
|
157
157
|
};
|
|
158
158
|
export interface KernelObservation {
|
|
159
159
|
kind: string;
|
|
160
|
+
/** control_request_rejected: stable control-plane operation name and optional subject id. */
|
|
161
|
+
operation?: string;
|
|
162
|
+
subject?: string;
|
|
160
163
|
action?: string;
|
|
161
164
|
rho_after?: number;
|
|
162
165
|
sprint?: number;
|
package/dist/runtime/runner.d.ts
CHANGED
|
@@ -374,6 +374,23 @@ export declare class RuntimeRunner {
|
|
|
374
374
|
private commitKernelAction;
|
|
375
375
|
private persistMemoryToStore;
|
|
376
376
|
private retrieveMemoryFromStore;
|
|
377
|
+
/**
|
|
378
|
+
* T5: run one memory query through the kernel's `query_memory → memory_query_result`
|
|
379
|
+
* effect lifecycle on the given runtime. The kernel injects each routed hit into history
|
|
380
|
+
* itself and derives the recall lifecycle (`memory_recalled`, edge-triggered
|
|
381
|
+
* `promotion_suggested`) from the routed hits — the store stays a pure query.
|
|
382
|
+
*
|
|
383
|
+
* `seenRecordIds` is one dedupe horizon: a record hit by several queries of the same
|
|
384
|
+
* prefetch is routed (recalled, injected) once. The kernel derives counts statelessly from
|
|
385
|
+
* each hit's payload, so host-side pre-filtering is the only place duplicates can be stopped.
|
|
386
|
+
*
|
|
387
|
+
* The recall lifecycle is consumed immediately rather than via the per-turn drain: the store
|
|
388
|
+
* must be up to date before any same-turn re-query, and a renewal prefetch fires inside the
|
|
389
|
+
* drain loop where queued observations would not be consumed until the next boundary.
|
|
390
|
+
* Non-memory observations are forwarded to `leftovers` (the run's pending queue) when given,
|
|
391
|
+
* and discarded for detached syscall runtimes (their kernel is throwaway).
|
|
392
|
+
*/
|
|
393
|
+
private queryMemoryThroughKernel;
|
|
377
394
|
writeMemory(memory: MemoryRecord, opts?: {
|
|
378
395
|
sessionId?: string;
|
|
379
396
|
agentId?: string;
|
|
@@ -394,7 +411,17 @@ export declare class RuntimeRunner {
|
|
|
394
411
|
* every policy from the first spawn. No config ⇒ the native-profile defaults (铁律: defaults only).
|
|
395
412
|
*/
|
|
396
413
|
private applyKernelPolicies;
|
|
397
|
-
|
|
414
|
+
/**
|
|
415
|
+
* Mirror one kernel memory-lifecycle observation into the durable store / host callbacks.
|
|
416
|
+
* Shared by the main run drain, the prefetch path, and the host memory syscalls so every
|
|
417
|
+
* query route has identical recall + promotion semantics (T5).
|
|
418
|
+
*
|
|
419
|
+
* M3: `memory_recalled` carries the kernel-derived count — the runner never computes
|
|
420
|
+
* `recall_count + 1` itself. M4: `promotion_suggested` is advisory and already
|
|
421
|
+
* edge-triggered by the kernel; the runner surfaces it and never auto-pins.
|
|
422
|
+
*/
|
|
423
|
+
private mirrorMemoryLifecycle;
|
|
424
|
+
private consumeMemoryLifecycleObservations;
|
|
398
425
|
/** Mount a tool capability on the currently-running kernel runtime. No-op if not running. */
|
|
399
426
|
mountTool(schema: ToolSchema): Promise<void>;
|
|
400
427
|
/** Mount a skill capability on the currently-running kernel runtime. No-op if not running. */
|
package/dist/runtime/runner.js
CHANGED
|
@@ -34,6 +34,25 @@ export function schedulerPolicyToKernel(policy) {
|
|
|
34
34
|
token_cost_weight: policy.tokenCostWeight,
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
/** Kernel observation kinds owned by the memory lifecycle consumer (journal + store mirror). */
|
|
38
|
+
function isMemoryLifecycleObservation(obs) {
|
|
39
|
+
return obs.kind === "memory_written"
|
|
40
|
+
|| obs.kind === "memory_queried"
|
|
41
|
+
|| obs.kind === "memory_validation_failed"
|
|
42
|
+
|| obs.kind === "memory_recalled"
|
|
43
|
+
|| obs.kind === "promotion_suggested";
|
|
44
|
+
}
|
|
45
|
+
function controlRequestRejection(observations, operation) {
|
|
46
|
+
const rejected = observations.find(observation => observation.kind === "control_request_rejected"
|
|
47
|
+
&& (!operation || observation.operation === operation));
|
|
48
|
+
if (!rejected)
|
|
49
|
+
return undefined;
|
|
50
|
+
return {
|
|
51
|
+
operation: rejected.operation ?? operation ?? "control_request",
|
|
52
|
+
...(rejected.subject ? { subject: rejected.subject } : {}),
|
|
53
|
+
reason: typeof rejected.reason === "string" ? rejected.reason : "request denied",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
37
56
|
function pendingCallIds(action) {
|
|
38
57
|
switch (action.kind) {
|
|
39
58
|
case "call_provider":
|
|
@@ -126,6 +145,66 @@ export class RuntimeRunner {
|
|
|
126
145
|
return (await this.opts.dreamStore.search(agentId, { ...query, top_k: requestedK }))
|
|
127
146
|
.slice(0, requestedK);
|
|
128
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* T5: run one memory query through the kernel's `query_memory → memory_query_result`
|
|
150
|
+
* effect lifecycle on the given runtime. The kernel injects each routed hit into history
|
|
151
|
+
* itself and derives the recall lifecycle (`memory_recalled`, edge-triggered
|
|
152
|
+
* `promotion_suggested`) from the routed hits — the store stays a pure query.
|
|
153
|
+
*
|
|
154
|
+
* `seenRecordIds` is one dedupe horizon: a record hit by several queries of the same
|
|
155
|
+
* prefetch is routed (recalled, injected) once. The kernel derives counts statelessly from
|
|
156
|
+
* each hit's payload, so host-side pre-filtering is the only place duplicates can be stopped.
|
|
157
|
+
*
|
|
158
|
+
* The recall lifecycle is consumed immediately rather than via the per-turn drain: the store
|
|
159
|
+
* must be up to date before any same-turn re-query, and a renewal prefetch fires inside the
|
|
160
|
+
* drain loop where queued observations would not be consumed until the next boundary.
|
|
161
|
+
* Non-memory observations are forwarded to `leftovers` (the run's pending queue) when given,
|
|
162
|
+
* and discarded for detached syscall runtimes (their kernel is throwaway).
|
|
163
|
+
*/
|
|
164
|
+
async queryMemoryThroughKernel(runtime, query, agentId, sessionId, seenRecordIds, leftovers) {
|
|
165
|
+
const observations = [];
|
|
166
|
+
const action = await this.commitKernelAction(runtime, observations, { kind: "query_memory", query }, sessionId);
|
|
167
|
+
if (action.kind !== "query_memory") {
|
|
168
|
+
throw new Error(`query_memory returned unexpected kernel effect: ${action.kind}`);
|
|
169
|
+
}
|
|
170
|
+
let hits = [];
|
|
171
|
+
let ioError;
|
|
172
|
+
try {
|
|
173
|
+
hits = await this.retrieveMemoryFromStore(query, action.requestedK, agentId);
|
|
174
|
+
if (seenRecordIds) {
|
|
175
|
+
hits = hits.filter(hit => {
|
|
176
|
+
const id = hit.record.record_id;
|
|
177
|
+
if (seenRecordIds.has(id))
|
|
178
|
+
return false;
|
|
179
|
+
seenRecordIds.add(id);
|
|
180
|
+
return true;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (cause) {
|
|
185
|
+
ioError = cause;
|
|
186
|
+
}
|
|
187
|
+
// Close the kernel effect even when the store failed — never leave a dangling query.
|
|
188
|
+
// The result commit resumes the kernel's reasoning path (`resume_after_preload`), which
|
|
189
|
+
// re-emits the next loop action with the routed hits now in history — callers on a live
|
|
190
|
+
// run kernel must adopt it (a query is a preload, not a detached side-channel).
|
|
191
|
+
const resumed = await this.commitKernelMaybeAction(runtime, observations, {
|
|
192
|
+
kind: "memory_query_result",
|
|
193
|
+
effect_id: action.effectId,
|
|
194
|
+
hits,
|
|
195
|
+
...(ioError ? { error: formatToolError(ioError) } : {}),
|
|
196
|
+
}, sessionId);
|
|
197
|
+
await this.consumeMemoryLifecycleObservations(sessionId, observations);
|
|
198
|
+
if (leftovers) {
|
|
199
|
+
for (const obs of observations) {
|
|
200
|
+
if (!isMemoryLifecycleObservation(obs))
|
|
201
|
+
leftovers.push(obs);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (ioError)
|
|
205
|
+
throw ioError;
|
|
206
|
+
return { hits, action: resumed };
|
|
207
|
+
}
|
|
129
208
|
async writeMemory(memory, opts = {}) {
|
|
130
209
|
const sessionId = opts.sessionId ?? this.currentSessionId;
|
|
131
210
|
const agentId = opts.agentId ?? this.opts.agentId;
|
|
@@ -136,7 +215,7 @@ export class RuntimeRunner {
|
|
|
136
215
|
const runtime = this.createSyscallRuntime();
|
|
137
216
|
const action = await this.commitKernelMaybeAction(runtime, observations, { kind: "write_memory", memory }, durableSessionId);
|
|
138
217
|
if (!action) {
|
|
139
|
-
await this.
|
|
218
|
+
await this.consumeMemoryLifecycleObservations(sessionId, observations);
|
|
140
219
|
return;
|
|
141
220
|
}
|
|
142
221
|
if (action.kind !== "persist_memory") {
|
|
@@ -154,7 +233,7 @@ export class RuntimeRunner {
|
|
|
154
233
|
effect_id: action.effectId,
|
|
155
234
|
...(ioError ? { error: formatToolError(ioError) } : {}),
|
|
156
235
|
}, durableSessionId);
|
|
157
|
-
await this.
|
|
236
|
+
await this.consumeMemoryLifecycleObservations(durableSessionId, observations);
|
|
158
237
|
if (ioError)
|
|
159
238
|
throw ioError;
|
|
160
239
|
}
|
|
@@ -164,29 +243,11 @@ export class RuntimeRunner {
|
|
|
164
243
|
if (!this.opts.dreamStore || !agentId)
|
|
165
244
|
return [];
|
|
166
245
|
const durableSessionId = this.durableSessionId(sessionId);
|
|
167
|
-
|
|
246
|
+
// Detached syscall runtime: the kernel's history injection and resumed action are inert
|
|
247
|
+
// (the kernel is throwaway), but the recall lifecycle is identical to an in-run query
|
|
248
|
+
// (T5 parity).
|
|
168
249
|
const runtime = this.createSyscallRuntime();
|
|
169
|
-
const
|
|
170
|
-
if (action.kind !== "query_memory") {
|
|
171
|
-
throw new Error(`query_memory returned unexpected kernel effect: ${action.kind}`);
|
|
172
|
-
}
|
|
173
|
-
let hits = [];
|
|
174
|
-
let ioError;
|
|
175
|
-
try {
|
|
176
|
-
hits = await this.retrieveMemoryFromStore(query, action.requestedK, agentId);
|
|
177
|
-
}
|
|
178
|
-
catch (cause) {
|
|
179
|
-
ioError = cause;
|
|
180
|
-
}
|
|
181
|
-
await this.commitKernelApply(runtime, observations, {
|
|
182
|
-
kind: "memory_query_result",
|
|
183
|
-
effect_id: action.effectId,
|
|
184
|
-
hits,
|
|
185
|
-
...(ioError ? { error: formatToolError(ioError) } : {}),
|
|
186
|
-
}, durableSessionId);
|
|
187
|
-
await this.appendMemorySyscallObservations(durableSessionId, observations);
|
|
188
|
-
if (ioError)
|
|
189
|
-
throw ioError;
|
|
250
|
+
const { hits } = await this.queryMemoryThroughKernel(runtime, query, agentId, durableSessionId);
|
|
190
251
|
await this.logMemoryRetrievalResult(durableSessionId, hits);
|
|
191
252
|
return hits;
|
|
192
253
|
}
|
|
@@ -322,6 +383,7 @@ export class RuntimeRunner {
|
|
|
322
383
|
...(q.maxConcurrentSubagents !== undefined ? { max_concurrent_subagents: q.maxConcurrentSubagents } : {}),
|
|
323
384
|
...(q.maxTotalSubagents !== undefined ? { max_total_subagents: q.maxTotalSubagents } : {}),
|
|
324
385
|
...(q.maxSpawnDepth !== undefined ? { max_spawn_depth: q.maxSpawnDepth } : {}),
|
|
386
|
+
...(q.maxWorkflowNodes !== undefined ? { max_workflow_nodes: q.maxWorkflowNodes } : {}),
|
|
325
387
|
...(q.memoryWritesPerWindow !== undefined
|
|
326
388
|
? { memory_writes_per_window: [q.memoryWritesPerWindow.maxWrites, q.memoryWritesPerWindow.windowMs] }
|
|
327
389
|
: {}),
|
|
@@ -371,14 +433,36 @@ export class RuntimeRunner {
|
|
|
371
433
|
}
|
|
372
434
|
await this.commitKernelApply(runtime, this.pendingObservations, { kind: "configure_run", config });
|
|
373
435
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
436
|
+
/**
|
|
437
|
+
* Mirror one kernel memory-lifecycle observation into the durable store / host callbacks.
|
|
438
|
+
* Shared by the main run drain, the prefetch path, and the host memory syscalls so every
|
|
439
|
+
* query route has identical recall + promotion semantics (T5).
|
|
440
|
+
*
|
|
441
|
+
* M3: `memory_recalled` carries the kernel-derived count — the runner never computes
|
|
442
|
+
* `recall_count + 1` itself. M4: `promotion_suggested` is advisory and already
|
|
443
|
+
* edge-triggered by the kernel; the runner surfaces it and never auto-pins.
|
|
444
|
+
*/
|
|
445
|
+
async mirrorMemoryLifecycle(obs) {
|
|
446
|
+
if (obs.kind === "memory_recalled" && obs.recalls?.length) {
|
|
447
|
+
const agentId = this.opts.agentId;
|
|
448
|
+
if (agentId && this.opts.dreamStore?.recordRecall) {
|
|
449
|
+
await this.opts.dreamStore.recordRecall(agentId, obs.recalls);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (obs.kind === "promotion_suggested" && obs.record_id) {
|
|
453
|
+
this.opts.onPromotionSuggested?.({
|
|
454
|
+
recordId: obs.record_id,
|
|
455
|
+
recallCount: obs.recall_count ?? 0,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async consumeMemoryLifecycleObservations(sessionId, observations) {
|
|
377
460
|
const turn = this.activeKernel?.turn() ?? 0;
|
|
378
461
|
for (const obs of observations) {
|
|
379
|
-
if (obs
|
|
380
|
-
|
|
381
|
-
|
|
462
|
+
if (!isMemoryLifecycleObservation(obs))
|
|
463
|
+
continue;
|
|
464
|
+
await this.mirrorMemoryLifecycle(obs);
|
|
465
|
+
if (!sessionId)
|
|
382
466
|
continue;
|
|
383
467
|
const event = kernelObservationToSessionEvent(obs, turn);
|
|
384
468
|
if (event)
|
|
@@ -459,8 +543,14 @@ export class RuntimeRunner {
|
|
|
459
543
|
});
|
|
460
544
|
this.nextArchiveStart = await this.appendObservations(parentSessionId, runtime, this.nextArchiveStart);
|
|
461
545
|
const spawned = findSpawnProcessObservation(observations);
|
|
462
|
-
if (!spawned)
|
|
546
|
+
if (!spawned) {
|
|
547
|
+
const rejected = controlRequestRejection(observations, "spawn_sub_agent");
|
|
548
|
+
if (rejected) {
|
|
549
|
+
yield { type: "error", message: `spawn_sub_agent denied: ${rejected.reason}` };
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
463
552
|
throw new Error("spawn_sub_agent did not emit agent_process_changed");
|
|
553
|
+
}
|
|
464
554
|
const manifest = spawnObservationToManifest(spawned, spec, parentSessionId);
|
|
465
555
|
const orchestrator = this.opts.subAgentOrchestrator ?? defaultSubAgentOrchestrator;
|
|
466
556
|
const result = await orchestrator.run({
|
|
@@ -856,6 +946,11 @@ export class RuntimeRunner {
|
|
|
856
946
|
}
|
|
857
947
|
if (!initialAction)
|
|
858
948
|
return { nodeOutcomes: [], outputs: {} };
|
|
949
|
+
const workflowRejection = controlRequestRejection(observations);
|
|
950
|
+
if (initialAction.kind === "call_provider" && workflowRejection) {
|
|
951
|
+
this.workflowContinuation = initialAction;
|
|
952
|
+
return { nodeOutcomes: [], outputs: {}, rejection: workflowRejection };
|
|
953
|
+
}
|
|
859
954
|
if (initialAction.kind !== "spawn_workflow") {
|
|
860
955
|
throw new Error(`workflow load returned unexpected kernel effect: ${initialAction.kind}`);
|
|
861
956
|
}
|
|
@@ -915,6 +1010,21 @@ export class RuntimeRunner {
|
|
|
915
1010
|
const observationStart = this.pendingObservations.length;
|
|
916
1011
|
const submitAction = await this.commitKernelMaybeAction(runtime, this.pendingObservations, submitEvent);
|
|
917
1012
|
const subObs = this.pendingObservations.slice(observationStart);
|
|
1013
|
+
const rejected = controlRequestRejection(subObs, "submit_workflow_nodes")
|
|
1014
|
+
?? (subObs.find(o => o.kind === "nodes_rejected")
|
|
1015
|
+
? { operation: "submit_workflow_nodes", reason: String(subObs.find(o => o.kind === "nodes_rejected")?.reason ?? "request denied") }
|
|
1016
|
+
: undefined);
|
|
1017
|
+
if (rejected) {
|
|
1018
|
+
const denial = `workflow node submission denied: ${rejected.reason}`;
|
|
1019
|
+
result.result = {
|
|
1020
|
+
...result.result,
|
|
1021
|
+
termination: "error",
|
|
1022
|
+
finalMessage: { role: "assistant", content: denial, toolCalls: [] },
|
|
1023
|
+
};
|
|
1024
|
+
outputs.set(result.agentId, denial);
|
|
1025
|
+
if (stableId !== result.agentId)
|
|
1026
|
+
outputs.set(stableId, denial);
|
|
1027
|
+
}
|
|
918
1028
|
if (submitAction?.kind === "spawn_workflow") {
|
|
919
1029
|
nextNodes.push(...submitAction.nodes);
|
|
920
1030
|
budget = submitAction.budget ?? budget;
|
|
@@ -1356,6 +1466,9 @@ export class RuntimeRunner {
|
|
|
1356
1466
|
...(m.validationEnabled !== undefined ? { validation_enabled: m.validationEnabled } : {}),
|
|
1357
1467
|
...(m.maxContentBytes !== undefined ? { max_content_bytes: m.maxContentBytes } : {}),
|
|
1358
1468
|
...(m.maxNameLength !== undefined ? { max_name_length: m.maxNameLength } : {}),
|
|
1469
|
+
...(m.promotionRecallThreshold !== undefined
|
|
1470
|
+
? { promotion_recall_threshold: m.promotionRecallThreshold }
|
|
1471
|
+
: {}),
|
|
1359
1472
|
});
|
|
1360
1473
|
}
|
|
1361
1474
|
if (this.opts.knowledgeSource) {
|
|
@@ -1465,21 +1578,24 @@ export class RuntimeRunner {
|
|
|
1465
1578
|
message: attachmentsToKernelMessage(attachments),
|
|
1466
1579
|
});
|
|
1467
1580
|
}
|
|
1468
|
-
// I4: pre-fetch memory before the first LLM turn so the model sees it on turn 1 instead of
|
|
1469
|
-
// discovering it via the `memory` tool on turn 3+. Skipped on resumes (already in prior
|
|
1470
|
-
// context) and when dreamStore/agentId is absent.
|
|
1471
|
-
//
|
|
1472
|
-
// Strict dynamic context control: this is single-use retrieval content (facts relevant to
|
|
1473
|
-
// THIS run's goal right now), not a stable method/skill — so it lands in `history` as an
|
|
1474
|
-
// ordinary turn, exactly like a real `memory` tool result would, and decays with the
|
|
1475
|
-
// compression pyramid over subsequent turns instead of pinning itself in `knowledge` forever.
|
|
1476
1581
|
this.currentGoal = goal;
|
|
1477
|
-
if (!resumeMidRun) {
|
|
1478
|
-
await this.prefetchMemoryIntoHistory(runtime, "initial");
|
|
1479
|
-
}
|
|
1480
1582
|
let action = resumeMidRun
|
|
1481
1583
|
? await this.commitKernelAction(runtime, this.pendingObservations, { kind: "resume" })
|
|
1482
1584
|
: await this.commitKernelAction(runtime, this.pendingObservations, startPayload);
|
|
1585
|
+
// I4/T5: pre-fetch memory before the first LLM turn so the model sees it on turn 1 instead
|
|
1586
|
+
// of discovering it via the `memory` tool on turn 3+. It routes through the kernel's
|
|
1587
|
+
// query_memory lifecycle and therefore runs AFTER start_run — a memory query is a kernel
|
|
1588
|
+
// preload whose result resumes the reasoning path, so issuing it pre-run would leave the
|
|
1589
|
+
// kernel Running and start_run would fault. The resumed action from the last query
|
|
1590
|
+
// supersedes start_run's (same pull contract; it renders the injected hits). Hits land in
|
|
1591
|
+
// `history` as ordinary turns — single-use retrieval content that decays with the
|
|
1592
|
+
// compression pyramid, never pinned into `knowledge`. Skipped on resumes (already in
|
|
1593
|
+
// prior context) and when dreamStore/agentId is absent.
|
|
1594
|
+
if (!resumeMidRun) {
|
|
1595
|
+
const resumed = await this.prefetchMemoryIntoHistory(runtime, "initial");
|
|
1596
|
+
if (resumed)
|
|
1597
|
+
action = resumed;
|
|
1598
|
+
}
|
|
1483
1599
|
// P0-C: the skill loaded and in effect going into the current turn (updated when the model's
|
|
1484
1600
|
// `skill` tool call resolves). Drives the per-turn `activeSkill` metric → dwell measurement.
|
|
1485
1601
|
let activeSkill;
|
|
@@ -1842,7 +1958,7 @@ export class RuntimeRunner {
|
|
|
1842
1958
|
const spec = parseStartWorkflowSpec(call.arguments);
|
|
1843
1959
|
if (spec) {
|
|
1844
1960
|
this.pendingAuthoredWorkflows.push(spec);
|
|
1845
|
-
const out = "workflow
|
|
1961
|
+
const out = "workflow submitted for governance adjudication";
|
|
1846
1962
|
toolResults.push({ callId: call.id, output: out, isError: false });
|
|
1847
1963
|
yield { type: "tool_result", callId: call.id, content: out, isError: false };
|
|
1848
1964
|
continue;
|
|
@@ -1853,13 +1969,13 @@ export class RuntimeRunner {
|
|
|
1853
1969
|
? parseStartWorkflowArgs(call.arguments)
|
|
1854
1970
|
: parseSubmitWorkflowNodesArgs(call.arguments);
|
|
1855
1971
|
yield { type: "workflow_nodes_submitted", nodes };
|
|
1856
|
-
const result = { callId: call.id, output: "submitted", isError: false };
|
|
1972
|
+
const result = { callId: call.id, output: "workflow nodes submitted for parent governance adjudication", isError: false };
|
|
1857
1973
|
toolResults.push(result);
|
|
1858
|
-
yield { type: "tool_result", callId: call.id, content:
|
|
1974
|
+
yield { type: "tool_result", callId: call.id, content: result.output, isError: false };
|
|
1859
1975
|
}
|
|
1860
1976
|
// O5 (PreToolUse-hook analog): give the host a STATEFUL veto over each kernel-approved
|
|
1861
|
-
// call. A blocked call never executes; its reason reaches the model as a
|
|
1862
|
-
// tool result
|
|
1977
|
+
// call. A blocked call never executes; its reason reaches the model as a committed
|
|
1978
|
+
// governance-denied tool result. Decision failures are closed
|
|
1863
1979
|
// unless the host explicitly marks this hook advisory with `onToolCallFailure: "open"`.
|
|
1864
1980
|
let executableCalls = normalCalls;
|
|
1865
1981
|
if (this.opts.onToolCall) {
|
|
@@ -2195,7 +2311,7 @@ export class RuntimeRunner {
|
|
|
2195
2311
|
* earlier memory hits, so the new sprint gets a fresh recall pass. Errs-open throughout. */
|
|
2196
2312
|
async prefetchMemoryIntoHistory(runtime, phase) {
|
|
2197
2313
|
if (!this.opts.dreamStore || !this.opts.agentId || !this.opts.memoryScope)
|
|
2198
|
-
return;
|
|
2314
|
+
return undefined;
|
|
2199
2315
|
// P10: recall is default-on (CC session-start recall) — with no hook configured,
|
|
2200
2316
|
// the goal itself is the query. preQueryMemory stays as the targeting override.
|
|
2201
2317
|
const preQuery = this.opts.preQueryMemory
|
|
@@ -2211,23 +2327,27 @@ export class RuntimeRunner {
|
|
|
2211
2327
|
runSpec: this.opts.runSpec,
|
|
2212
2328
|
phase,
|
|
2213
2329
|
});
|
|
2214
|
-
|
|
2330
|
+
// T5: route each query through the kernel's query_memory lifecycle instead of calling
|
|
2331
|
+
// the store directly — the kernel injects each routed hit into history itself and the
|
|
2332
|
+
// recall lifecycle (recordRecall / promotion) fires exactly like an in-run query.
|
|
2333
|
+
//
|
|
2334
|
+
// One prefetch = one dedupe horizon: a record hit by several short queries recalls and
|
|
2335
|
+
// injects once. A renewal prefetch starts a fresh horizon — renewal dropped the earlier
|
|
2336
|
+
// injection with the old history, so re-exposure is a genuine new recall.
|
|
2337
|
+
const seenRecordIds = new Set();
|
|
2338
|
+
let resumed;
|
|
2215
2339
|
for (const q of queries ?? []) {
|
|
2216
2340
|
if (!q.query.trim())
|
|
2217
2341
|
continue;
|
|
2218
|
-
const
|
|
2219
|
-
|
|
2220
|
-
lines.push(`[memory record_id=${hit.record.record_id} trust=${hit.record.provenance.trust} score=${hit.score.toFixed(3)}] ${hit.record.content}`);
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
if (lines.length > 0) {
|
|
2224
|
-
await this.commitKernelApply(runtime, this.pendingObservations, {
|
|
2225
|
-
kind: "add_history_message",
|
|
2226
|
-
message: { role: "user", content: lines.join("\n") },
|
|
2227
|
-
});
|
|
2342
|
+
const { action } = await this.queryMemoryThroughKernel(runtime, q, this.opts.agentId, this.durableSessionId(this.currentSessionId), seenRecordIds, this.pendingObservations);
|
|
2343
|
+
resumed = action ?? resumed;
|
|
2228
2344
|
}
|
|
2345
|
+
// Each memory_query_result resumes the reasoning path and re-emits the pending loop
|
|
2346
|
+
// action; the caller must continue from the LAST one (it renders the injected hits).
|
|
2347
|
+
return resumed;
|
|
2229
2348
|
}
|
|
2230
2349
|
catch { /* errs-open — a faulty pre-fetch never breaks the run */ }
|
|
2350
|
+
return undefined;
|
|
2231
2351
|
}
|
|
2232
2352
|
async appendObservations(sessionId, runtime, nextArchiveStart, _taskScope) {
|
|
2233
2353
|
const turn = runtime.turn();
|
|
@@ -2248,22 +2368,9 @@ export class RuntimeRunner {
|
|
|
2248
2368
|
});
|
|
2249
2369
|
this.activeGroupBudgetScope = undefined;
|
|
2250
2370
|
}
|
|
2251
|
-
// M3: mirror the kernel's journaled recall lifecycle into the durable store
|
|
2252
|
-
//
|
|
2253
|
-
|
|
2254
|
-
const agentId = this.opts.agentId;
|
|
2255
|
-
if (agentId && this.opts.dreamStore?.recordRecall) {
|
|
2256
|
-
await this.opts.dreamStore.recordRecall(agentId, obs.recalls);
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
// M4: a recall crossed the promotion threshold. Advisory — surface it for the host/model to
|
|
2260
|
-
// act on (pin or promote to knowledge); the runner does not auto-pin.
|
|
2261
|
-
if (obs.kind === "promotion_suggested" && obs.record_id) {
|
|
2262
|
-
this.opts.onPromotionSuggested?.({
|
|
2263
|
-
recordId: obs.record_id,
|
|
2264
|
-
recallCount: obs.recall_count ?? 0,
|
|
2265
|
-
});
|
|
2266
|
-
}
|
|
2371
|
+
// M3/M4: mirror the kernel's journaled recall lifecycle into the durable store /
|
|
2372
|
+
// host callbacks (shared consumer — identical semantics on every query route).
|
|
2373
|
+
await this.mirrorMemoryLifecycle(obs);
|
|
2267
2374
|
const latest = obs.kind === "compressed" ? await this.opts.sessionLog.latestSeq(sessionId) : undefined;
|
|
2268
2375
|
const event = kernelObservationToSessionEvent(obs, turn, {
|
|
2269
2376
|
nextArchiveStart,
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -210,9 +210,17 @@ export interface WorkflowNodeOutcome {
|
|
|
210
210
|
termination?: TerminationReason;
|
|
211
211
|
output?: Message;
|
|
212
212
|
}
|
|
213
|
+
/** A control-plane request rejected before any workflow effect started. */
|
|
214
|
+
export interface ControlRequestRejection {
|
|
215
|
+
operation: string;
|
|
216
|
+
subject?: string;
|
|
217
|
+
reason: string;
|
|
218
|
+
}
|
|
213
219
|
export interface WorkflowOutcome {
|
|
214
220
|
nodeOutcomes: WorkflowNodeOutcome[];
|
|
215
221
|
outputs: Record<string, string>;
|
|
222
|
+
/** Present when the workflow itself was rejected before any node ran. */
|
|
223
|
+
rejection?: ControlRequestRejection;
|
|
216
224
|
}
|
|
217
225
|
export declare function workflowNodeOutcomeFromKernel(raw: KernelWorkflowNodeOutcome): WorkflowNodeOutcome;
|
|
218
226
|
/** Per-node spawn descriptor carried in the `workflow_batch_spawned` observation. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstrike/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.42",
|
|
4
4
|
"description": "DeepStrike Node.js SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@anthropic-ai/sdk": "^0.99.0",
|
|
75
|
-
"@deepstrike/core": "0.2.
|
|
75
|
+
"@deepstrike/core": "0.2.42",
|
|
76
76
|
"@google/generative-ai": "^0.24.1",
|
|
77
77
|
"openai": "^5.23.2"
|
|
78
78
|
},
|