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.
- package/README.md +136 -182
- package/dist/canonical-event-writer.d.ts +20 -0
- package/dist/canonical-event-writer.d.ts.map +1 -0
- package/dist/canonical-event-writer.js +131 -0
- package/dist/canonical-event-writer.js.map +1 -0
- package/dist/canonical-state.d.ts +20 -16
- package/dist/canonical-state.d.ts.map +1 -1
- package/dist/canonical-state.js +211 -64
- package/dist/canonical-state.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -10
- package/dist/cli.js.map +1 -1
- package/dist/cognitive-governance.d.ts +48 -0
- package/dist/cognitive-governance.d.ts.map +1 -0
- package/dist/cognitive-governance.js +226 -0
- package/dist/cognitive-governance.js.map +1 -0
- package/dist/compiler-subgoals.d.ts +7 -0
- package/dist/compiler-subgoals.d.ts.map +1 -0
- package/dist/compiler-subgoals.js +93 -0
- package/dist/compiler-subgoals.js.map +1 -0
- package/dist/derive-id.d.ts +12 -0
- package/dist/derive-id.d.ts.map +1 -0
- package/dist/derive-id.js +31 -0
- package/dist/derive-id.js.map +1 -0
- package/dist/evidence-provider.d.ts +26 -26
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +45 -71
- package/dist/evidence-provider.js.map +1 -1
- package/dist/generate-schema.js +90 -2
- package/dist/generate-schema.js.map +1 -1
- package/dist/index.d.ts +23 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -26
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +2 -2
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +321 -315
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-extras-parser.d.ts +2 -2
- package/dist/loop-extras-parser.d.ts.map +1 -1
- package/dist/loop-extras-parser.js +2 -2
- package/dist/loop-extras-parser.js.map +1 -1
- package/dist/loop-store.d.ts +20 -58
- package/dist/loop-store.d.ts.map +1 -1
- package/dist/loop-store.js +232 -311
- package/dist/loop-store.js.map +1 -1
- package/dist/mcp/server.d.ts +3 -3
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +123 -19
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session-projection.d.ts +5 -0
- package/dist/mcp/session-projection.d.ts.map +1 -0
- package/dist/mcp/session-projection.js +18 -0
- package/dist/mcp/session-projection.js.map +1 -0
- package/dist/mcp/session-queue.d.ts +3 -0
- package/dist/mcp/session-queue.d.ts.map +1 -0
- package/dist/mcp/session-queue.js +18 -0
- package/dist/mcp/session-queue.js.map +1 -0
- package/dist/mcp/session-types.d.ts +72 -0
- package/dist/mcp/session-types.d.ts.map +1 -0
- package/dist/mcp/session-types.js +2 -0
- package/dist/mcp/session-types.js.map +1 -0
- package/dist/mcp/session.d.ts +65 -84
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +667 -224
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts +26 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +401 -254
- package/dist/mcp/tools.js.map +1 -1
- package/dist/policy-metrics.d.ts +24 -0
- package/dist/policy-metrics.d.ts.map +1 -1
- package/dist/policy-metrics.js +174 -0
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +74 -12
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +124 -19
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts +8 -4
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +533 -86
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/prompt-policy.d.ts +8 -1
- package/dist/prompt-policy.d.ts.map +1 -1
- package/dist/prompt-policy.js +26 -8
- package/dist/prompt-policy.js.map +1 -1
- package/dist/protocol.d.ts +259 -173
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +17 -96
- package/dist/protocol.js.map +1 -1
- package/dist/replay.d.ts +11 -3
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +51 -14
- package/dist/replay.js.map +1 -1
- package/dist/round-commit-store.d.ts +12 -0
- package/dist/round-commit-store.d.ts.map +1 -0
- package/dist/round-commit-store.js +13 -0
- package/dist/round-commit-store.js.map +1 -0
- package/dist/round-coordinator.d.ts +53 -22
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +140 -44
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-driver.d.ts +18 -10
- package/dist/round-driver.d.ts.map +1 -1
- package/dist/round-driver.js +11 -13
- package/dist/round-driver.js.map +1 -1
- package/dist/round-gate.d.ts +31 -0
- package/dist/round-gate.d.ts.map +1 -0
- package/dist/round-gate.js +259 -0
- package/dist/round-gate.js.map +1 -0
- package/dist/round-recovery.d.ts +17 -0
- package/dist/round-recovery.d.ts.map +1 -0
- package/dist/round-recovery.js +117 -0
- package/dist/round-recovery.js.map +1 -0
- package/dist/round-report.d.ts +16 -0
- package/dist/round-report.d.ts.map +1 -0
- package/dist/round-report.js +401 -0
- package/dist/round-report.js.map +1 -0
- package/dist/round-transaction.d.ts +23 -12
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +104 -91
- package/dist/round-transaction.js.map +1 -1
- package/dist/runtime-services.d.ts +59 -0
- package/dist/runtime-services.d.ts.map +1 -0
- package/dist/{engine.js → runtime-services.js} +157 -275
- package/dist/runtime-services.js.map +1 -0
- package/dist/storage.d.ts +3 -9
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +6 -17
- package/dist/storage.js.map +1 -1
- package/dist/token-utils.d.ts +12 -8
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +56 -10
- package/dist/token-utils.js.map +1 -1
- package/dist/vault-format.d.ts +4 -0
- package/dist/vault-format.d.ts.map +1 -0
- package/dist/vault-format.js +4 -0
- package/dist/vault-format.js.map +1 -0
- package/dist/vault-types.d.ts +24 -0
- package/dist/vault-types.d.ts.map +1 -0
- package/dist/vault-types.js +2 -0
- package/dist/vault-types.js.map +1 -0
- package/loop_policy.json +10 -12
- package/loopforge-policy.json +340 -0
- package/package.json +13 -7
- package/skills/perception/SKILL.md +200 -90
- package/dist/backends/interface.d.ts +0 -39
- package/dist/backends/interface.d.ts.map +0 -1
- package/dist/backends/interface.js +0 -6
- package/dist/backends/interface.js.map +0 -1
- package/dist/enforcement-gate.d.ts +0 -50
- package/dist/enforcement-gate.d.ts.map +0 -1
- package/dist/enforcement-gate.js +0 -501
- package/dist/enforcement-gate.js.map +0 -1
- package/dist/engine.d.ts +0 -59
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js.map +0 -1
- package/dist/interop.d.ts +0 -35
- package/dist/interop.d.ts.map +0 -1
- package/dist/interop.js +0 -34
- package/dist/interop.js.map +0 -1
- package/dist/run.d.ts +0 -51
- package/dist/run.d.ts.map +0 -1
- package/dist/run.js +0 -200
- package/dist/run.js.map +0 -1
- package/dist/self-eval.d.ts +0 -23
- package/dist/self-eval.d.ts.map +0 -1
- package/dist/self-eval.js +0 -151
- package/dist/self-eval.js.map +0 -1
- package/dist/verification-gate.d.ts +0 -52
- package/dist/verification-gate.d.ts.map +0 -1
- package/dist/verification-gate.js +0 -527
- package/dist/verification-gate.js.map +0 -1
|
@@ -1,44 +1,42 @@
|
|
|
1
|
-
/** LoopForge
|
|
1
|
+
/** LoopForge runtime services — stateless compiler, round-event and metrics boundary.
|
|
2
2
|
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
11
|
-
import { AgentStatus, Mode,
|
|
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 {
|
|
14
|
+
import { policyMetrics } from "./policy-metrics.js";
|
|
15
15
|
import { parseLoopExtras } from "./loop-extras-parser.js";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
function
|
|
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
|
-
//
|
|
25
|
+
// RuntimeServices
|
|
26
26
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
27
|
-
export class
|
|
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(
|
|
33
|
-
if (
|
|
34
|
-
this.backend =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
62
|
+
this.metrics = makeRuntimeMetrics();
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
// ═══════════════════════════════════════════════════════════════════════
|
|
65
|
-
//
|
|
66
|
+
// Canonical round-event persistence
|
|
66
67
|
// ═══════════════════════════════════════════════════════════════════════
|
|
67
|
-
|
|
68
|
+
persistRoundEvent(signal) {
|
|
68
69
|
if (this.metrics === null) {
|
|
69
|
-
this.metrics =
|
|
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.
|
|
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: "
|
|
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
|
-
|
|
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 ?? "
|
|
90
|
-
|
|
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.
|
|
94
|
-
|
|
95
|
-
|
|
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: "
|
|
116
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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().
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
224
|
-
//
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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 ??
|
|
250
|
+
const lineage = (entry.loop_lineage ?? {});
|
|
243
251
|
const rnd = lineage.round;
|
|
244
|
-
if (rnd &&
|
|
245
|
-
lineage.success =
|
|
246
|
-
entry.success =
|
|
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 ??
|
|
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
|
-
//
|
|
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
|
|
329
|
-
*
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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().
|
|
350
|
-
|
|
351
|
-
|
|
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:
|
|
374
|
-
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
|
-
|
|
381
|
-
|
|
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.
|
|
329
|
+
this.persistRoundEvent(signal);
|
|
402
330
|
if (transactionId && !transactionPersisted()) {
|
|
403
|
-
throw new Error(`Round
|
|
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
|
|
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:
|
|
339
|
+
success: roundReport.outcome === "success",
|
|
436
340
|
});
|
|
437
|
-
return
|
|
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
|
|
475
|
-
if (parsed.
|
|
476
|
-
const rr = parsed.
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
|
576
|
-
return new
|
|
457
|
+
export function createRuntimeServices(store) {
|
|
458
|
+
return new RuntimeServices(store);
|
|
577
459
|
}
|
|
578
|
-
//# sourceMappingURL=
|
|
460
|
+
//# sourceMappingURL=runtime-services.js.map
|