loopforge 1.8.0 → 1.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/backends/fs.d.ts.map +1 -1
  2. package/dist/backends/fs.js +13 -10
  3. package/dist/backends/fs.js.map +1 -1
  4. package/dist/backends/interface.d.ts +2 -0
  5. package/dist/backends/interface.d.ts.map +1 -1
  6. package/dist/builder.d.ts +13 -10
  7. package/dist/builder.d.ts.map +1 -1
  8. package/dist/builder.js +117 -96
  9. package/dist/builder.js.map +1 -1
  10. package/dist/enforcement-gate.d.ts +44 -0
  11. package/dist/enforcement-gate.d.ts.map +1 -0
  12. package/dist/enforcement-gate.js +253 -0
  13. package/dist/enforcement-gate.js.map +1 -0
  14. package/dist/engine.d.ts +13 -6
  15. package/dist/engine.d.ts.map +1 -1
  16. package/dist/engine.js +127 -171
  17. package/dist/engine.js.map +1 -1
  18. package/dist/index.d.ts +5 -5
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +4 -4
  21. package/dist/index.js.map +1 -1
  22. package/dist/loop-compiler.d.ts +10 -2
  23. package/dist/loop-compiler.d.ts.map +1 -1
  24. package/dist/loop-compiler.js +882 -583
  25. package/dist/loop-compiler.js.map +1 -1
  26. package/dist/mcp/session.d.ts +12 -2
  27. package/dist/mcp/session.d.ts.map +1 -1
  28. package/dist/mcp/session.js +127 -64
  29. package/dist/mcp/session.js.map +1 -1
  30. package/dist/mcp/tools.js +4 -4
  31. package/dist/memory-bridge.js +1 -1
  32. package/dist/memory-bridge.js.map +1 -1
  33. package/dist/observability.d.ts +1 -1
  34. package/dist/observability.js +1 -1
  35. package/dist/policy.d.ts +80 -10
  36. package/dist/policy.d.ts.map +1 -1
  37. package/dist/policy.js +101 -30
  38. package/dist/policy.js.map +1 -1
  39. package/dist/protocol.d.ts +70 -16
  40. package/dist/protocol.d.ts.map +1 -1
  41. package/dist/protocol.js +40 -8
  42. package/dist/protocol.js.map +1 -1
  43. package/dist/replay.js +7 -7
  44. package/dist/replay.js.map +1 -1
  45. package/dist/runtime.d.ts +11 -7
  46. package/dist/runtime.d.ts.map +1 -1
  47. package/dist/runtime.js +125 -144
  48. package/dist/runtime.js.map +1 -1
  49. package/dist/tests/builder.test.js +42 -28
  50. package/dist/tests/builder.test.js.map +1 -1
  51. package/dist/tests/enforcement-gate.test.d.ts +2 -0
  52. package/dist/tests/enforcement-gate.test.d.ts.map +1 -0
  53. package/dist/tests/enforcement-gate.test.js +328 -0
  54. package/dist/tests/enforcement-gate.test.js.map +1 -0
  55. package/dist/tests/engine.test.js +22 -22
  56. package/dist/tests/engine.test.js.map +1 -1
  57. package/dist/tests/generate-schema.test.js +10 -7
  58. package/dist/tests/generate-schema.test.js.map +1 -1
  59. package/dist/tests/loop-compiler.test.js +273 -47
  60. package/dist/tests/loop-compiler.test.js.map +1 -1
  61. package/dist/tests/mcp.test.js +31 -11
  62. package/dist/tests/mcp.test.js.map +1 -1
  63. package/dist/tests/memory-bridge.test.js +0 -1
  64. package/dist/tests/memory-bridge.test.js.map +1 -1
  65. package/dist/tests/policy.test.js +6 -16
  66. package/dist/tests/policy.test.js.map +1 -1
  67. package/dist/tests/protocol.test.js +3 -2
  68. package/dist/tests/protocol.test.js.map +1 -1
  69. package/dist/tests/replay.test.js +9 -9
  70. package/dist/tests/replay.test.js.map +1 -1
  71. package/dist/tests/runtime.test.js +27 -13
  72. package/dist/tests/runtime.test.js.map +1 -1
  73. package/dist/tests/verification-gate.test.js +1 -1
  74. package/dist/tests/verification-gate.test.js.map +1 -1
  75. package/dist/verification-gate.d.ts +8 -2
  76. package/dist/verification-gate.d.ts.map +1 -1
  77. package/dist/verification-gate.js +5 -4
  78. package/dist/verification-gate.js.map +1 -1
  79. package/loop_policy.json +13 -26
  80. package/package.json +1 -1
  81. package/skills/perception/SKILL.md +42 -23
  82. package/skills/prompt-techniques/SKILL.md +52 -52
  83. package/skills/prompt-techniques/references/chain-of-thought.md +207 -207
  84. package/skills/prompt-techniques/references/few-shot.md +174 -174
  85. package/skills/prompt-techniques/references/least-to-most.md +182 -182
  86. package/skills/prompt-techniques/references/step-back.md +160 -160
  87. package/skills/prompt-techniques/references/tree-of-thought.md +191 -191
  88. package/skills/prompt-techniques/references/zero-shot.md +97 -97
package/dist/engine.js CHANGED
@@ -9,10 +9,78 @@ import { randomUUID } from "node:crypto";
9
9
  import { getPolicy } from "./policy.js";
10
10
  import { FSBackend, readLineageMd } from "./backends/fs.js";
11
11
  import { AgentStatus, Mode, makeAnalysis, makeExecutionEvidence, makeExecutionFeedback, makeLoopCompileRequest, makeLoopObjective, makeLoopRoundResult, makeSelfEvaluation, makeSessionState, makeTaskId, makeVaultConfig, SELF_EVAL_REGEX, } from "./protocol.js";
12
- import { scoreQuality } from "./builder.js";
13
12
  import { compileLoop } from "./loop-compiler.js";
14
13
  import { logEvent } from "./observability.js";
15
14
  // ═══════════════════════════════════════════════════════════════════════════
15
+ // Shared helpers
16
+ // ═══════════════════════════════════════════════════════════════════════════
17
+ /** Parse ExecutionEvidence from a raw JSON object. Shared by buildSelfEvaluation
18
+ * and invokeLoopCompile — both parse the same execution_evidence shape. */
19
+ export function parseExecutionEvidence(raw) {
20
+ if (!raw || typeof raw !== "object")
21
+ return undefined;
22
+ const testResults = raw.test_results;
23
+ return makeExecutionEvidence({
24
+ files_changed: Array.isArray(raw.files_changed)
25
+ ? raw.files_changed.filter((v) => typeof v === "string")
26
+ : [],
27
+ test_results: testResults && typeof testResults.passed === "number"
28
+ ? {
29
+ passed: testResults.passed,
30
+ failed: testResults.failed ?? 0,
31
+ skipped: testResults.skipped ?? 0,
32
+ }
33
+ : null,
34
+ success_criteria_met: Array.isArray(raw.success_criteria_met)
35
+ ? raw.success_criteria_met.filter((v) => typeof v === "string")
36
+ : [],
37
+ success_criteria_remaining: Array.isArray(raw.success_criteria_remaining)
38
+ ? raw.success_criteria_remaining.filter((v) => typeof v === "string")
39
+ : [],
40
+ progress_estimate: typeof raw.progress_estimate === "number"
41
+ ? Math.max(0, Math.min(1, raw.progress_estimate))
42
+ : 0.0,
43
+ });
44
+ }
45
+ /** Parse CriterionRevision[] from a raw JSON array. Shared by buildSelfEvaluation
46
+ * and invokeLoopCompile — both parse the same revised_success_criteria shape. */
47
+ export function parseCriterionRevisions(raw) {
48
+ if (!Array.isArray(raw))
49
+ return [];
50
+ return raw
51
+ .filter((v) => typeof v === "object" && v !== null &&
52
+ typeof v.old === "string" &&
53
+ typeof v.new === "string")
54
+ .map((v) => {
55
+ const r = v;
56
+ return { old: r.old, new: r.new };
57
+ });
58
+ }
59
+ /** Parse WorkerResult[] from a raw JSON array. Shared by buildSelfEvaluation
60
+ * and invokeLoopCompile — both parse the same worker_results shape. */
61
+ export function parseWorkerResults(raw) {
62
+ if (!Array.isArray(raw))
63
+ return [];
64
+ return raw
65
+ .filter((v) => typeof v === "object" && v !== null &&
66
+ typeof v.agentId === "string" &&
67
+ typeof v.subTask === "string" &&
68
+ typeof v.resultSummary === "string")
69
+ .map((v) => {
70
+ const w = v;
71
+ return {
72
+ agentId: w.agentId,
73
+ subAgentType: typeof w.subAgentType === "string" ? w.subAgentType : "general-purpose",
74
+ subTask: w.subTask,
75
+ resultSummary: w.resultSummary,
76
+ success: typeof w.success === "boolean" ? w.success : false,
77
+ discoveredConstraints: Array.isArray(w.discoveredConstraints)
78
+ ? w.discoveredConstraints.filter((c) => typeof c === "string")
79
+ : [],
80
+ };
81
+ });
82
+ }
83
+ // ═══════════════════════════════════════════════════════════════════════════
16
84
  // Self-Evaluation extraction (v1.1 — autonomous loop feedback)
17
85
  // ═══════════════════════════════════════════════════════════════════════════
18
86
  /** Extract a structured SelfEvaluation from agent output text.
@@ -45,70 +113,17 @@ export function extractSelfEvaluation(text) {
45
113
  * (structured evaluation parameter path). */
46
114
  export function buildSelfEvaluation(raw) {
47
115
  // P4: Parse execution evidence from raw JSON
48
- let executionEvidence;
49
- const rawEvidence = raw.execution_evidence;
50
- if (rawEvidence && typeof rawEvidence === "object") {
51
- const testResults = rawEvidence.test_results;
52
- executionEvidence = makeExecutionEvidence({
53
- files_changed: Array.isArray(rawEvidence.files_changed)
54
- ? rawEvidence.files_changed.filter((v) => typeof v === "string")
55
- : [],
56
- test_results: testResults && typeof testResults.passed === "number"
57
- ? {
58
- passed: testResults.passed,
59
- failed: testResults.failed ?? 0,
60
- skipped: testResults.skipped ?? 0,
61
- }
62
- : null,
63
- success_criteria_met: Array.isArray(rawEvidence.success_criteria_met)
64
- ? rawEvidence.success_criteria_met.filter((v) => typeof v === "string")
65
- : [],
66
- success_criteria_remaining: Array.isArray(rawEvidence.success_criteria_remaining)
67
- ? rawEvidence.success_criteria_remaining.filter((v) => typeof v === "string")
68
- : [],
69
- progress_estimate: typeof rawEvidence.progress_estimate === "number"
70
- ? Math.max(0, Math.min(1, rawEvidence.progress_estimate))
71
- : 0.0,
72
- });
73
- }
116
+ const executionEvidence = parseExecutionEvidence(raw.execution_evidence);
74
117
  // P5: Parse corrections
75
118
  const retractedConstraints = Array.isArray(raw.retracted_constraints)
76
119
  ? raw.retracted_constraints.filter((v) => typeof v === "string")
77
120
  : [];
78
- const revisedCriteria = Array.isArray(raw.revised_success_criteria)
79
- ? raw.revised_success_criteria
80
- .filter((v) => typeof v === "object" && v !== null &&
81
- typeof v.old === "string" &&
82
- typeof v.new === "string")
83
- .map((v) => {
84
- const r = v;
85
- return { old: r.old, new: r.new };
86
- })
87
- : [];
121
+ const revisedCriteria = parseCriterionRevisions(raw.revised_success_criteria);
88
122
  const wrongAssumptions = Array.isArray(raw.wrong_assumptions)
89
123
  ? raw.wrong_assumptions.filter((v) => typeof v === "string")
90
124
  : [];
91
125
  // Multi-agent: Parse worker delegation results
92
- const workerResults = Array.isArray(raw.worker_results)
93
- ? raw.worker_results
94
- .filter((v) => typeof v === "object" && v !== null &&
95
- typeof v.agentId === "string" &&
96
- typeof v.subTask === "string" &&
97
- typeof v.resultSummary === "string")
98
- .map((v) => {
99
- const w = v;
100
- return {
101
- agentId: w.agentId,
102
- subAgentType: typeof w.subAgentType === "string" ? w.subAgentType : "general-purpose",
103
- subTask: w.subTask,
104
- resultSummary: w.resultSummary,
105
- success: typeof w.success === "boolean" ? w.success : false,
106
- discoveredConstraints: Array.isArray(w.discoveredConstraints)
107
- ? w.discoveredConstraints.filter((c) => typeof c === "string")
108
- : [],
109
- };
110
- })
111
- : [];
126
+ const workerResults = parseWorkerResults(raw.worker_results);
112
127
  return makeSelfEvaluation({
113
128
  success: typeof raw.success === "boolean" ? raw.success : false,
114
129
  output_summary: typeof raw.output_summary === "string" ? raw.output_summary : "",
@@ -134,6 +149,9 @@ export function buildSelfEvaluation(raw) {
134
149
  wrong_assumptions: wrongAssumptions,
135
150
  // Multi-agent: Worker delegation results
136
151
  worker_results: workerResults,
152
+ // v1.10: Checkpoint compression
153
+ compression_checkpoint: typeof raw.compression_checkpoint === "boolean" ? raw.compression_checkpoint : false,
154
+ checkpoint_label: typeof raw.checkpoint_label === "string" ? raw.checkpoint_label : "",
137
155
  });
138
156
  }
139
157
  /** Fallback heuristic when structured self-eval extraction fails.
@@ -173,15 +191,14 @@ function makeEngineMetrics() {
173
191
  // LoopForgeEngine
174
192
  // ═══════════════════════════════════════════════════════════════════════════
175
193
  export class LoopForgeEngine {
176
- skillsDir;
177
194
  state = null;
178
195
  backend = null;
179
196
  metrics = null;
180
197
  feedbackWriteBuffer = [];
181
198
  lastTask = null;
182
- seenConstraints = new Set();
183
- constructor(skillsDir = "skills", backend) {
184
- this.skillsDir = skillsDir;
199
+ // skillsDir parameter retained for API backward compatibility (v1.15).
200
+ // Previously stored as this.skillsDir but never read — now accepted & dropped.
201
+ constructor(_skillsDir = "skills", backend) {
185
202
  if (backend)
186
203
  this.backend = backend;
187
204
  }
@@ -244,12 +261,12 @@ export class LoopForgeEngine {
244
261
  is_active: true,
245
262
  timestamp: now,
246
263
  user_intent: String(signal.task_type ?? "").slice(0, 200),
247
- quality_score: signal.quality_score ?? 0,
264
+ success: signal.success ?? false,
248
265
  execution_feedback: JSON.stringify({
249
- status: (signal.quality_score ?? 0) >= 3
266
+ success: signal.success ?? false,
267
+ status: signal.success
250
268
  ? "success"
251
269
  : "partial",
252
- quality_score: signal.quality_score ?? 0,
253
270
  constraint_compliance: {
254
271
  all_hard_constraints_met: !Array.isArray(signal.violations) || signal.violations.length === 0,
255
272
  violations: signal.violations ?? [],
@@ -299,7 +316,6 @@ export class LoopForgeEngine {
299
316
  goal_id: response.goal_id,
300
317
  goal_text_hash: response.goal_text_hash,
301
318
  recompile_level: response.recompile_level,
302
- quality_score: 0,
303
319
  constraints_active: response.constraints_active,
304
320
  task: request.task,
305
321
  success: true,
@@ -319,7 +335,6 @@ export class LoopForgeEngine {
319
335
  timestamp: new Date().toISOString().replace(/\.\d+Z$/, ""),
320
336
  user_intent: `loop_compile round ${response.round} — ${response.goal_id}`,
321
337
  task_type: "loop_lineage",
322
- quality_score: 0,
323
338
  skill_used: response.technique_used,
324
339
  technique_used: response.technique_used,
325
340
  loop_id: response.loop_id,
@@ -352,7 +367,6 @@ export class LoopForgeEngine {
352
367
  technique_used: response.technique_used,
353
368
  loop_objective: loopObjDict,
354
369
  success: true,
355
- quality_score: 0,
356
370
  output_summary: lastOutputSummary,
357
371
  constraint_violations: lastViolations,
358
372
  });
@@ -410,30 +424,29 @@ export class LoopForgeEngine {
410
424
  hydrateLoopContext(loopId) {
411
425
  const prefix = `loop:${loopId}:r`;
412
426
  const results = this.resolveBackend().queryEntries({ prefix });
413
- // Merge feedback quality scores into lineage entries
427
+ // Merge feedback success flags into lineage entries
414
428
  const fbEntries = this.resolveBackend().queryEntries({
415
429
  prefix,
416
430
  feedbackOnly: true,
417
431
  });
418
- const fbQuality = new Map();
432
+ const fbSuccess = new Map();
419
433
  for (const fe of fbEntries) {
420
434
  const tid = String(fe.task_id ?? "");
421
435
  const parts = tid.split(":r");
422
436
  if (parts.length >= 2) {
423
437
  const roundStr = parts[1].split(":")[0];
424
438
  const fbRound = parseInt(roundStr, 10);
425
- const fbScore = fe.quality_score ?? 0;
426
- if (!Number.isNaN(fbRound) && fbScore > 0) {
427
- fbQuality.set(fbRound, Math.max(fbQuality.get(fbRound) ?? 0, fbScore));
439
+ if (!Number.isNaN(fbRound) && fe.success !== undefined) {
440
+ fbSuccess.set(fbRound, fe.success);
428
441
  }
429
442
  }
430
443
  }
431
444
  for (const entry of results) {
432
445
  const lineage = (entry.loop_lineage ?? entry.lineage ?? {});
433
446
  const rnd = lineage.round;
434
- if (rnd && fbQuality.has(rnd)) {
435
- lineage.quality_score = fbQuality.get(rnd);
436
- entry.quality_score = fbQuality.get(rnd);
447
+ if (rnd && fbSuccess.has(rnd)) {
448
+ lineage.success = fbSuccess.get(rnd);
449
+ entry.success = fbSuccess.get(rnd);
437
450
  }
438
451
  }
439
452
  // Fallback: scan Markdown files if JSON vault had no matches
@@ -456,10 +469,6 @@ export class LoopForgeEngine {
456
469
  entry.constraint_violations =
457
470
  lineage.constraint_violations ?? [];
458
471
  }
459
- // v1.7: Ensure quality_score is always populated for failure lineage weighting
460
- if (entry.quality_score === undefined || entry.quality_score === null) {
461
- entry.quality_score = lineage.quality_score ?? 0;
462
- }
463
472
  }
464
473
  // Enrich: attach full prompt text from Markdown for L0 cache reuse
465
474
  for (const entry of finalResults) {
@@ -499,11 +508,6 @@ export class LoopForgeEngine {
499
508
  const success = fb.success;
500
509
  const violations = fb.constraint_violations ?? [];
501
510
  const fixes = fb.manual_fixes_needed ?? "";
502
- const quality = scoreQuality({
503
- success,
504
- constraint_violations: violations,
505
- manual_fixes_needed: fixes,
506
- });
507
511
  // Loop-aware task_id for feedback→lineage backfill
508
512
  const loopId = request.loop_id;
509
513
  const loopRound = request.round;
@@ -517,7 +521,7 @@ export class LoopForgeEngine {
517
521
  const signal = {
518
522
  task_id: taskId,
519
523
  task_type: request.task.slice(0, 80),
520
- quality_score: quality,
524
+ success,
521
525
  skill_used: request.skill_name ?? "",
522
526
  violations,
523
527
  manual_fixes: fixes,
@@ -525,13 +529,13 @@ export class LoopForgeEngine {
525
529
  round: loopRound,
526
530
  };
527
531
  this.persistFeedbackToVault(signal);
528
- // Flush immediately so next compile cycle sees quality scores
532
+ // Flush immediately so next compile cycle sees success flags
529
533
  this.flushFeedbackBuffer();
530
534
  // Update state
531
535
  this.state.call_count++;
532
- this.state.quality_trend.push(quality);
533
- if (this.state.quality_trend.length > 20) {
534
- this.state.quality_trend = this.state.quality_trend.slice(-20);
536
+ this.state.success_trend.push(success);
537
+ if (this.state.success_trend.length > 20) {
538
+ this.state.success_trend = this.state.success_trend.slice(-20);
535
539
  }
536
540
  // Circuit breaker
537
541
  if (this.shouldBreak()) {
@@ -542,7 +546,7 @@ export class LoopForgeEngine {
542
546
  }
543
547
  logEvent("round_complete", {
544
548
  technique: "feedback",
545
- quality,
549
+ success,
546
550
  loopId: loopId ?? "unknown",
547
551
  round: loopRound ?? this.state.call_count,
548
552
  });
@@ -550,10 +554,10 @@ export class LoopForgeEngine {
550
554
  status: AgentStatus.OK,
551
555
  response: {
552
556
  status: AgentStatus.OK,
553
- prompt: `## Feedback Recorded\n\nQuality Score: ${quality}/5\nSignals: 1`,
557
+ prompt: `## Feedback Recorded\n\nSuccess: ${success}\nSignals: 1`,
554
558
  analysis: makeAnalysis({
555
559
  technique: "feedback",
556
- rationale: `quality=${quality}`,
560
+ rationale: `success=${success}`,
557
561
  independence: "n/a",
558
562
  cognitive_load: "low",
559
563
  }),
@@ -569,7 +573,7 @@ export class LoopForgeEngine {
569
573
  * P0–P2: Also persists discovered_constraints, objective_refinement,
570
574
  * and emerged_subtasks for the compiler to consume next round.
571
575
  * Call this BEFORE invokeLoopCompile for the next round so that
572
- * hydrateLoopContext picks up the latest quality scores. */
576
+ * hydrateLoopContext picks up the latest success flags. */
573
577
  autoFeedback(selfEval, loopId, round, task) {
574
578
  this.ensureInit({ task, mode: Mode.FEEDBACK, vault_config: makeVaultConfig(), feedback: null, skill_name: null, task_id: null });
575
579
  const fb = makeExecutionFeedback({
@@ -578,11 +582,6 @@ export class LoopForgeEngine {
578
582
  constraint_violations: selfEval.constraint_violations,
579
583
  manual_fixes_needed: "",
580
584
  });
581
- const quality = scoreQuality({
582
- success: fb.success,
583
- constraint_violations: fb.constraint_violations,
584
- manual_fixes_needed: fb.manual_fixes_needed,
585
- });
586
585
  const taskId = `loop:${loopId}:r${round}:feedback`;
587
586
  // Multi-agent: Merge sub-agent discovered constraints into the main constraint flow
588
587
  const subDiscovered = (selfEval.worker_results ?? [])
@@ -594,7 +593,7 @@ export class LoopForgeEngine {
594
593
  const signal = {
595
594
  task_id: taskId,
596
595
  task_type: task.slice(0, 80),
597
- quality_score: quality,
596
+ success: fb.success,
598
597
  skill_used: "",
599
598
  violations: selfEval.constraint_violations,
600
599
  manual_fixes: "",
@@ -630,9 +629,9 @@ export class LoopForgeEngine {
630
629
  }
631
630
  // Update state
632
631
  this.state.call_count++;
633
- this.state.quality_trend.push(quality);
634
- if (this.state.quality_trend.length > 20) {
635
- this.state.quality_trend = this.state.quality_trend.slice(-20);
632
+ this.state.success_trend.push(fb.success);
633
+ if (this.state.success_trend.length > 20) {
634
+ this.state.success_trend = this.state.success_trend.slice(-20);
636
635
  }
637
636
  // Circuit breaker
638
637
  if (this.shouldBreak()) {
@@ -644,10 +643,10 @@ export class LoopForgeEngine {
644
643
  logEvent("round_complete", {
645
644
  loopId,
646
645
  round,
647
- quality,
646
+ success: fb.success,
648
647
  technique: this.state?.last_technique ?? "feedback",
649
648
  });
650
- return quality;
649
+ return fb.success;
651
650
  }
652
651
  // ═══════════════════════════════════════════════════════════════════════
653
652
  // Loop Compile (public, primary)
@@ -670,53 +669,23 @@ export class LoopForgeEngine {
670
669
  force_level: extras.force_level ?? "auto",
671
670
  health_check_interval: extras.health_check_interval ?? 1,
672
671
  external_context: extras.external_context ?? "",
672
+ max_rounds: extras.max_rounds ?? undefined,
673
673
  });
674
674
  // Convert last_round_result if present
675
675
  const lastRR = extras.last_round_result;
676
676
  if (lastRR) {
677
677
  if (typeof lastRR === "object" && !Array.isArray(lastRR)) {
678
678
  const rr = lastRR;
679
- // Parse P4 execution evidence
680
- let executionEvidence;
681
- const rawEvidence = rr.execution_evidence;
682
- if (rawEvidence && typeof rawEvidence === "object") {
683
- const testResults = rawEvidence.test_results;
684
- executionEvidence = makeExecutionEvidence({
685
- files_changed: Array.isArray(rawEvidence.files_changed)
686
- ? rawEvidence.files_changed.filter((v) => typeof v === "string")
687
- : [],
688
- test_results: testResults && typeof testResults.passed === "number"
689
- ? {
690
- passed: testResults.passed,
691
- failed: testResults.failed ?? 0,
692
- skipped: testResults.skipped ?? 0,
693
- }
694
- : null,
695
- success_criteria_met: Array.isArray(rawEvidence.success_criteria_met)
696
- ? rawEvidence.success_criteria_met.filter((v) => typeof v === "string")
697
- : [],
698
- success_criteria_remaining: Array.isArray(rawEvidence.success_criteria_remaining)
699
- ? rawEvidence.success_criteria_remaining.filter((v) => typeof v === "string")
700
- : [],
701
- progress_estimate: typeof rawEvidence.progress_estimate === "number"
702
- ? Math.max(0, Math.min(1, rawEvidence.progress_estimate))
703
- : 0.0,
704
- });
705
- }
706
- // Parse P5 revised_success_criteria
707
- const revisedCriteria = Array.isArray(rr.revised_success_criteria)
708
- ? rr.revised_success_criteria
709
- .filter((v) => typeof v === "object" && v !== null &&
710
- typeof v.old === "string" && typeof v.new === "string")
711
- .map((v) => ({ old: v.old, new: v.new }))
712
- : [];
679
+ // Parse P4 execution evidence (shared helper)
680
+ const executionEvidence = parseExecutionEvidence(rr.execution_evidence);
681
+ // Parse P5 revised_success_criteria (shared parser)
682
+ const revisedCriteria = parseCriterionRevisions(rr.revised_success_criteria);
713
683
  lcr.last_round_result = makeLoopRoundResult({
714
684
  round: rr.round ?? 0,
715
685
  success: rr.success ?? false,
716
686
  output_summary: rr.output_summary ?? "",
717
687
  constraint_violations: rr.constraint_violations ?? [],
718
688
  manual_fixes_needed: rr.manual_fixes_needed ?? "",
719
- quality_score: rr.quality_score ?? 0,
720
689
  // P0–P2: Cognitive evolution fields
721
690
  discovered_constraints: Array.isArray(rr.discovered_constraints)
722
691
  ? rr.discovered_constraints.filter((v) => typeof v === "string")
@@ -737,21 +706,11 @@ export class LoopForgeEngine {
737
706
  wrong_assumptions: Array.isArray(rr.wrong_assumptions)
738
707
  ? rr.wrong_assumptions.filter((v) => typeof v === "string")
739
708
  : [],
740
- // Multi-agent: Worker delegation results
741
- worker_results: Array.isArray(rr.worker_results)
742
- ? rr.worker_results
743
- .filter((v) => typeof v?.agentId === "string" && typeof v?.subTask === "string")
744
- .map((v) => ({
745
- agentId: v.agentId,
746
- subAgentType: typeof v.subAgentType === "string" ? v.subAgentType : "general-purpose",
747
- subTask: v.subTask,
748
- resultSummary: typeof v.resultSummary === "string" ? v.resultSummary : "",
749
- success: typeof v.success === "boolean" ? v.success : false,
750
- discoveredConstraints: Array.isArray(v.discoveredConstraints)
751
- ? v.discoveredConstraints
752
- : [],
753
- }))
754
- : [],
709
+ // v1.10: Checkpoint boundary
710
+ compression_checkpoint: typeof rr.compression_checkpoint === "boolean" ? rr.compression_checkpoint : false,
711
+ checkpoint_label: typeof rr.checkpoint_label === "string" ? rr.checkpoint_label : "",
712
+ // Multi-agent: Worker delegation results (shared parser)
713
+ worker_results: parseWorkerResults(rr.worker_results),
755
714
  });
756
715
  }
757
716
  }
@@ -788,15 +747,10 @@ export class LoopForgeEngine {
788
747
  },
789
748
  };
790
749
  }
791
- // Build prompt text from response
792
- const promptLines = [
793
- `## LoopForge Loop Compile — Round ${response.round}`,
794
- `**Recompile Level**: ${response.recompile_level.toUpperCase()}`,
795
- `**Loop ID**: ${response.loop_id}`,
796
- `**Goal ID**: ${response.goal_id}`,
797
- "",
798
- response.prompt,
799
- ];
750
+ // Build prompt text from response.
751
+ // Compile functions (buildL2Prompt / specialist compilers via buildHeader)
752
+ // already produce their own round-level headers with technique + identity.
753
+ const promptLines = [response.prompt];
800
754
  if (response.warnings.length) {
801
755
  promptLines.push("");
802
756
  promptLines.push("### Warnings");
@@ -816,8 +770,8 @@ export class LoopForgeEngine {
816
770
  const hasError = verificationFlags.some((f) => f.severity === "error");
817
771
  if (hasError) {
818
772
  promptLines.push("");
819
- promptLines.push("**Gate Verdict: CONTRADICTED** — this round's quality score has been excluded " +
820
- "from the quality trend. Address each 🚫 flag explicitly in your next response.");
773
+ promptLines.push("**Gate Verdict: CONTRADICTED** — this round's success flag has been excluded " +
774
+ "from the success trend. Address each 🚫 flag explicitly in your next response.");
821
775
  }
822
776
  }
823
777
  if (response.loop_health) {
@@ -865,6 +819,7 @@ export class LoopForgeEngine {
865
819
  reference_file: response.reference_file,
866
820
  }),
867
821
  error: null,
822
+ state_file_content: response.state_file_content,
868
823
  },
869
824
  };
870
825
  }
@@ -876,17 +831,18 @@ export class LoopForgeEngine {
876
831
  return false;
877
832
  const policy = getPolicy();
878
833
  const maxCB = policy.engine.max_circuit_breaker;
879
- if (this.state.quality_trend.length < maxCB)
834
+ if (this.state.success_trend.length < maxCB)
880
835
  return false;
881
- const recent = this.state.quality_trend.slice(-maxCB);
882
- const isFlat = recent.every((val, i) => i === 0 || recent[i - 1] >= val);
883
- if (isFlat) {
836
+ const recent = this.state.success_trend.slice(-maxCB);
837
+ // Trip only when all recent rounds are failures (no false-positives on all-success)
838
+ const allFailed = recent.every((v) => v === false);
839
+ if (allFailed) {
884
840
  logEvent("circuit_breaker", {
885
841
  trend: recent,
886
- totalRounds: this.state.quality_trend.length,
842
+ totalRounds: this.state.success_trend.length,
887
843
  });
888
844
  }
889
- return isFlat;
845
+ return allFailed;
890
846
  }
891
847
  }
892
848
  // ═══════════════════════════════════════════════════════════════════════════