loopforge 1.8.0 → 1.12.0
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/backends/fs.d.ts.map +1 -1
- package/dist/backends/fs.js +1 -2
- package/dist/backends/fs.js.map +1 -1
- package/dist/backends/interface.d.ts +2 -0
- package/dist/backends/interface.d.ts.map +1 -1
- package/dist/builder.d.ts +6 -10
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +128 -63
- package/dist/builder.js.map +1 -1
- package/dist/engine.d.ts +2 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +40 -53
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +10 -2
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +207 -117
- package/dist/loop-compiler.js.map +1 -1
- package/dist/mcp/session.d.ts +3 -1
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +27 -20
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.js +4 -4
- package/dist/memory-bridge.js +1 -1
- package/dist/memory-bridge.js.map +1 -1
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +1 -1
- package/dist/policy.d.ts +9 -9
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +5 -28
- package/dist/policy.js.map +1 -1
- package/dist/protocol.d.ts +38 -15
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +19 -7
- package/dist/protocol.js.map +1 -1
- package/dist/replay.js +7 -7
- package/dist/replay.js.map +1 -1
- package/dist/runtime.d.ts +3 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +27 -40
- package/dist/runtime.js.map +1 -1
- package/dist/tests/builder.test.js +40 -28
- package/dist/tests/builder.test.js.map +1 -1
- package/dist/tests/engine.test.js +13 -17
- package/dist/tests/engine.test.js.map +1 -1
- package/dist/tests/generate-schema.test.js +10 -7
- package/dist/tests/generate-schema.test.js.map +1 -1
- package/dist/tests/loop-compiler.test.js +245 -27
- package/dist/tests/loop-compiler.test.js.map +1 -1
- package/dist/tests/mcp.test.js +9 -9
- package/dist/tests/mcp.test.js.map +1 -1
- package/dist/tests/memory-bridge.test.js +0 -1
- package/dist/tests/memory-bridge.test.js.map +1 -1
- package/dist/tests/policy.test.js +5 -15
- package/dist/tests/policy.test.js.map +1 -1
- package/dist/tests/protocol.test.js +3 -2
- package/dist/tests/protocol.test.js.map +1 -1
- package/dist/tests/replay.test.js +9 -9
- package/dist/tests/replay.test.js.map +1 -1
- package/dist/tests/runtime.test.js +8 -8
- package/dist/tests/runtime.test.js.map +1 -1
- package/dist/tests/verification-gate.test.js +1 -1
- package/dist/tests/verification-gate.test.js.map +1 -1
- package/dist/verification-gate.d.ts +2 -2
- package/dist/verification-gate.js +2 -2
- package/loop_policy.json +5 -23
- package/package.json +1 -1
- package/skills/perception/SKILL.md +12 -11
package/dist/loop-compiler.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { createHash } from "node:crypto";
|
|
13
13
|
import { readFileSync } from "node:fs";
|
|
14
14
|
import { getPolicy } from "./policy.js";
|
|
15
|
-
import { AgentStatus, makeLoopCompileResponse, makeLoopHealth, makeLoopObjective, makeRollingSummary, makeTaskAlignment, } from "./protocol.js";
|
|
15
|
+
import { AgentStatus, makeCheckpointSummary, makeLoopCompileResponse, makeLoopHealth, makeLoopObjective, makeRollingSummary, makeTaskAlignment, } from "./protocol.js";
|
|
16
16
|
import { routeTechniqueAdaptive } from "./builder.js";
|
|
17
17
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
18
18
|
// Repair cue detection
|
|
@@ -224,8 +224,7 @@ export function getPreviousRound(loopId, roundNum, vaultContext) {
|
|
|
224
224
|
return {
|
|
225
225
|
goal_id: lineage.goal_id ?? "",
|
|
226
226
|
goal_text_hash: lineage.goal_text_hash ?? "",
|
|
227
|
-
|
|
228
|
-
success: r.success ?? true,
|
|
227
|
+
success: r.success ?? lineage.success ?? true,
|
|
229
228
|
task: r.task ?? r.user_intent ?? "",
|
|
230
229
|
constraints_active: lineage.constraints_active ?? [],
|
|
231
230
|
prompt_text: r.full_prompt ?? "",
|
|
@@ -243,7 +242,7 @@ function getRecentRounds(loopId, n, vaultContext) {
|
|
|
243
242
|
const lineage = (r.loop_lineage || r.lineage || {});
|
|
244
243
|
if (lineage.loop_id === loopId) {
|
|
245
244
|
rounds.push({
|
|
246
|
-
|
|
245
|
+
success: r.success ?? lineage.success ?? false,
|
|
247
246
|
round: lineage.round ?? 0,
|
|
248
247
|
goal_text_hash: lineage.goal_text_hash ?? "",
|
|
249
248
|
});
|
|
@@ -303,7 +302,7 @@ function strategyCollapse(loopId, vaultContext) {
|
|
|
303
302
|
const recent = getRecentRounds(loopId, 3, vaultContext);
|
|
304
303
|
if (recent.length < 3)
|
|
305
304
|
return false;
|
|
306
|
-
return recent.every((r) => r.
|
|
305
|
+
return recent.every((r) => r.success === false);
|
|
307
306
|
}
|
|
308
307
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
309
308
|
// Constraint Retirement (v3.5)
|
|
@@ -412,7 +411,7 @@ function countProgressStallRounds(loopId, currentRound, vaultContext, threshold)
|
|
|
412
411
|
}
|
|
413
412
|
/** Detect repeated failure patterns in sampled rounds.
|
|
414
413
|
* A failure pattern = 2+ consecutive rounds where:
|
|
415
|
-
* 1.
|
|
414
|
+
* 1. success === false (failed)
|
|
416
415
|
* 2. same technique was used
|
|
417
416
|
* 3. task text is similar (Jaccard > 0.4)
|
|
418
417
|
*
|
|
@@ -424,7 +423,7 @@ function detectFailurePatterns(sampled) {
|
|
|
424
423
|
let runStart = -1;
|
|
425
424
|
for (let i = 0; i < sampled.length; i++) {
|
|
426
425
|
const r = sampled[i];
|
|
427
|
-
const isFailed = r.
|
|
426
|
+
const isFailed = r.success === false;
|
|
428
427
|
if (isFailed && runStart === -1) {
|
|
429
428
|
runStart = i;
|
|
430
429
|
}
|
|
@@ -445,7 +444,7 @@ function detectFailurePatterns(sampled) {
|
|
|
445
444
|
}
|
|
446
445
|
return patterns;
|
|
447
446
|
}
|
|
448
|
-
/** Classify a run of consecutive
|
|
447
|
+
/** Classify a run of consecutive failed rounds as a failure pattern.
|
|
449
448
|
* Returns null if the rounds are too dissimilar to be the same pattern. */
|
|
450
449
|
function classifyRun(run) {
|
|
451
450
|
if (run.length < 2)
|
|
@@ -465,9 +464,9 @@ function classifyRun(run) {
|
|
|
465
464
|
}
|
|
466
465
|
const rounds = `R${run[0].round}-R${run[run.length - 1].round}`;
|
|
467
466
|
const taskPreview = run[0].task.slice(0, 80);
|
|
468
|
-
const
|
|
467
|
+
const failureRate = `${run.length}/${run.length}`;
|
|
469
468
|
return (`technique '${technique}' on task '${taskPreview}' ` +
|
|
470
|
-
`failed ${run.length} consecutive rounds (${rounds}
|
|
469
|
+
`failed ${run.length} consecutive rounds (${rounds}) — ` +
|
|
471
470
|
`consider strategy change`);
|
|
472
471
|
}
|
|
473
472
|
/** Extract round numbers referenced in failure pattern descriptions.
|
|
@@ -494,7 +493,7 @@ function extractFailureRoundNums(patterns) {
|
|
|
494
493
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
495
494
|
// Rolling Summary (v3.5)
|
|
496
495
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
497
|
-
export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
496
|
+
export function buildRollingSummary(loopId, currentRound, vaultContext, sinceRound) {
|
|
498
497
|
if (vaultContext === null)
|
|
499
498
|
return null;
|
|
500
499
|
const results = vaultContext.results || [];
|
|
@@ -502,6 +501,7 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
502
501
|
return null;
|
|
503
502
|
const policy = getPolicy();
|
|
504
503
|
const window = policy.summary.window;
|
|
504
|
+
const minRound = sinceRound ?? 0;
|
|
505
505
|
// Collect rounds matching this loop_id, excluding current
|
|
506
506
|
const rounds = [];
|
|
507
507
|
for (const r of results) {
|
|
@@ -511,9 +511,11 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
511
511
|
const rnd = lineage.round;
|
|
512
512
|
if (rnd >= currentRound)
|
|
513
513
|
continue;
|
|
514
|
+
if (rnd < minRound)
|
|
515
|
+
continue;
|
|
514
516
|
rounds.push({
|
|
515
517
|
round: rnd,
|
|
516
|
-
|
|
518
|
+
success: r.success ?? lineage.success ?? false,
|
|
517
519
|
task: r.task ?? r.user_intent ?? "",
|
|
518
520
|
output_summary: r.output_summary ?? "",
|
|
519
521
|
constraint_violations: r.constraint_violations || [],
|
|
@@ -525,32 +527,13 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
525
527
|
// Sort by round descending, take last N
|
|
526
528
|
rounds.sort((a, b) => b.round - a.round);
|
|
527
529
|
const sampled = rounds.slice(0, window).reverse(); // chronological order
|
|
528
|
-
//
|
|
529
|
-
const
|
|
530
|
-
// Trajectory direction
|
|
531
|
-
let direction = "stable";
|
|
532
|
-
if (trajectory.length >= 2) {
|
|
533
|
-
const diffs = trajectory.slice(1).map((v, i) => v - trajectory[i]);
|
|
534
|
-
if (diffs.every((d) => d >= 0) && diffs.some((d) => d > 0)) {
|
|
535
|
-
direction = "improving";
|
|
536
|
-
}
|
|
537
|
-
else if (diffs.every((d) => d <= 0) && diffs.some((d) => d < 0)) {
|
|
538
|
-
direction = "declining";
|
|
539
|
-
}
|
|
540
|
-
else if (diffs.every((d) => d === 0)) {
|
|
541
|
-
direction = "stable";
|
|
542
|
-
}
|
|
543
|
-
else {
|
|
544
|
-
direction = "volatile";
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
// What worked
|
|
548
|
-
const whatWorked = [];
|
|
530
|
+
// v1.12: Key outcomes — all rounds with output_summary
|
|
531
|
+
const keyOutcomes = [];
|
|
549
532
|
for (const r of sampled) {
|
|
550
|
-
if (r.
|
|
551
|
-
r.
|
|
552
|
-
|
|
553
|
-
`${
|
|
533
|
+
if (r.output_summary) {
|
|
534
|
+
const status = r.success ? "✓" : "✗";
|
|
535
|
+
keyOutcomes.push(`[R${r.round}] ${status} (${r.technique_used || "n/a"}): ` +
|
|
536
|
+
`${String(r.output_summary).slice(0, 200)}`);
|
|
554
537
|
}
|
|
555
538
|
}
|
|
556
539
|
// Recurring issues
|
|
@@ -569,17 +552,10 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
569
552
|
.filter(([, count]) => count >= 2)
|
|
570
553
|
.sort((a, b) => b[1] - a[1])
|
|
571
554
|
.map(([v, count]) => `${v} (appeared in ${count} rounds)`);
|
|
572
|
-
// Key lessons
|
|
573
|
-
const keyLessons = [];
|
|
574
|
-
for (const r of sampled) {
|
|
575
|
-
if (r.quality_score >= 4 && r.output_summary) {
|
|
576
|
-
keyLessons.push(`[R${r.round}] ${String(r.output_summary).slice(0, 200)}`);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
555
|
// v1.7: Failure lineage weighting — detect and demote failure patterns
|
|
580
556
|
const sampledRounds = sampled.map((r) => ({
|
|
581
557
|
round: r.round,
|
|
582
|
-
|
|
558
|
+
success: r.success,
|
|
583
559
|
task: r.task,
|
|
584
560
|
output_summary: r.output_summary,
|
|
585
561
|
constraint_violations: r.constraint_violations,
|
|
@@ -587,21 +563,21 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
587
563
|
}));
|
|
588
564
|
const failurePatterns = detectFailurePatterns(sampledRounds);
|
|
589
565
|
const failedRoundNums = extractFailureRoundNums(failurePatterns);
|
|
590
|
-
// Re-weight
|
|
591
|
-
if (failedRoundNums.size > 0 &&
|
|
566
|
+
// Re-weight key_outcomes: outcomes from failure-pattern rounds go to end
|
|
567
|
+
if (failedRoundNums.size > 0 && keyOutcomes.length > 0) {
|
|
592
568
|
const promoted = [];
|
|
593
569
|
const demoted = [];
|
|
594
|
-
for (const
|
|
595
|
-
const rndMatch =
|
|
570
|
+
for (const ko of keyOutcomes) {
|
|
571
|
+
const rndMatch = ko.match(/^\[R(\d+)\]/);
|
|
596
572
|
if (rndMatch && failedRoundNums.has(parseInt(rndMatch[1], 10))) {
|
|
597
|
-
demoted.push(
|
|
573
|
+
demoted.push(ko.replace(/^\[R\d+\]/, "$& [Consider alternatives]"));
|
|
598
574
|
}
|
|
599
575
|
else {
|
|
600
|
-
promoted.push(
|
|
576
|
+
promoted.push(ko);
|
|
601
577
|
}
|
|
602
578
|
}
|
|
603
|
-
|
|
604
|
-
|
|
579
|
+
keyOutcomes.length = 0;
|
|
580
|
+
keyOutcomes.push(...promoted, ...demoted);
|
|
605
581
|
}
|
|
606
582
|
// Re-weight recurring_issues: mark issues that ONLY appear in failure-pattern rounds
|
|
607
583
|
if (failedRoundNums.size > 0 && recurringIssues.length > 0) {
|
|
@@ -628,11 +604,8 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
628
604
|
}));
|
|
629
605
|
}
|
|
630
606
|
return makeRollingSummary({
|
|
631
|
-
|
|
632
|
-
trajectory_direction: direction,
|
|
633
|
-
what_worked: whatWorked,
|
|
607
|
+
key_outcomes: keyOutcomes,
|
|
634
608
|
recurring_issues: recurringIssues,
|
|
635
|
-
key_lessons: keyLessons,
|
|
636
609
|
rounds_sampled: sampled.length,
|
|
637
610
|
generated_at_round: currentRound,
|
|
638
611
|
failed_patterns: failurePatterns.length ? failurePatterns : [],
|
|
@@ -644,14 +617,13 @@ export function formatRollingSummaryForPrompt(rs) {
|
|
|
644
617
|
const lines = [
|
|
645
618
|
"### Cross-Round Summary (Accumulated)",
|
|
646
619
|
"",
|
|
647
|
-
`**Sampled**: ${rs.rounds_sampled} prior rounds
|
|
648
|
-
`**Quality Trajectory**: [${rs.quality_trajectory.join(", ")}]`,
|
|
620
|
+
`**Sampled**: ${rs.rounds_sampled} prior rounds`,
|
|
649
621
|
"",
|
|
650
622
|
];
|
|
651
|
-
if (rs.
|
|
652
|
-
lines.push("**
|
|
653
|
-
for (const
|
|
654
|
-
lines.push(`- ${
|
|
623
|
+
if (rs.key_outcomes.length) {
|
|
624
|
+
lines.push("**Key Outcomes**:");
|
|
625
|
+
for (const ko of rs.key_outcomes)
|
|
626
|
+
lines.push(`- ${ko}`);
|
|
655
627
|
lines.push("");
|
|
656
628
|
}
|
|
657
629
|
if (rs.recurring_issues.length) {
|
|
@@ -665,21 +637,107 @@ export function formatRollingSummaryForPrompt(rs) {
|
|
|
665
637
|
lines.push("### ⚠️ Failure Patterns — Consider Strategy Change");
|
|
666
638
|
lines.push("");
|
|
667
639
|
lines.push("The following approaches failed repeatedly. The compiler has demoted " +
|
|
668
|
-
"their influence on key
|
|
640
|
+
"their influence on key outcomes. Consider a different technique or " +
|
|
669
641
|
"narrower scope.");
|
|
670
642
|
for (const fp of rs.failed_patterns) {
|
|
671
643
|
lines.push(`- 🚫 ${fp}`);
|
|
672
644
|
}
|
|
673
645
|
lines.push("");
|
|
674
646
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
647
|
+
return lines.join("\n");
|
|
648
|
+
}
|
|
649
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
650
|
+
// Checkpoint compression (v1.10 — subtask boundary snapshots)
|
|
651
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
652
|
+
/** Build a CheckpointSummary from the current round's self-evaluation state.
|
|
653
|
+
* Called when the Agent sets compression_checkpoint: true in its self-eval.
|
|
654
|
+
* Snapshots the current constraints and outcome so they survive rolling-window
|
|
655
|
+
* eviction in subsequent rounds. */
|
|
656
|
+
export function buildCheckpointSummary(selfEval, round, activeConstraints, retiredConstraints) {
|
|
657
|
+
const policy = getPolicy();
|
|
658
|
+
const cpCfg = policy.checkpoint;
|
|
659
|
+
const maxCarried = cpCfg?.max_carried_constraints ?? 10;
|
|
660
|
+
const outcomeMaxChars = cpCfg?.outcome_max_chars ?? 200;
|
|
661
|
+
const label = selfEval.checkpoint_label?.trim() ||
|
|
662
|
+
`Subtask completed at round ${round}`;
|
|
663
|
+
const outcome = (selfEval.output_summary || "").trim().slice(0, outcomeMaxChars);
|
|
664
|
+
// Carried: constraints still active, capped
|
|
665
|
+
const carried = activeConstraints.slice(0, maxCarried);
|
|
666
|
+
// Resolved: constraints retired this round (retracted + the retire delta)
|
|
667
|
+
const resolved = retiredConstraints.slice(0, maxCarried);
|
|
668
|
+
return {
|
|
669
|
+
label,
|
|
670
|
+
declared_at_round: round,
|
|
671
|
+
outcome,
|
|
672
|
+
carried_constraints: carried,
|
|
673
|
+
resolved_constraints: resolved,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
/** Format a CheckpointSummary as a prompt block.
|
|
677
|
+
* Renders as a fixed section that persists across rolling-window eviction.
|
|
678
|
+
* Returns empty string if the summary is null or has no meaningful data. */
|
|
679
|
+
export function formatCheckpointForPrompt(cs) {
|
|
680
|
+
if (!cs)
|
|
681
|
+
return "";
|
|
682
|
+
// Must have at least a label and some content to be meaningful
|
|
683
|
+
if (!cs.label && !cs.outcome && !cs.carried_constraints.length)
|
|
684
|
+
return "";
|
|
685
|
+
const lines = [
|
|
686
|
+
`### Checkpoint: ${cs.label} (Round ${cs.declared_at_round})`,
|
|
687
|
+
"",
|
|
688
|
+
];
|
|
689
|
+
if (cs.outcome) {
|
|
690
|
+
lines.push(`**Outcome**: ${cs.outcome}`);
|
|
691
|
+
lines.push("");
|
|
692
|
+
}
|
|
693
|
+
if (cs.carried_constraints.length) {
|
|
694
|
+
lines.push("**Carried Constraints** (still active):");
|
|
695
|
+
for (const c of cs.carried_constraints) {
|
|
696
|
+
lines.push(`- ${c}`);
|
|
697
|
+
}
|
|
698
|
+
lines.push("");
|
|
699
|
+
}
|
|
700
|
+
if (cs.resolved_constraints.length) {
|
|
701
|
+
lines.push("**Resolved Constraints** (closed with this checkpoint):");
|
|
702
|
+
for (const c of cs.resolved_constraints) {
|
|
703
|
+
lines.push(`- ${c}`);
|
|
704
|
+
}
|
|
679
705
|
lines.push("");
|
|
680
706
|
}
|
|
681
707
|
return lines.join("\n");
|
|
682
708
|
}
|
|
709
|
+
/** Query the vault for the most recent checkpoint entry before the current
|
|
710
|
+
* round. Returns null if no checkpoint has been declared yet. */
|
|
711
|
+
function loadLatestCheckpoint(loopId, currentRound, vaultContext) {
|
|
712
|
+
if (!vaultContext)
|
|
713
|
+
return null;
|
|
714
|
+
const results = vaultContext.results || [];
|
|
715
|
+
let latest = null;
|
|
716
|
+
let latestRound = -1;
|
|
717
|
+
for (const r of results) {
|
|
718
|
+
const lineage = (r.loop_lineage || r.lineage || {});
|
|
719
|
+
if (lineage.loop_id !== loopId)
|
|
720
|
+
continue;
|
|
721
|
+
const rnd = lineage.round;
|
|
722
|
+
if (rnd >= currentRound)
|
|
723
|
+
continue;
|
|
724
|
+
// Check for stored checkpoint summary on this entry
|
|
725
|
+
const cp = r.checkpoint_summary;
|
|
726
|
+
if (cp && typeof cp.declared_at_round === "number" && cp.declared_at_round > latestRound) {
|
|
727
|
+
latest = cp;
|
|
728
|
+
latestRound = cp.declared_at_round;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (!latest)
|
|
732
|
+
return null;
|
|
733
|
+
return makeCheckpointSummary({
|
|
734
|
+
label: latest.label ?? "",
|
|
735
|
+
declared_at_round: latest.declared_at_round ?? 0,
|
|
736
|
+
outcome: latest.outcome ?? "",
|
|
737
|
+
carried_constraints: latest.carried_constraints ?? [],
|
|
738
|
+
resolved_constraints: latest.resolved_constraints ?? [],
|
|
739
|
+
});
|
|
740
|
+
}
|
|
683
741
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
684
742
|
// External Context formatting (v1.7 — memory system integration)
|
|
685
743
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -853,7 +911,7 @@ export function checkLoopHealth(loopId, request, vaultContext) {
|
|
|
853
911
|
// 4. strategy_stability
|
|
854
912
|
const recent = getRecentRounds(loopId, 3, vaultContext);
|
|
855
913
|
const strategyStability = recent.length > 0
|
|
856
|
-
? recent.every((r) => r.
|
|
914
|
+
? recent.every((r) => r.success === true)
|
|
857
915
|
: true;
|
|
858
916
|
// 5. task_continuity
|
|
859
917
|
let taskContinuity = 1.0;
|
|
@@ -890,15 +948,22 @@ function computeSuggestedNextTask(loopId, vaultContext) {
|
|
|
890
948
|
if (vaultContext === null)
|
|
891
949
|
return "";
|
|
892
950
|
const results = vaultContext.results || [];
|
|
951
|
+
let bestTask = "";
|
|
952
|
+
let bestRound = -1;
|
|
893
953
|
for (const r of results) {
|
|
894
954
|
const lineage = (r.loop_lineage || r.lineage || {});
|
|
895
|
-
if (lineage.loop_id
|
|
955
|
+
if (lineage.loop_id !== loopId)
|
|
956
|
+
continue;
|
|
957
|
+
const rnd = lineage.round ?? 0;
|
|
958
|
+
if (rnd > bestRound) {
|
|
896
959
|
const task = r.task ?? r.user_intent ?? "";
|
|
897
|
-
if (task)
|
|
898
|
-
|
|
960
|
+
if (task) {
|
|
961
|
+
bestTask = task;
|
|
962
|
+
bestRound = rnd;
|
|
963
|
+
}
|
|
899
964
|
}
|
|
900
965
|
}
|
|
901
|
-
return "";
|
|
966
|
+
return bestTask ? `[R${bestRound}] Previous task: ${bestTask.slice(0, 120)}` : "";
|
|
902
967
|
}
|
|
903
968
|
export function computeAdvisories(request, vaultContext) {
|
|
904
969
|
const warnings = [];
|
|
@@ -914,7 +979,7 @@ export function computeAdvisories(request, vaultContext) {
|
|
|
914
979
|
}
|
|
915
980
|
// Strategy collapse check
|
|
916
981
|
if (strategyCollapse(request.loop_id, vaultContext)) {
|
|
917
|
-
warnings.push("strategy_collapse: 3 consecutive
|
|
982
|
+
warnings.push("strategy_collapse: 3 consecutive failed rounds — " +
|
|
918
983
|
"consider force_level=L2 rebuild");
|
|
919
984
|
}
|
|
920
985
|
// Repair cue detection
|
|
@@ -1226,19 +1291,38 @@ function compileL1(request, vaultContext, prevRound) {
|
|
|
1226
1291
|
}
|
|
1227
1292
|
export function compileL2(request, vaultContext) {
|
|
1228
1293
|
const goalId = deriveGoalId(request.loop_id, request.task, request.goal_id);
|
|
1229
|
-
//
|
|
1230
|
-
const
|
|
1294
|
+
// v4.0: Tier-gated technique routing
|
|
1295
|
+
const isCheckpoint = request.last_round_result?.compression_checkpoint === true;
|
|
1296
|
+
const analysis = routeTechniqueAdaptive(request.task, vaultContext, request.loop_id, isCheckpoint);
|
|
1231
1297
|
const technique = analysis.technique;
|
|
1232
1298
|
const referenceFile = analysis.reference_file;
|
|
1233
|
-
//
|
|
1234
|
-
|
|
1299
|
+
// v1.10: Checkpoint — load existing or build new from last round's self-eval
|
|
1300
|
+
let checkpointSummary = null;
|
|
1301
|
+
let checkpointRound = 0;
|
|
1302
|
+
// Check if the last round declared a checkpoint
|
|
1303
|
+
const lr = request.last_round_result;
|
|
1304
|
+
if (lr?.compression_checkpoint === true) {
|
|
1305
|
+
// Build fresh checkpoint from the last round's data
|
|
1306
|
+
// Constraints haven't been updated yet at this point, but we snapshot the
|
|
1307
|
+
// current state as seen by this compile call (pre-P5 correction).
|
|
1308
|
+
// We defer building until constraints are finalized below.
|
|
1309
|
+
checkpointRound = request.round - 1;
|
|
1310
|
+
}
|
|
1311
|
+
else if (request.round > 1) {
|
|
1312
|
+
// No new checkpoint — try loading the previous one from vault
|
|
1313
|
+
checkpointSummary = loadLatestCheckpoint(request.loop_id, request.round, vaultContext);
|
|
1314
|
+
if (checkpointSummary) {
|
|
1315
|
+
checkpointRound = checkpointSummary.declared_at_round;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
// v3.5: Rolling summary — optionally scoped to post-checkpoint rounds
|
|
1319
|
+
const rollingSummary = buildRollingSummary(request.loop_id, request.round, vaultContext, checkpointRound > 0 ? checkpointRound : undefined);
|
|
1235
1320
|
// P5c: Wrong assumptions — must be applied BEFORE formatting so they appear
|
|
1236
1321
|
// in the rendered cross-round summary text injected into the prompt.
|
|
1237
|
-
const lr = request.last_round_result;
|
|
1238
1322
|
if (lr?.wrong_assumptions?.length) {
|
|
1239
1323
|
if (rollingSummary) {
|
|
1240
|
-
rollingSummary.
|
|
1241
|
-
...rollingSummary.
|
|
1324
|
+
rollingSummary.key_outcomes = [
|
|
1325
|
+
...rollingSummary.key_outcomes,
|
|
1242
1326
|
...lr.wrong_assumptions.map((a) => `Wrong assumption corrected: ${a}`),
|
|
1243
1327
|
];
|
|
1244
1328
|
}
|
|
@@ -1362,6 +1446,21 @@ export function compileL2(request, vaultContext) {
|
|
|
1362
1446
|
])];
|
|
1363
1447
|
loopObjective = updatedLo;
|
|
1364
1448
|
}
|
|
1449
|
+
// ── v1.10: Build checkpoint summary if last round declared one ──
|
|
1450
|
+
if (checkpointRound > 0 && !checkpointSummary && lr) {
|
|
1451
|
+
// checkpointRound was set but no summary yet → fresh declaration
|
|
1452
|
+
// We need the self-eval from last round. It's embedded in lr fields.
|
|
1453
|
+
// Build a synthetic SelfEvaluation from the last_round_result for the builder.
|
|
1454
|
+
const synthEval = {
|
|
1455
|
+
success: lr.success,
|
|
1456
|
+
output_summary: lr.output_summary,
|
|
1457
|
+
constraint_violations: lr.constraint_violations,
|
|
1458
|
+
should_continue: true,
|
|
1459
|
+
compression_checkpoint: true,
|
|
1460
|
+
checkpoint_label: lr.checkpoint_label ?? "",
|
|
1461
|
+
};
|
|
1462
|
+
checkpointSummary = buildCheckpointSummary(synthEval, checkpointRound, constraints, retired);
|
|
1463
|
+
}
|
|
1365
1464
|
// ── Route to technique-specific specialist (v1.1 deep integration) ──
|
|
1366
1465
|
let prompt;
|
|
1367
1466
|
if (technique === "step-back") {
|
|
@@ -1376,6 +1475,12 @@ export function compileL2(request, vaultContext) {
|
|
|
1376
1475
|
else {
|
|
1377
1476
|
prompt = compileGeneric(request, goalId, constraints, loopObjective, rollingText, analysis, referenceFile, technique);
|
|
1378
1477
|
}
|
|
1478
|
+
// ── v1.10: Inject checkpoint block (before rolling summary in prompt) ──
|
|
1479
|
+
// The checkpoint is rendered before the external context so it appears
|
|
1480
|
+
// in a natural position: after Loop Objective, before Cross-Round Summary.
|
|
1481
|
+
if (checkpointSummary) {
|
|
1482
|
+
prompt += "\n" + formatCheckpointForPrompt(checkpointSummary);
|
|
1483
|
+
}
|
|
1379
1484
|
// ── v1.7: Inject external memory context (L2 only) ──
|
|
1380
1485
|
const policy = getPolicy();
|
|
1381
1486
|
if (policy.memory_injection.enabled && request.external_context) {
|
|
@@ -1504,6 +1609,7 @@ export function compileL2(request, vaultContext) {
|
|
|
1504
1609
|
technique_used: technique,
|
|
1505
1610
|
reference_file: referenceFile,
|
|
1506
1611
|
rolling_summary: rollingSummary,
|
|
1612
|
+
checkpoint_summary: checkpointSummary,
|
|
1507
1613
|
loop_id: request.loop_id,
|
|
1508
1614
|
round: request.round,
|
|
1509
1615
|
goal_id: goalId,
|
|
@@ -1594,10 +1700,8 @@ function compileGeneric(request, goalId, constraints, loopObjective, rollingText
|
|
|
1594
1700
|
"### Generation Instructions",
|
|
1595
1701
|
`1. Read \`${referenceFile}\` — study its structure rules, section count, and format requirements`,
|
|
1596
1702
|
"2. Generate a complete prompt following that technique's structure",
|
|
1597
|
-
"3.
|
|
1598
|
-
"4.
|
|
1599
|
-
"5. The prompt must be self-contained — ready for a coding agent to execute",
|
|
1600
|
-
"6. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1703
|
+
"3. The prompt must be self-contained — ready for a coding agent to execute",
|
|
1704
|
+
"4. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1601
1705
|
];
|
|
1602
1706
|
return lines.join("\n");
|
|
1603
1707
|
}
|
|
@@ -1656,19 +1760,12 @@ function compileStepBack(request, goalId, constraints, loopObjective, rollingTex
|
|
|
1656
1760
|
"**Section 6 transition sentence (MANDATORY)**:",
|
|
1657
1761
|
"\"基于上述抽象框架,实现以下所有功能。\"",
|
|
1658
1762
|
"",
|
|
1659
|
-
"### Quality Checklist",
|
|
1660
|
-
"- [ ] Frameworks are abstract concepts/formulas/principles, NOT concrete code",
|
|
1661
|
-
"- [ ] Section 6 starts with the mandatory transition sentence",
|
|
1662
|
-
"- [ ] Abstraction level is consistent across frameworks",
|
|
1663
|
-
"- [ ] Step-back question is tightened to minimum necessary generalisation",
|
|
1664
|
-
"",
|
|
1665
1763
|
"### Generation Instructions",
|
|
1666
1764
|
"1. Extract 2-3 abstract principles/frameworks from the task domain",
|
|
1667
1765
|
"2. Build ASCII diagrams for each framework (formulas, tables, rules)",
|
|
1668
1766
|
"3. Apply the abstraction back to the concrete task",
|
|
1669
|
-
"4.
|
|
1670
|
-
"5.
|
|
1671
|
-
"6. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1767
|
+
"4. Verify: the generated prompt satisfies all structural rules in the skeleton above",
|
|
1768
|
+
"5. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1672
1769
|
];
|
|
1673
1770
|
return lines.join("\n");
|
|
1674
1771
|
}
|
|
@@ -1726,21 +1823,13 @@ function compileLeastToMost(request, goalId, constraints, loopObjective, rolling
|
|
|
1726
1823
|
"- Each sub-problem must serve the original task — no unrelated steps",
|
|
1727
1824
|
"- The final sub-problem must be equivalent to or directly address the original task",
|
|
1728
1825
|
"",
|
|
1729
|
-
"### Quality Checklist",
|
|
1730
|
-
"- [ ] Sub-problems ordered simplest → most complex, dependency chain explicit",
|
|
1731
|
-
"- [ ] Each sub-problem declares its prerequisite sub-problems",
|
|
1732
|
-
"- [ ] Last sub-problem is \"综合实现完整模块\" with integration list",
|
|
1733
|
-
"- [ ] Section 6 expands per output format list (NOT per sub-problem)",
|
|
1734
|
-
"- [ ] Sub-problem count: 4-6, no more, no less",
|
|
1735
|
-
"",
|
|
1736
1826
|
"### Generation Instructions",
|
|
1737
1827
|
"1. Decompose the task into 4-6 ordered sub-problems with explicit dependencies",
|
|
1738
1828
|
"2. Sub-problem 1 starts with the simplest building block (data structures, enums, base config)",
|
|
1739
1829
|
"3. Each subsequent sub-problem builds on prior results",
|
|
1740
1830
|
"4. Final sub-problem integrates all components into the complete module",
|
|
1741
|
-
"5.
|
|
1742
|
-
"6.
|
|
1743
|
-
"7. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1831
|
+
"5. Verify: the generated prompt satisfies all structural rules in the skeleton above",
|
|
1832
|
+
"6. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1744
1833
|
];
|
|
1745
1834
|
return lines.join("\n");
|
|
1746
1835
|
}
|
|
@@ -1819,24 +1908,14 @@ function compileToT(request, goalId, constraints, loopObjective, rollingText, an
|
|
|
1819
1908
|
"- thought is a PUBLIC intermediate semantic unit, not hidden chain-of-thought",
|
|
1820
1909
|
"- State table MUST have: round, branch ID, candidate description, evaluation score/judgment, keep/prune decision",
|
|
1821
1910
|
"",
|
|
1822
|
-
"### Quality Checklist",
|
|
1823
|
-
"- [ ] Branch count 2-4, depth ≤3 (prevents token explosion)",
|
|
1824
|
-
"- [ ] Evaluation criteria table has ≥3 dimensions including correctness, feasibility, constraint matching",
|
|
1825
|
-
"- [ ] State table format explicit (round/branch/candidate/evaluation/decision)",
|
|
1826
|
-
"- [ ] Search strategy matches task type (beam/dfs/expert-panel)",
|
|
1827
|
-
"- [ ] Hard constraints listed FIRST in evaluation criteria",
|
|
1828
|
-
"- [ ] Not \"3 experts casually discussing\" — has explicit branch count, depth, scoring, and pruning rules",
|
|
1829
|
-
"- [ ] Ends with model outputting final selection and rationale",
|
|
1830
|
-
"",
|
|
1831
1911
|
"### Generation Instructions",
|
|
1832
1912
|
"1. Select the appropriate search strategy (beam/dfs/expert-panel) based on task type",
|
|
1833
1913
|
"2. Define 2-4 candidate approaches as initial branches",
|
|
1834
1914
|
"3. Build evaluation criteria table with hard constraints ranked first",
|
|
1835
1915
|
"4. Define the state table format for tracking exploration",
|
|
1836
1916
|
"5. Set branch count and max depth to prevent token explosion",
|
|
1837
|
-
"6.
|
|
1838
|
-
"7.
|
|
1839
|
-
"8. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1917
|
+
"6. Verify: the generated prompt satisfies all structural rules in the skeleton above",
|
|
1918
|
+
"7. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1840
1919
|
];
|
|
1841
1920
|
return lines.join("\n");
|
|
1842
1921
|
}
|
|
@@ -1874,7 +1953,9 @@ export function buildSelfEvalBlock(round) {
|
|
|
1874
1953
|
' },',
|
|
1875
1954
|
' "retracted_constraints": [],',
|
|
1876
1955
|
' "revised_success_criteria": [],',
|
|
1877
|
-
' "wrong_assumptions": []',
|
|
1956
|
+
' "wrong_assumptions": [],',
|
|
1957
|
+
' "compression_checkpoint": false,',
|
|
1958
|
+
' "checkpoint_label": ""',
|
|
1878
1959
|
"}",
|
|
1879
1960
|
"---end-loopforge-eval",
|
|
1880
1961
|
"```",
|
|
@@ -1907,6 +1988,15 @@ export function buildSelfEvalBlock(round) {
|
|
|
1907
1988
|
`Array of {old, new} objects. Applied to the Loop Objective. Omit or [] if none.`,
|
|
1908
1989
|
`- wrong_assumptions (P5 — optional): Assumptions from earlier rounds that turned ` +
|
|
1909
1990
|
`out to be incorrect. Recorded as key lessons. Omit or [] if none.`,
|
|
1991
|
+
`- compression_checkpoint (v1.10 — optional): Set to true when you have COMPLETED a ` +
|
|
1992
|
+
`major subtask. The compiler will snapshot the current state (constraints, outcome) ` +
|
|
1993
|
+
`into a persistent checkpoint that survives context-window eviction. ` +
|
|
1994
|
+
`Use sparingly — only at genuine subtask boundaries, not every round. ` +
|
|
1995
|
+
`Omit or false otherwise.`,
|
|
1996
|
+
`- checkpoint_label (v1.10 — optional): Human-readable label for this checkpoint. ` +
|
|
1997
|
+
`e.g. "数据模型层完成", "API layer complete", "Auth module done". ` +
|
|
1998
|
+
`Used as the checkpoint heading in subsequent prompts. Only meaningful when ` +
|
|
1999
|
+
`compression_checkpoint is true. Omit or "" otherwise.`,
|
|
1910
2000
|
`- The JSON MUST appear between the ---loopforge-eval and ---end-loopforge-eval markers`,
|
|
1911
2001
|
`- Do NOT wrap the markers in code fences — output them as raw text`,
|
|
1912
2002
|
"",
|