loopforge 1.12.0 → 2.0.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.
Files changed (182) hide show
  1. package/README.md +172 -95
  2. package/dist/backends/interface.d.ts +3 -9
  3. package/dist/backends/interface.d.ts.map +1 -1
  4. package/dist/backends/interface.js +1 -5
  5. package/dist/backends/interface.js.map +1 -1
  6. package/dist/canonical-state.d.ts +53 -0
  7. package/dist/canonical-state.d.ts.map +1 -0
  8. package/dist/canonical-state.js +161 -0
  9. package/dist/canonical-state.js.map +1 -0
  10. package/dist/cli.d.ts +4 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +227 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/enforcement-gate.d.ts +44 -0
  15. package/dist/enforcement-gate.d.ts.map +1 -0
  16. package/dist/enforcement-gate.js +271 -0
  17. package/dist/enforcement-gate.js.map +1 -0
  18. package/dist/engine.d.ts +17 -7
  19. package/dist/engine.d.ts.map +1 -1
  20. package/dist/engine.js +158 -249
  21. package/dist/engine.js.map +1 -1
  22. package/dist/evidence-provider.d.ts +113 -0
  23. package/dist/evidence-provider.d.ts.map +1 -0
  24. package/dist/evidence-provider.js +419 -0
  25. package/dist/evidence-provider.js.map +1 -0
  26. package/dist/index.d.ts +19 -11
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +16 -10
  29. package/dist/index.js.map +1 -1
  30. package/dist/init.d.ts +15 -0
  31. package/dist/init.d.ts.map +1 -0
  32. package/dist/init.js +49 -0
  33. package/dist/init.js.map +1 -0
  34. package/dist/interop.d.ts +30 -0
  35. package/dist/interop.d.ts.map +1 -0
  36. package/dist/interop.js +29 -0
  37. package/dist/interop.js.map +1 -0
  38. package/dist/loop-compiler.d.ts +14 -64
  39. package/dist/loop-compiler.d.ts.map +1 -1
  40. package/dist/loop-compiler.js +307 -2001
  41. package/dist/loop-compiler.js.map +1 -1
  42. package/dist/loop-store.d.ts +82 -0
  43. package/dist/loop-store.d.ts.map +1 -0
  44. package/dist/loop-store.js +332 -0
  45. package/dist/loop-store.js.map +1 -0
  46. package/dist/mcp/server.d.ts +9 -6
  47. package/dist/mcp/server.d.ts.map +1 -1
  48. package/dist/mcp/server.js +109 -73
  49. package/dist/mcp/server.js.map +1 -1
  50. package/dist/mcp/session.d.ts +84 -20
  51. package/dist/mcp/session.d.ts.map +1 -1
  52. package/dist/mcp/session.js +718 -316
  53. package/dist/mcp/session.js.map +1 -1
  54. package/dist/mcp/tools.d.ts +18 -268
  55. package/dist/mcp/tools.d.ts.map +1 -1
  56. package/dist/mcp/tools.js +225 -12
  57. package/dist/mcp/tools.js.map +1 -1
  58. package/dist/observability.d.ts +36 -16
  59. package/dist/observability.d.ts.map +1 -1
  60. package/dist/observability.js +82 -23
  61. package/dist/observability.js.map +1 -1
  62. package/dist/policy-metrics.d.ts +46 -0
  63. package/dist/policy-metrics.d.ts.map +1 -0
  64. package/dist/policy-metrics.js +146 -0
  65. package/dist/policy-metrics.js.map +1 -0
  66. package/dist/policy.d.ts +58 -75
  67. package/dist/policy.d.ts.map +1 -1
  68. package/dist/policy.js +122 -93
  69. package/dist/policy.js.map +1 -1
  70. package/dist/prompt-assembler.d.ts +30 -0
  71. package/dist/prompt-assembler.d.ts.map +1 -0
  72. package/dist/prompt-assembler.js +257 -0
  73. package/dist/prompt-assembler.js.map +1 -0
  74. package/dist/prompt-policy.d.ts +29 -0
  75. package/dist/prompt-policy.d.ts.map +1 -0
  76. package/dist/prompt-policy.js +51 -0
  77. package/dist/prompt-policy.js.map +1 -0
  78. package/dist/protocol.d.ts +117 -91
  79. package/dist/protocol.d.ts.map +1 -1
  80. package/dist/protocol.js +40 -36
  81. package/dist/protocol.js.map +1 -1
  82. package/dist/replay.d.ts.map +1 -1
  83. package/dist/replay.js +1 -27
  84. package/dist/replay.js.map +1 -1
  85. package/dist/round-coordinator.d.ts +90 -0
  86. package/dist/round-coordinator.d.ts.map +1 -0
  87. package/dist/round-coordinator.js +186 -0
  88. package/dist/round-coordinator.js.map +1 -0
  89. package/dist/round-driver.d.ts +53 -0
  90. package/dist/round-driver.d.ts.map +1 -0
  91. package/dist/round-driver.js +101 -0
  92. package/dist/round-driver.js.map +1 -0
  93. package/dist/round-transaction.d.ts +70 -0
  94. package/dist/round-transaction.d.ts.map +1 -0
  95. package/dist/round-transaction.js +290 -0
  96. package/dist/round-transaction.js.map +1 -0
  97. package/dist/runtime.d.ts +35 -17
  98. package/dist/runtime.d.ts.map +1 -1
  99. package/dist/runtime.js +357 -344
  100. package/dist/runtime.js.map +1 -1
  101. package/dist/storage.d.ts +38 -0
  102. package/dist/storage.d.ts.map +1 -0
  103. package/dist/storage.js +201 -0
  104. package/dist/storage.js.map +1 -0
  105. package/dist/verification-gate.d.ts +39 -7
  106. package/dist/verification-gate.d.ts.map +1 -1
  107. package/dist/verification-gate.js +198 -9
  108. package/dist/verification-gate.js.map +1 -1
  109. package/loop_policy.json +26 -47
  110. package/package.json +13 -10
  111. package/skills/perception/SKILL.md +80 -249
  112. package/dist/backends/fs.d.ts +0 -36
  113. package/dist/backends/fs.d.ts.map +0 -1
  114. package/dist/backends/fs.js +0 -358
  115. package/dist/backends/fs.js.map +0 -1
  116. package/dist/builder.d.ts +0 -12
  117. package/dist/builder.d.ts.map +0 -1
  118. package/dist/builder.js +0 -259
  119. package/dist/builder.js.map +0 -1
  120. package/dist/mcp-server.d.ts +0 -12
  121. package/dist/mcp-server.d.ts.map +0 -1
  122. package/dist/mcp-server.js +0 -14
  123. package/dist/mcp-server.js.map +0 -1
  124. package/dist/memory-bridge.d.ts +0 -47
  125. package/dist/memory-bridge.d.ts.map +0 -1
  126. package/dist/memory-bridge.js +0 -290
  127. package/dist/memory-bridge.js.map +0 -1
  128. package/dist/tests/_helpers.d.ts +0 -25
  129. package/dist/tests/_helpers.d.ts.map +0 -1
  130. package/dist/tests/_helpers.js +0 -47
  131. package/dist/tests/_helpers.js.map +0 -1
  132. package/dist/tests/builder.test.d.ts +0 -2
  133. package/dist/tests/builder.test.d.ts.map +0 -1
  134. package/dist/tests/builder.test.js +0 -110
  135. package/dist/tests/builder.test.js.map +0 -1
  136. package/dist/tests/engine.test.d.ts +0 -2
  137. package/dist/tests/engine.test.d.ts.map +0 -1
  138. package/dist/tests/engine.test.js +0 -371
  139. package/dist/tests/engine.test.js.map +0 -1
  140. package/dist/tests/generate-schema.test.d.ts +0 -8
  141. package/dist/tests/generate-schema.test.d.ts.map +0 -1
  142. package/dist/tests/generate-schema.test.js +0 -199
  143. package/dist/tests/generate-schema.test.js.map +0 -1
  144. package/dist/tests/loop-compiler.test.d.ts +0 -2
  145. package/dist/tests/loop-compiler.test.d.ts.map +0 -1
  146. package/dist/tests/loop-compiler.test.js +0 -1051
  147. package/dist/tests/loop-compiler.test.js.map +0 -1
  148. package/dist/tests/mcp.test.d.ts +0 -7
  149. package/dist/tests/mcp.test.d.ts.map +0 -1
  150. package/dist/tests/mcp.test.js +0 -485
  151. package/dist/tests/mcp.test.js.map +0 -1
  152. package/dist/tests/memory-bridge.test.d.ts +0 -8
  153. package/dist/tests/memory-bridge.test.d.ts.map +0 -1
  154. package/dist/tests/memory-bridge.test.js +0 -383
  155. package/dist/tests/memory-bridge.test.js.map +0 -1
  156. package/dist/tests/policy.test.d.ts +0 -2
  157. package/dist/tests/policy.test.d.ts.map +0 -1
  158. package/dist/tests/policy.test.js +0 -49
  159. package/dist/tests/policy.test.js.map +0 -1
  160. package/dist/tests/protocol.test.d.ts +0 -2
  161. package/dist/tests/protocol.test.d.ts.map +0 -1
  162. package/dist/tests/protocol.test.js +0 -73
  163. package/dist/tests/protocol.test.js.map +0 -1
  164. package/dist/tests/replay.test.d.ts +0 -2
  165. package/dist/tests/replay.test.d.ts.map +0 -1
  166. package/dist/tests/replay.test.js +0 -222
  167. package/dist/tests/replay.test.js.map +0 -1
  168. package/dist/tests/runtime.test.d.ts +0 -7
  169. package/dist/tests/runtime.test.d.ts.map +0 -1
  170. package/dist/tests/runtime.test.js +0 -323
  171. package/dist/tests/runtime.test.js.map +0 -1
  172. package/dist/tests/verification-gate.test.d.ts +0 -2
  173. package/dist/tests/verification-gate.test.d.ts.map +0 -1
  174. package/dist/tests/verification-gate.test.js +0 -407
  175. package/dist/tests/verification-gate.test.js.map +0 -1
  176. package/skills/prompt-techniques/SKILL.md +0 -52
  177. package/skills/prompt-techniques/references/chain-of-thought.md +0 -207
  178. package/skills/prompt-techniques/references/few-shot.md +0 -174
  179. package/skills/prompt-techniques/references/least-to-most.md +0 -182
  180. package/skills/prompt-techniques/references/step-back.md +0 -160
  181. package/skills/prompt-techniques/references/tree-of-thought.md +0 -191
  182. package/skills/prompt-techniques/references/zero-shot.md +0 -97
package/dist/engine.js CHANGED
@@ -7,11 +7,80 @@
7
7
  */
8
8
  import { randomUUID } from "node:crypto";
9
9
  import { getPolicy } from "./policy.js";
10
- import { FSBackend, readLineageMd } from "./backends/fs.js";
11
- import { AgentStatus, Mode, makeAnalysis, makeExecutionEvidence, makeExecutionFeedback, makeLoopCompileRequest, makeLoopObjective, makeLoopRoundResult, makeSelfEvaluation, makeSessionState, makeTaskId, makeVaultConfig, SELF_EVAL_REGEX, } from "./protocol.js";
10
+ import { FileLoopStore, LoopStoreBackend } from "./loop-store.js";
11
+ import { AgentStatus, Mode, makeExecutionEvidence, makeExecutionFeedback, makeLoopCompileRequest, makeLoopObjective, makeLoopRoundResult, makeSelfEvaluation, makeSessionState, makeTaskId, SELF_EVAL_REGEX, } from "./protocol.js";
12
12
  import { compileLoop } from "./loop-compiler.js";
13
13
  import { logEvent } from "./observability.js";
14
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
+ // ═══════════════════════════════════════════════════════════════════════════
15
84
  // Self-Evaluation extraction (v1.1 — autonomous loop feedback)
16
85
  // ═══════════════════════════════════════════════════════════════════════════
17
86
  /** Extract a structured SelfEvaluation from agent output text.
@@ -44,70 +113,17 @@ export function extractSelfEvaluation(text) {
44
113
  * (structured evaluation parameter path). */
45
114
  export function buildSelfEvaluation(raw) {
46
115
  // P4: Parse execution evidence from raw JSON
47
- let executionEvidence;
48
- const rawEvidence = raw.execution_evidence;
49
- if (rawEvidence && typeof rawEvidence === "object") {
50
- const testResults = rawEvidence.test_results;
51
- executionEvidence = makeExecutionEvidence({
52
- files_changed: Array.isArray(rawEvidence.files_changed)
53
- ? rawEvidence.files_changed.filter((v) => typeof v === "string")
54
- : [],
55
- test_results: testResults && typeof testResults.passed === "number"
56
- ? {
57
- passed: testResults.passed,
58
- failed: testResults.failed ?? 0,
59
- skipped: testResults.skipped ?? 0,
60
- }
61
- : null,
62
- success_criteria_met: Array.isArray(rawEvidence.success_criteria_met)
63
- ? rawEvidence.success_criteria_met.filter((v) => typeof v === "string")
64
- : [],
65
- success_criteria_remaining: Array.isArray(rawEvidence.success_criteria_remaining)
66
- ? rawEvidence.success_criteria_remaining.filter((v) => typeof v === "string")
67
- : [],
68
- progress_estimate: typeof rawEvidence.progress_estimate === "number"
69
- ? Math.max(0, Math.min(1, rawEvidence.progress_estimate))
70
- : 0.0,
71
- });
72
- }
116
+ const executionEvidence = parseExecutionEvidence(raw.execution_evidence);
73
117
  // P5: Parse corrections
74
118
  const retractedConstraints = Array.isArray(raw.retracted_constraints)
75
119
  ? raw.retracted_constraints.filter((v) => typeof v === "string")
76
120
  : [];
77
- const revisedCriteria = Array.isArray(raw.revised_success_criteria)
78
- ? raw.revised_success_criteria
79
- .filter((v) => typeof v === "object" && v !== null &&
80
- typeof v.old === "string" &&
81
- typeof v.new === "string")
82
- .map((v) => {
83
- const r = v;
84
- return { old: r.old, new: r.new };
85
- })
86
- : [];
121
+ const revisedCriteria = parseCriterionRevisions(raw.revised_success_criteria);
87
122
  const wrongAssumptions = Array.isArray(raw.wrong_assumptions)
88
123
  ? raw.wrong_assumptions.filter((v) => typeof v === "string")
89
124
  : [];
90
125
  // Multi-agent: Parse worker delegation results
91
- const workerResults = Array.isArray(raw.worker_results)
92
- ? raw.worker_results
93
- .filter((v) => typeof v === "object" && v !== null &&
94
- typeof v.agentId === "string" &&
95
- typeof v.subTask === "string" &&
96
- typeof v.resultSummary === "string")
97
- .map((v) => {
98
- const w = v;
99
- return {
100
- agentId: w.agentId,
101
- subAgentType: typeof w.subAgentType === "string" ? w.subAgentType : "general-purpose",
102
- subTask: w.subTask,
103
- resultSummary: w.resultSummary,
104
- success: typeof w.success === "boolean" ? w.success : false,
105
- discoveredConstraints: Array.isArray(w.discoveredConstraints)
106
- ? w.discoveredConstraints.filter((c) => typeof c === "string")
107
- : [],
108
- };
109
- })
110
- : [];
126
+ const workerResults = parseWorkerResults(raw.worker_results);
111
127
  return makeSelfEvaluation({
112
128
  success: typeof raw.success === "boolean" ? raw.success : false,
113
129
  output_summary: typeof raw.output_summary === "string" ? raw.output_summary : "",
@@ -136,6 +152,8 @@ export function buildSelfEvaluation(raw) {
136
152
  // v1.10: Checkpoint compression
137
153
  compression_checkpoint: typeof raw.compression_checkpoint === "boolean" ? raw.compression_checkpoint : false,
138
154
  checkpoint_label: typeof raw.checkpoint_label === "string" ? raw.checkpoint_label : "",
155
+ // v1.16: Agent's declared next action
156
+ next_action: typeof raw.next_action === "string" ? raw.next_action : undefined,
139
157
  });
140
158
  }
141
159
  /** Fallback heuristic when structured self-eval extraction fails.
@@ -175,21 +193,21 @@ function makeEngineMetrics() {
175
193
  // LoopForgeEngine
176
194
  // ═══════════════════════════════════════════════════════════════════════════
177
195
  export class LoopForgeEngine {
178
- skillsDir;
179
196
  state = null;
180
197
  backend = null;
181
198
  metrics = null;
182
199
  feedbackWriteBuffer = [];
183
200
  lastTask = null;
184
- seenConstraints = new Set();
185
- constructor(skillsDir = "skills", backend) {
186
- this.skillsDir = skillsDir;
187
- if (backend)
188
- this.backend = backend;
201
+ constructor(storeOrBackend) {
202
+ if (storeOrBackend) {
203
+ this.backend = "readSession" in storeOrBackend
204
+ ? new LoopStoreBackend(storeOrBackend)
205
+ : storeOrBackend;
206
+ }
189
207
  }
190
208
  resolveBackend() {
191
209
  if (this.backend === null) {
192
- this.backend = new FSBackend();
210
+ this.backend = new LoopStoreBackend(new FileLoopStore(getPolicy().backend.root_dir));
193
211
  }
194
212
  return this.backend;
195
213
  }
@@ -304,7 +322,6 @@ export class LoopForgeEngine {
304
322
  constraints_active: response.constraints_active,
305
323
  task: request.task,
306
324
  success: true,
307
- technique_used: response.technique_used,
308
325
  };
309
326
  let lastOutputSummary = "";
310
327
  let lastViolations = [];
@@ -320,8 +337,6 @@ export class LoopForgeEngine {
320
337
  timestamp: new Date().toISOString().replace(/\.\d+Z$/, ""),
321
338
  user_intent: `loop_compile round ${response.round} — ${response.goal_id}`,
322
339
  task_type: "loop_lineage",
323
- skill_used: response.technique_used,
324
- technique_used: response.technique_used,
325
340
  loop_id: response.loop_id,
326
341
  loop_lineage: structuredLineage,
327
342
  loop_objective: loopObjDict,
@@ -341,26 +356,6 @@ export class LoopForgeEngine {
341
356
  this.metrics.vaultWriteErrors++;
342
357
  logEvent("vault_write_error", { error: "persist_lineage_json" });
343
358
  }
344
- // 2. Markdown write (secondary, non-blocking)
345
- try {
346
- this.resolveBackend().writeLineageMd(response.loop_id, response.round, response.prompt || "", {
347
- goal_id: response.goal_id,
348
- goal_text_hash: response.goal_text_hash,
349
- recompile_level: response.recompile_level,
350
- constraints_active: response.constraints_active,
351
- task: request.task,
352
- technique_used: response.technique_used,
353
- loop_objective: loopObjDict,
354
- success: true,
355
- output_summary: lastOutputSummary,
356
- constraint_violations: lastViolations,
357
- });
358
- }
359
- catch {
360
- if (this.metrics)
361
- this.metrics.vaultWriteErrors++;
362
- logEvent("vault_write_error", { error: "persist_lineage_md" });
363
- }
364
359
  return vaultOk;
365
360
  }
366
361
  // ═══════════════════════════════════════════════════════════════════════
@@ -372,9 +367,14 @@ export class LoopForgeEngine {
372
367
  recordDelegation(loopId, round, entries) {
373
368
  if (!entries.length)
374
369
  return;
370
+ const taskId = `loop:${loopId}:r${round}:delegations`;
371
+ const existing = this.resolveBackend().queryEntries({ prefix: taskId })
372
+ .some((candidate) => candidate.task_id === taskId);
373
+ if (existing)
374
+ return;
375
375
  const entry = {
376
376
  id: randomUUID(),
377
- task_id: `loop:${loopId}:r${round}:delegations`,
377
+ task_id: taskId,
378
378
  version_tag: "v1",
379
379
  is_active: true,
380
380
  timestamp: new Date().toISOString().replace(/\.\d+Z$/, ""),
@@ -434,18 +434,8 @@ export class LoopForgeEngine {
434
434
  entry.success = fbSuccess.get(rnd);
435
435
  }
436
436
  }
437
- // Fallback: scan Markdown files if JSON vault had no matches
438
- let finalResults = results;
439
- if (!finalResults.length) {
440
- const mdResults = this.resolveBackend().scanLineageMd(loopId);
441
- if (mdResults.length)
442
- finalResults = mdResults;
443
- }
444
- // Normalize technique_used field
437
+ const finalResults = results;
445
438
  for (const entry of finalResults) {
446
- if (!entry.technique_used) {
447
- entry.technique_used = entry.skill_used;
448
- }
449
439
  const lineage = (entry.loop_lineage ?? entry.lineage ?? {});
450
440
  if (!entry.output_summary) {
451
441
  entry.output_summary = lineage.output_summary ?? "";
@@ -455,19 +445,6 @@ export class LoopForgeEngine {
455
445
  lineage.constraint_violations ?? [];
456
446
  }
457
447
  }
458
- // Enrich: attach full prompt text from Markdown for L0 cache reuse
459
- for (const entry of finalResults) {
460
- if (entry.full_prompt)
461
- continue;
462
- const lineage = (entry.loop_lineage ?? entry.lineage ?? {});
463
- const roundNum = lineage.round;
464
- if (roundNum) {
465
- const mdEntry = readLineageMd(loopId, roundNum);
466
- if (mdEntry?.full_prompt) {
467
- entry.full_prompt = mdEntry.full_prompt;
468
- }
469
- }
470
- }
471
448
  if (!finalResults.length)
472
449
  return null;
473
450
  return { results: finalResults, global_entries: [] };
@@ -485,7 +462,6 @@ export class LoopForgeEngine {
485
462
  response: {
486
463
  status: AgentStatus.ERROR,
487
464
  prompt: null,
488
- analysis: null,
489
465
  error: "Feedback mode requires a feedback payload.",
490
466
  },
491
467
  };
@@ -530,7 +506,6 @@ export class LoopForgeEngine {
530
506
  this.state.circuit_breaker_count = 0;
531
507
  }
532
508
  logEvent("round_complete", {
533
- technique: "feedback",
534
509
  success,
535
510
  loopId: loopId ?? "unknown",
536
511
  round: loopRound ?? this.state.call_count,
@@ -540,12 +515,6 @@ export class LoopForgeEngine {
540
515
  response: {
541
516
  status: AgentStatus.OK,
542
517
  prompt: `## Feedback Recorded\n\nSuccess: ${success}\nSignals: 1`,
543
- analysis: makeAnalysis({
544
- technique: "feedback",
545
- rationale: `success=${success}`,
546
- independence: "n/a",
547
- cognitive_load: "low",
548
- }),
549
518
  error: null,
550
519
  },
551
520
  };
@@ -559,8 +528,8 @@ export class LoopForgeEngine {
559
528
  * and emerged_subtasks for the compiler to consume next round.
560
529
  * Call this BEFORE invokeLoopCompile for the next round so that
561
530
  * hydrateLoopContext picks up the latest success flags. */
562
- autoFeedback(selfEval, loopId, round, task) {
563
- this.ensureInit({ task, mode: Mode.FEEDBACK, vault_config: makeVaultConfig(), feedback: null, skill_name: null, task_id: null });
531
+ autoFeedback(selfEval, loopId, round, task, roundTransaction) {
532
+ this.ensureInit({ task, mode: Mode.FEEDBACK, feedback: null, skill_name: null, task_id: null });
564
533
  const fb = makeExecutionFeedback({
565
534
  output: selfEval.output_summary,
566
535
  success: selfEval.success,
@@ -568,6 +537,27 @@ export class LoopForgeEngine {
568
537
  manual_fixes_needed: "",
569
538
  });
570
539
  const taskId = `loop:${loopId}:r${round}:feedback`;
540
+ const transactionId = typeof roundTransaction?.round_id === "string"
541
+ ? roundTransaction.round_id
542
+ : null;
543
+ const transactionPersisted = () => {
544
+ if (!transactionId)
545
+ return false;
546
+ return this.resolveBackend().queryEntries({
547
+ prefix: taskId,
548
+ feedbackOnly: true,
549
+ }).some((entry) => {
550
+ if (entry.task_id !== taskId)
551
+ return false;
552
+ const lineage = entry.loop_lineage;
553
+ const transaction = lineage?.round_transaction;
554
+ return transaction !== null &&
555
+ typeof transaction === "object" &&
556
+ !Array.isArray(transaction) &&
557
+ transaction.round_id === transactionId;
558
+ });
559
+ };
560
+ const alreadyCommitted = transactionPersisted();
571
561
  // Multi-agent: Merge sub-agent discovered constraints into the main constraint flow
572
562
  const subDiscovered = (selfEval.worker_results ?? [])
573
563
  .flatMap((w) => w.discoveredConstraints ?? [])
@@ -596,9 +586,21 @@ export class LoopForgeEngine {
596
586
  wrong_assumptions: selfEval.wrong_assumptions ?? [],
597
587
  // Multi-agent: Worker delegation results
598
588
  worker_results: selfEval.worker_results ?? [],
589
+ loop_lineage: roundTransaction
590
+ ? {
591
+ round,
592
+ round_id: roundTransaction.round_id,
593
+ round_transaction: roundTransaction,
594
+ }
595
+ : {},
599
596
  };
600
- this.persistFeedbackToVault(signal);
601
- this.flushFeedbackBuffer();
597
+ if (!alreadyCommitted) {
598
+ this.persistFeedbackToVault(signal);
599
+ this.flushFeedbackBuffer();
600
+ if (transactionId && !transactionPersisted()) {
601
+ throw new Error(`Round feedback commit failed: ${transactionId}`);
602
+ }
603
+ }
602
604
  // Multi-agent: Auto-record delegation journal from worker_results
603
605
  if (selfEval.worker_results && selfEval.worker_results.length > 0) {
604
606
  const entries = selfEval.worker_results.map((w, i) => ({
@@ -612,6 +614,10 @@ export class LoopForgeEngine {
612
614
  }));
613
615
  this.recordDelegation(loopId, round, entries);
614
616
  }
617
+ // Replaying a committed transaction may repair derived delegation data,
618
+ // but must never apply the feedback to mutable engine state twice.
619
+ if (alreadyCommitted)
620
+ return fb.success;
615
621
  // Update state
616
622
  this.state.call_count++;
617
623
  this.state.success_trend.push(fb.success);
@@ -629,14 +635,13 @@ export class LoopForgeEngine {
629
635
  loopId,
630
636
  round,
631
637
  success: fb.success,
632
- technique: this.state?.last_technique ?? "feedback",
633
638
  });
634
639
  return fb.success;
635
640
  }
636
641
  // ═══════════════════════════════════════════════════════════════════════
637
642
  // Loop Compile (public, primary)
638
643
  // ═══════════════════════════════════════════════════════════════════════
639
- invokeLoopCompile(request, hydrateResults) {
644
+ invokeLoopCompile(request, hydrateResults, options = {}) {
640
645
  this.ensureInit(request);
641
646
  this.lastTask = request.task;
642
647
  // Build LoopCompileRequest from LoopForgeRequest extras
@@ -654,46 +659,29 @@ export class LoopForgeEngine {
654
659
  force_level: extras.force_level ?? "auto",
655
660
  health_check_interval: extras.health_check_interval ?? 1,
656
661
  external_context: extras.external_context ?? "",
662
+ max_rounds: extras.max_rounds ?? undefined,
663
+ verification_flags: Array.isArray(extras.verification_flags)
664
+ ? extras.verification_flags
665
+ : [],
666
+ attempt: typeof extras.attempt === "number"
667
+ ? Math.max(1, Math.trunc(extras.attempt))
668
+ : 1,
669
+ consecutive_rejections: typeof extras.consecutive_rejections === "number"
670
+ ? Math.max(0, Math.trunc(extras.consecutive_rejections))
671
+ : 0,
672
+ rejection_notice: typeof extras.rejection_notice === "string"
673
+ ? extras.rejection_notice
674
+ : "",
657
675
  });
658
676
  // Convert last_round_result if present
659
677
  const lastRR = extras.last_round_result;
660
678
  if (lastRR) {
661
679
  if (typeof lastRR === "object" && !Array.isArray(lastRR)) {
662
680
  const rr = lastRR;
663
- // Parse P4 execution evidence
664
- let executionEvidence;
665
- const rawEvidence = rr.execution_evidence;
666
- if (rawEvidence && typeof rawEvidence === "object") {
667
- const testResults = rawEvidence.test_results;
668
- executionEvidence = makeExecutionEvidence({
669
- files_changed: Array.isArray(rawEvidence.files_changed)
670
- ? rawEvidence.files_changed.filter((v) => typeof v === "string")
671
- : [],
672
- test_results: testResults && typeof testResults.passed === "number"
673
- ? {
674
- passed: testResults.passed,
675
- failed: testResults.failed ?? 0,
676
- skipped: testResults.skipped ?? 0,
677
- }
678
- : null,
679
- success_criteria_met: Array.isArray(rawEvidence.success_criteria_met)
680
- ? rawEvidence.success_criteria_met.filter((v) => typeof v === "string")
681
- : [],
682
- success_criteria_remaining: Array.isArray(rawEvidence.success_criteria_remaining)
683
- ? rawEvidence.success_criteria_remaining.filter((v) => typeof v === "string")
684
- : [],
685
- progress_estimate: typeof rawEvidence.progress_estimate === "number"
686
- ? Math.max(0, Math.min(1, rawEvidence.progress_estimate))
687
- : 0.0,
688
- });
689
- }
690
- // Parse P5 revised_success_criteria
691
- const revisedCriteria = Array.isArray(rr.revised_success_criteria)
692
- ? rr.revised_success_criteria
693
- .filter((v) => typeof v === "object" && v !== null &&
694
- typeof v.old === "string" && typeof v.new === "string")
695
- .map((v) => ({ old: v.old, new: v.new }))
696
- : [];
681
+ // Parse P4 execution evidence (shared helper)
682
+ const executionEvidence = parseExecutionEvidence(rr.execution_evidence);
683
+ // Parse P5 revised_success_criteria (shared parser)
684
+ const revisedCriteria = parseCriterionRevisions(rr.revised_success_criteria);
697
685
  lcr.last_round_result = makeLoopRoundResult({
698
686
  round: rr.round ?? 0,
699
687
  success: rr.success ?? false,
@@ -723,21 +711,8 @@ export class LoopForgeEngine {
723
711
  // v1.10: Checkpoint boundary
724
712
  compression_checkpoint: typeof rr.compression_checkpoint === "boolean" ? rr.compression_checkpoint : false,
725
713
  checkpoint_label: typeof rr.checkpoint_label === "string" ? rr.checkpoint_label : "",
726
- // Multi-agent: Worker delegation results
727
- worker_results: Array.isArray(rr.worker_results)
728
- ? rr.worker_results
729
- .filter((v) => typeof v?.agentId === "string" && typeof v?.subTask === "string")
730
- .map((v) => ({
731
- agentId: v.agentId,
732
- subAgentType: typeof v.subAgentType === "string" ? v.subAgentType : "general-purpose",
733
- subTask: v.subTask,
734
- resultSummary: typeof v.resultSummary === "string" ? v.resultSummary : "",
735
- success: typeof v.success === "boolean" ? v.success : false,
736
- discoveredConstraints: Array.isArray(v.discoveredConstraints)
737
- ? v.discoveredConstraints
738
- : [],
739
- }))
740
- : [],
714
+ // Multi-agent: Worker delegation results (shared parser)
715
+ worker_results: parseWorkerResults(rr.worker_results),
741
716
  });
742
717
  }
743
718
  }
@@ -769,88 +744,22 @@ export class LoopForgeEngine {
769
744
  response: {
770
745
  status: AgentStatus.ERROR,
771
746
  prompt: null,
772
- analysis: null,
773
747
  error: `loop_compile failed: ${exc}`,
774
748
  },
775
749
  };
776
750
  }
777
- // Build prompt text from response
778
- const promptLines = [
779
- `## LoopForge Loop Compile — Round ${response.round}`,
780
- `**Recompile Level**: ${response.recompile_level.toUpperCase()}`,
781
- `**Loop ID**: ${response.loop_id}`,
782
- `**Goal ID**: ${response.goal_id}`,
783
- "",
784
- response.prompt,
785
- ];
786
- if (response.warnings.length) {
787
- promptLines.push("");
788
- promptLines.push("### Warnings");
789
- for (const w of response.warnings) {
790
- promptLines.push(`- ⚠️ ${w}`);
791
- }
792
- }
793
- // Verification gate flags (v1.6) — injected after compiler warnings
794
- const verificationFlags = extras.verification_flags ?? [];
795
- if (verificationFlags.length) {
796
- promptLines.push("");
797
- promptLines.push("### Verification Gate");
798
- for (const f of verificationFlags) {
799
- const icon = f.severity === "error" ? "🚫" : "⚠️";
800
- promptLines.push(`- ${icon} [${f.check}] ${f.detail}`);
801
- }
802
- const hasError = verificationFlags.some((f) => f.severity === "error");
803
- if (hasError) {
804
- promptLines.push("");
805
- promptLines.push("**Gate Verdict: CONTRADICTED** — this round's success flag has been excluded " +
806
- "from the success trend. Address each 🚫 flag explicitly in your next response.");
807
- }
808
- }
809
- if (response.loop_health) {
810
- const h = response.loop_health;
811
- promptLines.push("");
812
- promptLines.push("### Loop Health");
813
- promptLines.push(`- Goal Alignment: ${h.goal_alignment.toFixed(2)}`);
814
- promptLines.push(`- Constraint Integrity: ${h.constraint_integrity.toFixed(2)}`);
815
- promptLines.push(`- Task Continuity: ${h.task_continuity.toFixed(2)}`);
816
- promptLines.push(`- Drift Detected: ${h.drift_detected}`);
817
- promptLines.push(`- Strategy Stability: ${h.strategy_stability}`);
818
- }
819
- if (response.task_alignment &&
820
- response.task_alignment.escalation !== "none") {
821
- promptLines.push("");
822
- promptLines.push("### Task Alignment Advisory");
823
- promptLines.push(`- Score: ${response.task_alignment.alignment_score.toFixed(2)}`);
824
- promptLines.push(`- Escalation: ${response.task_alignment.escalation}`);
825
- promptLines.push(`- ${response.task_alignment.warning}`);
826
- }
827
- if (response.suggested_next_task) {
828
- promptLines.push("");
829
- promptLines.push(`### Suggested Next Task\n${response.suggested_next_task}`);
830
- }
831
751
  // Persist lineage to vault
832
- this.persistLoopLineage(response, lcr);
833
- // Track technique for status queries
834
- if (this.state) {
835
- this.state.last_technique = response.technique_used;
752
+ if (options.persistLineage !== false) {
753
+ this.persistLoopLineage(response, lcr);
836
754
  }
837
755
  return {
838
756
  status: AgentStatus.OK,
839
757
  response: {
840
758
  status: AgentStatus.OK,
841
- prompt: promptLines.join("\n"),
842
- analysis: makeAnalysis({
843
- technique: response.technique_used,
844
- rationale: `Recompile level: ${response.recompile_level}`,
845
- independence: "n/a",
846
- cognitive_load: response.recompile_level === "l0"
847
- ? "low"
848
- : response.recompile_level === "l1"
849
- ? "medium"
850
- : "high",
851
- reference_file: response.reference_file,
852
- }),
759
+ prompt: response.prompt,
853
760
  error: null,
761
+ state_file_content: response.state_file_content,
762
+ prompt_artifact: response.prompt_artifact,
854
763
  },
855
764
  };
856
765
  }
@@ -879,7 +788,7 @@ export class LoopForgeEngine {
879
788
  // ═══════════════════════════════════════════════════════════════════════════
880
789
  // Factory
881
790
  // ═══════════════════════════════════════════════════════════════════════════
882
- export function createEngine(skillsDir = "skills", backend) {
883
- return new LoopForgeEngine(skillsDir, backend);
791
+ export function createEngine(store) {
792
+ return new LoopForgeEngine(store);
884
793
  }
885
794
  //# sourceMappingURL=engine.js.map