@deepstrike/wasm 0.2.36 → 0.2.38
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/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/runtime/facade.js +11 -7
- package/dist/runtime/kernel-event-log.d.ts +0 -6
- package/dist/runtime/kernel-event-log.js +57 -62
- package/dist/runtime/kernel-step.d.ts +17 -0
- package/dist/runtime/kernel-step.js +12 -0
- package/dist/runtime/os-snapshot.d.ts +0 -1
- package/dist/runtime/os-snapshot.js +0 -19
- package/dist/runtime/runner.d.ts +22 -3
- package/dist/runtime/runner.js +192 -25
- package/dist/runtime/session-log.d.ts +30 -54
- package/dist/runtime/session-repair.d.ts +29 -6
- package/dist/runtime/session-repair.js +37 -8
- package/dist/runtime/sub-agent-orchestrator.d.ts +7 -0
- package/dist/runtime/sub-agent-orchestrator.js +33 -3
- package/dist/runtime/types/agent.d.ts +36 -1
- package/dist/runtime/types/agent.js +54 -1
- package/dist/runtime/workflow-control-flow.d.ts +10 -2
- package/dist/runtime/workflow-control-flow.js +27 -6
- package/dist/types.d.ts +34 -1
- package/package.json +2 -2
package/dist/runtime/runner.js
CHANGED
|
@@ -10,7 +10,7 @@ import { agentRunSpecToKernel, findSpawnProcessObservation, milestoneCheckPass,
|
|
|
10
10
|
import { defaultSubAgentOrchestrator } from "./sub-agent-orchestrator.js";
|
|
11
11
|
import { extractJsonValue, schemaInstruction, schemaRetryInstruction, validateAgainstSchema, } from "./output-schema.js";
|
|
12
12
|
import { resolveReducer } from "./reducers.js";
|
|
13
|
-
import { loopInstruction, classifyInstruction, judgeGoal, extractLoopContinue, extractClassifyBranch, extractJudgeWinner, } from "./workflow-control-flow.js";
|
|
13
|
+
import { loopInstruction, classifyInstruction, judgeGoal, dependencyOutputsNote, extractLoopContinue, extractClassifyBranch, extractJudgeWinner, } from "./workflow-control-flow.js";
|
|
14
14
|
import { kernelObservationToSessionEvent } from "./kernel-event-log.js";
|
|
15
15
|
import { assertNativeProfile } from "./os-profile.js";
|
|
16
16
|
import { LargeResultSpool } from "./large-result-spool.js";
|
|
@@ -28,6 +28,8 @@ export class RuntimeRunner {
|
|
|
28
28
|
* run — guards against re-pushing a duplicate entry if the model calls `skill(name)` again for
|
|
29
29
|
* an already-active skill (loading is idempotent; the knowledge push should be too). */
|
|
30
30
|
knowledgePushedSkills = new Set();
|
|
31
|
+
/** Most recent kernel entropy sample of the active/last run (see `latestEntropy`). */
|
|
32
|
+
lastEntropySample = null;
|
|
31
33
|
/** K4: the active run's goal, kept for the renewal-boundary memory re-query. */
|
|
32
34
|
currentGoal = "";
|
|
33
35
|
nextArchiveStart = 0;
|
|
@@ -48,6 +50,11 @@ export class RuntimeRunner {
|
|
|
48
50
|
injectNote(text, urgency = "normal") {
|
|
49
51
|
this.injectedSignals.push({ source: "custom", signalType: "event", urgency, payload: { goal: text } });
|
|
50
52
|
}
|
|
53
|
+
/** The most recent kernel session-entropy sample (one per completed turn), or `null` before the
|
|
54
|
+
* first boundary. A pull companion to the streamed `entropy_sample` events. */
|
|
55
|
+
latestEntropy() {
|
|
56
|
+
return this.lastEntropySample;
|
|
57
|
+
}
|
|
51
58
|
/** Injected-note drain shared with the main loop's per-turn poll: injected notes first (FIFO), then
|
|
52
59
|
* the configured `signalSource` — one code path so the two inbound channels never drift. */
|
|
53
60
|
async nextInboundSignal() {
|
|
@@ -859,10 +866,35 @@ export class RuntimeRunner {
|
|
|
859
866
|
}
|
|
860
867
|
catch { /* skip */ }
|
|
861
868
|
}
|
|
869
|
+
const entropyObsStart = this.pendingObservations.length;
|
|
862
870
|
action = kernelAction(runtime, this.pendingObservations, {
|
|
863
871
|
kind: "tool_results",
|
|
864
872
|
results: toolResults.map(toolResultToKernel),
|
|
865
873
|
});
|
|
874
|
+
// Surface the boundary's entropy measurement live (the heartbeat watch source).
|
|
875
|
+
for (const obs of this.pendingObservations.slice(entropyObsStart)) {
|
|
876
|
+
if (obs.kind === "entropy_sample") {
|
|
877
|
+
this.lastEntropySample = {
|
|
878
|
+
turn: obs.turn ?? 0,
|
|
879
|
+
score: obs.score ?? 0,
|
|
880
|
+
scoreVersion: obs.score_version ?? 0,
|
|
881
|
+
rho: obs.rho ?? 0,
|
|
882
|
+
repeatPressure: obs.repeat_pressure ?? 0,
|
|
883
|
+
failureRate: obs.failure_rate ?? 0,
|
|
884
|
+
rollbacksInWindow: obs.rollbacks_in_window ?? 0,
|
|
885
|
+
windowTurns: obs.window_turns ?? 0,
|
|
886
|
+
};
|
|
887
|
+
yield { type: "entropy_sample", sample: this.lastEntropySample };
|
|
888
|
+
}
|
|
889
|
+
else if (obs.kind === "entropy_alert") {
|
|
890
|
+
yield {
|
|
891
|
+
type: "entropy_alert",
|
|
892
|
+
turn: obs.turn ?? 0,
|
|
893
|
+
score: obs.score ?? 0,
|
|
894
|
+
threshold: obs.threshold ?? 0,
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
}
|
|
866
898
|
}
|
|
867
899
|
else if (action.kind === "evaluate_milestone") {
|
|
868
900
|
const milestonePolicy = this.opts.milestonePolicy ?? "require_verifier";
|
|
@@ -959,7 +991,14 @@ export class RuntimeRunner {
|
|
|
959
991
|
catch { /* non-fatal */ }
|
|
960
992
|
}
|
|
961
993
|
}
|
|
962
|
-
yield {
|
|
994
|
+
yield {
|
|
995
|
+
type: "done",
|
|
996
|
+
iterations: turnsUsed,
|
|
997
|
+
totalTokens,
|
|
998
|
+
status,
|
|
999
|
+
// ③ loop-agent: surface the kernel-adjudicated after-round decision to the driver.
|
|
1000
|
+
...(result?.paceDecision ? { paceDecision: result.paceDecision } : {}),
|
|
1001
|
+
};
|
|
963
1002
|
this.activeKernel = null;
|
|
964
1003
|
this.currentSessionId = null;
|
|
965
1004
|
}
|
|
@@ -1009,7 +1048,10 @@ export class RuntimeRunner {
|
|
|
1009
1048
|
const manifest = workflowNodeToManifest(node, parentSessionId);
|
|
1010
1049
|
// G4: surface remaining workflow budget so a coordinator node can size its submission.
|
|
1011
1050
|
const budgetNote = workflowBudgetNote(budget);
|
|
1012
|
-
|
|
1051
|
+
// W-N2: a DAG edge carries data — every dependent node sees its dependencies' outputs (the
|
|
1052
|
+
// kernel sends `input_agent_ids` for all dependents; judges/reduce keep their special paths).
|
|
1053
|
+
const depsNote = dependencyOutputsNote(node.input_agent_ids, outputs);
|
|
1054
|
+
const withBudget = (goal) => [goal, depsNote, budgetNote].filter(Boolean).join("\n\n");
|
|
1013
1055
|
const mkCtx = (goal) => ({
|
|
1014
1056
|
parentOpts: this.opts,
|
|
1015
1057
|
parentSessionId,
|
|
@@ -1018,6 +1060,10 @@ export class RuntimeRunner {
|
|
|
1018
1060
|
sessionLog: this.opts.sessionLog,
|
|
1019
1061
|
// M5 v2.1: this child IS a workflow node — its `start_workflow` flattens to this kernel.
|
|
1020
1062
|
isWorkflowNode: true,
|
|
1063
|
+
// W-N1: trusted workflow nodes run on the parent's execution plane (they carry no grant list
|
|
1064
|
+
// by design — filtering on the missing list ran every DAG node TOOL-LESS); quarantined nodes
|
|
1065
|
+
// stay deny-all filtered (they read untrusted content).
|
|
1066
|
+
toolAccess: (node.trust === "quarantined" ? "filtered" : "inherit"),
|
|
1021
1067
|
// #2-B-ii: the per-node abort signal the driver fires when the kernel preempts this node.
|
|
1022
1068
|
...(abortSignal ? { abortSignal } : {}),
|
|
1023
1069
|
});
|
|
@@ -1037,9 +1083,18 @@ export class RuntimeRunner {
|
|
|
1037
1083
|
const winnerId = winner === "right" ? node.judge_match.right : node.judge_match.left;
|
|
1038
1084
|
return withSignal(result, { tournamentWinner: winnerId });
|
|
1039
1085
|
}
|
|
1040
|
-
// A#2 v2 loop iteration: run the increment
|
|
1086
|
+
// A#2 v2 loop iteration: run the increment under the armed pacing trap (workflowNodeToSpec set
|
|
1087
|
+
// `loopRound`, and the iteration resumes the loop's stable session — transcript-as-carry).
|
|
1088
|
+
// DW-3 one vocabulary: the kernel-adjudicated `pace` verb IS the continuation signal
|
|
1089
|
+
// (stop → loopContinue=false); the legacy text-sniffed JSON blob survives only as the fallback
|
|
1090
|
+
// when no pace decision arrives (stub orchestrators, harness children), where no signal still
|
|
1091
|
+
// means "run to max_iters" (v1).
|
|
1041
1092
|
if (node.loop_max_iters != null) {
|
|
1042
|
-
const
|
|
1093
|
+
const iteration = Number(/-i(\d+)$/.exec(node.agent_id)?.[1] ?? "0");
|
|
1094
|
+
const result = await orchestrator.run(mkCtx(`${baseSpec.goal}\n\n${loopInstruction(node.loop_max_iters, iteration)}`));
|
|
1095
|
+
const pace = result.result.paceDecision;
|
|
1096
|
+
if (pace)
|
|
1097
|
+
return withSignal(result, { loopContinue: pace.action !== "stop" });
|
|
1043
1098
|
const cont = extractLoopContinue(textOf(result));
|
|
1044
1099
|
return cont === undefined ? result : withSignal(result, { loopContinue: cont });
|
|
1045
1100
|
}
|
|
@@ -1151,6 +1206,17 @@ export class RuntimeRunner {
|
|
|
1151
1206
|
if (this.opts.knowledgeBudgetRatio !== undefined) {
|
|
1152
1207
|
config.knowledge_budget_ratio = this.opts.knowledgeBudgetRatio;
|
|
1153
1208
|
}
|
|
1209
|
+
// Entropy watch (opt-in): threshold alerting over the per-turn session-entropy score.
|
|
1210
|
+
if (this.opts.entropyWatch !== undefined) {
|
|
1211
|
+
const ew = this.opts.entropyWatch;
|
|
1212
|
+
config.entropy_watch = {
|
|
1213
|
+
enabled: ew.enabled ?? true,
|
|
1214
|
+
...(ew.threshold !== undefined ? { threshold: ew.threshold } : {}),
|
|
1215
|
+
...(ew.hysteresis !== undefined ? { hysteresis: ew.hysteresis } : {}),
|
|
1216
|
+
...(ew.cooldownTurns !== undefined ? { cooldown_turns: ew.cooldownTurns } : {}),
|
|
1217
|
+
...(ew.notifyModel !== undefined ? { notify_model: ew.notifyModel } : {}),
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1154
1220
|
kernelApply(runtime, this.pendingObservations, { kind: "configure_run", config });
|
|
1155
1221
|
if (this.opts.memoryPolicy) {
|
|
1156
1222
|
const m = this.opts.memoryPolicy;
|
|
@@ -1215,10 +1281,22 @@ export class RuntimeRunner {
|
|
|
1215
1281
|
parent_session_id: parentSessionId,
|
|
1216
1282
|
// W0-ABI resume: skip nodes already completed before an interruption.
|
|
1217
1283
|
...(opts?.resumedCompleted && opts.resumedCompleted.length ? { resumed_completed: opts.resumedCompleted } : {}),
|
|
1284
|
+
// W-1: signal-carrying completion records (classify branch / loop stop replay).
|
|
1285
|
+
...(opts?.resumedResults?.length
|
|
1286
|
+
? {
|
|
1287
|
+
resumed_results: opts.resumedResults.map(r => ({
|
|
1288
|
+
agent_id: r.agentId,
|
|
1289
|
+
...(r.classifyBranch !== undefined ? { classify_branch: r.classifyBranch } : {}),
|
|
1290
|
+
...(r.tournamentWinner !== undefined ? { tournament_winner: r.tournamentWinner } : {}),
|
|
1291
|
+
...(r.loopContinue !== undefined ? { loop_continue: r.loopContinue } : {}),
|
|
1292
|
+
})),
|
|
1293
|
+
}
|
|
1294
|
+
: {}),
|
|
1218
1295
|
// R3-1: re-apply recorded runtime submissions so dynamically-appended nodes are reconstructed.
|
|
1219
1296
|
...(opts?.resumedSubmissions && opts.resumedSubmissions.length ? { resumed_submissions: opts.resumedSubmissions } : {}),
|
|
1297
|
+
...(opts?.resumedSubmissionBases && opts.resumedSubmissionBases.length ? { resumed_submission_bases: opts.resumedSubmissionBases } : {}),
|
|
1220
1298
|
});
|
|
1221
|
-
return await this.driveWorkflow(observations, parentSessionId, runtime);
|
|
1299
|
+
return await this.driveWorkflow(observations, parentSessionId, runtime, opts?.resumedOutputs);
|
|
1222
1300
|
}
|
|
1223
1301
|
finally {
|
|
1224
1302
|
if (bootstrapped) {
|
|
@@ -1241,6 +1319,18 @@ export class RuntimeRunner {
|
|
|
1241
1319
|
const parentSessionId = this.currentSessionId;
|
|
1242
1320
|
const runtime = this.activeKernel;
|
|
1243
1321
|
const observations = kernelApply(runtime, this.pendingObservations, submitWorkflowToKernel(spec, parentSessionId, opts?.submitterAgentId));
|
|
1322
|
+
// W-3: persist the agent-authored batch (bootstrap base 0 / flatten base N — the kernel now
|
|
1323
|
+
// announces BOTH) so an interrupted authored workflow reconstructs on resume; the host never
|
|
1324
|
+
// had this spec, unlike the `runWorkflow` path.
|
|
1325
|
+
const submitted = observations.find(o => o.kind === "workflow_nodes_submitted");
|
|
1326
|
+
if (submitted) {
|
|
1327
|
+
await this.opts.sessionLog.append(parentSessionId, buildWorkflowNodesSubmittedEvent({
|
|
1328
|
+
turn: runtime.turn(),
|
|
1329
|
+
nodes: workflowSpecToKernel(spec).nodes ?? [],
|
|
1330
|
+
baseIndex: submitted.base,
|
|
1331
|
+
submitterAgentId: opts?.submitterAgentId,
|
|
1332
|
+
}));
|
|
1333
|
+
}
|
|
1244
1334
|
return this.driveWorkflow(observations, parentSessionId, runtime);
|
|
1245
1335
|
}
|
|
1246
1336
|
/**
|
|
@@ -1297,7 +1387,7 @@ export class RuntimeRunner {
|
|
|
1297
1387
|
* `submit_workflow`): run each kernel-emitted batch in parallel, feed completions back (appending any
|
|
1298
1388
|
* agent-submitted nodes first), and loop until the kernel reports the workflow complete.
|
|
1299
1389
|
*/
|
|
1300
|
-
async driveWorkflow(initial, parentSessionId, runtime) {
|
|
1390
|
+
async driveWorkflow(initial, parentSessionId, runtime, seedOutputs) {
|
|
1301
1391
|
const observations = initial;
|
|
1302
1392
|
const orchestrator = this.opts.subAgentOrchestrator ?? defaultSubAgentOrchestrator;
|
|
1303
1393
|
const collectNodes = (obs) => obs.find(o => o.kind === "workflow_batch_spawned")?.nodes ?? [];
|
|
@@ -1310,7 +1400,9 @@ export class RuntimeRunner {
|
|
|
1310
1400
|
let nodes = collectNodes(observations);
|
|
1311
1401
|
let budget = collectBudget(observations);
|
|
1312
1402
|
// G2: each completed node's output, keyed by agent id — a reduce node reads its deps' outputs.
|
|
1313
|
-
|
|
1403
|
+
// W-1: on resume it is pre-seeded from the persisted node outputs, so post-resume dependents
|
|
1404
|
+
// still see their (pre-crash) dependencies' outputs.
|
|
1405
|
+
const outputs = new Map(seedOutputs ?? []);
|
|
1314
1406
|
for (;;) {
|
|
1315
1407
|
if (nodes.length === 0)
|
|
1316
1408
|
return { completed: [], failed: [], outputs: Object.fromEntries(outputs) };
|
|
@@ -1330,7 +1422,13 @@ export class RuntimeRunner {
|
|
|
1330
1422
|
for (const result of results) {
|
|
1331
1423
|
// G2: record this node's output so a downstream reduce node can consume it.
|
|
1332
1424
|
const outContent = result.result.finalMessage?.content;
|
|
1333
|
-
|
|
1425
|
+
const outText = typeof outContent === "string" ? outContent : outContent != null ? JSON.stringify(outContent) : "";
|
|
1426
|
+
outputs.set(result.agentId, outText);
|
|
1427
|
+
// A loop iteration completes under `wf-node{N}-i{k}` but its dependents consume the STABLE
|
|
1428
|
+
// node id `wf-node{N}` — alias it so the LAST iteration's output is what dependents see.
|
|
1429
|
+
const stableId = result.agentId.replace(/-i\d+$/, "");
|
|
1430
|
+
if (stableId !== result.agentId)
|
|
1431
|
+
outputs.set(stableId, outText);
|
|
1334
1432
|
// R3-1: if this node's agent submitted more nodes, append them to the parent DAG BEFORE
|
|
1335
1433
|
// reporting the node's completion — the workflow is still active, so even a last-node
|
|
1336
1434
|
// submission keeps the DAG alive.
|
|
@@ -1341,10 +1439,15 @@ export class RuntimeRunner {
|
|
|
1341
1439
|
const subObs = kernelApply(runtime, this.pendingObservations, submitEvent);
|
|
1342
1440
|
nextNodes.push(...collectNodes(subObs));
|
|
1343
1441
|
budget = collectBudget(subObs) ?? budget;
|
|
1344
|
-
// R3-1: persist the submission (kernel-shape nodes)
|
|
1442
|
+
// R3-1: persist the submission (kernel-shape nodes) + its kernel-reported base index
|
|
1443
|
+
// so resume can re-apply the batch at the exact original graph position. W-N3: also the
|
|
1444
|
+
// submitter, so resume drops batches whose submitter re-runs (it will re-submit).
|
|
1445
|
+
const submitted = subObs.find(o => o.kind === "workflow_nodes_submitted");
|
|
1345
1446
|
await this.opts.sessionLog.append(parentSessionId, buildWorkflowNodesSubmittedEvent({
|
|
1346
1447
|
turn: runtime.turn(),
|
|
1347
1448
|
nodes: submitEvent.nodes ?? [],
|
|
1449
|
+
baseIndex: submitted?.base,
|
|
1450
|
+
submitterAgentId: result.agentId,
|
|
1348
1451
|
}));
|
|
1349
1452
|
}
|
|
1350
1453
|
const obs = kernelApply(runtime, this.pendingObservations, {
|
|
@@ -1356,11 +1459,17 @@ export class RuntimeRunner {
|
|
|
1356
1459
|
const d = findDone(obs);
|
|
1357
1460
|
if (d)
|
|
1358
1461
|
done = d;
|
|
1359
|
-
// Persist node completion for resume recovery.
|
|
1462
|
+
// Persist node completion for resume recovery. W-1: the result-borne control signals ride
|
|
1463
|
+
// along (a resumed classifier re-prunes; a recorded loop stop is honored) plus the output
|
|
1464
|
+
// text (post-resume dependents/reduce still see this node's output).
|
|
1360
1465
|
await this.opts.sessionLog.append(parentSessionId, buildWorkflowNodeCompletedEvent({
|
|
1361
1466
|
turn: runtime.turn(),
|
|
1362
1467
|
agentId: result.agentId,
|
|
1363
1468
|
termination: result.result.termination,
|
|
1469
|
+
classifyBranch: result.result.classifyBranch,
|
|
1470
|
+
tournamentWinner: result.result.tournamentWinner,
|
|
1471
|
+
loopContinue: result.result.loopContinue,
|
|
1472
|
+
...(result.result.termination === "completed" && outText ? { output: outText } : {}),
|
|
1364
1473
|
}));
|
|
1365
1474
|
}
|
|
1366
1475
|
if (done && nextNodes.length === 0) {
|
|
@@ -1371,8 +1480,9 @@ export class RuntimeRunner {
|
|
|
1371
1480
|
}
|
|
1372
1481
|
/**
|
|
1373
1482
|
* Resume a workflow from the parent session's completed nodes.
|
|
1374
|
-
* Reads the session log, extracts completed workflow node
|
|
1375
|
-
* calls runWorkflow
|
|
1483
|
+
* Reads the session log, extracts completed workflow node records (with their W-1 control
|
|
1484
|
+
* signals + outputs), and calls runWorkflow so the kernel skips those nodes, replays control
|
|
1485
|
+
* flow (classify prune / loop stop), and the driver re-seeds its outputs map.
|
|
1376
1486
|
*/
|
|
1377
1487
|
async resumeWorkflow(spec, opts) {
|
|
1378
1488
|
// Standalone resume: a stateless handler passes the prior `sessionId`; mid-run callers omit it.
|
|
@@ -1381,9 +1491,34 @@ export class RuntimeRunner {
|
|
|
1381
1491
|
throw new Error("resumeWorkflow requires an active parent run or an explicit sessionId");
|
|
1382
1492
|
}
|
|
1383
1493
|
const events = await this.opts.sessionLog.read(sessionId);
|
|
1384
|
-
const
|
|
1385
|
-
const
|
|
1386
|
-
|
|
1494
|
+
const resumedResults = recoverCompletedWorkflowNodes(events);
|
|
1495
|
+
const completedIds = new Set(resumedResults.map(r => r.agentId));
|
|
1496
|
+
const recovered = recoverSubmittedWorkflowNodes(events);
|
|
1497
|
+
// W-N3: DROP batches whose submitter did NOT complete — that node re-runs on resume and will
|
|
1498
|
+
// re-submit its batch; replaying the logged copy too would duplicate its nodes in the DAG.
|
|
1499
|
+
// Only safe with exact bases (the dropped batch's slots become inert placeholders); a legacy
|
|
1500
|
+
// order-only log keeps every batch, since dropping would shift all later indices.
|
|
1501
|
+
let { submissions, bases } = recovered;
|
|
1502
|
+
if (bases.length === submissions.length && submissions.length > 0) {
|
|
1503
|
+
const keep = recovered.submitters.map(s => s === undefined || completedIds.has(s));
|
|
1504
|
+
submissions = submissions.filter((_, i) => keep[i]);
|
|
1505
|
+
bases = bases.filter((_, i) => keep[i]);
|
|
1506
|
+
}
|
|
1507
|
+
const resumedOutputs = new Map(resumedResults.filter(r => r.output).map(r => [r.agentId, r.output]));
|
|
1508
|
+
// Alias loop iterations onto their stable node id (last iteration wins) — dependents consume
|
|
1509
|
+
// `wf-node{N}`, not `wf-node{N}-i{k}`.
|
|
1510
|
+
for (const r of resumedResults) {
|
|
1511
|
+
const stableId = r.agentId.replace(/-i\d+$/, "");
|
|
1512
|
+
if (stableId !== r.agentId && r.output)
|
|
1513
|
+
resumedOutputs.set(stableId, r.output);
|
|
1514
|
+
}
|
|
1515
|
+
return this.runWorkflow(spec, {
|
|
1516
|
+
resumedResults,
|
|
1517
|
+
resumedSubmissions: submissions,
|
|
1518
|
+
resumedSubmissionBases: bases,
|
|
1519
|
+
resumedOutputs,
|
|
1520
|
+
sessionId,
|
|
1521
|
+
});
|
|
1387
1522
|
}
|
|
1388
1523
|
async appendObservations(sessionId, runtime, nextArchiveStart) {
|
|
1389
1524
|
const turn = runtime.turn();
|
|
@@ -1422,12 +1557,23 @@ export class RuntimeRunner {
|
|
|
1422
1557
|
const compressedSeq = await this.opts.sessionLog.append(sessionId, event);
|
|
1423
1558
|
if (event.kind === "compressed") {
|
|
1424
1559
|
nextArchiveStart = compressedSeq + 1;
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1560
|
+
// One compaction = one kernel observation: the page_out session record (and the
|
|
1561
|
+
// semantic-archive branch) is DERIVED from Compressed.tier_hint, preserving the
|
|
1562
|
+
// session-log format and OsSnapshot page_out_count.
|
|
1563
|
+
const archived = obs.archived;
|
|
1564
|
+
if (obs.tier_hint && Array.isArray(archived) && archived.length > 0) {
|
|
1565
|
+
await this.opts.sessionLog.append(sessionId, {
|
|
1566
|
+
kind: "page_out",
|
|
1567
|
+
turn: obs.turn ?? turn,
|
|
1568
|
+
action: compressionAction(obs.action),
|
|
1569
|
+
summary: obs.summary,
|
|
1570
|
+
tier_hint: obs.tier_hint ?? "durable",
|
|
1571
|
+
message_count: archived.length,
|
|
1572
|
+
});
|
|
1573
|
+
if (obs.tier_hint === "semantic") {
|
|
1574
|
+
void this.archiveSemanticPageOut(archived, compressionAction(obs.action));
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1431
1577
|
}
|
|
1432
1578
|
// K4: a sprint renewal dropped the old history — including any earlier memory hits — so
|
|
1433
1579
|
// re-run the preQueryMemory prefetch for the new sprint (live observations only).
|
|
@@ -1443,10 +1589,14 @@ export class RuntimeRunner {
|
|
|
1443
1589
|
* re-fired after each sprint renewal (renewal drops the old history INCLUDING earlier memory
|
|
1444
1590
|
* hits). Errs-open throughout. */
|
|
1445
1591
|
async prefetchMemoryIntoHistory(runtime, phase) {
|
|
1446
|
-
if (!this.opts.
|
|
1592
|
+
if (!this.opts.dreamStore || !this.opts.agentId)
|
|
1447
1593
|
return;
|
|
1594
|
+
// P10: recall is default-on (CC session-start recall) — with no hook configured,
|
|
1595
|
+
// the goal itself is the query. preQueryMemory stays as the targeting override.
|
|
1596
|
+
const preQuery = this.opts.preQueryMemory
|
|
1597
|
+
?? ((ctx) => [ctx.goal]);
|
|
1448
1598
|
try {
|
|
1449
|
-
const queries = await
|
|
1599
|
+
const queries = await preQuery({ goal: this.currentGoal, phase });
|
|
1450
1600
|
const lines = [];
|
|
1451
1601
|
for (const q of queries ?? []) {
|
|
1452
1602
|
if (typeof q !== "string" || !q.trim())
|
|
@@ -1473,8 +1623,12 @@ export class RuntimeRunner {
|
|
|
1473
1623
|
? await this.opts.dreamSummarizer.summarize(archived, { action })
|
|
1474
1624
|
: await summarizeForLongTermMemory(this.opts.dreamProvider ?? this.opts.provider, archived, this.opts.dreamSystemPrompt);
|
|
1475
1625
|
const existing = await this.opts.dreamStore.loadMemories(this.opts.agentId);
|
|
1626
|
+
// P2 write-funnel (wasm surface has no kernel write gate yet): jaccard dedup +
|
|
1627
|
+
// advisory 0.6 score — an automatic summary must never outrank curated content.
|
|
1628
|
+
if (existing.some(e => jaccardSimilarity(e.text, summary) >= 0.9))
|
|
1629
|
+
return;
|
|
1476
1630
|
await this.opts.dreamStore.commit(this.opts.agentId, {
|
|
1477
|
-
toAdd: [{ text: summary, score:
|
|
1631
|
+
toAdd: [{ text: summary, score: 0.6, metadata: { source: "semantic_page_out", action } }],
|
|
1478
1632
|
toRemoveIndices: [],
|
|
1479
1633
|
stats: {
|
|
1480
1634
|
insightsProcessed: 1,
|
|
@@ -1489,6 +1643,19 @@ export class RuntimeRunner {
|
|
|
1489
1643
|
}
|
|
1490
1644
|
}
|
|
1491
1645
|
}
|
|
1646
|
+
/** Word-set jaccard similarity — the curator's dedup rule at the write funnel. */
|
|
1647
|
+
function jaccardSimilarity(a, b) {
|
|
1648
|
+
const sa = new Set(a.split(/\s+/).filter(Boolean));
|
|
1649
|
+
const sb = new Set(b.split(/\s+/).filter(Boolean));
|
|
1650
|
+
if (sa.size === 0 && sb.size === 0)
|
|
1651
|
+
return 1;
|
|
1652
|
+
let inter = 0;
|
|
1653
|
+
for (const w of sa)
|
|
1654
|
+
if (sb.has(w))
|
|
1655
|
+
inter++;
|
|
1656
|
+
const union = sa.size + sb.size - inter;
|
|
1657
|
+
return union === 0 ? 0 : inter / union;
|
|
1658
|
+
}
|
|
1492
1659
|
async function summarizeForLongTermMemory(provider, archived, systemPrompt) {
|
|
1493
1660
|
const transcript = archived
|
|
1494
1661
|
.map(m => `${m.role}: ${m.content}`)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ProviderReplay, ToolCall, ToolErrorKind } from "../types.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { KernelPrimitive } from "./kernel-event-log.js";
|
|
3
3
|
export type RollbackReason = {
|
|
4
4
|
kind: "fatal_tool_error";
|
|
5
5
|
tool_name: string;
|
|
@@ -74,8 +74,6 @@ export type SessionEvent = {
|
|
|
74
74
|
} | {
|
|
75
75
|
kind: "compressed";
|
|
76
76
|
turn: number;
|
|
77
|
-
category?: KernelEventCategory;
|
|
78
|
-
primitive?: KernelPrimitive;
|
|
79
77
|
archived_seq_range: [number, number];
|
|
80
78
|
action?: "snip_compact" | "micro_compact" | "context_collapse" | "auto_compact";
|
|
81
79
|
summary?: string;
|
|
@@ -85,8 +83,6 @@ export type SessionEvent = {
|
|
|
85
83
|
} | {
|
|
86
84
|
kind: "page_out";
|
|
87
85
|
turn: number;
|
|
88
|
-
category?: KernelEventCategory;
|
|
89
|
-
primitive?: KernelPrimitive;
|
|
90
86
|
action?: "snip_compact" | "micro_compact" | "context_collapse" | "auto_compact";
|
|
91
87
|
summary?: string;
|
|
92
88
|
tier_hint?: string;
|
|
@@ -94,14 +90,10 @@ export type SessionEvent = {
|
|
|
94
90
|
} | {
|
|
95
91
|
kind: "page_in";
|
|
96
92
|
turn: number;
|
|
97
|
-
category?: KernelEventCategory;
|
|
98
|
-
primitive?: KernelPrimitive;
|
|
99
93
|
entry_count: number;
|
|
100
94
|
} | {
|
|
101
95
|
kind: "large_result_spooled";
|
|
102
96
|
turn: number;
|
|
103
|
-
category?: KernelEventCategory;
|
|
104
|
-
primitive?: KernelPrimitive;
|
|
105
97
|
call_id: string;
|
|
106
98
|
tool: string;
|
|
107
99
|
original_size: number;
|
|
@@ -110,15 +102,11 @@ export type SessionEvent = {
|
|
|
110
102
|
} | {
|
|
111
103
|
kind: "rollbacked";
|
|
112
104
|
turn: number;
|
|
113
|
-
category?: KernelEventCategory;
|
|
114
|
-
primitive?: KernelPrimitive;
|
|
115
105
|
checkpoint_history_len: number;
|
|
116
106
|
reason?: RollbackReason;
|
|
117
107
|
} | {
|
|
118
108
|
kind: "capability_changed";
|
|
119
109
|
turn: number;
|
|
120
|
-
category?: KernelEventCategory;
|
|
121
|
-
primitive?: KernelPrimitive;
|
|
122
110
|
added: string[];
|
|
123
111
|
removed: string[];
|
|
124
112
|
change_kind?: string;
|
|
@@ -129,78 +117,66 @@ export type SessionEvent = {
|
|
|
129
117
|
} | {
|
|
130
118
|
kind: "context_renewed";
|
|
131
119
|
turn: number;
|
|
132
|
-
category?: KernelEventCategory;
|
|
133
|
-
primitive?: KernelPrimitive;
|
|
134
120
|
sprint: number;
|
|
135
121
|
handoff_ref: string;
|
|
136
122
|
} | {
|
|
137
123
|
kind: "suspended";
|
|
138
124
|
turn: number;
|
|
139
|
-
category?: KernelEventCategory;
|
|
140
|
-
primitive?: KernelPrimitive;
|
|
141
125
|
reason: string;
|
|
142
126
|
pending_calls?: string[];
|
|
143
127
|
} | {
|
|
144
128
|
kind: "resumed";
|
|
145
129
|
turn: number;
|
|
146
|
-
category?: KernelEventCategory;
|
|
147
|
-
primitive?: KernelPrimitive;
|
|
148
130
|
approved?: string[];
|
|
149
131
|
denied?: string[];
|
|
150
132
|
} | {
|
|
151
133
|
kind: "tool_gated";
|
|
152
134
|
turn: number;
|
|
153
|
-
category?: KernelEventCategory;
|
|
154
|
-
primitive?: KernelPrimitive;
|
|
155
135
|
call_id: string;
|
|
156
136
|
tool: string;
|
|
157
137
|
reason: string;
|
|
158
138
|
} | {
|
|
159
139
|
kind: "signal_disposed";
|
|
160
140
|
turn: number;
|
|
161
|
-
category?: KernelEventCategory;
|
|
162
|
-
primitive?: KernelPrimitive;
|
|
163
141
|
signal_id: string;
|
|
164
142
|
disposition: string;
|
|
165
143
|
queue_depth: number;
|
|
166
144
|
} | {
|
|
167
145
|
kind: "budget_exceeded";
|
|
168
146
|
turn: number;
|
|
169
|
-
category?: KernelEventCategory;
|
|
170
|
-
primitive?: KernelPrimitive;
|
|
171
147
|
budget: string;
|
|
172
148
|
} | {
|
|
173
149
|
kind: "milestone_advanced";
|
|
174
150
|
turn: number;
|
|
175
|
-
category?: KernelEventCategory;
|
|
176
|
-
primitive?: KernelPrimitive;
|
|
177
151
|
phase_id: string;
|
|
178
152
|
capabilities_unlocked: string[];
|
|
179
153
|
} | {
|
|
180
154
|
kind: "milestone_blocked";
|
|
181
155
|
turn: number;
|
|
182
|
-
category?: KernelEventCategory;
|
|
183
|
-
primitive?: KernelPrimitive;
|
|
184
156
|
phase_id: string;
|
|
185
157
|
reason: string;
|
|
186
|
-
} | {
|
|
187
|
-
kind: "milestone_evidence";
|
|
188
|
-
turn: number;
|
|
189
|
-
category?: KernelEventCategory;
|
|
190
|
-
primitive?: KernelPrimitive;
|
|
191
|
-
phase_id: string;
|
|
192
|
-
evidence: string[];
|
|
193
158
|
} | {
|
|
194
159
|
kind: "checkpoint_taken";
|
|
195
160
|
turn: number;
|
|
196
|
-
category?: KernelEventCategory;
|
|
197
|
-
primitive?: KernelPrimitive;
|
|
198
161
|
history_len: number;
|
|
162
|
+
} | {
|
|
163
|
+
kind: "entropy_sample";
|
|
164
|
+
turn: number;
|
|
165
|
+
score: number;
|
|
166
|
+
score_version: number;
|
|
167
|
+
rho: number;
|
|
168
|
+
repeat_pressure: number;
|
|
169
|
+
failure_rate: number;
|
|
170
|
+
rollbacks_in_window: number;
|
|
171
|
+
window_turns: number;
|
|
172
|
+
} | {
|
|
173
|
+
kind: "entropy_alert";
|
|
174
|
+
turn: number;
|
|
175
|
+
score: number;
|
|
176
|
+
threshold: number;
|
|
199
177
|
} | {
|
|
200
178
|
kind: "agent_process_changed";
|
|
201
179
|
turn: number;
|
|
202
|
-
category?: KernelEventCategory;
|
|
203
|
-
primitive?: KernelPrimitive;
|
|
204
180
|
agent_id: string;
|
|
205
181
|
parent_session_id: string;
|
|
206
182
|
role: string;
|
|
@@ -212,52 +188,52 @@ export type SessionEvent = {
|
|
|
212
188
|
} | {
|
|
213
189
|
kind: "memory_written";
|
|
214
190
|
turn: number;
|
|
215
|
-
category?: KernelEventCategory;
|
|
216
|
-
primitive?: KernelPrimitive;
|
|
217
191
|
memory_id: string;
|
|
218
192
|
memory_kind: string;
|
|
219
193
|
size_bytes: number;
|
|
220
194
|
} | {
|
|
221
195
|
kind: "memory_queried";
|
|
222
196
|
turn: number;
|
|
223
|
-
category?: KernelEventCategory;
|
|
224
|
-
primitive?: KernelPrimitive;
|
|
225
197
|
query_context: string;
|
|
226
198
|
requested_k: number;
|
|
227
199
|
requires_async_response: boolean;
|
|
228
200
|
} | {
|
|
229
201
|
kind: "memory_validation_failed";
|
|
230
202
|
turn: number;
|
|
231
|
-
category?: KernelEventCategory;
|
|
232
|
-
primitive?: KernelPrimitive;
|
|
233
203
|
memory_id: string;
|
|
234
204
|
error: string;
|
|
235
205
|
} | {
|
|
236
206
|
kind: "workflow_node_completed";
|
|
237
207
|
turn: number;
|
|
238
|
-
category?: KernelEventCategory;
|
|
239
|
-
primitive?: KernelPrimitive;
|
|
240
208
|
agent_id: string;
|
|
241
209
|
termination: string;
|
|
210
|
+
/** W-1: result-borne control signals, persisted so resume replays control flow faithfully —
|
|
211
|
+
* a classifier re-prunes its rejected branches, a recorded loop stop is honored. */
|
|
212
|
+
classify_branch?: string;
|
|
213
|
+
tournament_winner?: string;
|
|
214
|
+
loop_continue?: boolean;
|
|
215
|
+
/** W-1: the node's final output text — resume re-seeds the driver's outputs map from it so
|
|
216
|
+
* post-resume reduce/judge/dependent nodes still see their dependencies' outputs. */
|
|
217
|
+
output?: string;
|
|
242
218
|
} | {
|
|
243
219
|
kind: "workflow_nodes_submitted";
|
|
244
220
|
turn: number;
|
|
245
|
-
category?: KernelEventCategory;
|
|
246
|
-
primitive?: KernelPrimitive;
|
|
247
221
|
/** Kernel-shape (snake_case) submitted node specs — persisted so resume can re-apply them. */
|
|
248
222
|
nodes: Record<string, unknown>[];
|
|
223
|
+
/** R3-1: graph base index the batch was appended at (from the kernel's
|
|
224
|
+
* WorkflowNodesSubmitted observation) — lets resume rebuild exact indices. */
|
|
225
|
+
base_index?: number;
|
|
226
|
+
/** W-N3: the submitting node's agent id (absent = host/bootstrap). Resume DROPS batches whose
|
|
227
|
+
* submitter re-runs — it will re-submit — instead of duplicating their nodes. */
|
|
228
|
+
submitter_agent_id?: string;
|
|
249
229
|
} | {
|
|
250
230
|
kind: "workflow_batch_spawned";
|
|
251
231
|
turn: number;
|
|
252
|
-
category?: KernelEventCategory;
|
|
253
|
-
primitive?: KernelPrimitive;
|
|
254
232
|
node_count: number;
|
|
255
233
|
node_ids: string[];
|
|
256
234
|
} | {
|
|
257
235
|
kind: "workflow_completed";
|
|
258
236
|
turn: number;
|
|
259
|
-
category?: KernelEventCategory;
|
|
260
|
-
primitive?: KernelPrimitive;
|
|
261
237
|
completed: string[];
|
|
262
238
|
failed: string[];
|
|
263
239
|
total_nodes: number;
|
|
@@ -36,33 +36,56 @@ export declare function buildRunTerminalEvent(input: {
|
|
|
36
36
|
}): Extract<SessionEvent, {
|
|
37
37
|
kind: "run_terminal";
|
|
38
38
|
}>;
|
|
39
|
+
/** Build workflow_node_completed for persistence after a node finishes. W-1: carries the
|
|
40
|
+
* result-borne control signals + output so resume replays control flow and re-seeds outputs. */
|
|
39
41
|
export declare function buildWorkflowNodeCompletedEvent(input: {
|
|
40
42
|
turn: number;
|
|
41
43
|
agentId: string;
|
|
42
44
|
termination: string;
|
|
45
|
+
classifyBranch?: string;
|
|
46
|
+
tournamentWinner?: string;
|
|
47
|
+
loopContinue?: boolean;
|
|
48
|
+
output?: string;
|
|
43
49
|
}): Extract<SessionEvent, {
|
|
44
50
|
kind: "workflow_node_completed";
|
|
45
51
|
}>;
|
|
52
|
+
/** One recovered node completion: the agent id plus its persisted control signals and output. */
|
|
53
|
+
export interface RecoveredNodeCompletion {
|
|
54
|
+
agentId: string;
|
|
55
|
+
classifyBranch?: string;
|
|
56
|
+
tournamentWinner?: string;
|
|
57
|
+
loopContinue?: boolean;
|
|
58
|
+
output?: string;
|
|
59
|
+
}
|
|
46
60
|
/**
|
|
47
|
-
* Recover completed workflow node
|
|
48
|
-
*
|
|
49
|
-
*
|
|
61
|
+
* Recover completed workflow node records from a session event stream. Scans for
|
|
62
|
+
* workflow_node_completed events with termination "completed" and returns them WITH their
|
|
63
|
+
* result-borne control signals (W-1) — resumeWorkflow lowers these to the kernel's
|
|
64
|
+
* `resumed_results` so a classifier re-prunes and a loop stop is honored, and re-seeds the
|
|
65
|
+
* driver's outputs map from the persisted output text.
|
|
50
66
|
*/
|
|
51
67
|
export declare function recoverCompletedWorkflowNodes(events: Array<{
|
|
52
68
|
seq: number;
|
|
53
69
|
event: SessionEvent;
|
|
54
|
-
}>):
|
|
70
|
+
}>): RecoveredNodeCompletion[];
|
|
55
71
|
/** R3-1: build workflow_nodes_submitted for persistence after a runtime submission, so resume can
|
|
56
72
|
* re-apply it. `nodes` is the kernel-shape (snake_case) submitted node array. */
|
|
57
73
|
export declare function buildWorkflowNodesSubmittedEvent(input: {
|
|
58
74
|
turn: number;
|
|
59
75
|
nodes: Record<string, unknown>[];
|
|
76
|
+
baseIndex?: number;
|
|
77
|
+
submitterAgentId?: string;
|
|
60
78
|
}): Extract<SessionEvent, {
|
|
61
79
|
kind: "workflow_nodes_submitted";
|
|
62
80
|
}>;
|
|
63
81
|
/** R3-1: recover the runtime submission batches (in order) to rebuild `resumed_submissions` for
|
|
64
|
-
* resumeWorkflow, so dynamically-appended nodes are reconstructed.
|
|
82
|
+
* resumeWorkflow, so dynamically-appended nodes are reconstructed.
|
|
83
|
+
* `submitters` is parallel to `submissions` (undefined = host/bootstrap submission). */
|
|
65
84
|
export declare function recoverSubmittedWorkflowNodes(events: Array<{
|
|
66
85
|
seq: number;
|
|
67
86
|
event: SessionEvent;
|
|
68
|
-
}>):
|
|
87
|
+
}>): {
|
|
88
|
+
submissions: Record<string, unknown>[][];
|
|
89
|
+
bases: number[];
|
|
90
|
+
submitters: Array<string | undefined>;
|
|
91
|
+
};
|