loopforge 1.3.1 → 1.8.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 +64 -3
- package/dist/backends/fs.d.ts +5 -0
- package/dist/backends/fs.d.ts.map +1 -1
- package/dist/backends/fs.js +66 -16
- package/dist/backends/fs.js.map +1 -1
- package/dist/builder.d.ts +0 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +14 -25
- package/dist/builder.js.map +1 -1
- package/dist/engine.d.ts +28 -5
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +303 -105
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +9 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -10
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +28 -2
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +718 -38
- package/dist/loop-compiler.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +6 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +24 -4
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +303 -34
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts +165 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +144 -15
- package/dist/mcp/tools.js.map +1 -1
- package/dist/memory-bridge.d.ts +47 -0
- package/dist/memory-bridge.d.ts.map +1 -0
- package/dist/memory-bridge.js +290 -0
- package/dist/memory-bridge.js.map +1 -0
- package/dist/observability.d.ts +23 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +29 -0
- package/dist/observability.js.map +1 -0
- package/dist/policy.d.ts +72 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +44 -0
- package/dist/policy.js.map +1 -1
- package/dist/protocol.d.ts +197 -7
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +48 -5
- package/dist/protocol.js.map +1 -1
- package/dist/runtime.d.ts +19 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +446 -142
- package/dist/runtime.js.map +1 -1
- package/dist/tests/builder.test.js +1 -25
- package/dist/tests/builder.test.js.map +1 -1
- package/dist/tests/engine.test.js +213 -38
- package/dist/tests/engine.test.js.map +1 -1
- package/dist/tests/generate-schema.test.js +4 -4
- package/dist/tests/generate-schema.test.js.map +1 -1
- package/dist/tests/loop-compiler.test.js +427 -2
- package/dist/tests/loop-compiler.test.js.map +1 -1
- package/dist/tests/mcp.test.js +166 -79
- package/dist/tests/mcp.test.js.map +1 -1
- package/dist/tests/memory-bridge.test.d.ts +8 -0
- package/dist/tests/memory-bridge.test.d.ts.map +1 -0
- package/dist/tests/memory-bridge.test.js +384 -0
- package/dist/tests/memory-bridge.test.js.map +1 -0
- package/dist/tests/protocol.test.js +14 -5
- package/dist/tests/protocol.test.js.map +1 -1
- package/dist/tests/verification-gate.test.d.ts +2 -0
- package/dist/tests/verification-gate.test.d.ts.map +1 -0
- package/dist/tests/verification-gate.test.js +407 -0
- package/dist/tests/verification-gate.test.js.map +1 -0
- package/dist/verification-gate.d.ts +26 -0
- package/dist/verification-gate.d.ts.map +1 -0
- package/dist/verification-gate.js +232 -0
- package/dist/verification-gate.js.map +1 -0
- package/loop_policy.json +31 -0
- package/package.json +1 -2
- package/skills/perception/SKILL.md +147 -55
- package/dist/adapter.d.ts +0 -17
- package/dist/adapter.d.ts.map +0 -1
- package/dist/adapter.js +0 -228
- package/dist/adapter.js.map +0 -1
- package/dist/cli.d.ts +0 -7
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -458
- package/dist/cli.js.map +0 -1
- package/dist/tests/adapter.test.d.ts +0 -2
- package/dist/tests/adapter.test.d.ts.map +0 -1
- package/dist/tests/adapter.test.js +0 -104
- package/dist/tests/adapter.test.js.map +0 -1
package/dist/engine.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/** LoopForge-loop_compile — Engine (outer loop manager).
|
|
2
2
|
*
|
|
3
|
-
*
|
|
4
|
-
* invokeLoopCompile (primary),
|
|
3
|
+
* 2-mode engine with vault-backed loop lineage persistence.
|
|
4
|
+
* invokeLoopCompile (primary), invokeFeedback.
|
|
5
5
|
* Circuit breaker prevents infinite stall loops.
|
|
6
6
|
* EngineMetrics tracks silent-failure counters for observability.
|
|
7
7
|
*/
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { getPolicy } from "./policy.js";
|
|
10
10
|
import { FSBackend, readLineageMd } from "./backends/fs.js";
|
|
11
|
-
import { AgentStatus, Mode, makeAnalysis, makeExecutionFeedback, makeLoopCompileRequest, makeLoopObjective, makeLoopRoundResult, makeSelfEvaluation, makeSessionState, makeTaskId, makeVaultConfig, SELF_EVAL_REGEX, } from "./protocol.js";
|
|
12
|
-
import { routeTechnique } from "./builder.js";
|
|
11
|
+
import { AgentStatus, Mode, makeAnalysis, makeExecutionEvidence, makeExecutionFeedback, makeLoopCompileRequest, makeLoopObjective, makeLoopRoundResult, makeSelfEvaluation, makeSessionState, makeTaskId, makeVaultConfig, SELF_EVAL_REGEX, } from "./protocol.js";
|
|
13
12
|
import { scoreQuality } from "./builder.js";
|
|
14
13
|
import { compileLoop } from "./loop-compiler.js";
|
|
14
|
+
import { logEvent } from "./observability.js";
|
|
15
15
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
16
16
|
// Self-Evaluation extraction (v1.1 — autonomous loop feedback)
|
|
17
17
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
18
18
|
/** Extract a structured SelfEvaluation from agent output text.
|
|
19
|
-
* Returns null if no valid self-
|
|
19
|
+
* Returns null if no valid self-eval block is found.
|
|
20
20
|
* The agent is instructed to output JSON between the delimiters. */
|
|
21
21
|
export function extractSelfEvaluation(text) {
|
|
22
22
|
const match = text.match(SELF_EVAL_REGEX);
|
|
@@ -33,17 +33,109 @@ export function extractSelfEvaluation(text) {
|
|
|
33
33
|
return null;
|
|
34
34
|
if (typeof raw.should_continue !== "boolean")
|
|
35
35
|
return null;
|
|
36
|
-
return
|
|
37
|
-
success: raw.success,
|
|
38
|
-
output_summary: raw.output_summary,
|
|
39
|
-
constraint_violations: raw.constraint_violations,
|
|
40
|
-
should_continue: raw.should_continue,
|
|
41
|
-
});
|
|
36
|
+
return buildSelfEvaluation(raw);
|
|
42
37
|
}
|
|
43
38
|
catch {
|
|
44
39
|
return null;
|
|
45
40
|
}
|
|
46
41
|
}
|
|
42
|
+
/** Build a SelfEvaluation from a parsed JSON object.
|
|
43
|
+
* Lenient parsing: missing optional fields get sensible defaults.
|
|
44
|
+
* Used by extractSelfEvaluation() (regex path) and MCP tool handler
|
|
45
|
+
* (structured evaluation parameter path). */
|
|
46
|
+
export function buildSelfEvaluation(raw) {
|
|
47
|
+
// P4: Parse execution evidence from raw JSON
|
|
48
|
+
let executionEvidence;
|
|
49
|
+
const rawEvidence = raw.execution_evidence;
|
|
50
|
+
if (rawEvidence && typeof rawEvidence === "object") {
|
|
51
|
+
const testResults = rawEvidence.test_results;
|
|
52
|
+
executionEvidence = makeExecutionEvidence({
|
|
53
|
+
files_changed: Array.isArray(rawEvidence.files_changed)
|
|
54
|
+
? rawEvidence.files_changed.filter((v) => typeof v === "string")
|
|
55
|
+
: [],
|
|
56
|
+
test_results: testResults && typeof testResults.passed === "number"
|
|
57
|
+
? {
|
|
58
|
+
passed: testResults.passed,
|
|
59
|
+
failed: testResults.failed ?? 0,
|
|
60
|
+
skipped: testResults.skipped ?? 0,
|
|
61
|
+
}
|
|
62
|
+
: null,
|
|
63
|
+
success_criteria_met: Array.isArray(rawEvidence.success_criteria_met)
|
|
64
|
+
? rawEvidence.success_criteria_met.filter((v) => typeof v === "string")
|
|
65
|
+
: [],
|
|
66
|
+
success_criteria_remaining: Array.isArray(rawEvidence.success_criteria_remaining)
|
|
67
|
+
? rawEvidence.success_criteria_remaining.filter((v) => typeof v === "string")
|
|
68
|
+
: [],
|
|
69
|
+
progress_estimate: typeof rawEvidence.progress_estimate === "number"
|
|
70
|
+
? Math.max(0, Math.min(1, rawEvidence.progress_estimate))
|
|
71
|
+
: 0.0,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// P5: Parse corrections
|
|
75
|
+
const retractedConstraints = Array.isArray(raw.retracted_constraints)
|
|
76
|
+
? raw.retracted_constraints.filter((v) => typeof v === "string")
|
|
77
|
+
: [];
|
|
78
|
+
const revisedCriteria = Array.isArray(raw.revised_success_criteria)
|
|
79
|
+
? raw.revised_success_criteria
|
|
80
|
+
.filter((v) => typeof v === "object" && v !== null &&
|
|
81
|
+
typeof v.old === "string" &&
|
|
82
|
+
typeof v.new === "string")
|
|
83
|
+
.map((v) => {
|
|
84
|
+
const r = v;
|
|
85
|
+
return { old: r.old, new: r.new };
|
|
86
|
+
})
|
|
87
|
+
: [];
|
|
88
|
+
const wrongAssumptions = Array.isArray(raw.wrong_assumptions)
|
|
89
|
+
? raw.wrong_assumptions.filter((v) => typeof v === "string")
|
|
90
|
+
: [];
|
|
91
|
+
// Multi-agent: Parse worker delegation results
|
|
92
|
+
const workerResults = Array.isArray(raw.worker_results)
|
|
93
|
+
? raw.worker_results
|
|
94
|
+
.filter((v) => typeof v === "object" && v !== null &&
|
|
95
|
+
typeof v.agentId === "string" &&
|
|
96
|
+
typeof v.subTask === "string" &&
|
|
97
|
+
typeof v.resultSummary === "string")
|
|
98
|
+
.map((v) => {
|
|
99
|
+
const w = v;
|
|
100
|
+
return {
|
|
101
|
+
agentId: w.agentId,
|
|
102
|
+
subAgentType: typeof w.subAgentType === "string" ? w.subAgentType : "general-purpose",
|
|
103
|
+
subTask: w.subTask,
|
|
104
|
+
resultSummary: w.resultSummary,
|
|
105
|
+
success: typeof w.success === "boolean" ? w.success : false,
|
|
106
|
+
discoveredConstraints: Array.isArray(w.discoveredConstraints)
|
|
107
|
+
? w.discoveredConstraints.filter((c) => typeof c === "string")
|
|
108
|
+
: [],
|
|
109
|
+
};
|
|
110
|
+
})
|
|
111
|
+
: [];
|
|
112
|
+
return makeSelfEvaluation({
|
|
113
|
+
success: typeof raw.success === "boolean" ? raw.success : false,
|
|
114
|
+
output_summary: typeof raw.output_summary === "string" ? raw.output_summary : "",
|
|
115
|
+
constraint_violations: Array.isArray(raw.constraint_violations)
|
|
116
|
+
? raw.constraint_violations.filter((v) => typeof v === "string")
|
|
117
|
+
: [],
|
|
118
|
+
should_continue: typeof raw.should_continue === "boolean" ? raw.should_continue : true,
|
|
119
|
+
// P0–P2: Optional evolution fields
|
|
120
|
+
discovered_constraints: Array.isArray(raw.discovered_constraints)
|
|
121
|
+
? raw.discovered_constraints.filter((v) => typeof v === "string")
|
|
122
|
+
: [],
|
|
123
|
+
objective_refinement: typeof raw.objective_refinement === "string"
|
|
124
|
+
? raw.objective_refinement
|
|
125
|
+
: "",
|
|
126
|
+
emerged_subtasks: Array.isArray(raw.emerged_subtasks)
|
|
127
|
+
? raw.emerged_subtasks.filter((v) => typeof v === "string")
|
|
128
|
+
: [],
|
|
129
|
+
// P4: Execution evidence
|
|
130
|
+
execution_evidence: executionEvidence,
|
|
131
|
+
// P5: Self-correction
|
|
132
|
+
retracted_constraints: retractedConstraints,
|
|
133
|
+
revised_success_criteria: revisedCriteria,
|
|
134
|
+
wrong_assumptions: wrongAssumptions,
|
|
135
|
+
// Multi-agent: Worker delegation results
|
|
136
|
+
worker_results: workerResults,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
47
139
|
/** Fallback heuristic when structured self-eval extraction fails.
|
|
48
140
|
* Scans agent output for completion and error signals.
|
|
49
141
|
* Returns a low-confidence SelfEvaluation — the autonomous runner
|
|
@@ -99,6 +191,17 @@ export class LoopForgeEngine {
|
|
|
99
191
|
}
|
|
100
192
|
return this.backend;
|
|
101
193
|
}
|
|
194
|
+
/** Public accessor for the vault backend — used by runtime/verification gate. */
|
|
195
|
+
getBackend() {
|
|
196
|
+
return this.resolveBackend();
|
|
197
|
+
}
|
|
198
|
+
/** Expose engine health counters for observability (MCP status, logging). */
|
|
199
|
+
getMetrics() {
|
|
200
|
+
if (this.metrics === null) {
|
|
201
|
+
this.metrics = makeEngineMetrics();
|
|
202
|
+
}
|
|
203
|
+
return this.metrics;
|
|
204
|
+
}
|
|
102
205
|
ensureInit(request) {
|
|
103
206
|
if (this.state === null) {
|
|
104
207
|
this.state = makeSessionState(request.task_id || makeTaskId(request.task));
|
|
@@ -148,7 +251,7 @@ export class LoopForgeEngine {
|
|
|
148
251
|
: "partial",
|
|
149
252
|
quality_score: signal.quality_score ?? 0,
|
|
150
253
|
constraint_compliance: {
|
|
151
|
-
all_hard_constraints_met: !signal.violations,
|
|
254
|
+
all_hard_constraints_met: !Array.isArray(signal.violations) || signal.violations.length === 0,
|
|
152
255
|
violations: signal.violations ?? [],
|
|
153
256
|
},
|
|
154
257
|
output_summary: signal.task_type ?? "",
|
|
@@ -165,6 +268,7 @@ export class LoopForgeEngine {
|
|
|
165
268
|
catch {
|
|
166
269
|
if (this.metrics)
|
|
167
270
|
this.metrics.vaultWriteErrors++;
|
|
271
|
+
logEvent("vault_write_error", { error: "feedback_entry_build" });
|
|
168
272
|
}
|
|
169
273
|
}
|
|
170
274
|
if (entries.length > 0) {
|
|
@@ -174,6 +278,7 @@ export class LoopForgeEngine {
|
|
|
174
278
|
catch {
|
|
175
279
|
if (this.metrics)
|
|
176
280
|
this.metrics.vaultWriteErrors += entries.length;
|
|
281
|
+
logEvent("vault_write_error", { error: "feedback_append_entries", count: entries.length });
|
|
177
282
|
return 0;
|
|
178
283
|
}
|
|
179
284
|
}
|
|
@@ -234,6 +339,7 @@ export class LoopForgeEngine {
|
|
|
234
339
|
catch {
|
|
235
340
|
if (this.metrics)
|
|
236
341
|
this.metrics.vaultWriteErrors++;
|
|
342
|
+
logEvent("vault_write_error", { error: "persist_lineage_json" });
|
|
237
343
|
}
|
|
238
344
|
// 2. Markdown write (secondary, non-blocking)
|
|
239
345
|
try {
|
|
@@ -254,10 +360,51 @@ export class LoopForgeEngine {
|
|
|
254
360
|
catch {
|
|
255
361
|
if (this.metrics)
|
|
256
362
|
this.metrics.vaultWriteErrors++;
|
|
363
|
+
logEvent("vault_write_error", { error: "persist_lineage_md" });
|
|
257
364
|
}
|
|
258
365
|
return vaultOk;
|
|
259
366
|
}
|
|
260
367
|
// ═══════════════════════════════════════════════════════════════════════
|
|
368
|
+
// Delegation journal (v1.9 — AgentTool mode)
|
|
369
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
370
|
+
/** Record sub-agent delegations for this round into the vault.
|
|
371
|
+
* Written as a lightweight journal entry so the main agent's rolling
|
|
372
|
+
* summary can reference delegation history in subsequent rounds. */
|
|
373
|
+
recordDelegation(loopId, round, entries) {
|
|
374
|
+
if (!entries.length)
|
|
375
|
+
return;
|
|
376
|
+
const entry = {
|
|
377
|
+
id: randomUUID(),
|
|
378
|
+
task_id: `loop:${loopId}:r${round}:delegations`,
|
|
379
|
+
version_tag: "v1",
|
|
380
|
+
is_active: true,
|
|
381
|
+
timestamp: new Date().toISOString().replace(/\.\d+Z$/, ""),
|
|
382
|
+
user_intent: `Delegation journal — round ${round}`,
|
|
383
|
+
task_type: "delegation_journal",
|
|
384
|
+
loop_id: loopId,
|
|
385
|
+
loop_lineage: {
|
|
386
|
+
round,
|
|
387
|
+
delegations: entries.map((e) => ({
|
|
388
|
+
index: e.index,
|
|
389
|
+
agentId: e.agentId,
|
|
390
|
+
subAgentType: e.subAgentType,
|
|
391
|
+
subTask: e.subTask,
|
|
392
|
+
resultSummary: e.resultSummary,
|
|
393
|
+
success: e.success,
|
|
394
|
+
discoveredConstraints: e.discoveredConstraints,
|
|
395
|
+
})),
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
try {
|
|
399
|
+
this.resolveBackend().appendEntry(entry);
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
if (this.metrics)
|
|
403
|
+
this.metrics.vaultWriteErrors++;
|
|
404
|
+
logEvent("vault_write_error", { error: "record_delegation" });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
261
408
|
// Hydrate loop context from vault
|
|
262
409
|
// ═══════════════════════════════════════════════════════════════════════
|
|
263
410
|
hydrateLoopContext(loopId) {
|
|
@@ -309,6 +456,10 @@ export class LoopForgeEngine {
|
|
|
309
456
|
entry.constraint_violations =
|
|
310
457
|
lineage.constraint_violations ?? [];
|
|
311
458
|
}
|
|
459
|
+
// v1.7: Ensure quality_score is always populated for failure lineage weighting
|
|
460
|
+
if (entry.quality_score === undefined || entry.quality_score === null) {
|
|
461
|
+
entry.quality_score = lineage.quality_score ?? 0;
|
|
462
|
+
}
|
|
312
463
|
}
|
|
313
464
|
// Enrich: attach full prompt text from Markdown for L0 cache reuse
|
|
314
465
|
for (const entry of finalResults) {
|
|
@@ -328,37 +479,6 @@ export class LoopForgeEngine {
|
|
|
328
479
|
return { results: finalResults, global_entries: [] };
|
|
329
480
|
}
|
|
330
481
|
// ═══════════════════════════════════════════════════════════════════════
|
|
331
|
-
// Build (internal)
|
|
332
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
333
|
-
invokeBuild(request, _hydrateResults) {
|
|
334
|
-
this.ensureInit(request);
|
|
335
|
-
this.lastTask = request.task;
|
|
336
|
-
const analysis = routeTechnique(request.task);
|
|
337
|
-
const technique = analysis.technique;
|
|
338
|
-
const rationale = analysis.rationale;
|
|
339
|
-
const promptSections = [
|
|
340
|
-
"## LoopForge Build",
|
|
341
|
-
`**Technique**: ${technique}`,
|
|
342
|
-
`**Rationale**: ${rationale}`,
|
|
343
|
-
"",
|
|
344
|
-
"### Task",
|
|
345
|
-
request.task,
|
|
346
|
-
"",
|
|
347
|
-
"### Instructions",
|
|
348
|
-
`Apply the **${technique}** technique to complete the task above.`,
|
|
349
|
-
"Respect all hard constraints and provide verifiable output.",
|
|
350
|
-
];
|
|
351
|
-
return {
|
|
352
|
-
status: AgentStatus.OK,
|
|
353
|
-
response: {
|
|
354
|
-
status: AgentStatus.OK,
|
|
355
|
-
prompt: promptSections.join("\n"),
|
|
356
|
-
analysis,
|
|
357
|
-
error: null,
|
|
358
|
-
},
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
362
482
|
// Feedback (public)
|
|
363
483
|
// ═══════════════════════════════════════════════════════════════════════
|
|
364
484
|
invokeFeedback(request, _hydrateResults) {
|
|
@@ -420,6 +540,12 @@ export class LoopForgeEngine {
|
|
|
420
540
|
else {
|
|
421
541
|
this.state.circuit_breaker_count = 0;
|
|
422
542
|
}
|
|
543
|
+
logEvent("round_complete", {
|
|
544
|
+
technique: "feedback",
|
|
545
|
+
quality,
|
|
546
|
+
loopId: loopId ?? "unknown",
|
|
547
|
+
round: loopRound ?? this.state.call_count,
|
|
548
|
+
});
|
|
423
549
|
return {
|
|
424
550
|
status: AgentStatus.OK,
|
|
425
551
|
response: {
|
|
@@ -440,6 +566,8 @@ export class LoopForgeEngine {
|
|
|
440
566
|
// ═══════════════════════════════════════════════════════════════════════
|
|
441
567
|
/** Record self-evaluation from agent output without human intervention.
|
|
442
568
|
* Converts SelfEvaluation → ExecutionFeedback → vault persistence.
|
|
569
|
+
* P0–P2: Also persists discovered_constraints, objective_refinement,
|
|
570
|
+
* and emerged_subtasks for the compiler to consume next round.
|
|
443
571
|
* Call this BEFORE invokeLoopCompile for the next round so that
|
|
444
572
|
* hydrateLoopContext picks up the latest quality scores. */
|
|
445
573
|
autoFeedback(selfEval, loopId, round, task) {
|
|
@@ -456,6 +584,13 @@ export class LoopForgeEngine {
|
|
|
456
584
|
manual_fixes_needed: fb.manual_fixes_needed,
|
|
457
585
|
});
|
|
458
586
|
const taskId = `loop:${loopId}:r${round}:feedback`;
|
|
587
|
+
// Multi-agent: Merge sub-agent discovered constraints into the main constraint flow
|
|
588
|
+
const subDiscovered = (selfEval.worker_results ?? [])
|
|
589
|
+
.flatMap((w) => w.discoveredConstraints ?? [])
|
|
590
|
+
.filter((c) => c.length > 0);
|
|
591
|
+
const mergedDiscovered = [
|
|
592
|
+
...new Set([...(selfEval.discovered_constraints ?? []), ...subDiscovered]),
|
|
593
|
+
];
|
|
459
594
|
const signal = {
|
|
460
595
|
task_id: taskId,
|
|
461
596
|
task_type: task.slice(0, 80),
|
|
@@ -465,9 +600,34 @@ export class LoopForgeEngine {
|
|
|
465
600
|
manual_fixes: "",
|
|
466
601
|
loop_id: loopId,
|
|
467
602
|
round,
|
|
603
|
+
// P0–P2: Evolution fields
|
|
604
|
+
discovered_constraints: mergedDiscovered,
|
|
605
|
+
objective_refinement: selfEval.objective_refinement ?? "",
|
|
606
|
+
emerged_subtasks: selfEval.emerged_subtasks ?? [],
|
|
607
|
+
// P4: Execution evidence
|
|
608
|
+
execution_evidence: selfEval.execution_evidence ?? null,
|
|
609
|
+
// P5: Self-correction
|
|
610
|
+
retracted_constraints: selfEval.retracted_constraints ?? [],
|
|
611
|
+
revised_success_criteria: selfEval.revised_success_criteria ?? [],
|
|
612
|
+
wrong_assumptions: selfEval.wrong_assumptions ?? [],
|
|
613
|
+
// Multi-agent: Worker delegation results
|
|
614
|
+
worker_results: selfEval.worker_results ?? [],
|
|
468
615
|
};
|
|
469
616
|
this.persistFeedbackToVault(signal);
|
|
470
617
|
this.flushFeedbackBuffer();
|
|
618
|
+
// Multi-agent: Auto-record delegation journal from worker_results
|
|
619
|
+
if (selfEval.worker_results && selfEval.worker_results.length > 0) {
|
|
620
|
+
const entries = selfEval.worker_results.map((w, i) => ({
|
|
621
|
+
index: i + 1,
|
|
622
|
+
agentId: w.agentId,
|
|
623
|
+
subAgentType: w.subAgentType,
|
|
624
|
+
subTask: w.subTask,
|
|
625
|
+
resultSummary: w.resultSummary,
|
|
626
|
+
success: w.success,
|
|
627
|
+
discoveredConstraints: w.discoveredConstraints ?? [],
|
|
628
|
+
}));
|
|
629
|
+
this.recordDelegation(loopId, round, entries);
|
|
630
|
+
}
|
|
471
631
|
// Update state
|
|
472
632
|
this.state.call_count++;
|
|
473
633
|
this.state.quality_trend.push(quality);
|
|
@@ -481,6 +641,12 @@ export class LoopForgeEngine {
|
|
|
481
641
|
else {
|
|
482
642
|
this.state.circuit_breaker_count = 0;
|
|
483
643
|
}
|
|
644
|
+
logEvent("round_complete", {
|
|
645
|
+
loopId,
|
|
646
|
+
round,
|
|
647
|
+
quality,
|
|
648
|
+
technique: this.state?.last_technique ?? "feedback",
|
|
649
|
+
});
|
|
484
650
|
return quality;
|
|
485
651
|
}
|
|
486
652
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -503,12 +669,47 @@ export class LoopForgeEngine {
|
|
|
503
669
|
new_since_last_round: extras.new_since_last_round ?? "",
|
|
504
670
|
force_level: extras.force_level ?? "auto",
|
|
505
671
|
health_check_interval: extras.health_check_interval ?? 1,
|
|
672
|
+
external_context: extras.external_context ?? "",
|
|
506
673
|
});
|
|
507
674
|
// Convert last_round_result if present
|
|
508
675
|
const lastRR = extras.last_round_result;
|
|
509
676
|
if (lastRR) {
|
|
510
677
|
if (typeof lastRR === "object" && !Array.isArray(lastRR)) {
|
|
511
678
|
const rr = lastRR;
|
|
679
|
+
// Parse P4 execution evidence
|
|
680
|
+
let executionEvidence;
|
|
681
|
+
const rawEvidence = rr.execution_evidence;
|
|
682
|
+
if (rawEvidence && typeof rawEvidence === "object") {
|
|
683
|
+
const testResults = rawEvidence.test_results;
|
|
684
|
+
executionEvidence = makeExecutionEvidence({
|
|
685
|
+
files_changed: Array.isArray(rawEvidence.files_changed)
|
|
686
|
+
? rawEvidence.files_changed.filter((v) => typeof v === "string")
|
|
687
|
+
: [],
|
|
688
|
+
test_results: testResults && typeof testResults.passed === "number"
|
|
689
|
+
? {
|
|
690
|
+
passed: testResults.passed,
|
|
691
|
+
failed: testResults.failed ?? 0,
|
|
692
|
+
skipped: testResults.skipped ?? 0,
|
|
693
|
+
}
|
|
694
|
+
: null,
|
|
695
|
+
success_criteria_met: Array.isArray(rawEvidence.success_criteria_met)
|
|
696
|
+
? rawEvidence.success_criteria_met.filter((v) => typeof v === "string")
|
|
697
|
+
: [],
|
|
698
|
+
success_criteria_remaining: Array.isArray(rawEvidence.success_criteria_remaining)
|
|
699
|
+
? rawEvidence.success_criteria_remaining.filter((v) => typeof v === "string")
|
|
700
|
+
: [],
|
|
701
|
+
progress_estimate: typeof rawEvidence.progress_estimate === "number"
|
|
702
|
+
? Math.max(0, Math.min(1, rawEvidence.progress_estimate))
|
|
703
|
+
: 0.0,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
// Parse P5 revised_success_criteria
|
|
707
|
+
const revisedCriteria = Array.isArray(rr.revised_success_criteria)
|
|
708
|
+
? rr.revised_success_criteria
|
|
709
|
+
.filter((v) => typeof v === "object" && v !== null &&
|
|
710
|
+
typeof v.old === "string" && typeof v.new === "string")
|
|
711
|
+
.map((v) => ({ old: v.old, new: v.new }))
|
|
712
|
+
: [];
|
|
512
713
|
lcr.last_round_result = makeLoopRoundResult({
|
|
513
714
|
round: rr.round ?? 0,
|
|
514
715
|
success: rr.success ?? false,
|
|
@@ -516,6 +717,41 @@ export class LoopForgeEngine {
|
|
|
516
717
|
constraint_violations: rr.constraint_violations ?? [],
|
|
517
718
|
manual_fixes_needed: rr.manual_fixes_needed ?? "",
|
|
518
719
|
quality_score: rr.quality_score ?? 0,
|
|
720
|
+
// P0–P2: Cognitive evolution fields
|
|
721
|
+
discovered_constraints: Array.isArray(rr.discovered_constraints)
|
|
722
|
+
? rr.discovered_constraints.filter((v) => typeof v === "string")
|
|
723
|
+
: [],
|
|
724
|
+
objective_refinement: typeof rr.objective_refinement === "string"
|
|
725
|
+
? rr.objective_refinement
|
|
726
|
+
: "",
|
|
727
|
+
emerged_subtasks: Array.isArray(rr.emerged_subtasks)
|
|
728
|
+
? rr.emerged_subtasks.filter((v) => typeof v === "string")
|
|
729
|
+
: [],
|
|
730
|
+
// P4: Execution evidence
|
|
731
|
+
execution_evidence: executionEvidence,
|
|
732
|
+
// P5: Self-correction
|
|
733
|
+
retracted_constraints: Array.isArray(rr.retracted_constraints)
|
|
734
|
+
? rr.retracted_constraints.filter((v) => typeof v === "string")
|
|
735
|
+
: [],
|
|
736
|
+
revised_success_criteria: revisedCriteria,
|
|
737
|
+
wrong_assumptions: Array.isArray(rr.wrong_assumptions)
|
|
738
|
+
? rr.wrong_assumptions.filter((v) => typeof v === "string")
|
|
739
|
+
: [],
|
|
740
|
+
// Multi-agent: Worker delegation results
|
|
741
|
+
worker_results: Array.isArray(rr.worker_results)
|
|
742
|
+
? rr.worker_results
|
|
743
|
+
.filter((v) => typeof v?.agentId === "string" && typeof v?.subTask === "string")
|
|
744
|
+
.map((v) => ({
|
|
745
|
+
agentId: v.agentId,
|
|
746
|
+
subAgentType: typeof v.subAgentType === "string" ? v.subAgentType : "general-purpose",
|
|
747
|
+
subTask: v.subTask,
|
|
748
|
+
resultSummary: typeof v.resultSummary === "string" ? v.resultSummary : "",
|
|
749
|
+
success: typeof v.success === "boolean" ? v.success : false,
|
|
750
|
+
discoveredConstraints: Array.isArray(v.discoveredConstraints)
|
|
751
|
+
? v.discoveredConstraints
|
|
752
|
+
: [],
|
|
753
|
+
}))
|
|
754
|
+
: [],
|
|
519
755
|
});
|
|
520
756
|
}
|
|
521
757
|
}
|
|
@@ -568,6 +804,22 @@ export class LoopForgeEngine {
|
|
|
568
804
|
promptLines.push(`- ⚠️ ${w}`);
|
|
569
805
|
}
|
|
570
806
|
}
|
|
807
|
+
// Verification gate flags (v1.6) — injected after compiler warnings
|
|
808
|
+
const verificationFlags = extras.verification_flags ?? [];
|
|
809
|
+
if (verificationFlags.length) {
|
|
810
|
+
promptLines.push("");
|
|
811
|
+
promptLines.push("### Verification Gate");
|
|
812
|
+
for (const f of verificationFlags) {
|
|
813
|
+
const icon = f.severity === "error" ? "🚫" : "⚠️";
|
|
814
|
+
promptLines.push(`- ${icon} [${f.check}] ${f.detail}`);
|
|
815
|
+
}
|
|
816
|
+
const hasError = verificationFlags.some((f) => f.severity === "error");
|
|
817
|
+
if (hasError) {
|
|
818
|
+
promptLines.push("");
|
|
819
|
+
promptLines.push("**Gate Verdict: CONTRADICTED** — this round's quality score has been excluded " +
|
|
820
|
+
"from the quality trend. Address each 🚫 flag explicitly in your next response.");
|
|
821
|
+
}
|
|
822
|
+
}
|
|
571
823
|
if (response.loop_health) {
|
|
572
824
|
const h = response.loop_health;
|
|
573
825
|
promptLines.push("");
|
|
@@ -617,67 +869,6 @@ export class LoopForgeEngine {
|
|
|
617
869
|
};
|
|
618
870
|
}
|
|
619
871
|
// ═══════════════════════════════════════════════════════════════════════
|
|
620
|
-
// Review handler
|
|
621
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
622
|
-
handleReview(request, hydrateResults) {
|
|
623
|
-
if (!hydrateResults) {
|
|
624
|
-
return {
|
|
625
|
-
status: AgentStatus.ERROR,
|
|
626
|
-
response: {
|
|
627
|
-
status: AgentStatus.ERROR,
|
|
628
|
-
prompt: null,
|
|
629
|
-
analysis: null,
|
|
630
|
-
error: "Review mode requires hydrate_results (prompt to review).",
|
|
631
|
-
},
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
const results = hydrateResults.results ?? [];
|
|
635
|
-
if (!results.length) {
|
|
636
|
-
return {
|
|
637
|
-
status: AgentStatus.ERROR,
|
|
638
|
-
response: {
|
|
639
|
-
status: AgentStatus.ERROR,
|
|
640
|
-
prompt: null,
|
|
641
|
-
analysis: null,
|
|
642
|
-
error: "No matching prompt found in vault to review.",
|
|
643
|
-
},
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
const promptData = results[0];
|
|
647
|
-
const fullText = promptData.full_prompt ?? "";
|
|
648
|
-
const issues = [];
|
|
649
|
-
// Structural checks
|
|
650
|
-
const requiredSections = [
|
|
651
|
-
"角色", "任务", "输入", "输出格式", "硬约束", "生成要求",
|
|
652
|
-
];
|
|
653
|
-
for (const section of requiredSections) {
|
|
654
|
-
if (!fullText.includes(section)) {
|
|
655
|
-
issues.push(`Missing section: ${section}`);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
// Constraint check
|
|
659
|
-
const globalEntries = hydrateResults.global_entries ?? [];
|
|
660
|
-
for (const entry of globalEntries) {
|
|
661
|
-
for (const c of entry.hard_constraints_added ?? []) {
|
|
662
|
-
if (!fullText.includes(c)) {
|
|
663
|
-
issues.push(`GLOBAL constraint not reflected: ${c}`);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
const reviewReport = issues.length
|
|
668
|
-
? issues.map((i) => `- ${i}`).join("\n")
|
|
669
|
-
: "All checks passed.";
|
|
670
|
-
return {
|
|
671
|
-
status: AgentStatus.OK,
|
|
672
|
-
response: {
|
|
673
|
-
status: AgentStatus.OK,
|
|
674
|
-
prompt: `## Review Report\n\n${reviewReport}\n\n---\n\n${fullText}`,
|
|
675
|
-
analysis: null,
|
|
676
|
-
error: null,
|
|
677
|
-
},
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
681
872
|
// Circuit breaker
|
|
682
873
|
// ═══════════════════════════════════════════════════════════════════════
|
|
683
874
|
shouldBreak() {
|
|
@@ -688,7 +879,14 @@ export class LoopForgeEngine {
|
|
|
688
879
|
if (this.state.quality_trend.length < maxCB)
|
|
689
880
|
return false;
|
|
690
881
|
const recent = this.state.quality_trend.slice(-maxCB);
|
|
691
|
-
|
|
882
|
+
const isFlat = recent.every((val, i) => i === 0 || recent[i - 1] >= val);
|
|
883
|
+
if (isFlat) {
|
|
884
|
+
logEvent("circuit_breaker", {
|
|
885
|
+
trend: recent,
|
|
886
|
+
totalRounds: this.state.quality_trend.length,
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
return isFlat;
|
|
692
890
|
}
|
|
693
891
|
}
|
|
694
892
|
// ═══════════════════════════════════════════════════════════════════════════
|