@deepstrike/wasm 0.2.36 → 0.2.37
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 +1 -1
- 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 +38 -62
- package/dist/runtime/kernel-step.d.ts +9 -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 +12 -2
- package/dist/runtime/runner.js +149 -25
- package/dist/runtime/session-log.d.ts +15 -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 +2 -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";
|
|
@@ -959,7 +959,14 @@ export class RuntimeRunner {
|
|
|
959
959
|
catch { /* non-fatal */ }
|
|
960
960
|
}
|
|
961
961
|
}
|
|
962
|
-
yield {
|
|
962
|
+
yield {
|
|
963
|
+
type: "done",
|
|
964
|
+
iterations: turnsUsed,
|
|
965
|
+
totalTokens,
|
|
966
|
+
status,
|
|
967
|
+
// ③ loop-agent: surface the kernel-adjudicated after-round decision to the driver.
|
|
968
|
+
...(result?.paceDecision ? { paceDecision: result.paceDecision } : {}),
|
|
969
|
+
};
|
|
963
970
|
this.activeKernel = null;
|
|
964
971
|
this.currentSessionId = null;
|
|
965
972
|
}
|
|
@@ -1009,7 +1016,10 @@ export class RuntimeRunner {
|
|
|
1009
1016
|
const manifest = workflowNodeToManifest(node, parentSessionId);
|
|
1010
1017
|
// G4: surface remaining workflow budget so a coordinator node can size its submission.
|
|
1011
1018
|
const budgetNote = workflowBudgetNote(budget);
|
|
1012
|
-
|
|
1019
|
+
// W-N2: a DAG edge carries data — every dependent node sees its dependencies' outputs (the
|
|
1020
|
+
// kernel sends `input_agent_ids` for all dependents; judges/reduce keep their special paths).
|
|
1021
|
+
const depsNote = dependencyOutputsNote(node.input_agent_ids, outputs);
|
|
1022
|
+
const withBudget = (goal) => [goal, depsNote, budgetNote].filter(Boolean).join("\n\n");
|
|
1013
1023
|
const mkCtx = (goal) => ({
|
|
1014
1024
|
parentOpts: this.opts,
|
|
1015
1025
|
parentSessionId,
|
|
@@ -1018,6 +1028,10 @@ export class RuntimeRunner {
|
|
|
1018
1028
|
sessionLog: this.opts.sessionLog,
|
|
1019
1029
|
// M5 v2.1: this child IS a workflow node — its `start_workflow` flattens to this kernel.
|
|
1020
1030
|
isWorkflowNode: true,
|
|
1031
|
+
// W-N1: trusted workflow nodes run on the parent's execution plane (they carry no grant list
|
|
1032
|
+
// by design — filtering on the missing list ran every DAG node TOOL-LESS); quarantined nodes
|
|
1033
|
+
// stay deny-all filtered (they read untrusted content).
|
|
1034
|
+
toolAccess: (node.trust === "quarantined" ? "filtered" : "inherit"),
|
|
1021
1035
|
// #2-B-ii: the per-node abort signal the driver fires when the kernel preempts this node.
|
|
1022
1036
|
...(abortSignal ? { abortSignal } : {}),
|
|
1023
1037
|
});
|
|
@@ -1037,9 +1051,18 @@ export class RuntimeRunner {
|
|
|
1037
1051
|
const winnerId = winner === "right" ? node.judge_match.right : node.judge_match.left;
|
|
1038
1052
|
return withSignal(result, { tournamentWinner: winnerId });
|
|
1039
1053
|
}
|
|
1040
|
-
// A#2 v2 loop iteration: run the increment
|
|
1054
|
+
// A#2 v2 loop iteration: run the increment under the armed pacing trap (workflowNodeToSpec set
|
|
1055
|
+
// `loopRound`, and the iteration resumes the loop's stable session — transcript-as-carry).
|
|
1056
|
+
// DW-3 one vocabulary: the kernel-adjudicated `pace` verb IS the continuation signal
|
|
1057
|
+
// (stop → loopContinue=false); the legacy text-sniffed JSON blob survives only as the fallback
|
|
1058
|
+
// when no pace decision arrives (stub orchestrators, harness children), where no signal still
|
|
1059
|
+
// means "run to max_iters" (v1).
|
|
1041
1060
|
if (node.loop_max_iters != null) {
|
|
1042
|
-
const
|
|
1061
|
+
const iteration = Number(/-i(\d+)$/.exec(node.agent_id)?.[1] ?? "0");
|
|
1062
|
+
const result = await orchestrator.run(mkCtx(`${baseSpec.goal}\n\n${loopInstruction(node.loop_max_iters, iteration)}`));
|
|
1063
|
+
const pace = result.result.paceDecision;
|
|
1064
|
+
if (pace)
|
|
1065
|
+
return withSignal(result, { loopContinue: pace.action !== "stop" });
|
|
1043
1066
|
const cont = extractLoopContinue(textOf(result));
|
|
1044
1067
|
return cont === undefined ? result : withSignal(result, { loopContinue: cont });
|
|
1045
1068
|
}
|
|
@@ -1215,10 +1238,22 @@ export class RuntimeRunner {
|
|
|
1215
1238
|
parent_session_id: parentSessionId,
|
|
1216
1239
|
// W0-ABI resume: skip nodes already completed before an interruption.
|
|
1217
1240
|
...(opts?.resumedCompleted && opts.resumedCompleted.length ? { resumed_completed: opts.resumedCompleted } : {}),
|
|
1241
|
+
// W-1: signal-carrying completion records (classify branch / loop stop replay).
|
|
1242
|
+
...(opts?.resumedResults?.length
|
|
1243
|
+
? {
|
|
1244
|
+
resumed_results: opts.resumedResults.map(r => ({
|
|
1245
|
+
agent_id: r.agentId,
|
|
1246
|
+
...(r.classifyBranch !== undefined ? { classify_branch: r.classifyBranch } : {}),
|
|
1247
|
+
...(r.tournamentWinner !== undefined ? { tournament_winner: r.tournamentWinner } : {}),
|
|
1248
|
+
...(r.loopContinue !== undefined ? { loop_continue: r.loopContinue } : {}),
|
|
1249
|
+
})),
|
|
1250
|
+
}
|
|
1251
|
+
: {}),
|
|
1218
1252
|
// R3-1: re-apply recorded runtime submissions so dynamically-appended nodes are reconstructed.
|
|
1219
1253
|
...(opts?.resumedSubmissions && opts.resumedSubmissions.length ? { resumed_submissions: opts.resumedSubmissions } : {}),
|
|
1254
|
+
...(opts?.resumedSubmissionBases && opts.resumedSubmissionBases.length ? { resumed_submission_bases: opts.resumedSubmissionBases } : {}),
|
|
1220
1255
|
});
|
|
1221
|
-
return await this.driveWorkflow(observations, parentSessionId, runtime);
|
|
1256
|
+
return await this.driveWorkflow(observations, parentSessionId, runtime, opts?.resumedOutputs);
|
|
1222
1257
|
}
|
|
1223
1258
|
finally {
|
|
1224
1259
|
if (bootstrapped) {
|
|
@@ -1241,6 +1276,18 @@ export class RuntimeRunner {
|
|
|
1241
1276
|
const parentSessionId = this.currentSessionId;
|
|
1242
1277
|
const runtime = this.activeKernel;
|
|
1243
1278
|
const observations = kernelApply(runtime, this.pendingObservations, submitWorkflowToKernel(spec, parentSessionId, opts?.submitterAgentId));
|
|
1279
|
+
// W-3: persist the agent-authored batch (bootstrap base 0 / flatten base N — the kernel now
|
|
1280
|
+
// announces BOTH) so an interrupted authored workflow reconstructs on resume; the host never
|
|
1281
|
+
// had this spec, unlike the `runWorkflow` path.
|
|
1282
|
+
const submitted = observations.find(o => o.kind === "workflow_nodes_submitted");
|
|
1283
|
+
if (submitted) {
|
|
1284
|
+
await this.opts.sessionLog.append(parentSessionId, buildWorkflowNodesSubmittedEvent({
|
|
1285
|
+
turn: runtime.turn(),
|
|
1286
|
+
nodes: workflowSpecToKernel(spec).nodes ?? [],
|
|
1287
|
+
baseIndex: submitted.base,
|
|
1288
|
+
submitterAgentId: opts?.submitterAgentId,
|
|
1289
|
+
}));
|
|
1290
|
+
}
|
|
1244
1291
|
return this.driveWorkflow(observations, parentSessionId, runtime);
|
|
1245
1292
|
}
|
|
1246
1293
|
/**
|
|
@@ -1297,7 +1344,7 @@ export class RuntimeRunner {
|
|
|
1297
1344
|
* `submit_workflow`): run each kernel-emitted batch in parallel, feed completions back (appending any
|
|
1298
1345
|
* agent-submitted nodes first), and loop until the kernel reports the workflow complete.
|
|
1299
1346
|
*/
|
|
1300
|
-
async driveWorkflow(initial, parentSessionId, runtime) {
|
|
1347
|
+
async driveWorkflow(initial, parentSessionId, runtime, seedOutputs) {
|
|
1301
1348
|
const observations = initial;
|
|
1302
1349
|
const orchestrator = this.opts.subAgentOrchestrator ?? defaultSubAgentOrchestrator;
|
|
1303
1350
|
const collectNodes = (obs) => obs.find(o => o.kind === "workflow_batch_spawned")?.nodes ?? [];
|
|
@@ -1310,7 +1357,9 @@ export class RuntimeRunner {
|
|
|
1310
1357
|
let nodes = collectNodes(observations);
|
|
1311
1358
|
let budget = collectBudget(observations);
|
|
1312
1359
|
// G2: each completed node's output, keyed by agent id — a reduce node reads its deps' outputs.
|
|
1313
|
-
|
|
1360
|
+
// W-1: on resume it is pre-seeded from the persisted node outputs, so post-resume dependents
|
|
1361
|
+
// still see their (pre-crash) dependencies' outputs.
|
|
1362
|
+
const outputs = new Map(seedOutputs ?? []);
|
|
1314
1363
|
for (;;) {
|
|
1315
1364
|
if (nodes.length === 0)
|
|
1316
1365
|
return { completed: [], failed: [], outputs: Object.fromEntries(outputs) };
|
|
@@ -1330,7 +1379,13 @@ export class RuntimeRunner {
|
|
|
1330
1379
|
for (const result of results) {
|
|
1331
1380
|
// G2: record this node's output so a downstream reduce node can consume it.
|
|
1332
1381
|
const outContent = result.result.finalMessage?.content;
|
|
1333
|
-
|
|
1382
|
+
const outText = typeof outContent === "string" ? outContent : outContent != null ? JSON.stringify(outContent) : "";
|
|
1383
|
+
outputs.set(result.agentId, outText);
|
|
1384
|
+
// A loop iteration completes under `wf-node{N}-i{k}` but its dependents consume the STABLE
|
|
1385
|
+
// node id `wf-node{N}` — alias it so the LAST iteration's output is what dependents see.
|
|
1386
|
+
const stableId = result.agentId.replace(/-i\d+$/, "");
|
|
1387
|
+
if (stableId !== result.agentId)
|
|
1388
|
+
outputs.set(stableId, outText);
|
|
1334
1389
|
// R3-1: if this node's agent submitted more nodes, append them to the parent DAG BEFORE
|
|
1335
1390
|
// reporting the node's completion — the workflow is still active, so even a last-node
|
|
1336
1391
|
// submission keeps the DAG alive.
|
|
@@ -1341,10 +1396,15 @@ export class RuntimeRunner {
|
|
|
1341
1396
|
const subObs = kernelApply(runtime, this.pendingObservations, submitEvent);
|
|
1342
1397
|
nextNodes.push(...collectNodes(subObs));
|
|
1343
1398
|
budget = collectBudget(subObs) ?? budget;
|
|
1344
|
-
// R3-1: persist the submission (kernel-shape nodes)
|
|
1399
|
+
// R3-1: persist the submission (kernel-shape nodes) + its kernel-reported base index
|
|
1400
|
+
// so resume can re-apply the batch at the exact original graph position. W-N3: also the
|
|
1401
|
+
// submitter, so resume drops batches whose submitter re-runs (it will re-submit).
|
|
1402
|
+
const submitted = subObs.find(o => o.kind === "workflow_nodes_submitted");
|
|
1345
1403
|
await this.opts.sessionLog.append(parentSessionId, buildWorkflowNodesSubmittedEvent({
|
|
1346
1404
|
turn: runtime.turn(),
|
|
1347
1405
|
nodes: submitEvent.nodes ?? [],
|
|
1406
|
+
baseIndex: submitted?.base,
|
|
1407
|
+
submitterAgentId: result.agentId,
|
|
1348
1408
|
}));
|
|
1349
1409
|
}
|
|
1350
1410
|
const obs = kernelApply(runtime, this.pendingObservations, {
|
|
@@ -1356,11 +1416,17 @@ export class RuntimeRunner {
|
|
|
1356
1416
|
const d = findDone(obs);
|
|
1357
1417
|
if (d)
|
|
1358
1418
|
done = d;
|
|
1359
|
-
// Persist node completion for resume recovery.
|
|
1419
|
+
// Persist node completion for resume recovery. W-1: the result-borne control signals ride
|
|
1420
|
+
// along (a resumed classifier re-prunes; a recorded loop stop is honored) plus the output
|
|
1421
|
+
// text (post-resume dependents/reduce still see this node's output).
|
|
1360
1422
|
await this.opts.sessionLog.append(parentSessionId, buildWorkflowNodeCompletedEvent({
|
|
1361
1423
|
turn: runtime.turn(),
|
|
1362
1424
|
agentId: result.agentId,
|
|
1363
1425
|
termination: result.result.termination,
|
|
1426
|
+
classifyBranch: result.result.classifyBranch,
|
|
1427
|
+
tournamentWinner: result.result.tournamentWinner,
|
|
1428
|
+
loopContinue: result.result.loopContinue,
|
|
1429
|
+
...(result.result.termination === "completed" && outText ? { output: outText } : {}),
|
|
1364
1430
|
}));
|
|
1365
1431
|
}
|
|
1366
1432
|
if (done && nextNodes.length === 0) {
|
|
@@ -1371,8 +1437,9 @@ export class RuntimeRunner {
|
|
|
1371
1437
|
}
|
|
1372
1438
|
/**
|
|
1373
1439
|
* Resume a workflow from the parent session's completed nodes.
|
|
1374
|
-
* Reads the session log, extracts completed workflow node
|
|
1375
|
-
* calls runWorkflow
|
|
1440
|
+
* Reads the session log, extracts completed workflow node records (with their W-1 control
|
|
1441
|
+
* signals + outputs), and calls runWorkflow so the kernel skips those nodes, replays control
|
|
1442
|
+
* flow (classify prune / loop stop), and the driver re-seeds its outputs map.
|
|
1376
1443
|
*/
|
|
1377
1444
|
async resumeWorkflow(spec, opts) {
|
|
1378
1445
|
// Standalone resume: a stateless handler passes the prior `sessionId`; mid-run callers omit it.
|
|
@@ -1381,9 +1448,34 @@ export class RuntimeRunner {
|
|
|
1381
1448
|
throw new Error("resumeWorkflow requires an active parent run or an explicit sessionId");
|
|
1382
1449
|
}
|
|
1383
1450
|
const events = await this.opts.sessionLog.read(sessionId);
|
|
1384
|
-
const
|
|
1385
|
-
const
|
|
1386
|
-
|
|
1451
|
+
const resumedResults = recoverCompletedWorkflowNodes(events);
|
|
1452
|
+
const completedIds = new Set(resumedResults.map(r => r.agentId));
|
|
1453
|
+
const recovered = recoverSubmittedWorkflowNodes(events);
|
|
1454
|
+
// W-N3: DROP batches whose submitter did NOT complete — that node re-runs on resume and will
|
|
1455
|
+
// re-submit its batch; replaying the logged copy too would duplicate its nodes in the DAG.
|
|
1456
|
+
// Only safe with exact bases (the dropped batch's slots become inert placeholders); a legacy
|
|
1457
|
+
// order-only log keeps every batch, since dropping would shift all later indices.
|
|
1458
|
+
let { submissions, bases } = recovered;
|
|
1459
|
+
if (bases.length === submissions.length && submissions.length > 0) {
|
|
1460
|
+
const keep = recovered.submitters.map(s => s === undefined || completedIds.has(s));
|
|
1461
|
+
submissions = submissions.filter((_, i) => keep[i]);
|
|
1462
|
+
bases = bases.filter((_, i) => keep[i]);
|
|
1463
|
+
}
|
|
1464
|
+
const resumedOutputs = new Map(resumedResults.filter(r => r.output).map(r => [r.agentId, r.output]));
|
|
1465
|
+
// Alias loop iterations onto their stable node id (last iteration wins) — dependents consume
|
|
1466
|
+
// `wf-node{N}`, not `wf-node{N}-i{k}`.
|
|
1467
|
+
for (const r of resumedResults) {
|
|
1468
|
+
const stableId = r.agentId.replace(/-i\d+$/, "");
|
|
1469
|
+
if (stableId !== r.agentId && r.output)
|
|
1470
|
+
resumedOutputs.set(stableId, r.output);
|
|
1471
|
+
}
|
|
1472
|
+
return this.runWorkflow(spec, {
|
|
1473
|
+
resumedResults,
|
|
1474
|
+
resumedSubmissions: submissions,
|
|
1475
|
+
resumedSubmissionBases: bases,
|
|
1476
|
+
resumedOutputs,
|
|
1477
|
+
sessionId,
|
|
1478
|
+
});
|
|
1387
1479
|
}
|
|
1388
1480
|
async appendObservations(sessionId, runtime, nextArchiveStart) {
|
|
1389
1481
|
const turn = runtime.turn();
|
|
@@ -1422,12 +1514,23 @@ export class RuntimeRunner {
|
|
|
1422
1514
|
const compressedSeq = await this.opts.sessionLog.append(sessionId, event);
|
|
1423
1515
|
if (event.kind === "compressed") {
|
|
1424
1516
|
nextArchiveStart = compressedSeq + 1;
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1517
|
+
// One compaction = one kernel observation: the page_out session record (and the
|
|
1518
|
+
// semantic-archive branch) is DERIVED from Compressed.tier_hint, preserving the
|
|
1519
|
+
// session-log format and OsSnapshot page_out_count.
|
|
1520
|
+
const archived = obs.archived;
|
|
1521
|
+
if (obs.tier_hint && Array.isArray(archived) && archived.length > 0) {
|
|
1522
|
+
await this.opts.sessionLog.append(sessionId, {
|
|
1523
|
+
kind: "page_out",
|
|
1524
|
+
turn: obs.turn ?? turn,
|
|
1525
|
+
action: compressionAction(obs.action),
|
|
1526
|
+
summary: obs.summary,
|
|
1527
|
+
tier_hint: obs.tier_hint ?? "durable",
|
|
1528
|
+
message_count: archived.length,
|
|
1529
|
+
});
|
|
1530
|
+
if (obs.tier_hint === "semantic") {
|
|
1531
|
+
void this.archiveSemanticPageOut(archived, compressionAction(obs.action));
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1431
1534
|
}
|
|
1432
1535
|
// K4: a sprint renewal dropped the old history — including any earlier memory hits — so
|
|
1433
1536
|
// re-run the preQueryMemory prefetch for the new sprint (live observations only).
|
|
@@ -1443,10 +1546,14 @@ export class RuntimeRunner {
|
|
|
1443
1546
|
* re-fired after each sprint renewal (renewal drops the old history INCLUDING earlier memory
|
|
1444
1547
|
* hits). Errs-open throughout. */
|
|
1445
1548
|
async prefetchMemoryIntoHistory(runtime, phase) {
|
|
1446
|
-
if (!this.opts.
|
|
1549
|
+
if (!this.opts.dreamStore || !this.opts.agentId)
|
|
1447
1550
|
return;
|
|
1551
|
+
// P10: recall is default-on (CC session-start recall) — with no hook configured,
|
|
1552
|
+
// the goal itself is the query. preQueryMemory stays as the targeting override.
|
|
1553
|
+
const preQuery = this.opts.preQueryMemory
|
|
1554
|
+
?? ((ctx) => [ctx.goal]);
|
|
1448
1555
|
try {
|
|
1449
|
-
const queries = await
|
|
1556
|
+
const queries = await preQuery({ goal: this.currentGoal, phase });
|
|
1450
1557
|
const lines = [];
|
|
1451
1558
|
for (const q of queries ?? []) {
|
|
1452
1559
|
if (typeof q !== "string" || !q.trim())
|
|
@@ -1473,8 +1580,12 @@ export class RuntimeRunner {
|
|
|
1473
1580
|
? await this.opts.dreamSummarizer.summarize(archived, { action })
|
|
1474
1581
|
: await summarizeForLongTermMemory(this.opts.dreamProvider ?? this.opts.provider, archived, this.opts.dreamSystemPrompt);
|
|
1475
1582
|
const existing = await this.opts.dreamStore.loadMemories(this.opts.agentId);
|
|
1583
|
+
// P2 write-funnel (wasm surface has no kernel write gate yet): jaccard dedup +
|
|
1584
|
+
// advisory 0.6 score — an automatic summary must never outrank curated content.
|
|
1585
|
+
if (existing.some(e => jaccardSimilarity(e.text, summary) >= 0.9))
|
|
1586
|
+
return;
|
|
1476
1587
|
await this.opts.dreamStore.commit(this.opts.agentId, {
|
|
1477
|
-
toAdd: [{ text: summary, score:
|
|
1588
|
+
toAdd: [{ text: summary, score: 0.6, metadata: { source: "semantic_page_out", action } }],
|
|
1478
1589
|
toRemoveIndices: [],
|
|
1479
1590
|
stats: {
|
|
1480
1591
|
insightsProcessed: 1,
|
|
@@ -1489,6 +1600,19 @@ export class RuntimeRunner {
|
|
|
1489
1600
|
}
|
|
1490
1601
|
}
|
|
1491
1602
|
}
|
|
1603
|
+
/** Word-set jaccard similarity — the curator's dedup rule at the write funnel. */
|
|
1604
|
+
function jaccardSimilarity(a, b) {
|
|
1605
|
+
const sa = new Set(a.split(/\s+/).filter(Boolean));
|
|
1606
|
+
const sb = new Set(b.split(/\s+/).filter(Boolean));
|
|
1607
|
+
if (sa.size === 0 && sb.size === 0)
|
|
1608
|
+
return 1;
|
|
1609
|
+
let inter = 0;
|
|
1610
|
+
for (const w of sa)
|
|
1611
|
+
if (sb.has(w))
|
|
1612
|
+
inter++;
|
|
1613
|
+
const union = sa.size + sb.size - inter;
|
|
1614
|
+
return union === 0 ? 0 : inter / union;
|
|
1615
|
+
}
|
|
1492
1616
|
async function summarizeForLongTermMemory(provider, archived, systemPrompt) {
|
|
1493
1617
|
const transcript = archived
|
|
1494
1618
|
.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,51 @@ 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;
|
|
199
162
|
} | {
|
|
200
163
|
kind: "agent_process_changed";
|
|
201
164
|
turn: number;
|
|
202
|
-
category?: KernelEventCategory;
|
|
203
|
-
primitive?: KernelPrimitive;
|
|
204
165
|
agent_id: string;
|
|
205
166
|
parent_session_id: string;
|
|
206
167
|
role: string;
|
|
@@ -212,52 +173,52 @@ export type SessionEvent = {
|
|
|
212
173
|
} | {
|
|
213
174
|
kind: "memory_written";
|
|
214
175
|
turn: number;
|
|
215
|
-
category?: KernelEventCategory;
|
|
216
|
-
primitive?: KernelPrimitive;
|
|
217
176
|
memory_id: string;
|
|
218
177
|
memory_kind: string;
|
|
219
178
|
size_bytes: number;
|
|
220
179
|
} | {
|
|
221
180
|
kind: "memory_queried";
|
|
222
181
|
turn: number;
|
|
223
|
-
category?: KernelEventCategory;
|
|
224
|
-
primitive?: KernelPrimitive;
|
|
225
182
|
query_context: string;
|
|
226
183
|
requested_k: number;
|
|
227
184
|
requires_async_response: boolean;
|
|
228
185
|
} | {
|
|
229
186
|
kind: "memory_validation_failed";
|
|
230
187
|
turn: number;
|
|
231
|
-
category?: KernelEventCategory;
|
|
232
|
-
primitive?: KernelPrimitive;
|
|
233
188
|
memory_id: string;
|
|
234
189
|
error: string;
|
|
235
190
|
} | {
|
|
236
191
|
kind: "workflow_node_completed";
|
|
237
192
|
turn: number;
|
|
238
|
-
category?: KernelEventCategory;
|
|
239
|
-
primitive?: KernelPrimitive;
|
|
240
193
|
agent_id: string;
|
|
241
194
|
termination: string;
|
|
195
|
+
/** W-1: result-borne control signals, persisted so resume replays control flow faithfully —
|
|
196
|
+
* a classifier re-prunes its rejected branches, a recorded loop stop is honored. */
|
|
197
|
+
classify_branch?: string;
|
|
198
|
+
tournament_winner?: string;
|
|
199
|
+
loop_continue?: boolean;
|
|
200
|
+
/** W-1: the node's final output text — resume re-seeds the driver's outputs map from it so
|
|
201
|
+
* post-resume reduce/judge/dependent nodes still see their dependencies' outputs. */
|
|
202
|
+
output?: string;
|
|
242
203
|
} | {
|
|
243
204
|
kind: "workflow_nodes_submitted";
|
|
244
205
|
turn: number;
|
|
245
|
-
category?: KernelEventCategory;
|
|
246
|
-
primitive?: KernelPrimitive;
|
|
247
206
|
/** Kernel-shape (snake_case) submitted node specs — persisted so resume can re-apply them. */
|
|
248
207
|
nodes: Record<string, unknown>[];
|
|
208
|
+
/** R3-1: graph base index the batch was appended at (from the kernel's
|
|
209
|
+
* WorkflowNodesSubmitted observation) — lets resume rebuild exact indices. */
|
|
210
|
+
base_index?: number;
|
|
211
|
+
/** W-N3: the submitting node's agent id (absent = host/bootstrap). Resume DROPS batches whose
|
|
212
|
+
* submitter re-runs — it will re-submit — instead of duplicating their nodes. */
|
|
213
|
+
submitter_agent_id?: string;
|
|
249
214
|
} | {
|
|
250
215
|
kind: "workflow_batch_spawned";
|
|
251
216
|
turn: number;
|
|
252
|
-
category?: KernelEventCategory;
|
|
253
|
-
primitive?: KernelPrimitive;
|
|
254
217
|
node_count: number;
|
|
255
218
|
node_ids: string[];
|
|
256
219
|
} | {
|
|
257
220
|
kind: "workflow_completed";
|
|
258
221
|
turn: number;
|
|
259
|
-
category?: KernelEventCategory;
|
|
260
|
-
primitive?: KernelPrimitive;
|
|
261
222
|
completed: string[];
|
|
262
223
|
failed: string[];
|
|
263
224
|
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
|
+
};
|
|
@@ -48,24 +48,38 @@ export function buildRunTerminalEvent(input) {
|
|
|
48
48
|
total_tokens: Math.max(0, input.totalTokens),
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
+
/** Build workflow_node_completed for persistence after a node finishes. W-1: carries the
|
|
52
|
+
* result-borne control signals + output so resume replays control flow and re-seeds outputs. */
|
|
51
53
|
export function buildWorkflowNodeCompletedEvent(input) {
|
|
52
54
|
return {
|
|
53
55
|
kind: "workflow_node_completed",
|
|
54
56
|
turn: input.turn,
|
|
55
57
|
agent_id: input.agentId,
|
|
56
58
|
termination: input.termination,
|
|
59
|
+
...(input.classifyBranch !== undefined ? { classify_branch: input.classifyBranch } : {}),
|
|
60
|
+
...(input.tournamentWinner !== undefined ? { tournament_winner: input.tournamentWinner } : {}),
|
|
61
|
+
...(input.loopContinue !== undefined ? { loop_continue: input.loopContinue } : {}),
|
|
62
|
+
...(input.output ? { output: input.output } : {}),
|
|
57
63
|
};
|
|
58
64
|
}
|
|
59
65
|
/**
|
|
60
|
-
* Recover completed workflow node
|
|
61
|
-
*
|
|
62
|
-
*
|
|
66
|
+
* Recover completed workflow node records from a session event stream. Scans for
|
|
67
|
+
* workflow_node_completed events with termination "completed" and returns them WITH their
|
|
68
|
+
* result-borne control signals (W-1) — resumeWorkflow lowers these to the kernel's
|
|
69
|
+
* `resumed_results` so a classifier re-prunes and a loop stop is honored, and re-seeds the
|
|
70
|
+
* driver's outputs map from the persisted output text.
|
|
63
71
|
*/
|
|
64
72
|
export function recoverCompletedWorkflowNodes(events) {
|
|
65
73
|
const completed = [];
|
|
66
74
|
for (const { event } of events) {
|
|
67
75
|
if (event.kind === "workflow_node_completed" && event.termination === "completed") {
|
|
68
|
-
completed.push(
|
|
76
|
+
completed.push({
|
|
77
|
+
agentId: event.agent_id,
|
|
78
|
+
...(event.classify_branch !== undefined ? { classifyBranch: event.classify_branch } : {}),
|
|
79
|
+
...(event.tournament_winner !== undefined ? { tournamentWinner: event.tournament_winner } : {}),
|
|
80
|
+
...(event.loop_continue !== undefined ? { loopContinue: event.loop_continue } : {}),
|
|
81
|
+
...(event.output !== undefined ? { output: event.output } : {}),
|
|
82
|
+
});
|
|
69
83
|
}
|
|
70
84
|
}
|
|
71
85
|
return completed;
|
|
@@ -73,15 +87,30 @@ export function recoverCompletedWorkflowNodes(events) {
|
|
|
73
87
|
/** R3-1: build workflow_nodes_submitted for persistence after a runtime submission, so resume can
|
|
74
88
|
* re-apply it. `nodes` is the kernel-shape (snake_case) submitted node array. */
|
|
75
89
|
export function buildWorkflowNodesSubmittedEvent(input) {
|
|
76
|
-
return {
|
|
90
|
+
return {
|
|
91
|
+
kind: "workflow_nodes_submitted",
|
|
92
|
+
turn: input.turn,
|
|
93
|
+
nodes: input.nodes,
|
|
94
|
+
...(input.baseIndex !== undefined ? { base_index: input.baseIndex } : {}),
|
|
95
|
+
...(input.submitterAgentId !== undefined ? { submitter_agent_id: input.submitterAgentId } : {}),
|
|
96
|
+
};
|
|
77
97
|
}
|
|
78
98
|
/** R3-1: recover the runtime submission batches (in order) to rebuild `resumed_submissions` for
|
|
79
|
-
* resumeWorkflow, so dynamically-appended nodes are reconstructed.
|
|
99
|
+
* resumeWorkflow, so dynamically-appended nodes are reconstructed.
|
|
100
|
+
* `submitters` is parallel to `submissions` (undefined = host/bootstrap submission). */
|
|
80
101
|
export function recoverSubmittedWorkflowNodes(events) {
|
|
81
102
|
const submissions = [];
|
|
103
|
+
const bases = [];
|
|
104
|
+
const submitters = [];
|
|
82
105
|
for (const { event } of events) {
|
|
83
|
-
if (event.kind === "workflow_nodes_submitted")
|
|
106
|
+
if (event.kind === "workflow_nodes_submitted") {
|
|
84
107
|
submissions.push(event.nodes);
|
|
108
|
+
submitters.push(event.submitter_agent_id);
|
|
109
|
+
// Absent on legacy logs → order-only replay (bases array stays parallel-short only
|
|
110
|
+
// if ALL records carry it; a mixed log degrades to order-only for safety).
|
|
111
|
+
if (event.base_index !== undefined)
|
|
112
|
+
bases.push(event.base_index);
|
|
113
|
+
}
|
|
85
114
|
}
|
|
86
|
-
return submissions;
|
|
115
|
+
return { submissions, bases: bases.length === submissions.length ? bases : [], submitters };
|
|
87
116
|
}
|