loopforge 2.7.0 → 3.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 (173) hide show
  1. package/README.md +136 -182
  2. package/dist/canonical-event-writer.d.ts +20 -0
  3. package/dist/canonical-event-writer.d.ts.map +1 -0
  4. package/dist/canonical-event-writer.js +131 -0
  5. package/dist/canonical-event-writer.js.map +1 -0
  6. package/dist/canonical-state.d.ts +20 -16
  7. package/dist/canonical-state.d.ts.map +1 -1
  8. package/dist/canonical-state.js +211 -64
  9. package/dist/canonical-state.js.map +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +11 -10
  12. package/dist/cli.js.map +1 -1
  13. package/dist/cognitive-governance.d.ts +48 -0
  14. package/dist/cognitive-governance.d.ts.map +1 -0
  15. package/dist/cognitive-governance.js +226 -0
  16. package/dist/cognitive-governance.js.map +1 -0
  17. package/dist/compiler-subgoals.d.ts +7 -0
  18. package/dist/compiler-subgoals.d.ts.map +1 -0
  19. package/dist/compiler-subgoals.js +93 -0
  20. package/dist/compiler-subgoals.js.map +1 -0
  21. package/dist/derive-id.d.ts +12 -0
  22. package/dist/derive-id.d.ts.map +1 -0
  23. package/dist/derive-id.js +31 -0
  24. package/dist/derive-id.js.map +1 -0
  25. package/dist/evidence-provider.d.ts +26 -26
  26. package/dist/evidence-provider.d.ts.map +1 -1
  27. package/dist/evidence-provider.js +45 -71
  28. package/dist/evidence-provider.js.map +1 -1
  29. package/dist/generate-schema.js +90 -2
  30. package/dist/generate-schema.js.map +1 -1
  31. package/dist/index.d.ts +23 -29
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +16 -26
  34. package/dist/index.js.map +1 -1
  35. package/dist/loop-compiler.d.ts +2 -2
  36. package/dist/loop-compiler.d.ts.map +1 -1
  37. package/dist/loop-compiler.js +321 -315
  38. package/dist/loop-compiler.js.map +1 -1
  39. package/dist/loop-extras-parser.d.ts +2 -2
  40. package/dist/loop-extras-parser.d.ts.map +1 -1
  41. package/dist/loop-extras-parser.js +2 -2
  42. package/dist/loop-extras-parser.js.map +1 -1
  43. package/dist/loop-store.d.ts +20 -58
  44. package/dist/loop-store.d.ts.map +1 -1
  45. package/dist/loop-store.js +232 -311
  46. package/dist/loop-store.js.map +1 -1
  47. package/dist/mcp/server.d.ts +3 -3
  48. package/dist/mcp/server.d.ts.map +1 -1
  49. package/dist/mcp/server.js +123 -19
  50. package/dist/mcp/server.js.map +1 -1
  51. package/dist/mcp/session-projection.d.ts +5 -0
  52. package/dist/mcp/session-projection.d.ts.map +1 -0
  53. package/dist/mcp/session-projection.js +18 -0
  54. package/dist/mcp/session-projection.js.map +1 -0
  55. package/dist/mcp/session-queue.d.ts +3 -0
  56. package/dist/mcp/session-queue.d.ts.map +1 -0
  57. package/dist/mcp/session-queue.js +18 -0
  58. package/dist/mcp/session-queue.js.map +1 -0
  59. package/dist/mcp/session-types.d.ts +72 -0
  60. package/dist/mcp/session-types.d.ts.map +1 -0
  61. package/dist/mcp/session-types.js +2 -0
  62. package/dist/mcp/session-types.js.map +1 -0
  63. package/dist/mcp/session.d.ts +65 -84
  64. package/dist/mcp/session.d.ts.map +1 -1
  65. package/dist/mcp/session.js +667 -224
  66. package/dist/mcp/session.js.map +1 -1
  67. package/dist/mcp/tools.d.ts +26 -1
  68. package/dist/mcp/tools.d.ts.map +1 -1
  69. package/dist/mcp/tools.js +401 -254
  70. package/dist/mcp/tools.js.map +1 -1
  71. package/dist/policy-metrics.d.ts +24 -0
  72. package/dist/policy-metrics.d.ts.map +1 -1
  73. package/dist/policy-metrics.js +174 -0
  74. package/dist/policy-metrics.js.map +1 -1
  75. package/dist/policy.d.ts +74 -12
  76. package/dist/policy.d.ts.map +1 -1
  77. package/dist/policy.js +124 -19
  78. package/dist/policy.js.map +1 -1
  79. package/dist/prompt-assembler.d.ts +8 -4
  80. package/dist/prompt-assembler.d.ts.map +1 -1
  81. package/dist/prompt-assembler.js +533 -86
  82. package/dist/prompt-assembler.js.map +1 -1
  83. package/dist/prompt-policy.d.ts +8 -1
  84. package/dist/prompt-policy.d.ts.map +1 -1
  85. package/dist/prompt-policy.js +26 -8
  86. package/dist/prompt-policy.js.map +1 -1
  87. package/dist/protocol.d.ts +259 -173
  88. package/dist/protocol.d.ts.map +1 -1
  89. package/dist/protocol.js +17 -96
  90. package/dist/protocol.js.map +1 -1
  91. package/dist/replay.d.ts +11 -3
  92. package/dist/replay.d.ts.map +1 -1
  93. package/dist/replay.js +51 -14
  94. package/dist/replay.js.map +1 -1
  95. package/dist/round-commit-store.d.ts +12 -0
  96. package/dist/round-commit-store.d.ts.map +1 -0
  97. package/dist/round-commit-store.js +13 -0
  98. package/dist/round-commit-store.js.map +1 -0
  99. package/dist/round-coordinator.d.ts +53 -22
  100. package/dist/round-coordinator.d.ts.map +1 -1
  101. package/dist/round-coordinator.js +140 -44
  102. package/dist/round-coordinator.js.map +1 -1
  103. package/dist/round-driver.d.ts +18 -10
  104. package/dist/round-driver.d.ts.map +1 -1
  105. package/dist/round-driver.js +11 -13
  106. package/dist/round-driver.js.map +1 -1
  107. package/dist/round-gate.d.ts +31 -0
  108. package/dist/round-gate.d.ts.map +1 -0
  109. package/dist/round-gate.js +259 -0
  110. package/dist/round-gate.js.map +1 -0
  111. package/dist/round-recovery.d.ts +17 -0
  112. package/dist/round-recovery.d.ts.map +1 -0
  113. package/dist/round-recovery.js +117 -0
  114. package/dist/round-recovery.js.map +1 -0
  115. package/dist/round-report.d.ts +16 -0
  116. package/dist/round-report.d.ts.map +1 -0
  117. package/dist/round-report.js +401 -0
  118. package/dist/round-report.js.map +1 -0
  119. package/dist/round-transaction.d.ts +23 -12
  120. package/dist/round-transaction.d.ts.map +1 -1
  121. package/dist/round-transaction.js +104 -91
  122. package/dist/round-transaction.js.map +1 -1
  123. package/dist/runtime-services.d.ts +59 -0
  124. package/dist/runtime-services.d.ts.map +1 -0
  125. package/dist/{engine.js → runtime-services.js} +157 -275
  126. package/dist/runtime-services.js.map +1 -0
  127. package/dist/storage.d.ts +3 -9
  128. package/dist/storage.d.ts.map +1 -1
  129. package/dist/storage.js +6 -17
  130. package/dist/storage.js.map +1 -1
  131. package/dist/token-utils.d.ts +12 -8
  132. package/dist/token-utils.d.ts.map +1 -1
  133. package/dist/token-utils.js +56 -10
  134. package/dist/token-utils.js.map +1 -1
  135. package/dist/vault-format.d.ts +4 -0
  136. package/dist/vault-format.d.ts.map +1 -0
  137. package/dist/vault-format.js +4 -0
  138. package/dist/vault-format.js.map +1 -0
  139. package/dist/vault-types.d.ts +24 -0
  140. package/dist/vault-types.d.ts.map +1 -0
  141. package/dist/vault-types.js +2 -0
  142. package/dist/vault-types.js.map +1 -0
  143. package/loop_policy.json +10 -12
  144. package/loopforge-policy.json +340 -0
  145. package/package.json +13 -7
  146. package/skills/perception/SKILL.md +200 -90
  147. package/dist/backends/interface.d.ts +0 -39
  148. package/dist/backends/interface.d.ts.map +0 -1
  149. package/dist/backends/interface.js +0 -6
  150. package/dist/backends/interface.js.map +0 -1
  151. package/dist/enforcement-gate.d.ts +0 -50
  152. package/dist/enforcement-gate.d.ts.map +0 -1
  153. package/dist/enforcement-gate.js +0 -501
  154. package/dist/enforcement-gate.js.map +0 -1
  155. package/dist/engine.d.ts +0 -59
  156. package/dist/engine.d.ts.map +0 -1
  157. package/dist/engine.js.map +0 -1
  158. package/dist/interop.d.ts +0 -35
  159. package/dist/interop.d.ts.map +0 -1
  160. package/dist/interop.js +0 -34
  161. package/dist/interop.js.map +0 -1
  162. package/dist/run.d.ts +0 -51
  163. package/dist/run.d.ts.map +0 -1
  164. package/dist/run.js +0 -200
  165. package/dist/run.js.map +0 -1
  166. package/dist/self-eval.d.ts +0 -23
  167. package/dist/self-eval.d.ts.map +0 -1
  168. package/dist/self-eval.js +0 -151
  169. package/dist/self-eval.js.map +0 -1
  170. package/dist/verification-gate.d.ts +0 -52
  171. package/dist/verification-gate.d.ts.map +0 -1
  172. package/dist/verification-gate.js +0 -527
  173. package/dist/verification-gate.js.map +0 -1
@@ -1,44 +1,42 @@
1
- /** LoopForge-loop_compile — Engine (outer loop manager).
1
+ /** LoopForge runtime services — stateless compiler, round-event and metrics boundary.
2
2
  *
3
- * 2-mode engine with vault-backed loop lineage persistence.
4
- * invokeLoopCompile (primary), invokeFeedback.
5
- * Circuit breaker prevents infinite stall loops.
6
- * EngineMetrics tracks silent-failure counters for observability.
3
+ * The public execution path owns session and transaction state; this service
4
+ * provides the pure compile and round-event operations used by that path.
5
+ * invokeLoopCompile is the primary operation. RuntimeMetrics tracks
6
+ * silent-failure counters for observability.
7
7
  */
8
8
  import { randomUUID } from "node:crypto";
9
9
  import { getPolicy } from "./policy.js";
10
- import { FileLoopStore, LoopStoreBackend } from "./loop-store.js";
11
- import { AgentStatus, Mode, makeExecutionFeedback, makeLoopCompileRequest, makeLoopObjective, makeLoopRoundResult, makeSessionState, makeTaskId, } from "./protocol.js";
10
+ import { FileLoopStore } from "./loop-store.js";
11
+ import { AgentStatus, Mode, makeLoopCompileRequest, makeLoopObjective, makeSessionState, makeTaskId, } from "./protocol.js";
12
12
  import { compileLoop } from "./loop-compiler.js";
13
13
  import { logEvent } from "./observability.js";
14
- import { parseExecutionEvidence, parseCriterionRevisions, parseWorkerResults, } from "./self-eval.js";
14
+ import { policyMetrics } from "./policy-metrics.js";
15
15
  import { parseLoopExtras } from "./loop-extras-parser.js";
16
- // ── Re-export self-evaluation utilities (moved to self-eval.ts) ──────────
17
- export { parseExecutionEvidence, parseCriterionRevisions, parseWorkerResults, extractSelfEvaluation, buildSelfEvaluation, } from "./self-eval.js";
18
- function makeEngineMetrics() {
16
+ import { CanonicalEventWriter } from "./canonical-event-writer.js";
17
+ import { parseRoundReport, } from "./round-report.js";
18
+ function makeRuntimeMetrics() {
19
19
  return {
20
20
  vaultWriteErrors: 0,
21
21
  sessionStart: Date.now(),
22
22
  };
23
23
  }
24
24
  // ═══════════════════════════════════════════════════════════════════════════
25
- // LoopForgeEngine
25
+ // RuntimeServices
26
26
  // ═══════════════════════════════════════════════════════════════════════════
27
- export class LoopForgeEngine {
27
+ export class RuntimeServices {
28
28
  state = null;
29
29
  backend = null;
30
30
  metrics = null;
31
+ eventWriter = null;
31
32
  lastTask = null;
32
- constructor(storeOrBackend) {
33
- if (storeOrBackend) {
34
- this.backend = "readSession" in storeOrBackend
35
- ? new LoopStoreBackend(storeOrBackend)
36
- : storeOrBackend;
37
- }
33
+ constructor(store) {
34
+ if (store)
35
+ this.backend = store;
38
36
  }
39
37
  resolveBackend() {
40
38
  if (this.backend === null) {
41
- this.backend = new LoopStoreBackend(new FileLoopStore(getPolicy().backend.root_dir));
39
+ this.backend = new FileLoopStore(getPolicy().backend.root_dir);
42
40
  }
43
41
  return this.backend;
44
42
  }
@@ -46,10 +44,13 @@ export class LoopForgeEngine {
46
44
  getBackend() {
47
45
  return this.resolveBackend();
48
46
  }
49
- /** Expose engine health counters for observability (MCP status, logging). */
47
+ writer() {
48
+ return this.eventWriter ??= new CanonicalEventWriter(this.resolveBackend());
49
+ }
50
+ /** Expose runtime health counters for observability (MCP status, logging). */
50
51
  getMetrics() {
51
52
  if (this.metrics === null) {
52
- this.metrics = makeEngineMetrics();
53
+ this.metrics = makeRuntimeMetrics();
53
54
  }
54
55
  return this.metrics;
55
56
  }
@@ -58,62 +59,58 @@ export class LoopForgeEngine {
58
59
  this.state = makeSessionState(request.task_id || makeTaskId(request.task));
59
60
  }
60
61
  if (this.metrics === null) {
61
- this.metrics = makeEngineMetrics();
62
+ this.metrics = makeRuntimeMetrics();
62
63
  }
63
64
  }
64
65
  // ═══════════════════════════════════════════════════════════════════════
65
- // Feedback persistence
66
+ // Canonical round-event persistence
66
67
  // ═══════════════════════════════════════════════════════════════════════
67
- persistFeedbackToVault(signal) {
68
+ persistRoundEvent(signal) {
68
69
  if (this.metrics === null) {
69
- this.metrics = makeEngineMetrics();
70
+ this.metrics = makeRuntimeMetrics();
71
+ }
72
+ // Round events are committed synchronously by the canonical writer.
73
+ const entry = this.buildRoundEventEntry(signal);
74
+ if (!entry) {
75
+ throw new Error("Cannot persist a round event without a valid canonical RoundReport");
70
76
  }
71
- // v2.6: write directly — the buffer was always flushed immediately
72
- // (autoFeedback calls persistFeedbackToVault then immediately flushFeedbackBuffer).
73
- const entry = this.buildFeedbackEntry(signal);
74
- if (!entry)
75
- return;
76
77
  try {
77
- this.resolveBackend().appendEntry(entry);
78
+ this.writer().append(entry);
78
79
  }
79
- catch {
80
+ catch (error) {
80
81
  if (this.metrics)
81
82
  this.metrics.vaultWriteErrors++;
82
- logEvent("vault_write_error", { error: "feedback_persist" });
83
+ logEvent("vault_write_error", { error: "round_event_persist" });
84
+ logEvent("persist_failure", { operation: "round_event" });
85
+ policyMetrics.recordVaultWriteError("round_event_persist");
86
+ throw new Error(`Round event persistence failed: ${error instanceof Error ? error.message : String(error)}`);
83
87
  }
84
88
  }
85
- buildFeedbackEntry(signal) {
89
+ buildRoundEventEntry(signal) {
86
90
  try {
91
+ const canonicalReport = parseRoundReport(signal.round_report);
92
+ if (!canonicalReport)
93
+ return null;
87
94
  return {
88
95
  id: randomUUID(),
89
- task_id: signal.task_id ?? "feedback",
90
- version_tag: "v1",
96
+ task_id: signal.task_id ?? "round-event",
97
+ format: "loopforge.event",
91
98
  is_active: true,
92
99
  timestamp: new Date().toISOString().replace(/\.\d+Z$/, ""),
93
- user_intent: String(signal.task_type ?? "").slice(0, 200),
94
- success: signal.success ?? false,
95
- execution_feedback: JSON.stringify({
96
- success: signal.success ?? false,
97
- status: signal.success ? "success" : "partial",
98
- constraint_compliance: {
99
- all_hard_constraints_met: !Array.isArray(signal.violations) || signal.violations.length === 0,
100
- violations: signal.violations ?? [],
101
- },
102
- output_summary: signal.task_type ?? "",
103
- improvement_notes: signal.manual_fixes ?? "",
104
- }),
105
- task_type: signal.task_type ?? "",
106
- tags: signal.skill_used ? [signal.skill_used] : [],
107
- skill_used: signal.skill_used ?? "",
100
+ user_intent: `round event ${String(signal.round ?? "")}`.trim(),
101
+ task_type: "round_event",
102
+ success: signal.success === true,
108
103
  loop_id: signal.loop_id,
109
104
  loop_lineage: signal.loop_lineage ?? {},
105
+ round_report: canonicalReport,
110
106
  };
111
107
  }
112
- catch {
108
+ catch (error) {
113
109
  if (this.metrics)
114
110
  this.metrics.vaultWriteErrors++;
115
- logEvent("vault_write_error", { error: "feedback_entry_build" });
116
- return null;
111
+ logEvent("vault_write_error", { error: "round_event_build" });
112
+ logEvent("persist_failure", { operation: "round_event_build" });
113
+ throw new Error(`Round event construction failed: ${error instanceof Error ? error.message : String(error)}`);
117
114
  }
118
115
  }
119
116
  // ═══════════════════════════════════════════════════════════════════════
@@ -121,7 +118,7 @@ export class LoopForgeEngine {
121
118
  // ═══════════════════════════════════════════════════════════════════════
122
119
  persistLoopLineage(response, request) {
123
120
  if (this.metrics === null)
124
- this.metrics = makeEngineMetrics();
121
+ this.metrics = makeRuntimeMetrics();
125
122
  const loopObjDict = response.loop_objective
126
123
  ? response.loop_objective
127
124
  : null;
@@ -133,18 +130,11 @@ export class LoopForgeEngine {
133
130
  recompile_level: response.recompile_level,
134
131
  constraints_active: response.constraints_active,
135
132
  task: request.task,
136
- success: true,
137
133
  };
138
- let lastOutputSummary = "";
139
- let lastViolations = [];
140
- if (request.last_round_result) {
141
- lastOutputSummary = request.last_round_result.output_summary || "";
142
- lastViolations = request.last_round_result.constraint_violations || [];
143
- }
144
134
  const entry = {
145
135
  id: randomUUID(),
146
136
  task_id: `loop:${response.loop_id}:r${response.round}`,
147
- version_tag: "v1",
137
+ format: "loopforge.event",
148
138
  is_active: true,
149
139
  timestamp: new Date().toISOString().replace(/\.\d+Z$/, ""),
150
140
  user_intent: `loop_compile round ${response.round} — ${response.goal_id}`,
@@ -153,20 +143,21 @@ export class LoopForgeEngine {
153
143
  loop_lineage: structuredLineage,
154
144
  loop_objective: loopObjDict,
155
145
  task: request.task,
156
- output_summary: lastOutputSummary,
157
- constraint_violations: lastViolations,
158
146
  tags: [response.loop_id, response.recompile_level, response.goal_id],
159
147
  };
160
148
  // 1. JSON vault write (primary)
161
149
  let vaultOk = false;
162
150
  try {
163
- this.resolveBackend().appendEntry(entry);
151
+ this.writer().append(entry);
164
152
  vaultOk = true;
165
153
  }
166
- catch {
154
+ catch (error) {
167
155
  if (this.metrics)
168
156
  this.metrics.vaultWriteErrors++;
169
157
  logEvent("vault_write_error", { error: "persist_lineage_json" });
158
+ logEvent("persist_failure", { operation: "lineage" });
159
+ policyMetrics.recordVaultWriteError("lineage_persist");
160
+ throw new Error(`Loop lineage persistence failed: ${error instanceof Error ? error.message : String(error)}`);
170
161
  }
171
162
  return vaultOk;
172
163
  }
@@ -180,14 +171,14 @@ export class LoopForgeEngine {
180
171
  if (!entries.length)
181
172
  return;
182
173
  const taskId = `loop:${loopId}:r${round}:delegations`;
183
- const existing = this.resolveBackend().queryEntries({ prefix: taskId })
174
+ const existing = this.resolveBackend().queryLoopEvents(loopId)
184
175
  .some((candidate) => candidate.task_id === taskId);
185
176
  if (existing)
186
177
  return;
187
178
  const entry = {
188
179
  id: randomUUID(),
189
180
  task_id: taskId,
190
- version_tag: "v1",
181
+ format: "loopforge.event",
191
182
  is_active: true,
192
183
  timestamp: new Date().toISOString().replace(/\.\d+Z$/, ""),
193
184
  user_intent: `Delegation journal — round ${round}`,
@@ -207,12 +198,15 @@ export class LoopForgeEngine {
207
198
  },
208
199
  };
209
200
  try {
210
- this.resolveBackend().appendEntry(entry);
201
+ this.writer().append(entry);
211
202
  }
212
- catch {
203
+ catch (error) {
213
204
  if (this.metrics)
214
205
  this.metrics.vaultWriteErrors++;
215
206
  logEvent("vault_write_error", { error: "record_delegation" });
207
+ logEvent("persist_failure", { operation: "delegation" });
208
+ policyMetrics.recordVaultWriteError("delegation_persist");
209
+ throw new Error(`Failed to persist delegation journal for ${loopId} round ${round}`, { cause: error instanceof Error ? error : undefined });
216
210
  }
217
211
  }
218
212
  // ═══════════════════════════════════════════════════════════════════════
@@ -220,35 +214,55 @@ export class LoopForgeEngine {
220
214
  // ═══════════════════════════════════════════════════════════════════════
221
215
  hydrateLoopContext(loopId) {
222
216
  const prefix = `loop:${loopId}:r`;
223
- const results = this.resolveBackend().queryEntries({ prefix });
224
- // Merge feedback success flags into lineage entries
225
- const fbEntries = this.resolveBackend().queryEntries({
226
- prefix,
227
- feedbackOnly: true,
228
- });
229
- const fbSuccess = new Map();
230
- for (const fe of fbEntries) {
231
- const tid = String(fe.task_id ?? "");
232
- const parts = tid.split(":r");
233
- if (parts.length >= 2) {
234
- const roundStr = parts[1].split(":")[0];
235
- const fbRound = parseInt(roundStr, 10);
236
- if (!Number.isNaN(fbRound) && fe.success !== undefined) {
237
- fbSuccess.set(fbRound, fe.success);
217
+ // Hydration builds an ephemeral event projection. Clone entries
218
+ // before merging round events so a read can never mutate the Vault backend.
219
+ const loopEntries = this.resolveBackend().queryLoopEvents(loopId);
220
+ const results = [
221
+ ...loopEntries.filter((entry) => String(entry.task_id ?? "").startsWith(prefix)),
222
+ // Gate decisions are loop facts too; include them so the compiler can
223
+ // rebuild the active gate after a user decision across restarts.
224
+ ...loopEntries.filter((entry) => String(entry.task_id ?? "").startsWith(`loop:${loopId}:gate`)),
225
+ ].map((entry) => ({
226
+ ...entry,
227
+ loop_lineage: entry.loop_lineage ? { ...entry.loop_lineage } : undefined,
228
+ }));
229
+ // Merge round-event success flags into lineage entries
230
+ const roundEvents = loopEntries.filter((entry) => String(entry.task_id ?? "").startsWith(prefix) &&
231
+ String(entry.task_id ?? "").endsWith(":event"));
232
+ const eventSuccess = new Map();
233
+ const eventByRound = new Map();
234
+ for (const event of roundEvents) {
235
+ const tid = String(event.task_id ?? "");
236
+ // task_id is `loop:<loopId>:r<round>:event`. loopId may itself contain
237
+ // ":r" (validateLoopId permits colons), so a naive split(":r") would
238
+ // misparse. Greedy (.+) backtracks to the LAST `:r<digits>` segment,
239
+ // which is always the real round number.
240
+ const match = tid.match(/^loop:(.+):r(\d+)(?::.+)?$/);
241
+ if (match) {
242
+ const eventRound = parseInt(match[2], 10);
243
+ if (!Number.isNaN(eventRound) && event.success !== undefined) {
244
+ eventSuccess.set(eventRound, event.success);
245
+ eventByRound.set(eventRound, event);
238
246
  }
239
247
  }
240
248
  }
241
249
  for (const entry of results) {
242
- const lineage = (entry.loop_lineage ?? entry.lineage ?? {});
250
+ const lineage = (entry.loop_lineage ?? {});
243
251
  const rnd = lineage.round;
244
- if (rnd && fbSuccess.has(rnd)) {
245
- lineage.success = fbSuccess.get(rnd);
246
- entry.success = fbSuccess.get(rnd);
252
+ if (rnd && eventSuccess.has(rnd)) {
253
+ lineage.success = eventSuccess.get(rnd);
254
+ entry.success = eventSuccess.get(rnd);
255
+ const event = eventByRound.get(rnd);
256
+ if (event?.round_report) {
257
+ const report = parseRoundReport(event.round_report);
258
+ if (report)
259
+ entry.round_report = report;
260
+ }
247
261
  }
248
262
  }
249
263
  const finalResults = results;
250
264
  for (const entry of finalResults) {
251
- const lineage = (entry.loop_lineage ?? entry.lineage ?? {});
265
+ const lineage = (entry.loop_lineage ?? {});
252
266
  if (!entry.output_summary) {
253
267
  entry.output_summary = lineage.output_summary ?? "";
254
268
  }
@@ -262,94 +276,28 @@ export class LoopForgeEngine {
262
276
  return { results: finalResults, global_entries: [] };
263
277
  }
264
278
  // ═══════════════════════════════════════════════════════════════════════
265
- // Feedback (public)
266
- // ═══════════════════════════════════════════════════════════════════════
267
- invokeFeedback(request, _hydrateResults) {
268
- this.ensureInit(request);
269
- this.lastTask = request.task;
270
- const fb = request.feedback;
271
- if (!fb) {
272
- return {
273
- status: AgentStatus.ERROR,
274
- response: {
275
- status: AgentStatus.ERROR,
276
- prompt: null,
277
- error: "Feedback mode requires a feedback payload.",
278
- },
279
- };
280
- }
281
- const success = fb.success;
282
- const violations = fb.constraint_violations ?? [];
283
- const fixes = fb.manual_fixes_needed ?? "";
284
- // Loop-aware task_id for feedback→lineage backfill
285
- const loopId = request.loop_id;
286
- const loopRound = request.round;
287
- let taskId;
288
- if (loopId && loopRound !== undefined) {
289
- taskId = `loop:${loopId}:r${loopRound}:feedback`;
290
- }
291
- else {
292
- taskId = request.task_id ?? request.task.slice(0, 60);
293
- }
294
- const signal = {
295
- task_id: taskId,
296
- task_type: request.task.slice(0, 80),
297
- success,
298
- skill_used: request.skill_name ?? "",
299
- violations,
300
- manual_fixes: fixes,
301
- loop_id: loopId,
302
- round: loopRound,
303
- };
304
- this.persistFeedbackToVault(signal);
305
- // Update state
306
- this.state.call_count++;
307
- this.state.success_trend.push(success);
308
- if (this.state.success_trend.length > 20) {
309
- this.state.success_trend = this.state.success_trend.slice(-20);
310
- }
311
- logEvent("round_complete", {
312
- success,
313
- loopId: loopId ?? "unknown",
314
- round: loopRound ?? this.state.call_count,
315
- });
316
- return {
317
- status: AgentStatus.OK,
318
- response: {
319
- status: AgentStatus.OK,
320
- prompt: `## Feedback Recorded\n\nSuccess: ${success}\nSignals: 1`,
321
- error: null,
322
- },
323
- };
324
- }
325
- // ═══════════════════════════════════════════════════════════════════════
326
- // Auto-Feedback (v1.1 — autonomous loop, no human in the loop)
279
+ // Canonical round-event commit
327
280
  // ═══════════════════════════════════════════════════════════════════════
328
- /** Record self-evaluation from agent output without human intervention.
329
- * Converts SelfEvaluation → ExecutionFeedback → vault persistence.
281
+ /** Record a round report and persist the canonical event.
282
+ * Compiler views remain ephemeral and are never written as facts.
330
283
  * P0–P2: Also persists discovered_constraints, objective_refinement,
331
284
  * and emerged_subtasks for the compiler to consume next round.
332
285
  * Call this BEFORE invokeLoopCompile for the next round so that
333
286
  * hydrateLoopContext picks up the latest success flags. */
334
- autoFeedback(selfEval, loopId, round, task, roundTransaction) {
335
- this.ensureInit({ task, mode: Mode.FEEDBACK, feedback: null, skill_name: null, task_id: null });
336
- const fb = makeExecutionFeedback({
337
- output: selfEval.output_summary,
338
- success: selfEval.success,
339
- constraint_violations: selfEval.constraint_violations,
340
- manual_fixes_needed: "",
341
- });
342
- const taskId = `loop:${loopId}:r${round}:feedback`;
287
+ commitRoundReport(loopId, round, task, roundTransaction, roundReport) {
288
+ if (!roundReport)
289
+ throw new Error("commitRoundReport requires a canonical RoundReport");
290
+ this.ensureInit({ task, mode: Mode.LOOP_COMPILE, skill_name: null, task_id: null });
291
+ const taskId = `loop:${loopId}:r${round}:event`;
343
292
  const transactionId = typeof roundTransaction?.round_id === "string"
344
293
  ? roundTransaction.round_id
345
294
  : null;
346
295
  const transactionPersisted = () => {
347
296
  if (!transactionId)
348
297
  return false;
349
- return this.resolveBackend().queryEntries({
350
- prefix: taskId,
351
- feedbackOnly: true,
352
- }).some((entry) => {
298
+ return this.resolveBackend().queryLoopEvents(loopId)
299
+ .filter((entry) => entry.task_id === taskId)
300
+ .some((entry) => {
353
301
  if (entry.task_id !== taskId)
354
302
  return false;
355
303
  const lineage = entry.loop_lineage;
@@ -361,34 +309,14 @@ export class LoopForgeEngine {
361
309
  });
362
310
  };
363
311
  const alreadyCommitted = transactionPersisted();
364
- // Multi-agent: Merge sub-agent discovered constraints into the main constraint flow
365
- const subDiscovered = (selfEval.worker_results ?? [])
366
- .flatMap((w) => w.discoveredConstraints ?? [])
367
- .filter((c) => c.length > 0);
368
- const mergedDiscovered = [
369
- ...new Set([...(selfEval.discovered_constraints ?? []), ...subDiscovered]),
370
- ];
371
312
  const signal = {
372
313
  task_id: taskId,
373
- task_type: task.slice(0, 80),
374
- success: fb.success,
375
- skill_used: "",
376
- violations: selfEval.constraint_violations,
377
- manual_fixes: "",
314
+ task_type: "round_event",
315
+ success: roundReport.outcome === "success",
378
316
  loop_id: loopId,
379
317
  round,
380
- // P0–P2: Evolution fields
381
- discovered_constraints: mergedDiscovered,
382
- objective_refinement: selfEval.objective_refinement ?? "",
383
- emerged_subtasks: selfEval.emerged_subtasks ?? [],
384
- // P4: Execution evidence
385
- execution_evidence: selfEval.execution_evidence ?? null,
386
- // P5: Self-correction
387
- retracted_constraints: selfEval.retracted_constraints ?? [],
388
- revised_success_criteria: selfEval.revised_success_criteria ?? [],
389
- wrong_assumptions: selfEval.wrong_assumptions ?? [],
390
- // Multi-agent: Worker delegation results
391
- worker_results: selfEval.worker_results ?? [],
318
+ format: "loopforge.event",
319
+ round_report: roundReport,
392
320
  loop_lineage: roundTransaction
393
321
  ? {
394
322
  round,
@@ -398,43 +326,19 @@ export class LoopForgeEngine {
398
326
  : {},
399
327
  };
400
328
  if (!alreadyCommitted) {
401
- this.persistFeedbackToVault(signal);
329
+ this.persistRoundEvent(signal);
402
330
  if (transactionId && !transactionPersisted()) {
403
- throw new Error(`Round feedback commit failed: ${transactionId}`);
331
+ throw new Error(`Round event commit failed: ${transactionId}`);
404
332
  }
405
333
  }
406
- // Multi-agent: Auto-record delegation journal from worker_results
407
- if (selfEval.worker_results && selfEval.worker_results.length > 0) {
408
- const entries = selfEval.worker_results.map((w, i) => ({
409
- index: i + 1,
410
- agentId: w.agentId,
411
- subAgentType: w.subAgentType,
412
- subTask: w.subTask,
413
- resultSummary: w.resultSummary,
414
- success: w.success,
415
- discoveredConstraints: w.discoveredConstraints ?? [],
416
- }));
417
- this.recordDelegation(loopId, round, entries);
418
- }
419
- // Replaying a committed transaction may repair derived delegation data,
420
- // but must never apply the feedback to mutable engine state twice.
421
334
  if (alreadyCommitted)
422
- return fb.success;
423
- // Update state
424
- this.state.call_count++;
425
- this.state.success_trend.push(fb.success);
426
- if (this.state.success_trend.length > 20) {
427
- this.state.success_trend = this.state.success_trend.slice(-20);
428
- }
429
- // v2.5: Old binary-success circuit breaker removed — stalled loops
430
- // are now detected by the enforcement gate (R4/R5) using
431
- // progress_estimate gradients rather than success=true/false count.
335
+ return roundReport.outcome === "success";
432
336
  logEvent("round_complete", {
433
337
  loopId,
434
338
  round,
435
- success: fb.success,
339
+ success: roundReport.outcome === "success",
436
340
  });
437
- return fb.success;
341
+ return roundReport.outcome === "success";
438
342
  }
439
343
  // ═══════════════════════════════════════════════════════════════════════
440
344
  // Loop Compile (public, primary)
@@ -470,58 +374,29 @@ export class LoopForgeEngine {
470
374
  attempt: parsed.attempt,
471
375
  consecutive_rejections: parsed.consecutive_rejections,
472
376
  rejection_notice: parsed.rejection_notice,
377
+ loop_objective: parsed.loop_objective,
473
378
  });
474
- // Convert last_round_result if present (object already validated by parser)
475
- if (parsed.last_round_result) {
476
- const rr = parsed.last_round_result;
477
- // Parse P4 execution evidence (shared helper)
478
- const executionEvidence = parseExecutionEvidence(rr.execution_evidence);
479
- // Parse P5 revised_success_criteria (shared parser)
480
- const revisedCriteria = parseCriterionRevisions(rr.revised_success_criteria);
481
- lcr.last_round_result = makeLoopRoundResult({
482
- round: rr.round ?? 0,
483
- success: rr.success ?? false,
484
- output_summary: rr.output_summary ?? "",
485
- constraint_violations: rr.constraint_violations ?? [],
486
- manual_fixes_needed: rr.manual_fixes_needed ?? "",
487
- // P0–P2: Cognitive evolution fields
488
- discovered_constraints: Array.isArray(rr.discovered_constraints)
489
- ? rr.discovered_constraints.filter((v) => typeof v === "string")
490
- : [],
491
- objective_refinement: typeof rr.objective_refinement === "string"
492
- ? rr.objective_refinement
493
- : "",
494
- emerged_subtasks: Array.isArray(rr.emerged_subtasks)
495
- ? rr.emerged_subtasks.filter((v) => typeof v === "string")
496
- : [],
497
- // P4: Execution evidence
498
- execution_evidence: executionEvidence,
499
- // P5: Self-correction
500
- retracted_constraints: Array.isArray(rr.retracted_constraints)
501
- ? rr.retracted_constraints.filter((v) => typeof v === "string")
502
- : [],
503
- revised_success_criteria: revisedCriteria,
504
- wrong_assumptions: Array.isArray(rr.wrong_assumptions)
505
- ? rr.wrong_assumptions.filter((v) => typeof v === "string")
506
- : [],
507
- // v1.10: Checkpoint boundary
508
- compression_checkpoint: typeof rr.compression_checkpoint === "boolean" ? rr.compression_checkpoint : false,
509
- checkpoint_label: typeof rr.checkpoint_label === "string" ? rr.checkpoint_label : "",
510
- // v2.2: Sub-goal lifecycle
511
- completed_subtasks: Array.isArray(rr.completed_subtasks)
512
- ? rr.completed_subtasks.filter((v) => typeof v === "string")
513
- : [],
514
- blocked_subtasks: Array.isArray(rr.blocked_subtasks)
515
- ? rr.blocked_subtasks.filter((v) => typeof v === "string")
516
- : [],
517
- canceled_subtasks: Array.isArray(rr.canceled_subtasks)
518
- ? rr.canceled_subtasks.filter((v) => typeof v === "string")
519
- : [],
520
- // Multi-agent: Worker delegation results (shared parser)
521
- worker_results: parseWorkerResults(rr.worker_results),
522
- });
379
+ // Convert only the canonical compiler projection.
380
+ if (parsed.last_round_view) {
381
+ const rr = parsed.last_round_view;
382
+ if (rr.evidence && typeof rr.evidence === "object" &&
383
+ typeof rr.summary === "string") {
384
+ lcr.last_round_view = rr;
385
+ }
386
+ else {
387
+ return {
388
+ status: AgentStatus.ERROR,
389
+ response: {
390
+ status: AgentStatus.ERROR,
391
+ prompt: null,
392
+ error: "last_round_view must be a CompilerRoundProjection",
393
+ },
394
+ };
395
+ }
523
396
  }
524
- // Convert loop_objective if present (object already validated by parser)
397
+ // Convert loop_objective if present (object already validated by parser).
398
+ // Preserve the caller-supplied version and refinement_history so
399
+ // objective versioning (P1) survives the request round-trip.
525
400
  if (parsed.loop_objective) {
526
401
  const obj = parsed.loop_objective;
527
402
  lcr.loop_objective = makeLoopObjective({
@@ -530,6 +405,12 @@ export class LoopForgeEngine {
530
405
  hard_constraints: obj.hard_constraints ?? [],
531
406
  created_at_round: obj.created_at_round ?? 1,
532
407
  loop_id: obj.loop_id ?? "",
408
+ ...(typeof obj.version === "number" ? { version: obj.version } : {}),
409
+ ...(Array.isArray(obj.refinement_history)
410
+ ? {
411
+ refinement_history: obj.refinement_history.filter((item) => typeof item === "string"),
412
+ }
413
+ : {}),
533
414
  });
534
415
  }
535
416
  // Hydrate vault context for cross-round memory
@@ -564,6 +445,7 @@ export class LoopForgeEngine {
564
445
  error: null,
565
446
  state_file_content: response.state_file_content,
566
447
  prompt_artifact: response.prompt_artifact,
448
+ canonical_state: response.canonical_state,
567
449
  warnings: response.warnings,
568
450
  },
569
451
  };
@@ -572,7 +454,7 @@ export class LoopForgeEngine {
572
454
  // ═══════════════════════════════════════════════════════════════════════════
573
455
  // Factory
574
456
  // ═══════════════════════════════════════════════════════════════════════════
575
- export function createEngine(store) {
576
- return new LoopForgeEngine(store);
457
+ export function createRuntimeServices(store) {
458
+ return new RuntimeServices(store);
577
459
  }
578
- //# sourceMappingURL=engine.js.map
460
+ //# sourceMappingURL=runtime-services.js.map