loopforge 1.7.0 → 1.12.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.map +1 -1
- package/dist/backends/fs.js +1 -2
- package/dist/backends/fs.js.map +1 -1
- package/dist/backends/interface.d.ts +2 -0
- package/dist/backends/interface.d.ts.map +1 -1
- package/dist/builder.d.ts +6 -10
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +128 -63
- package/dist/builder.js.map +1 -1
- package/dist/engine.d.ts +16 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +142 -54
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +33 -2
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +391 -135
- 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 +18 -4
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +225 -24
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts +32 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +33 -14
- 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 +1 -1
- package/dist/observability.js +1 -1
- package/dist/policy.d.ts +66 -9
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +41 -28
- package/dist/policy.js.map +1 -1
- package/dist/protocol.d.ts +120 -16
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +23 -9
- package/dist/protocol.js.map +1 -1
- package/dist/replay.js +7 -7
- package/dist/replay.js.map +1 -1
- package/dist/runtime.d.ts +20 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +420 -177
- package/dist/runtime.js.map +1 -1
- package/dist/tests/builder.test.js +40 -28
- package/dist/tests/builder.test.js.map +1 -1
- package/dist/tests/engine.test.js +13 -17
- package/dist/tests/engine.test.js.map +1 -1
- package/dist/tests/generate-schema.test.js +10 -7
- package/dist/tests/generate-schema.test.js.map +1 -1
- package/dist/tests/loop-compiler.test.js +481 -27
- package/dist/tests/loop-compiler.test.js.map +1 -1
- package/dist/tests/mcp.test.js +103 -103
- 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 +383 -0
- package/dist/tests/memory-bridge.test.js.map +1 -0
- package/dist/tests/policy.test.js +5 -15
- package/dist/tests/policy.test.js.map +1 -1
- package/dist/tests/protocol.test.js +15 -3
- package/dist/tests/protocol.test.js.map +1 -1
- package/dist/tests/replay.test.js +9 -9
- package/dist/tests/replay.test.js.map +1 -1
- package/dist/tests/runtime.test.js +8 -8
- package/dist/tests/runtime.test.js.map +1 -1
- package/dist/tests/verification-gate.test.js +1 -1
- package/dist/tests/verification-gate.test.js.map +1 -1
- package/dist/verification-gate.d.ts +2 -2
- package/dist/verification-gate.js +2 -2
- package/loop_policy.json +28 -23
- package/package.json +1 -1
- package/skills/perception/SKILL.md +64 -12
- 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/tests/adapter.test.d.ts +0 -2
- package/dist/tests/adapter.test.d.ts.map +0 -1
package/dist/loop-compiler.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { createHash } from "node:crypto";
|
|
13
13
|
import { readFileSync } from "node:fs";
|
|
14
14
|
import { getPolicy } from "./policy.js";
|
|
15
|
-
import { makeLoopCompileResponse, makeLoopHealth, makeLoopObjective, makeRollingSummary, makeTaskAlignment, } from "./protocol.js";
|
|
15
|
+
import { AgentStatus, makeCheckpointSummary, makeLoopCompileResponse, makeLoopHealth, makeLoopObjective, makeRollingSummary, makeTaskAlignment, } from "./protocol.js";
|
|
16
16
|
import { routeTechniqueAdaptive } from "./builder.js";
|
|
17
17
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
18
18
|
// Repair cue detection
|
|
@@ -34,7 +34,7 @@ function detectsRepairSignal(request) {
|
|
|
34
34
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
35
35
|
// Tokenization helpers
|
|
36
36
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
37
|
-
function tokenize(text) {
|
|
37
|
+
export function tokenize(text) {
|
|
38
38
|
const tokens = text.split(/\s+/);
|
|
39
39
|
const result = new Set();
|
|
40
40
|
for (let token of tokens) {
|
|
@@ -52,7 +52,7 @@ function tokenize(text) {
|
|
|
52
52
|
}
|
|
53
53
|
return result;
|
|
54
54
|
}
|
|
55
|
-
function jaccard(a, b) {
|
|
55
|
+
export function jaccard(a, b) {
|
|
56
56
|
if (a.size === 0 || b.size === 0)
|
|
57
57
|
return 0.0;
|
|
58
58
|
let intersection = 0;
|
|
@@ -64,6 +64,141 @@ function jaccard(a, b) {
|
|
|
64
64
|
return union > 0 ? intersection / union : 0.0;
|
|
65
65
|
}
|
|
66
66
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
67
|
+
// Delegation helpers (v1.9 — AgentTool mode lightweight utilities)
|
|
68
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
69
|
+
/** Filter relevant constraints for a sub-agent task using Jaccard token similarity.
|
|
70
|
+
* Returns constraints whose token overlap with the subTask exceeds the threshold.
|
|
71
|
+
* Default threshold 0.15 is intentionally lower than the 0.3/0.5 alignment thresholds
|
|
72
|
+
* — constraint filtering should err on the side of inclusion. */
|
|
73
|
+
export function filterConstraintsForSubTask(allConstraints, subTask, threshold = 0.15) {
|
|
74
|
+
if (!allConstraints.length || !subTask.trim())
|
|
75
|
+
return [];
|
|
76
|
+
const taskTokens = tokenize(subTask);
|
|
77
|
+
if (taskTokens.size === 0)
|
|
78
|
+
return [];
|
|
79
|
+
return allConstraints.filter((c) => {
|
|
80
|
+
const cTokens = tokenize(c);
|
|
81
|
+
return jaccard(taskTokens, cTokens) >= threshold;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** Format a self-contained delegation prompt for a sub-agent.
|
|
85
|
+
* Produces a prompt that stands alone — no references to parent conversation,
|
|
86
|
+
* no "based on above", no "continue from previous". This matches the AgentTool
|
|
87
|
+
* contract: "Workers can't see your conversation." */
|
|
88
|
+
export function formatDelegationPrompt(subTask, subAgentType, relevantConstraints, options) {
|
|
89
|
+
const lines = [];
|
|
90
|
+
// Header
|
|
91
|
+
lines.push("### Delegated Task");
|
|
92
|
+
lines.push("");
|
|
93
|
+
lines.push(subTask);
|
|
94
|
+
lines.push("");
|
|
95
|
+
// Context (optional — e.g. file paths, module names)
|
|
96
|
+
if (options?.context) {
|
|
97
|
+
lines.push("### Context");
|
|
98
|
+
lines.push("");
|
|
99
|
+
lines.push(options.context);
|
|
100
|
+
lines.push("");
|
|
101
|
+
}
|
|
102
|
+
// Constraints (only for types that need them — Explore is read-only)
|
|
103
|
+
if (relevantConstraints.length && subAgentType !== "explore") {
|
|
104
|
+
lines.push("### Relevant Constraints");
|
|
105
|
+
for (const c of relevantConstraints)
|
|
106
|
+
lines.push(`- ${c}`);
|
|
107
|
+
lines.push("");
|
|
108
|
+
}
|
|
109
|
+
// Output format (for Explore and Plan agents)
|
|
110
|
+
if (subAgentType === "explore") {
|
|
111
|
+
if (options?.outputFormat) {
|
|
112
|
+
lines.push("### Output Format");
|
|
113
|
+
lines.push("");
|
|
114
|
+
lines.push(options.outputFormat);
|
|
115
|
+
lines.push("");
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
lines.push("### Output Format");
|
|
119
|
+
lines.push("");
|
|
120
|
+
lines.push("Report findings as a structured list with file paths and line numbers.");
|
|
121
|
+
lines.push("");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (subAgentType === "plan") {
|
|
125
|
+
lines.push("### Instructions");
|
|
126
|
+
lines.push("");
|
|
127
|
+
lines.push("Design a concrete implementation plan. Output must include:");
|
|
128
|
+
lines.push("- Step-by-step approach");
|
|
129
|
+
lines.push("- Critical files for implementation (3-5 files with paths)");
|
|
130
|
+
lines.push("- Key design decisions and tradeoffs");
|
|
131
|
+
lines.push("");
|
|
132
|
+
}
|
|
133
|
+
if (subAgentType === "general-purpose") {
|
|
134
|
+
lines.push("### Instructions");
|
|
135
|
+
lines.push("");
|
|
136
|
+
lines.push("Complete the task fully — don't gold-plate, but don't leave it half-done.");
|
|
137
|
+
lines.push("Report what was done, what files were changed, and any issues encountered.");
|
|
138
|
+
lines.push("");
|
|
139
|
+
}
|
|
140
|
+
if (!["explore", "general-purpose", "plan"].includes(subAgentType)) {
|
|
141
|
+
// Custom sub-agent type — generic template
|
|
142
|
+
lines.push("### Instructions");
|
|
143
|
+
lines.push("");
|
|
144
|
+
lines.push("Complete the task above and report results concisely.");
|
|
145
|
+
lines.push("");
|
|
146
|
+
}
|
|
147
|
+
return lines.join("\n");
|
|
148
|
+
}
|
|
149
|
+
/** Build a delegation history summary from vault context (v1.9 — multi-agent).
|
|
150
|
+
* Scans vault for delegation_journal entries and formats them as a table.
|
|
151
|
+
* Returns empty string if no delegation history exists. */
|
|
152
|
+
export function buildDelegationSummary(vaultContext) {
|
|
153
|
+
if (!vaultContext)
|
|
154
|
+
return "";
|
|
155
|
+
const results = vaultContext.results;
|
|
156
|
+
if (!results?.length)
|
|
157
|
+
return "";
|
|
158
|
+
const delegations = results.filter((r) => r?.task_type === "delegation_journal");
|
|
159
|
+
if (!delegations.length)
|
|
160
|
+
return "";
|
|
161
|
+
// Helper: escape pipe characters so they don't break the markdown table
|
|
162
|
+
const esc = (s) => s.replace(/\|/g, "\\|");
|
|
163
|
+
// Collect all delegation entries across rounds
|
|
164
|
+
const entries = [];
|
|
165
|
+
for (const d of delegations) {
|
|
166
|
+
const lineage = d.loop_lineage;
|
|
167
|
+
const round = typeof lineage?.round === "number" ? lineage.round : 0;
|
|
168
|
+
const dels = lineage?.delegations;
|
|
169
|
+
if (!dels?.length)
|
|
170
|
+
continue;
|
|
171
|
+
for (const e of dels) {
|
|
172
|
+
entries.push({
|
|
173
|
+
round,
|
|
174
|
+
agentId: typeof e.agentId === "string" ? e.agentId : (typeof e.index === "number" ? `worker-${e.index}` : "?"),
|
|
175
|
+
type: typeof e.subAgentType === "string" ? e.subAgentType : "?",
|
|
176
|
+
task: typeof e.subTask === "string" ? e.subTask : "?",
|
|
177
|
+
result: typeof e.resultSummary === "string" ? e.resultSummary : "?",
|
|
178
|
+
success: typeof e.success === "boolean" ? e.success : true,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!entries.length)
|
|
183
|
+
return "";
|
|
184
|
+
const lines = [
|
|
185
|
+
"",
|
|
186
|
+
"### Delegation History (Multi-Agent)",
|
|
187
|
+
"",
|
|
188
|
+
"| Round | Agent | Type | Task | Result | ✓/✗ |",
|
|
189
|
+
"|-------|-------|------|------|--------|-----|",
|
|
190
|
+
];
|
|
191
|
+
for (const e of entries) {
|
|
192
|
+
const status = e.success ? "✓" : "✗";
|
|
193
|
+
const shortAgent = e.agentId.length > 24 ? e.agentId.slice(0, 21) + "..." : e.agentId;
|
|
194
|
+
const shortTask = e.task.length > 50 ? e.task.slice(0, 47) + "..." : e.task;
|
|
195
|
+
const shortResult = e.result.length > 40 ? e.result.slice(0, 37) + "..." : e.result;
|
|
196
|
+
lines.push(`| R${e.round} | ${esc(shortAgent)} | ${e.type} | ${esc(shortTask)} | ${esc(shortResult)} | ${status} |`);
|
|
197
|
+
}
|
|
198
|
+
lines.push("");
|
|
199
|
+
return lines.join("\n");
|
|
200
|
+
}
|
|
201
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
67
202
|
// Goal identity
|
|
68
203
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
69
204
|
export function computeGoalTextHash(task) {
|
|
@@ -77,17 +212,6 @@ export function deriveGoalId(loopId, task, explicitGoalId = "") {
|
|
|
77
212
|
taskPrefix = taskPrefix.replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
78
213
|
return loopId ? `${loopId}:${taskPrefix}` : taskPrefix;
|
|
79
214
|
}
|
|
80
|
-
function makePreviousRound() {
|
|
81
|
-
return {
|
|
82
|
-
goal_id: "",
|
|
83
|
-
goal_text_hash: "",
|
|
84
|
-
quality_score: 0,
|
|
85
|
-
success: true,
|
|
86
|
-
task: "",
|
|
87
|
-
constraints_active: [],
|
|
88
|
-
prompt_text: "",
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
215
|
export function getPreviousRound(loopId, roundNum, vaultContext) {
|
|
92
216
|
if (vaultContext === null)
|
|
93
217
|
return null;
|
|
@@ -100,8 +224,7 @@ export function getPreviousRound(loopId, roundNum, vaultContext) {
|
|
|
100
224
|
return {
|
|
101
225
|
goal_id: lineage.goal_id ?? "",
|
|
102
226
|
goal_text_hash: lineage.goal_text_hash ?? "",
|
|
103
|
-
|
|
104
|
-
success: r.success ?? true,
|
|
227
|
+
success: r.success ?? lineage.success ?? true,
|
|
105
228
|
task: r.task ?? r.user_intent ?? "",
|
|
106
229
|
constraints_active: lineage.constraints_active ?? [],
|
|
107
230
|
prompt_text: r.full_prompt ?? "",
|
|
@@ -119,7 +242,7 @@ function getRecentRounds(loopId, n, vaultContext) {
|
|
|
119
242
|
const lineage = (r.loop_lineage || r.lineage || {});
|
|
120
243
|
if (lineage.loop_id === loopId) {
|
|
121
244
|
rounds.push({
|
|
122
|
-
|
|
245
|
+
success: r.success ?? lineage.success ?? false,
|
|
123
246
|
round: lineage.round ?? 0,
|
|
124
247
|
goal_text_hash: lineage.goal_text_hash ?? "",
|
|
125
248
|
});
|
|
@@ -179,7 +302,7 @@ function strategyCollapse(loopId, vaultContext) {
|
|
|
179
302
|
const recent = getRecentRounds(loopId, 3, vaultContext);
|
|
180
303
|
if (recent.length < 3)
|
|
181
304
|
return false;
|
|
182
|
-
return recent.every((r) => r.
|
|
305
|
+
return recent.every((r) => r.success === false);
|
|
183
306
|
}
|
|
184
307
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
185
308
|
// Constraint Retirement (v3.5)
|
|
@@ -288,7 +411,7 @@ function countProgressStallRounds(loopId, currentRound, vaultContext, threshold)
|
|
|
288
411
|
}
|
|
289
412
|
/** Detect repeated failure patterns in sampled rounds.
|
|
290
413
|
* A failure pattern = 2+ consecutive rounds where:
|
|
291
|
-
* 1.
|
|
414
|
+
* 1. success === false (failed)
|
|
292
415
|
* 2. same technique was used
|
|
293
416
|
* 3. task text is similar (Jaccard > 0.4)
|
|
294
417
|
*
|
|
@@ -300,7 +423,7 @@ function detectFailurePatterns(sampled) {
|
|
|
300
423
|
let runStart = -1;
|
|
301
424
|
for (let i = 0; i < sampled.length; i++) {
|
|
302
425
|
const r = sampled[i];
|
|
303
|
-
const isFailed = r.
|
|
426
|
+
const isFailed = r.success === false;
|
|
304
427
|
if (isFailed && runStart === -1) {
|
|
305
428
|
runStart = i;
|
|
306
429
|
}
|
|
@@ -321,7 +444,7 @@ function detectFailurePatterns(sampled) {
|
|
|
321
444
|
}
|
|
322
445
|
return patterns;
|
|
323
446
|
}
|
|
324
|
-
/** Classify a run of consecutive
|
|
447
|
+
/** Classify a run of consecutive failed rounds as a failure pattern.
|
|
325
448
|
* Returns null if the rounds are too dissimilar to be the same pattern. */
|
|
326
449
|
function classifyRun(run) {
|
|
327
450
|
if (run.length < 2)
|
|
@@ -341,9 +464,9 @@ function classifyRun(run) {
|
|
|
341
464
|
}
|
|
342
465
|
const rounds = `R${run[0].round}-R${run[run.length - 1].round}`;
|
|
343
466
|
const taskPreview = run[0].task.slice(0, 80);
|
|
344
|
-
const
|
|
467
|
+
const failureRate = `${run.length}/${run.length}`;
|
|
345
468
|
return (`technique '${technique}' on task '${taskPreview}' ` +
|
|
346
|
-
`failed ${run.length} consecutive rounds (${rounds}
|
|
469
|
+
`failed ${run.length} consecutive rounds (${rounds}) — ` +
|
|
347
470
|
`consider strategy change`);
|
|
348
471
|
}
|
|
349
472
|
/** Extract round numbers referenced in failure pattern descriptions.
|
|
@@ -370,7 +493,7 @@ function extractFailureRoundNums(patterns) {
|
|
|
370
493
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
371
494
|
// Rolling Summary (v3.5)
|
|
372
495
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
373
|
-
export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
496
|
+
export function buildRollingSummary(loopId, currentRound, vaultContext, sinceRound) {
|
|
374
497
|
if (vaultContext === null)
|
|
375
498
|
return null;
|
|
376
499
|
const results = vaultContext.results || [];
|
|
@@ -378,6 +501,7 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
378
501
|
return null;
|
|
379
502
|
const policy = getPolicy();
|
|
380
503
|
const window = policy.summary.window;
|
|
504
|
+
const minRound = sinceRound ?? 0;
|
|
381
505
|
// Collect rounds matching this loop_id, excluding current
|
|
382
506
|
const rounds = [];
|
|
383
507
|
for (const r of results) {
|
|
@@ -387,9 +511,11 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
387
511
|
const rnd = lineage.round;
|
|
388
512
|
if (rnd >= currentRound)
|
|
389
513
|
continue;
|
|
514
|
+
if (rnd < minRound)
|
|
515
|
+
continue;
|
|
390
516
|
rounds.push({
|
|
391
517
|
round: rnd,
|
|
392
|
-
|
|
518
|
+
success: r.success ?? lineage.success ?? false,
|
|
393
519
|
task: r.task ?? r.user_intent ?? "",
|
|
394
520
|
output_summary: r.output_summary ?? "",
|
|
395
521
|
constraint_violations: r.constraint_violations || [],
|
|
@@ -401,32 +527,13 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
401
527
|
// Sort by round descending, take last N
|
|
402
528
|
rounds.sort((a, b) => b.round - a.round);
|
|
403
529
|
const sampled = rounds.slice(0, window).reverse(); // chronological order
|
|
404
|
-
//
|
|
405
|
-
const
|
|
406
|
-
// Trajectory direction
|
|
407
|
-
let direction = "stable";
|
|
408
|
-
if (trajectory.length >= 2) {
|
|
409
|
-
const diffs = trajectory.slice(1).map((v, i) => v - trajectory[i]);
|
|
410
|
-
if (diffs.every((d) => d >= 0) && diffs.some((d) => d > 0)) {
|
|
411
|
-
direction = "improving";
|
|
412
|
-
}
|
|
413
|
-
else if (diffs.every((d) => d <= 0) && diffs.some((d) => d < 0)) {
|
|
414
|
-
direction = "declining";
|
|
415
|
-
}
|
|
416
|
-
else if (diffs.every((d) => d === 0)) {
|
|
417
|
-
direction = "stable";
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
direction = "volatile";
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
// What worked
|
|
424
|
-
const whatWorked = [];
|
|
530
|
+
// v1.12: Key outcomes — all rounds with output_summary
|
|
531
|
+
const keyOutcomes = [];
|
|
425
532
|
for (const r of sampled) {
|
|
426
|
-
if (r.
|
|
427
|
-
r.
|
|
428
|
-
|
|
429
|
-
`${
|
|
533
|
+
if (r.output_summary) {
|
|
534
|
+
const status = r.success ? "✓" : "✗";
|
|
535
|
+
keyOutcomes.push(`[R${r.round}] ${status} (${r.technique_used || "n/a"}): ` +
|
|
536
|
+
`${String(r.output_summary).slice(0, 200)}`);
|
|
430
537
|
}
|
|
431
538
|
}
|
|
432
539
|
// Recurring issues
|
|
@@ -445,17 +552,10 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
445
552
|
.filter(([, count]) => count >= 2)
|
|
446
553
|
.sort((a, b) => b[1] - a[1])
|
|
447
554
|
.map(([v, count]) => `${v} (appeared in ${count} rounds)`);
|
|
448
|
-
// Key lessons
|
|
449
|
-
const keyLessons = [];
|
|
450
|
-
for (const r of sampled) {
|
|
451
|
-
if (r.quality_score >= 4 && r.output_summary) {
|
|
452
|
-
keyLessons.push(`[R${r.round}] ${String(r.output_summary).slice(0, 200)}`);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
555
|
// v1.7: Failure lineage weighting — detect and demote failure patterns
|
|
456
556
|
const sampledRounds = sampled.map((r) => ({
|
|
457
557
|
round: r.round,
|
|
458
|
-
|
|
558
|
+
success: r.success,
|
|
459
559
|
task: r.task,
|
|
460
560
|
output_summary: r.output_summary,
|
|
461
561
|
constraint_violations: r.constraint_violations,
|
|
@@ -463,21 +563,21 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
463
563
|
}));
|
|
464
564
|
const failurePatterns = detectFailurePatterns(sampledRounds);
|
|
465
565
|
const failedRoundNums = extractFailureRoundNums(failurePatterns);
|
|
466
|
-
// Re-weight
|
|
467
|
-
if (failedRoundNums.size > 0 &&
|
|
566
|
+
// Re-weight key_outcomes: outcomes from failure-pattern rounds go to end
|
|
567
|
+
if (failedRoundNums.size > 0 && keyOutcomes.length > 0) {
|
|
468
568
|
const promoted = [];
|
|
469
569
|
const demoted = [];
|
|
470
|
-
for (const
|
|
471
|
-
const rndMatch =
|
|
570
|
+
for (const ko of keyOutcomes) {
|
|
571
|
+
const rndMatch = ko.match(/^\[R(\d+)\]/);
|
|
472
572
|
if (rndMatch && failedRoundNums.has(parseInt(rndMatch[1], 10))) {
|
|
473
|
-
demoted.push(
|
|
573
|
+
demoted.push(ko.replace(/^\[R\d+\]/, "$& [Consider alternatives]"));
|
|
474
574
|
}
|
|
475
575
|
else {
|
|
476
|
-
promoted.push(
|
|
576
|
+
promoted.push(ko);
|
|
477
577
|
}
|
|
478
578
|
}
|
|
479
|
-
|
|
480
|
-
|
|
579
|
+
keyOutcomes.length = 0;
|
|
580
|
+
keyOutcomes.push(...promoted, ...demoted);
|
|
481
581
|
}
|
|
482
582
|
// Re-weight recurring_issues: mark issues that ONLY appear in failure-pattern rounds
|
|
483
583
|
if (failedRoundNums.size > 0 && recurringIssues.length > 0) {
|
|
@@ -504,11 +604,8 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
504
604
|
}));
|
|
505
605
|
}
|
|
506
606
|
return makeRollingSummary({
|
|
507
|
-
|
|
508
|
-
trajectory_direction: direction,
|
|
509
|
-
what_worked: whatWorked,
|
|
607
|
+
key_outcomes: keyOutcomes,
|
|
510
608
|
recurring_issues: recurringIssues,
|
|
511
|
-
key_lessons: keyLessons,
|
|
512
609
|
rounds_sampled: sampled.length,
|
|
513
610
|
generated_at_round: currentRound,
|
|
514
611
|
failed_patterns: failurePatterns.length ? failurePatterns : [],
|
|
@@ -520,14 +617,13 @@ export function formatRollingSummaryForPrompt(rs) {
|
|
|
520
617
|
const lines = [
|
|
521
618
|
"### Cross-Round Summary (Accumulated)",
|
|
522
619
|
"",
|
|
523
|
-
`**Sampled**: ${rs.rounds_sampled} prior rounds
|
|
524
|
-
`**Quality Trajectory**: [${rs.quality_trajectory.join(", ")}]`,
|
|
620
|
+
`**Sampled**: ${rs.rounds_sampled} prior rounds`,
|
|
525
621
|
"",
|
|
526
622
|
];
|
|
527
|
-
if (rs.
|
|
528
|
-
lines.push("**
|
|
529
|
-
for (const
|
|
530
|
-
lines.push(`- ${
|
|
623
|
+
if (rs.key_outcomes.length) {
|
|
624
|
+
lines.push("**Key Outcomes**:");
|
|
625
|
+
for (const ko of rs.key_outcomes)
|
|
626
|
+
lines.push(`- ${ko}`);
|
|
531
627
|
lines.push("");
|
|
532
628
|
}
|
|
533
629
|
if (rs.recurring_issues.length) {
|
|
@@ -541,21 +637,137 @@ export function formatRollingSummaryForPrompt(rs) {
|
|
|
541
637
|
lines.push("### ⚠️ Failure Patterns — Consider Strategy Change");
|
|
542
638
|
lines.push("");
|
|
543
639
|
lines.push("The following approaches failed repeatedly. The compiler has demoted " +
|
|
544
|
-
"their influence on key
|
|
640
|
+
"their influence on key outcomes. Consider a different technique or " +
|
|
545
641
|
"narrower scope.");
|
|
546
642
|
for (const fp of rs.failed_patterns) {
|
|
547
643
|
lines.push(`- 🚫 ${fp}`);
|
|
548
644
|
}
|
|
549
645
|
lines.push("");
|
|
550
646
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
647
|
+
return lines.join("\n");
|
|
648
|
+
}
|
|
649
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
650
|
+
// Checkpoint compression (v1.10 — subtask boundary snapshots)
|
|
651
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
652
|
+
/** Build a CheckpointSummary from the current round's self-evaluation state.
|
|
653
|
+
* Called when the Agent sets compression_checkpoint: true in its self-eval.
|
|
654
|
+
* Snapshots the current constraints and outcome so they survive rolling-window
|
|
655
|
+
* eviction in subsequent rounds. */
|
|
656
|
+
export function buildCheckpointSummary(selfEval, round, activeConstraints, retiredConstraints) {
|
|
657
|
+
const policy = getPolicy();
|
|
658
|
+
const cpCfg = policy.checkpoint;
|
|
659
|
+
const maxCarried = cpCfg?.max_carried_constraints ?? 10;
|
|
660
|
+
const outcomeMaxChars = cpCfg?.outcome_max_chars ?? 200;
|
|
661
|
+
const label = selfEval.checkpoint_label?.trim() ||
|
|
662
|
+
`Subtask completed at round ${round}`;
|
|
663
|
+
const outcome = (selfEval.output_summary || "").trim().slice(0, outcomeMaxChars);
|
|
664
|
+
// Carried: constraints still active, capped
|
|
665
|
+
const carried = activeConstraints.slice(0, maxCarried);
|
|
666
|
+
// Resolved: constraints retired this round (retracted + the retire delta)
|
|
667
|
+
const resolved = retiredConstraints.slice(0, maxCarried);
|
|
668
|
+
return {
|
|
669
|
+
label,
|
|
670
|
+
declared_at_round: round,
|
|
671
|
+
outcome,
|
|
672
|
+
carried_constraints: carried,
|
|
673
|
+
resolved_constraints: resolved,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
/** Format a CheckpointSummary as a prompt block.
|
|
677
|
+
* Renders as a fixed section that persists across rolling-window eviction.
|
|
678
|
+
* Returns empty string if the summary is null or has no meaningful data. */
|
|
679
|
+
export function formatCheckpointForPrompt(cs) {
|
|
680
|
+
if (!cs)
|
|
681
|
+
return "";
|
|
682
|
+
// Must have at least a label and some content to be meaningful
|
|
683
|
+
if (!cs.label && !cs.outcome && !cs.carried_constraints.length)
|
|
684
|
+
return "";
|
|
685
|
+
const lines = [
|
|
686
|
+
`### Checkpoint: ${cs.label} (Round ${cs.declared_at_round})`,
|
|
687
|
+
"",
|
|
688
|
+
];
|
|
689
|
+
if (cs.outcome) {
|
|
690
|
+
lines.push(`**Outcome**: ${cs.outcome}`);
|
|
691
|
+
lines.push("");
|
|
692
|
+
}
|
|
693
|
+
if (cs.carried_constraints.length) {
|
|
694
|
+
lines.push("**Carried Constraints** (still active):");
|
|
695
|
+
for (const c of cs.carried_constraints) {
|
|
696
|
+
lines.push(`- ${c}`);
|
|
697
|
+
}
|
|
698
|
+
lines.push("");
|
|
699
|
+
}
|
|
700
|
+
if (cs.resolved_constraints.length) {
|
|
701
|
+
lines.push("**Resolved Constraints** (closed with this checkpoint):");
|
|
702
|
+
for (const c of cs.resolved_constraints) {
|
|
703
|
+
lines.push(`- ${c}`);
|
|
704
|
+
}
|
|
555
705
|
lines.push("");
|
|
556
706
|
}
|
|
557
707
|
return lines.join("\n");
|
|
558
708
|
}
|
|
709
|
+
/** Query the vault for the most recent checkpoint entry before the current
|
|
710
|
+
* round. Returns null if no checkpoint has been declared yet. */
|
|
711
|
+
function loadLatestCheckpoint(loopId, currentRound, vaultContext) {
|
|
712
|
+
if (!vaultContext)
|
|
713
|
+
return null;
|
|
714
|
+
const results = vaultContext.results || [];
|
|
715
|
+
let latest = null;
|
|
716
|
+
let latestRound = -1;
|
|
717
|
+
for (const r of results) {
|
|
718
|
+
const lineage = (r.loop_lineage || r.lineage || {});
|
|
719
|
+
if (lineage.loop_id !== loopId)
|
|
720
|
+
continue;
|
|
721
|
+
const rnd = lineage.round;
|
|
722
|
+
if (rnd >= currentRound)
|
|
723
|
+
continue;
|
|
724
|
+
// Check for stored checkpoint summary on this entry
|
|
725
|
+
const cp = r.checkpoint_summary;
|
|
726
|
+
if (cp && typeof cp.declared_at_round === "number" && cp.declared_at_round > latestRound) {
|
|
727
|
+
latest = cp;
|
|
728
|
+
latestRound = cp.declared_at_round;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (!latest)
|
|
732
|
+
return null;
|
|
733
|
+
return makeCheckpointSummary({
|
|
734
|
+
label: latest.label ?? "",
|
|
735
|
+
declared_at_round: latest.declared_at_round ?? 0,
|
|
736
|
+
outcome: latest.outcome ?? "",
|
|
737
|
+
carried_constraints: latest.carried_constraints ?? [],
|
|
738
|
+
resolved_constraints: latest.resolved_constraints ?? [],
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
742
|
+
// External Context formatting (v1.7 — memory system integration)
|
|
743
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
744
|
+
/** Format an external context string for injection into an L2 prompt.
|
|
745
|
+
* Wraps the raw context in a marked section with a priority disclaimer.
|
|
746
|
+
* Returns empty string if context is empty or injection is disabled by policy. */
|
|
747
|
+
export function formatExternalContext(externalContext, sectionTitle, maxLength) {
|
|
748
|
+
if (!externalContext || externalContext.trim().length === 0)
|
|
749
|
+
return "";
|
|
750
|
+
const trimmed = externalContext.trim().slice(0, maxLength);
|
|
751
|
+
const truncationNote = trimmed.length < externalContext.trim().length
|
|
752
|
+
? `\n> ⚠️ Context was truncated to ${maxLength} characters.`
|
|
753
|
+
: "";
|
|
754
|
+
return [
|
|
755
|
+
"",
|
|
756
|
+
sectionTitle,
|
|
757
|
+
"",
|
|
758
|
+
"> ⚠️ **Role**: Situational awareness only. These are point-in-time",
|
|
759
|
+
"> observations from past sessions — claims about code locations or",
|
|
760
|
+
"> behaviour may be outdated. If any insight contradicts the Loop",
|
|
761
|
+
"> Objective or Active Constraints above, the LoopForge specification",
|
|
762
|
+
"> takes absolute precedence.",
|
|
763
|
+
"",
|
|
764
|
+
trimmed,
|
|
765
|
+
truncationNote,
|
|
766
|
+
"",
|
|
767
|
+
"---",
|
|
768
|
+
"",
|
|
769
|
+
].join("\n");
|
|
770
|
+
}
|
|
559
771
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
560
772
|
// LAYER 1 — Hard Gates (can change compile level)
|
|
561
773
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -699,7 +911,7 @@ export function checkLoopHealth(loopId, request, vaultContext) {
|
|
|
699
911
|
// 4. strategy_stability
|
|
700
912
|
const recent = getRecentRounds(loopId, 3, vaultContext);
|
|
701
913
|
const strategyStability = recent.length > 0
|
|
702
|
-
? recent.every((r) => r.
|
|
914
|
+
? recent.every((r) => r.success === true)
|
|
703
915
|
: true;
|
|
704
916
|
// 5. task_continuity
|
|
705
917
|
let taskContinuity = 1.0;
|
|
@@ -736,15 +948,22 @@ function computeSuggestedNextTask(loopId, vaultContext) {
|
|
|
736
948
|
if (vaultContext === null)
|
|
737
949
|
return "";
|
|
738
950
|
const results = vaultContext.results || [];
|
|
951
|
+
let bestTask = "";
|
|
952
|
+
let bestRound = -1;
|
|
739
953
|
for (const r of results) {
|
|
740
954
|
const lineage = (r.loop_lineage || r.lineage || {});
|
|
741
|
-
if (lineage.loop_id
|
|
955
|
+
if (lineage.loop_id !== loopId)
|
|
956
|
+
continue;
|
|
957
|
+
const rnd = lineage.round ?? 0;
|
|
958
|
+
if (rnd > bestRound) {
|
|
742
959
|
const task = r.task ?? r.user_intent ?? "";
|
|
743
|
-
if (task)
|
|
744
|
-
|
|
960
|
+
if (task) {
|
|
961
|
+
bestTask = task;
|
|
962
|
+
bestRound = rnd;
|
|
963
|
+
}
|
|
745
964
|
}
|
|
746
965
|
}
|
|
747
|
-
return "";
|
|
966
|
+
return bestTask ? `[R${bestRound}] Previous task: ${bestTask.slice(0, 120)}` : "";
|
|
748
967
|
}
|
|
749
968
|
export function computeAdvisories(request, vaultContext) {
|
|
750
969
|
const warnings = [];
|
|
@@ -760,7 +979,7 @@ export function computeAdvisories(request, vaultContext) {
|
|
|
760
979
|
}
|
|
761
980
|
// Strategy collapse check
|
|
762
981
|
if (strategyCollapse(request.loop_id, vaultContext)) {
|
|
763
|
-
warnings.push("strategy_collapse: 3 consecutive
|
|
982
|
+
warnings.push("strategy_collapse: 3 consecutive failed rounds — " +
|
|
764
983
|
"consider force_level=L2 rebuild");
|
|
765
984
|
}
|
|
766
985
|
// Repair cue detection
|
|
@@ -959,7 +1178,7 @@ function compileL0(request, vaultContext, prevRound) {
|
|
|
959
1178
|
const cachedPrompt = prev?.prompt_text ?? "";
|
|
960
1179
|
if (cachedPrompt) {
|
|
961
1180
|
return makeLoopCompileResponse({
|
|
962
|
-
status:
|
|
1181
|
+
status: AgentStatus.OK,
|
|
963
1182
|
prompt: cachedPrompt,
|
|
964
1183
|
recompile_level: "l0",
|
|
965
1184
|
diff_from_previous: `L0 cache hit — reusing prompt from round ${request.round - 1}`,
|
|
@@ -1053,7 +1272,7 @@ function compileL1(request, vaultContext, prevRound) {
|
|
|
1053
1272
|
lines.push("### Task");
|
|
1054
1273
|
lines.push(request.task);
|
|
1055
1274
|
return makeLoopCompileResponse({
|
|
1056
|
-
status:
|
|
1275
|
+
status: AgentStatus.OK,
|
|
1057
1276
|
prompt: lines.join("\n"),
|
|
1058
1277
|
recompile_level: "l1",
|
|
1059
1278
|
diff_from_previous: diffParts.length ? diffParts.join("; ") : "Patch applied.",
|
|
@@ -1072,19 +1291,38 @@ function compileL1(request, vaultContext, prevRound) {
|
|
|
1072
1291
|
}
|
|
1073
1292
|
export function compileL2(request, vaultContext) {
|
|
1074
1293
|
const goalId = deriveGoalId(request.loop_id, request.task, request.goal_id);
|
|
1075
|
-
//
|
|
1076
|
-
const
|
|
1294
|
+
// v4.0: Tier-gated technique routing
|
|
1295
|
+
const isCheckpoint = request.last_round_result?.compression_checkpoint === true;
|
|
1296
|
+
const analysis = routeTechniqueAdaptive(request.task, vaultContext, request.loop_id, isCheckpoint);
|
|
1077
1297
|
const technique = analysis.technique;
|
|
1078
1298
|
const referenceFile = analysis.reference_file;
|
|
1079
|
-
//
|
|
1080
|
-
|
|
1299
|
+
// v1.10: Checkpoint — load existing or build new from last round's self-eval
|
|
1300
|
+
let checkpointSummary = null;
|
|
1301
|
+
let checkpointRound = 0;
|
|
1302
|
+
// Check if the last round declared a checkpoint
|
|
1303
|
+
const lr = request.last_round_result;
|
|
1304
|
+
if (lr?.compression_checkpoint === true) {
|
|
1305
|
+
// Build fresh checkpoint from the last round's data
|
|
1306
|
+
// Constraints haven't been updated yet at this point, but we snapshot the
|
|
1307
|
+
// current state as seen by this compile call (pre-P5 correction).
|
|
1308
|
+
// We defer building until constraints are finalized below.
|
|
1309
|
+
checkpointRound = request.round - 1;
|
|
1310
|
+
}
|
|
1311
|
+
else if (request.round > 1) {
|
|
1312
|
+
// No new checkpoint — try loading the previous one from vault
|
|
1313
|
+
checkpointSummary = loadLatestCheckpoint(request.loop_id, request.round, vaultContext);
|
|
1314
|
+
if (checkpointSummary) {
|
|
1315
|
+
checkpointRound = checkpointSummary.declared_at_round;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
// v3.5: Rolling summary — optionally scoped to post-checkpoint rounds
|
|
1319
|
+
const rollingSummary = buildRollingSummary(request.loop_id, request.round, vaultContext, checkpointRound > 0 ? checkpointRound : undefined);
|
|
1081
1320
|
// P5c: Wrong assumptions — must be applied BEFORE formatting so they appear
|
|
1082
1321
|
// in the rendered cross-round summary text injected into the prompt.
|
|
1083
|
-
const lr = request.last_round_result;
|
|
1084
1322
|
if (lr?.wrong_assumptions?.length) {
|
|
1085
1323
|
if (rollingSummary) {
|
|
1086
|
-
rollingSummary.
|
|
1087
|
-
...rollingSummary.
|
|
1324
|
+
rollingSummary.key_outcomes = [
|
|
1325
|
+
...rollingSummary.key_outcomes,
|
|
1088
1326
|
...lr.wrong_assumptions.map((a) => `Wrong assumption corrected: ${a}`),
|
|
1089
1327
|
];
|
|
1090
1328
|
}
|
|
@@ -1208,6 +1446,21 @@ export function compileL2(request, vaultContext) {
|
|
|
1208
1446
|
])];
|
|
1209
1447
|
loopObjective = updatedLo;
|
|
1210
1448
|
}
|
|
1449
|
+
// ── v1.10: Build checkpoint summary if last round declared one ──
|
|
1450
|
+
if (checkpointRound > 0 && !checkpointSummary && lr) {
|
|
1451
|
+
// checkpointRound was set but no summary yet → fresh declaration
|
|
1452
|
+
// We need the self-eval from last round. It's embedded in lr fields.
|
|
1453
|
+
// Build a synthetic SelfEvaluation from the last_round_result for the builder.
|
|
1454
|
+
const synthEval = {
|
|
1455
|
+
success: lr.success,
|
|
1456
|
+
output_summary: lr.output_summary,
|
|
1457
|
+
constraint_violations: lr.constraint_violations,
|
|
1458
|
+
should_continue: true,
|
|
1459
|
+
compression_checkpoint: true,
|
|
1460
|
+
checkpoint_label: lr.checkpoint_label ?? "",
|
|
1461
|
+
};
|
|
1462
|
+
checkpointSummary = buildCheckpointSummary(synthEval, checkpointRound, constraints, retired);
|
|
1463
|
+
}
|
|
1211
1464
|
// ── Route to technique-specific specialist (v1.1 deep integration) ──
|
|
1212
1465
|
let prompt;
|
|
1213
1466
|
if (technique === "step-back") {
|
|
@@ -1222,6 +1475,17 @@ export function compileL2(request, vaultContext) {
|
|
|
1222
1475
|
else {
|
|
1223
1476
|
prompt = compileGeneric(request, goalId, constraints, loopObjective, rollingText, analysis, referenceFile, technique);
|
|
1224
1477
|
}
|
|
1478
|
+
// ── v1.10: Inject checkpoint block (before rolling summary in prompt) ──
|
|
1479
|
+
// The checkpoint is rendered before the external context so it appears
|
|
1480
|
+
// in a natural position: after Loop Objective, before Cross-Round Summary.
|
|
1481
|
+
if (checkpointSummary) {
|
|
1482
|
+
prompt += "\n" + formatCheckpointForPrompt(checkpointSummary);
|
|
1483
|
+
}
|
|
1484
|
+
// ── v1.7: Inject external memory context (L2 only) ──
|
|
1485
|
+
const policy = getPolicy();
|
|
1486
|
+
if (policy.memory_injection.enabled && request.external_context) {
|
|
1487
|
+
prompt += formatExternalContext(request.external_context, policy.memory_injection.section_title, policy.memory_injection.max_context_length);
|
|
1488
|
+
}
|
|
1225
1489
|
// ── P2: Emerged subtasks → suggested next task ──
|
|
1226
1490
|
let suggestedNextTask = "";
|
|
1227
1491
|
if (request.last_round_result?.emerged_subtasks?.length) {
|
|
@@ -1333,7 +1597,7 @@ export function compileL2(request, vaultContext) {
|
|
|
1333
1597
|
progressDashboard = lines.join("\n");
|
|
1334
1598
|
}
|
|
1335
1599
|
return makeLoopCompileResponse({
|
|
1336
|
-
status:
|
|
1600
|
+
status: AgentStatus.OK,
|
|
1337
1601
|
prompt: prompt + progressDashboard,
|
|
1338
1602
|
recompile_level: "l2",
|
|
1339
1603
|
diff_from_previous: request.round === 1 || request.plan_source
|
|
@@ -1345,6 +1609,7 @@ export function compileL2(request, vaultContext) {
|
|
|
1345
1609
|
technique_used: technique,
|
|
1346
1610
|
reference_file: referenceFile,
|
|
1347
1611
|
rolling_summary: rollingSummary,
|
|
1612
|
+
checkpoint_summary: checkpointSummary,
|
|
1348
1613
|
loop_id: request.loop_id,
|
|
1349
1614
|
round: request.round,
|
|
1350
1615
|
goal_id: goalId,
|
|
@@ -1435,10 +1700,8 @@ function compileGeneric(request, goalId, constraints, loopObjective, rollingText
|
|
|
1435
1700
|
"### Generation Instructions",
|
|
1436
1701
|
`1. Read \`${referenceFile}\` — study its structure rules, section count, and format requirements`,
|
|
1437
1702
|
"2. Generate a complete prompt following that technique's structure",
|
|
1438
|
-
"3.
|
|
1439
|
-
"4.
|
|
1440
|
-
"5. The prompt must be self-contained — ready for a coding agent to execute",
|
|
1441
|
-
"6. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1703
|
+
"3. The prompt must be self-contained — ready for a coding agent to execute",
|
|
1704
|
+
"4. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1442
1705
|
];
|
|
1443
1706
|
return lines.join("\n");
|
|
1444
1707
|
}
|
|
@@ -1497,19 +1760,12 @@ function compileStepBack(request, goalId, constraints, loopObjective, rollingTex
|
|
|
1497
1760
|
"**Section 6 transition sentence (MANDATORY)**:",
|
|
1498
1761
|
"\"基于上述抽象框架,实现以下所有功能。\"",
|
|
1499
1762
|
"",
|
|
1500
|
-
"### Quality Checklist",
|
|
1501
|
-
"- [ ] Frameworks are abstract concepts/formulas/principles, NOT concrete code",
|
|
1502
|
-
"- [ ] Section 6 starts with the mandatory transition sentence",
|
|
1503
|
-
"- [ ] Abstraction level is consistent across frameworks",
|
|
1504
|
-
"- [ ] Step-back question is tightened to minimum necessary generalisation",
|
|
1505
|
-
"",
|
|
1506
1763
|
"### Generation Instructions",
|
|
1507
1764
|
"1. Extract 2-3 abstract principles/frameworks from the task domain",
|
|
1508
1765
|
"2. Build ASCII diagrams for each framework (formulas, tables, rules)",
|
|
1509
1766
|
"3. Apply the abstraction back to the concrete task",
|
|
1510
|
-
"4.
|
|
1511
|
-
"5.
|
|
1512
|
-
"6. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1767
|
+
"4. Verify: the generated prompt satisfies all structural rules in the skeleton above",
|
|
1768
|
+
"5. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1513
1769
|
];
|
|
1514
1770
|
return lines.join("\n");
|
|
1515
1771
|
}
|
|
@@ -1567,21 +1823,13 @@ function compileLeastToMost(request, goalId, constraints, loopObjective, rolling
|
|
|
1567
1823
|
"- Each sub-problem must serve the original task — no unrelated steps",
|
|
1568
1824
|
"- The final sub-problem must be equivalent to or directly address the original task",
|
|
1569
1825
|
"",
|
|
1570
|
-
"### Quality Checklist",
|
|
1571
|
-
"- [ ] Sub-problems ordered simplest → most complex, dependency chain explicit",
|
|
1572
|
-
"- [ ] Each sub-problem declares its prerequisite sub-problems",
|
|
1573
|
-
"- [ ] Last sub-problem is \"综合实现完整模块\" with integration list",
|
|
1574
|
-
"- [ ] Section 6 expands per output format list (NOT per sub-problem)",
|
|
1575
|
-
"- [ ] Sub-problem count: 4-6, no more, no less",
|
|
1576
|
-
"",
|
|
1577
1826
|
"### Generation Instructions",
|
|
1578
1827
|
"1. Decompose the task into 4-6 ordered sub-problems with explicit dependencies",
|
|
1579
1828
|
"2. Sub-problem 1 starts with the simplest building block (data structures, enums, base config)",
|
|
1580
1829
|
"3. Each subsequent sub-problem builds on prior results",
|
|
1581
1830
|
"4. Final sub-problem integrates all components into the complete module",
|
|
1582
|
-
"5.
|
|
1583
|
-
"6.
|
|
1584
|
-
"7. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1831
|
+
"5. Verify: the generated prompt satisfies all structural rules in the skeleton above",
|
|
1832
|
+
"6. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1585
1833
|
];
|
|
1586
1834
|
return lines.join("\n");
|
|
1587
1835
|
}
|
|
@@ -1660,24 +1908,14 @@ function compileToT(request, goalId, constraints, loopObjective, rollingText, an
|
|
|
1660
1908
|
"- thought is a PUBLIC intermediate semantic unit, not hidden chain-of-thought",
|
|
1661
1909
|
"- State table MUST have: round, branch ID, candidate description, evaluation score/judgment, keep/prune decision",
|
|
1662
1910
|
"",
|
|
1663
|
-
"### Quality Checklist",
|
|
1664
|
-
"- [ ] Branch count 2-4, depth ≤3 (prevents token explosion)",
|
|
1665
|
-
"- [ ] Evaluation criteria table has ≥3 dimensions including correctness, feasibility, constraint matching",
|
|
1666
|
-
"- [ ] State table format explicit (round/branch/candidate/evaluation/decision)",
|
|
1667
|
-
"- [ ] Search strategy matches task type (beam/dfs/expert-panel)",
|
|
1668
|
-
"- [ ] Hard constraints listed FIRST in evaluation criteria",
|
|
1669
|
-
"- [ ] Not \"3 experts casually discussing\" — has explicit branch count, depth, scoring, and pruning rules",
|
|
1670
|
-
"- [ ] Ends with model outputting final selection and rationale",
|
|
1671
|
-
"",
|
|
1672
1911
|
"### Generation Instructions",
|
|
1673
1912
|
"1. Select the appropriate search strategy (beam/dfs/expert-panel) based on task type",
|
|
1674
1913
|
"2. Define 2-4 candidate approaches as initial branches",
|
|
1675
1914
|
"3. Build evaluation criteria table with hard constraints ranked first",
|
|
1676
1915
|
"4. Define the state table format for tracking exploration",
|
|
1677
1916
|
"5. Set branch count and max depth to prevent token explosion",
|
|
1678
|
-
"6.
|
|
1679
|
-
"7.
|
|
1680
|
-
"8. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1917
|
+
"6. Verify: the generated prompt satisfies all structural rules in the skeleton above",
|
|
1918
|
+
"7. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1681
1919
|
];
|
|
1682
1920
|
return lines.join("\n");
|
|
1683
1921
|
}
|
|
@@ -1715,7 +1953,9 @@ export function buildSelfEvalBlock(round) {
|
|
|
1715
1953
|
' },',
|
|
1716
1954
|
' "retracted_constraints": [],',
|
|
1717
1955
|
' "revised_success_criteria": [],',
|
|
1718
|
-
' "wrong_assumptions": []',
|
|
1956
|
+
' "wrong_assumptions": [],',
|
|
1957
|
+
' "compression_checkpoint": false,',
|
|
1958
|
+
' "checkpoint_label": ""',
|
|
1719
1959
|
"}",
|
|
1720
1960
|
"---end-loopforge-eval",
|
|
1721
1961
|
"```",
|
|
@@ -1748,6 +1988,15 @@ export function buildSelfEvalBlock(round) {
|
|
|
1748
1988
|
`Array of {old, new} objects. Applied to the Loop Objective. Omit or [] if none.`,
|
|
1749
1989
|
`- wrong_assumptions (P5 — optional): Assumptions from earlier rounds that turned ` +
|
|
1750
1990
|
`out to be incorrect. Recorded as key lessons. Omit or [] if none.`,
|
|
1991
|
+
`- compression_checkpoint (v1.10 — optional): Set to true when you have COMPLETED a ` +
|
|
1992
|
+
`major subtask. The compiler will snapshot the current state (constraints, outcome) ` +
|
|
1993
|
+
`into a persistent checkpoint that survives context-window eviction. ` +
|
|
1994
|
+
`Use sparingly — only at genuine subtask boundaries, not every round. ` +
|
|
1995
|
+
`Omit or false otherwise.`,
|
|
1996
|
+
`- checkpoint_label (v1.10 — optional): Human-readable label for this checkpoint. ` +
|
|
1997
|
+
`e.g. "数据模型层完成", "API layer complete", "Auth module done". ` +
|
|
1998
|
+
`Used as the checkpoint heading in subsequent prompts. Only meaningful when ` +
|
|
1999
|
+
`compression_checkpoint is true. Omit or "" otherwise.`,
|
|
1751
2000
|
`- The JSON MUST appear between the ---loopforge-eval and ---end-loopforge-eval markers`,
|
|
1752
2001
|
`- Do NOT wrap the markers in code fences — output them as raw text`,
|
|
1753
2002
|
"",
|
|
@@ -1772,8 +2021,8 @@ export function compileLoop(request, vaultContext = null) {
|
|
|
1772
2021
|
}
|
|
1773
2022
|
// Layer 2: Compute advisories
|
|
1774
2023
|
const { warnings, suggestedNextTask, alignment, health } = computeAdvisories(request, vaultContext);
|
|
1775
|
-
// Merge advisories into response
|
|
1776
|
-
response.warnings = warnings;
|
|
2024
|
+
// Merge advisories into response (preserve P4 consistency warnings from L2)
|
|
2025
|
+
response.warnings = [...(response.warnings || []), ...warnings];
|
|
1777
2026
|
// Only overwrite suggested_next_task from advisories if the compile function
|
|
1778
2027
|
// didn't already set one (e.g. L2 sets it from emerged_subtasks)
|
|
1779
2028
|
if (!response.suggested_next_task) {
|
|
@@ -1784,6 +2033,13 @@ export function compileLoop(request, vaultContext = null) {
|
|
|
1784
2033
|
response.recompile_level = level;
|
|
1785
2034
|
// v1.1: Append self-evaluation block for autonomous loop feedback
|
|
1786
2035
|
response.prompt += buildSelfEvalBlock(request.round);
|
|
2036
|
+
// Multi-agent: Inject delegation history if available
|
|
2037
|
+
if (vaultContext) {
|
|
2038
|
+
const delegationText = buildDelegationSummary(vaultContext);
|
|
2039
|
+
if (delegationText) {
|
|
2040
|
+
response.prompt += delegationText;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
1787
2043
|
return response;
|
|
1788
2044
|
}
|
|
1789
2045
|
//# sourceMappingURL=loop-compiler.js.map
|