loopforge 1.8.0 → 1.15.1
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/dist/backends/fs.d.ts.map +1 -1
- package/dist/backends/fs.js +13 -10
- 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 +13 -10
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +117 -96
- package/dist/builder.js.map +1 -1
- package/dist/enforcement-gate.d.ts +44 -0
- package/dist/enforcement-gate.d.ts.map +1 -0
- package/dist/enforcement-gate.js +253 -0
- package/dist/enforcement-gate.js.map +1 -0
- package/dist/engine.d.ts +13 -6
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +127 -171
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +10 -2
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +882 -583
- package/dist/loop-compiler.js.map +1 -1
- package/dist/mcp/session.d.ts +12 -2
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +127 -64
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.js +4 -4
- package/dist/memory-bridge.js +1 -1
- package/dist/memory-bridge.js.map +1 -1
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +1 -1
- package/dist/policy.d.ts +80 -10
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +101 -30
- package/dist/policy.js.map +1 -1
- package/dist/protocol.d.ts +70 -16
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +40 -8
- 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 +11 -7
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +125 -144
- package/dist/runtime.js.map +1 -1
- package/dist/tests/builder.test.js +42 -28
- package/dist/tests/builder.test.js.map +1 -1
- package/dist/tests/enforcement-gate.test.d.ts +2 -0
- package/dist/tests/enforcement-gate.test.d.ts.map +1 -0
- package/dist/tests/enforcement-gate.test.js +328 -0
- package/dist/tests/enforcement-gate.test.js.map +1 -0
- package/dist/tests/engine.test.js +22 -22
- 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 +273 -47
- package/dist/tests/loop-compiler.test.js.map +1 -1
- package/dist/tests/mcp.test.js +31 -11
- package/dist/tests/mcp.test.js.map +1 -1
- package/dist/tests/memory-bridge.test.js +0 -1
- package/dist/tests/memory-bridge.test.js.map +1 -1
- package/dist/tests/policy.test.js +6 -16
- package/dist/tests/policy.test.js.map +1 -1
- package/dist/tests/protocol.test.js +3 -2
- 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 +27 -13
- 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 +8 -2
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +5 -4
- package/dist/verification-gate.js.map +1 -1
- package/loop_policy.json +13 -26
- package/package.json +1 -1
- package/skills/perception/SKILL.md +42 -23
- package/skills/prompt-techniques/SKILL.md +52 -52
- package/skills/prompt-techniques/references/chain-of-thought.md +207 -207
- package/skills/prompt-techniques/references/few-shot.md +174 -174
- package/skills/prompt-techniques/references/least-to-most.md +182 -182
- package/skills/prompt-techniques/references/step-back.md +160 -160
- package/skills/prompt-techniques/references/tree-of-thought.md +191 -191
- package/skills/prompt-techniques/references/zero-shot.md +97 -97
package/dist/loop-compiler.js
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
import { createHash } from "node:crypto";
|
|
13
13
|
import { readFileSync } from "node:fs";
|
|
14
14
|
import { getPolicy } from "./policy.js";
|
|
15
|
-
import { AgentStatus, makeLoopCompileResponse, makeLoopHealth, makeLoopObjective, makeRollingSummary, makeTaskAlignment, } from "./protocol.js";
|
|
16
|
-
import { routeTechniqueAdaptive } from "./builder.js";
|
|
15
|
+
import { AgentStatus, makeCheckpointSummary, makeLoopCompileResponse, makeLoopHealth, makeLoopObjective, makeRollingSummary, makeTaskAlignment, } from "./protocol.js";
|
|
16
|
+
import { routeTechniqueAdaptive, SKILLS_DIR } from "./builder.js";
|
|
17
17
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
18
18
|
// Repair cue detection
|
|
19
19
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -224,8 +224,7 @@ export function getPreviousRound(loopId, roundNum, vaultContext) {
|
|
|
224
224
|
return {
|
|
225
225
|
goal_id: lineage.goal_id ?? "",
|
|
226
226
|
goal_text_hash: lineage.goal_text_hash ?? "",
|
|
227
|
-
|
|
228
|
-
success: r.success ?? true,
|
|
227
|
+
success: r.success ?? lineage.success ?? true,
|
|
229
228
|
task: r.task ?? r.user_intent ?? "",
|
|
230
229
|
constraints_active: lineage.constraints_active ?? [],
|
|
231
230
|
prompt_text: r.full_prompt ?? "",
|
|
@@ -243,7 +242,7 @@ function getRecentRounds(loopId, n, vaultContext) {
|
|
|
243
242
|
const lineage = (r.loop_lineage || r.lineage || {});
|
|
244
243
|
if (lineage.loop_id === loopId) {
|
|
245
244
|
rounds.push({
|
|
246
|
-
|
|
245
|
+
success: r.success ?? lineage.success ?? false,
|
|
247
246
|
round: lineage.round ?? 0,
|
|
248
247
|
goal_text_hash: lineage.goal_text_hash ?? "",
|
|
249
248
|
});
|
|
@@ -252,6 +251,192 @@ function getRecentRounds(loopId, n, vaultContext) {
|
|
|
252
251
|
rounds.sort((a, b) => b.round - a.round);
|
|
253
252
|
return rounds.slice(0, n);
|
|
254
253
|
}
|
|
254
|
+
/** Extract the technique used in the most recent round before currentRound.
|
|
255
|
+
* Scans vault entries for this loop_id and returns the technique_used field
|
|
256
|
+
* from the highest round number below currentRound.
|
|
257
|
+
* Returns null if no prior rounds exist (round 1 scenario). */
|
|
258
|
+
function getPreviousTechnique(loopId, currentRound, vaultContext) {
|
|
259
|
+
if (vaultContext === null || currentRound <= 1)
|
|
260
|
+
return null;
|
|
261
|
+
const results = vaultContext.results || [];
|
|
262
|
+
if (!results.length)
|
|
263
|
+
return null;
|
|
264
|
+
let bestTechnique = null;
|
|
265
|
+
let bestRound = -1;
|
|
266
|
+
for (const r of results) {
|
|
267
|
+
const lineage = (r.loop_lineage || r.lineage || {});
|
|
268
|
+
if (lineage.loop_id !== loopId)
|
|
269
|
+
continue;
|
|
270
|
+
const rnd = lineage.round;
|
|
271
|
+
if (rnd >= currentRound || rnd <= bestRound)
|
|
272
|
+
continue;
|
|
273
|
+
// technique_used can be on the entry directly or in lineage
|
|
274
|
+
const technique = r.technique_used ??
|
|
275
|
+
lineage.technique_used ?? "";
|
|
276
|
+
if (technique) {
|
|
277
|
+
bestTechnique = technique;
|
|
278
|
+
bestRound = rnd;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return bestTechnique;
|
|
282
|
+
}
|
|
283
|
+
function techniqueChanged(loopId, newTechnique, currentRound, vaultContext) {
|
|
284
|
+
if (currentRound === 1)
|
|
285
|
+
return true; // Round 1: first selection is always a "change"
|
|
286
|
+
const prevTech = getPreviousTechnique(loopId, currentRound, vaultContext);
|
|
287
|
+
if (!prevTech)
|
|
288
|
+
return true; // No prior technique found → treat as change
|
|
289
|
+
return newTechnique !== prevTech;
|
|
290
|
+
}
|
|
291
|
+
/** Returns the mandatory state file read instruction that goes at the top
|
|
292
|
+
* of every compiled prompt. Tells the agent to read the loop state file
|
|
293
|
+
* before executing the round. */
|
|
294
|
+
function renderStateFilePointer(loopId) {
|
|
295
|
+
return [
|
|
296
|
+
`⚠️ **BEFORE anything else**: Read \`.loopforge/state/${loopId}-state.md\``,
|
|
297
|
+
"for accumulated loop state — objective, constraints, progress, and cross-round summary.",
|
|
298
|
+
"This file is rewritten each round and is the single source of truth for loop state.",
|
|
299
|
+
"",
|
|
300
|
+
].join("\n");
|
|
301
|
+
}
|
|
302
|
+
/** Render the full loop state file as markdown. Called by compileL2() every round.
|
|
303
|
+
* The file is a snapshot — rewritten in full each round, not appended.
|
|
304
|
+
* All sections have hard size caps from policy to prevent bloat.
|
|
305
|
+
*
|
|
306
|
+
* Sections: Loop Objective, Progress, Cross-Round Summary, Active Constraints,
|
|
307
|
+
* Checkpoints, Delegation Summary. */
|
|
308
|
+
function renderStateFile(params) {
|
|
309
|
+
const lines = [];
|
|
310
|
+
const sfPolicy = getPolicy().state_file;
|
|
311
|
+
// ── Header ──
|
|
312
|
+
lines.push(`# LoopForge State — ${params.loopId}`);
|
|
313
|
+
lines.push("");
|
|
314
|
+
lines.push(`**Round**: ${params.currentRound}/${params.maxRounds}`);
|
|
315
|
+
lines.push("");
|
|
316
|
+
// ── Loop Objective ──
|
|
317
|
+
if (params.loopObjective) {
|
|
318
|
+
const lo = params.loopObjective;
|
|
319
|
+
lines.push("## Loop Objective");
|
|
320
|
+
if (lo.version && lo.version > 1) {
|
|
321
|
+
lines.push(`> Version ${lo.version}, created at Round ${lo.created_at_round}`);
|
|
322
|
+
}
|
|
323
|
+
lines.push("");
|
|
324
|
+
lines.push(`**Objective**: ${lo.objective}`);
|
|
325
|
+
lines.push("");
|
|
326
|
+
if (lo.success_criteria.length) {
|
|
327
|
+
lines.push("### Success Criteria");
|
|
328
|
+
for (const sc of lo.success_criteria) {
|
|
329
|
+
// Check if this criterion has been met (from execution_evidence)
|
|
330
|
+
lines.push(`- ${sc}`);
|
|
331
|
+
}
|
|
332
|
+
lines.push("");
|
|
333
|
+
}
|
|
334
|
+
if (lo.hard_constraints.length) {
|
|
335
|
+
lines.push("### Hard Constraints");
|
|
336
|
+
for (const hc of lo.hard_constraints) {
|
|
337
|
+
lines.push(`- ${hc}`);
|
|
338
|
+
}
|
|
339
|
+
lines.push("");
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// ── Progress Dashboard ──
|
|
343
|
+
if (params.progressDashboard) {
|
|
344
|
+
lines.push(params.progressDashboard);
|
|
345
|
+
lines.push("");
|
|
346
|
+
}
|
|
347
|
+
// ── Cross-Round Summary ──
|
|
348
|
+
if (params.rollingSummary && params.rollingSummary.rounds_sampled > 0) {
|
|
349
|
+
const rs = params.rollingSummary;
|
|
350
|
+
const maxRounds = sfPolicy.max_summary_rounds;
|
|
351
|
+
lines.push(`## Cross-Round Summary (Last ${Math.min(rs.rounds_sampled, maxRounds)} Rounds)`);
|
|
352
|
+
lines.push("");
|
|
353
|
+
if (rs.key_outcomes.length) {
|
|
354
|
+
lines.push("**Key Outcomes**:");
|
|
355
|
+
for (const ko of rs.key_outcomes.slice(0, maxRounds)) {
|
|
356
|
+
lines.push(`- ${ko}`);
|
|
357
|
+
}
|
|
358
|
+
lines.push("");
|
|
359
|
+
}
|
|
360
|
+
if (rs.recurring_issues.length) {
|
|
361
|
+
lines.push("**Recurring Issues**:");
|
|
362
|
+
for (const ri of rs.recurring_issues) {
|
|
363
|
+
lines.push(`- ⚠️ ${ri}`);
|
|
364
|
+
}
|
|
365
|
+
lines.push("");
|
|
366
|
+
}
|
|
367
|
+
if (rs.failed_patterns && rs.failed_patterns.length) {
|
|
368
|
+
lines.push("**Failure Patterns**:");
|
|
369
|
+
for (const fp of rs.failed_patterns) {
|
|
370
|
+
lines.push(`- 🚫 ${fp}`);
|
|
371
|
+
}
|
|
372
|
+
lines.push("");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// ── Active Constraints ──
|
|
376
|
+
lines.push("## Active Constraints");
|
|
377
|
+
lines.push("");
|
|
378
|
+
if (params.constraints.length) {
|
|
379
|
+
for (const c of params.constraints) {
|
|
380
|
+
lines.push(`- ${c}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
lines.push("_(none)_");
|
|
385
|
+
}
|
|
386
|
+
lines.push("");
|
|
387
|
+
if (params.retiredConstraints.length) {
|
|
388
|
+
lines.push("## Retired Constraints");
|
|
389
|
+
lines.push("");
|
|
390
|
+
for (const c of params.retiredConstraints.slice(0, 10)) {
|
|
391
|
+
lines.push(`- ~${c}~`);
|
|
392
|
+
}
|
|
393
|
+
lines.push("");
|
|
394
|
+
}
|
|
395
|
+
// ── Checkpoints ──
|
|
396
|
+
if (params.checkpoints.length) {
|
|
397
|
+
const maxCp = sfPolicy.max_checkpoints;
|
|
398
|
+
const shown = params.checkpoints.slice(-maxCp).reverse(); // newest first
|
|
399
|
+
lines.push(`## Checkpoints (${shown.length})`);
|
|
400
|
+
lines.push("");
|
|
401
|
+
for (const cp of shown) {
|
|
402
|
+
lines.push(`### ${cp.label || `Round ${cp.declared_at_round}`}`);
|
|
403
|
+
lines.push(`> Declared at Round ${cp.declared_at_round}`);
|
|
404
|
+
lines.push("");
|
|
405
|
+
if (cp.outcome) {
|
|
406
|
+
lines.push(`**Outcome**: ${cp.outcome}`);
|
|
407
|
+
lines.push("");
|
|
408
|
+
}
|
|
409
|
+
if (cp.carried_constraints.length) {
|
|
410
|
+
lines.push("**Carried Constraints**:");
|
|
411
|
+
for (const cc of cp.carried_constraints) {
|
|
412
|
+
lines.push(`- ${cc}`);
|
|
413
|
+
}
|
|
414
|
+
lines.push("");
|
|
415
|
+
}
|
|
416
|
+
if (cp.resolved_constraints.length) {
|
|
417
|
+
lines.push("**Resolved Constraints**:");
|
|
418
|
+
for (const rc of cp.resolved_constraints) {
|
|
419
|
+
lines.push(`- ✅ ${rc}`);
|
|
420
|
+
}
|
|
421
|
+
lines.push("");
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// ── Delegation Summary ──
|
|
426
|
+
if (params.delegationSummary) {
|
|
427
|
+
lines.push(params.delegationSummary);
|
|
428
|
+
}
|
|
429
|
+
// ── External Context (current round only) ──
|
|
430
|
+
if (params.externalContext) {
|
|
431
|
+
lines.push("---");
|
|
432
|
+
lines.push("");
|
|
433
|
+
lines.push("## External Context (This Round)");
|
|
434
|
+
lines.push("");
|
|
435
|
+
lines.push(params.externalContext);
|
|
436
|
+
lines.push("");
|
|
437
|
+
}
|
|
438
|
+
return lines.join("\n");
|
|
439
|
+
}
|
|
255
440
|
function getPreviousRoundTask(loopId, roundNum, vaultContext) {
|
|
256
441
|
const prev = getPreviousRound(loopId, roundNum, vaultContext);
|
|
257
442
|
return prev?.task ?? "";
|
|
@@ -299,13 +484,6 @@ function countConsecutiveHashMismatches(loopId, vaultContext) {
|
|
|
299
484
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
300
485
|
// Strategy collapse detection
|
|
301
486
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
302
|
-
function strategyCollapse(loopId, vaultContext) {
|
|
303
|
-
const recent = getRecentRounds(loopId, 3, vaultContext);
|
|
304
|
-
if (recent.length < 3)
|
|
305
|
-
return false;
|
|
306
|
-
return recent.every((r) => r.quality_score < 3);
|
|
307
|
-
}
|
|
308
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
309
487
|
// Constraint Retirement (v3.5)
|
|
310
488
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
311
489
|
function computeConstraintRetirement(activeConstraints, loopId, currentRound, vaultContext) {
|
|
@@ -412,7 +590,7 @@ function countProgressStallRounds(loopId, currentRound, vaultContext, threshold)
|
|
|
412
590
|
}
|
|
413
591
|
/** Detect repeated failure patterns in sampled rounds.
|
|
414
592
|
* A failure pattern = 2+ consecutive rounds where:
|
|
415
|
-
* 1.
|
|
593
|
+
* 1. success === false (failed)
|
|
416
594
|
* 2. same technique was used
|
|
417
595
|
* 3. task text is similar (Jaccard > 0.4)
|
|
418
596
|
*
|
|
@@ -424,7 +602,7 @@ function detectFailurePatterns(sampled) {
|
|
|
424
602
|
let runStart = -1;
|
|
425
603
|
for (let i = 0; i < sampled.length; i++) {
|
|
426
604
|
const r = sampled[i];
|
|
427
|
-
const isFailed = r.
|
|
605
|
+
const isFailed = r.success === false;
|
|
428
606
|
if (isFailed && runStart === -1) {
|
|
429
607
|
runStart = i;
|
|
430
608
|
}
|
|
@@ -445,7 +623,7 @@ function detectFailurePatterns(sampled) {
|
|
|
445
623
|
}
|
|
446
624
|
return patterns;
|
|
447
625
|
}
|
|
448
|
-
/** Classify a run of consecutive
|
|
626
|
+
/** Classify a run of consecutive failed rounds as a failure pattern.
|
|
449
627
|
* Returns null if the rounds are too dissimilar to be the same pattern. */
|
|
450
628
|
function classifyRun(run) {
|
|
451
629
|
if (run.length < 2)
|
|
@@ -465,9 +643,9 @@ function classifyRun(run) {
|
|
|
465
643
|
}
|
|
466
644
|
const rounds = `R${run[0].round}-R${run[run.length - 1].round}`;
|
|
467
645
|
const taskPreview = run[0].task.slice(0, 80);
|
|
468
|
-
const
|
|
646
|
+
const failureRate = `${run.length}/${run.length}`;
|
|
469
647
|
return (`technique '${technique}' on task '${taskPreview}' ` +
|
|
470
|
-
`failed ${run.length} consecutive rounds (${rounds}
|
|
648
|
+
`failed ${run.length} consecutive rounds (${rounds}) — ` +
|
|
471
649
|
`consider strategy change`);
|
|
472
650
|
}
|
|
473
651
|
/** Extract round numbers referenced in failure pattern descriptions.
|
|
@@ -494,7 +672,7 @@ function extractFailureRoundNums(patterns) {
|
|
|
494
672
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
495
673
|
// Rolling Summary (v3.5)
|
|
496
674
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
497
|
-
export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
675
|
+
export function buildRollingSummary(loopId, currentRound, vaultContext, sinceRound) {
|
|
498
676
|
if (vaultContext === null)
|
|
499
677
|
return null;
|
|
500
678
|
const results = vaultContext.results || [];
|
|
@@ -502,6 +680,7 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
502
680
|
return null;
|
|
503
681
|
const policy = getPolicy();
|
|
504
682
|
const window = policy.summary.window;
|
|
683
|
+
const minRound = sinceRound ?? 0;
|
|
505
684
|
// Collect rounds matching this loop_id, excluding current
|
|
506
685
|
const rounds = [];
|
|
507
686
|
for (const r of results) {
|
|
@@ -511,9 +690,11 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
511
690
|
const rnd = lineage.round;
|
|
512
691
|
if (rnd >= currentRound)
|
|
513
692
|
continue;
|
|
693
|
+
if (rnd < minRound)
|
|
694
|
+
continue;
|
|
514
695
|
rounds.push({
|
|
515
696
|
round: rnd,
|
|
516
|
-
|
|
697
|
+
success: r.success ?? lineage.success ?? false,
|
|
517
698
|
task: r.task ?? r.user_intent ?? "",
|
|
518
699
|
output_summary: r.output_summary ?? "",
|
|
519
700
|
constraint_violations: r.constraint_violations || [],
|
|
@@ -525,32 +706,13 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
525
706
|
// Sort by round descending, take last N
|
|
526
707
|
rounds.sort((a, b) => b.round - a.round);
|
|
527
708
|
const sampled = rounds.slice(0, window).reverse(); // chronological order
|
|
528
|
-
//
|
|
529
|
-
const
|
|
530
|
-
// Trajectory direction
|
|
531
|
-
let direction = "stable";
|
|
532
|
-
if (trajectory.length >= 2) {
|
|
533
|
-
const diffs = trajectory.slice(1).map((v, i) => v - trajectory[i]);
|
|
534
|
-
if (diffs.every((d) => d >= 0) && diffs.some((d) => d > 0)) {
|
|
535
|
-
direction = "improving";
|
|
536
|
-
}
|
|
537
|
-
else if (diffs.every((d) => d <= 0) && diffs.some((d) => d < 0)) {
|
|
538
|
-
direction = "declining";
|
|
539
|
-
}
|
|
540
|
-
else if (diffs.every((d) => d === 0)) {
|
|
541
|
-
direction = "stable";
|
|
542
|
-
}
|
|
543
|
-
else {
|
|
544
|
-
direction = "volatile";
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
// What worked
|
|
548
|
-
const whatWorked = [];
|
|
709
|
+
// v1.12: Key outcomes — all rounds with output_summary
|
|
710
|
+
const keyOutcomes = [];
|
|
549
711
|
for (const r of sampled) {
|
|
550
|
-
if (r.
|
|
551
|
-
r.
|
|
552
|
-
|
|
553
|
-
`${
|
|
712
|
+
if (r.output_summary) {
|
|
713
|
+
const status = r.success ? "✓" : "✗";
|
|
714
|
+
keyOutcomes.push(`[R${r.round}] ${status} (${r.technique_used || "n/a"}): ` +
|
|
715
|
+
`${String(r.output_summary).slice(0, 200)}`);
|
|
554
716
|
}
|
|
555
717
|
}
|
|
556
718
|
// Recurring issues
|
|
@@ -569,17 +731,10 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
569
731
|
.filter(([, count]) => count >= 2)
|
|
570
732
|
.sort((a, b) => b[1] - a[1])
|
|
571
733
|
.map(([v, count]) => `${v} (appeared in ${count} rounds)`);
|
|
572
|
-
// Key lessons
|
|
573
|
-
const keyLessons = [];
|
|
574
|
-
for (const r of sampled) {
|
|
575
|
-
if (r.quality_score >= 4 && r.output_summary) {
|
|
576
|
-
keyLessons.push(`[R${r.round}] ${String(r.output_summary).slice(0, 200)}`);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
734
|
// v1.7: Failure lineage weighting — detect and demote failure patterns
|
|
580
735
|
const sampledRounds = sampled.map((r) => ({
|
|
581
736
|
round: r.round,
|
|
582
|
-
|
|
737
|
+
success: r.success,
|
|
583
738
|
task: r.task,
|
|
584
739
|
output_summary: r.output_summary,
|
|
585
740
|
constraint_violations: r.constraint_violations,
|
|
@@ -587,21 +742,21 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
587
742
|
}));
|
|
588
743
|
const failurePatterns = detectFailurePatterns(sampledRounds);
|
|
589
744
|
const failedRoundNums = extractFailureRoundNums(failurePatterns);
|
|
590
|
-
// Re-weight
|
|
591
|
-
if (failedRoundNums.size > 0 &&
|
|
745
|
+
// Re-weight key_outcomes: outcomes from failure-pattern rounds go to end
|
|
746
|
+
if (failedRoundNums.size > 0 && keyOutcomes.length > 0) {
|
|
592
747
|
const promoted = [];
|
|
593
748
|
const demoted = [];
|
|
594
|
-
for (const
|
|
595
|
-
const rndMatch =
|
|
749
|
+
for (const ko of keyOutcomes) {
|
|
750
|
+
const rndMatch = ko.match(/^\[R(\d+)\]/);
|
|
596
751
|
if (rndMatch && failedRoundNums.has(parseInt(rndMatch[1], 10))) {
|
|
597
|
-
demoted.push(
|
|
752
|
+
demoted.push(ko.replace(/^\[R\d+\]/, "$& [Consider alternatives]"));
|
|
598
753
|
}
|
|
599
754
|
else {
|
|
600
|
-
promoted.push(
|
|
755
|
+
promoted.push(ko);
|
|
601
756
|
}
|
|
602
757
|
}
|
|
603
|
-
|
|
604
|
-
|
|
758
|
+
keyOutcomes.length = 0;
|
|
759
|
+
keyOutcomes.push(...promoted, ...demoted);
|
|
605
760
|
}
|
|
606
761
|
// Re-weight recurring_issues: mark issues that ONLY appear in failure-pattern rounds
|
|
607
762
|
if (failedRoundNums.size > 0 && recurringIssues.length > 0) {
|
|
@@ -628,11 +783,8 @@ export function buildRollingSummary(loopId, currentRound, vaultContext) {
|
|
|
628
783
|
}));
|
|
629
784
|
}
|
|
630
785
|
return makeRollingSummary({
|
|
631
|
-
|
|
632
|
-
trajectory_direction: direction,
|
|
633
|
-
what_worked: whatWorked,
|
|
786
|
+
key_outcomes: keyOutcomes,
|
|
634
787
|
recurring_issues: recurringIssues,
|
|
635
|
-
key_lessons: keyLessons,
|
|
636
788
|
rounds_sampled: sampled.length,
|
|
637
789
|
generated_at_round: currentRound,
|
|
638
790
|
failed_patterns: failurePatterns.length ? failurePatterns : [],
|
|
@@ -644,14 +796,13 @@ export function formatRollingSummaryForPrompt(rs) {
|
|
|
644
796
|
const lines = [
|
|
645
797
|
"### Cross-Round Summary (Accumulated)",
|
|
646
798
|
"",
|
|
647
|
-
`**Sampled**: ${rs.rounds_sampled} prior rounds
|
|
648
|
-
`**Quality Trajectory**: [${rs.quality_trajectory.join(", ")}]`,
|
|
799
|
+
`**Sampled**: ${rs.rounds_sampled} prior rounds`,
|
|
649
800
|
"",
|
|
650
801
|
];
|
|
651
|
-
if (rs.
|
|
652
|
-
lines.push("**
|
|
653
|
-
for (const
|
|
654
|
-
lines.push(`- ${
|
|
802
|
+
if (rs.key_outcomes.length) {
|
|
803
|
+
lines.push("**Key Outcomes**:");
|
|
804
|
+
for (const ko of rs.key_outcomes)
|
|
805
|
+
lines.push(`- ${ko}`);
|
|
655
806
|
lines.push("");
|
|
656
807
|
}
|
|
657
808
|
if (rs.recurring_issues.length) {
|
|
@@ -665,21 +816,217 @@ export function formatRollingSummaryForPrompt(rs) {
|
|
|
665
816
|
lines.push("### ⚠️ Failure Patterns — Consider Strategy Change");
|
|
666
817
|
lines.push("");
|
|
667
818
|
lines.push("The following approaches failed repeatedly. The compiler has demoted " +
|
|
668
|
-
"their influence on key
|
|
819
|
+
"their influence on key outcomes. Consider a different technique or " +
|
|
669
820
|
"narrower scope.");
|
|
670
821
|
for (const fp of rs.failed_patterns) {
|
|
671
822
|
lines.push(`- 🚫 ${fp}`);
|
|
672
823
|
}
|
|
673
824
|
lines.push("");
|
|
674
825
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
826
|
+
return lines.join("\n");
|
|
827
|
+
}
|
|
828
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
829
|
+
// Checkpoint compression (v1.10 — subtask boundary snapshots)
|
|
830
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
831
|
+
/** Build a CheckpointSummary from the current round's self-evaluation state.
|
|
832
|
+
* Called when the Agent sets compression_checkpoint: true in its self-eval.
|
|
833
|
+
* Snapshots the current constraints and outcome so they survive rolling-window
|
|
834
|
+
* eviction in subsequent rounds. */
|
|
835
|
+
export function buildCheckpointSummary(selfEval, round, activeConstraints, retiredConstraints) {
|
|
836
|
+
const policy = getPolicy();
|
|
837
|
+
const cpCfg = policy.checkpoint;
|
|
838
|
+
const maxCarried = cpCfg?.max_carried_constraints ?? 10;
|
|
839
|
+
const outcomeMaxChars = cpCfg?.outcome_max_chars ?? 200;
|
|
840
|
+
const label = selfEval.checkpoint_label?.trim() ||
|
|
841
|
+
`Subtask completed at round ${round}`;
|
|
842
|
+
const outcome = (selfEval.output_summary || "").trim().slice(0, outcomeMaxChars);
|
|
843
|
+
// Carried: constraints still active, capped
|
|
844
|
+
const carried = activeConstraints.slice(0, maxCarried);
|
|
845
|
+
// Resolved: constraints retired this round (retracted + the retire delta)
|
|
846
|
+
const resolved = retiredConstraints.slice(0, maxCarried);
|
|
847
|
+
return {
|
|
848
|
+
label,
|
|
849
|
+
declared_at_round: round,
|
|
850
|
+
outcome,
|
|
851
|
+
carried_constraints: carried,
|
|
852
|
+
resolved_constraints: resolved,
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
/** Format a CheckpointSummary as a prompt block.
|
|
856
|
+
* Renders as a fixed section that persists across rolling-window eviction.
|
|
857
|
+
* Returns empty string if the summary is null or has no meaningful data. */
|
|
858
|
+
export function formatCheckpointForPrompt(cs) {
|
|
859
|
+
if (!cs)
|
|
860
|
+
return "";
|
|
861
|
+
// Must have at least a label and some content to be meaningful
|
|
862
|
+
if (!cs.label && !cs.outcome && !cs.carried_constraints.length)
|
|
863
|
+
return "";
|
|
864
|
+
const lines = [
|
|
865
|
+
`### Checkpoint: ${cs.label} (Round ${cs.declared_at_round})`,
|
|
866
|
+
"",
|
|
867
|
+
];
|
|
868
|
+
if (cs.outcome) {
|
|
869
|
+
lines.push(`**Outcome**: ${cs.outcome}`);
|
|
870
|
+
lines.push("");
|
|
871
|
+
}
|
|
872
|
+
if (cs.carried_constraints.length) {
|
|
873
|
+
lines.push("**Carried Constraints** (still active):");
|
|
874
|
+
for (const c of cs.carried_constraints) {
|
|
875
|
+
lines.push(`- ${c}`);
|
|
876
|
+
}
|
|
877
|
+
lines.push("");
|
|
878
|
+
}
|
|
879
|
+
if (cs.resolved_constraints.length) {
|
|
880
|
+
lines.push("**Resolved Constraints** (closed with this checkpoint):");
|
|
881
|
+
for (const c of cs.resolved_constraints) {
|
|
882
|
+
lines.push(`- ${c}`);
|
|
883
|
+
}
|
|
679
884
|
lines.push("");
|
|
680
885
|
}
|
|
681
886
|
return lines.join("\n");
|
|
682
887
|
}
|
|
888
|
+
/** Query the vault for the most recent checkpoint entry before the current
|
|
889
|
+
* round. Returns null if no checkpoint has been declared yet. */
|
|
890
|
+
function loadLatestCheckpoint(loopId, currentRound, vaultContext) {
|
|
891
|
+
if (!vaultContext)
|
|
892
|
+
return null;
|
|
893
|
+
const results = vaultContext.results || [];
|
|
894
|
+
let latest = null;
|
|
895
|
+
let latestRound = -1;
|
|
896
|
+
for (const r of results) {
|
|
897
|
+
const lineage = (r.loop_lineage || r.lineage || {});
|
|
898
|
+
if (lineage.loop_id !== loopId)
|
|
899
|
+
continue;
|
|
900
|
+
const rnd = lineage.round;
|
|
901
|
+
if (rnd >= currentRound)
|
|
902
|
+
continue;
|
|
903
|
+
// Check for stored checkpoint summary on this entry
|
|
904
|
+
const cp = r.checkpoint_summary;
|
|
905
|
+
if (cp && typeof cp.declared_at_round === "number" && cp.declared_at_round > latestRound) {
|
|
906
|
+
latest = cp;
|
|
907
|
+
latestRound = cp.declared_at_round;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (!latest)
|
|
911
|
+
return null;
|
|
912
|
+
return makeCheckpointSummary({
|
|
913
|
+
label: latest.label ?? "",
|
|
914
|
+
declared_at_round: latest.declared_at_round ?? 0,
|
|
915
|
+
outcome: latest.outcome ?? "",
|
|
916
|
+
carried_constraints: latest.carried_constraints ?? [],
|
|
917
|
+
resolved_constraints: latest.resolved_constraints ?? [],
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
921
|
+
// Shared state evolution (P0/P1/P5) — used by both compileL1 and compileL2
|
|
922
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
923
|
+
/** Apply P1 objective refinement from last round's self-evaluation.
|
|
924
|
+
* Appends the refinement to the objective with a round marker.
|
|
925
|
+
* Returns the updated loop objective (or unchanged if no refinement). */
|
|
926
|
+
function applyObjectiveRefinement(loopObjective, lastRoundResult, currentRound) {
|
|
927
|
+
if (!loopObjective || !lastRoundResult?.objective_refinement)
|
|
928
|
+
return loopObjective;
|
|
929
|
+
const refinement = lastRoundResult.objective_refinement.trim();
|
|
930
|
+
if (!refinement)
|
|
931
|
+
return loopObjective;
|
|
932
|
+
const version = (loopObjective.version ?? 1) + 1;
|
|
933
|
+
const history = [...(loopObjective.refinement_history ?? []), refinement];
|
|
934
|
+
const policy = getPolicy();
|
|
935
|
+
const maxVersions = policy.evolution?.max_objective_versions ?? 10;
|
|
936
|
+
const prunedHistory = history.slice(-maxVersions);
|
|
937
|
+
const refinedObjective = `${loopObjective.objective} [R${currentRound}: ${refinement}]`;
|
|
938
|
+
return makeLoopObjective({
|
|
939
|
+
...loopObjective,
|
|
940
|
+
objective: refinedObjective,
|
|
941
|
+
version,
|
|
942
|
+
refinement_history: prunedHistory,
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
/** Apply P0 discovered constraints and P5 retractions/revisions.
|
|
946
|
+
* Pure function — returns updated constraints, loop objective, and retired list. */
|
|
947
|
+
function applyConstraintEvolution(constraints, loopObjective, lastRoundResult, currentRound) {
|
|
948
|
+
const retired = [];
|
|
949
|
+
let lo = loopObjective;
|
|
950
|
+
let cs = [...constraints];
|
|
951
|
+
// P0: Merge discovered constraints
|
|
952
|
+
if (lastRoundResult?.discovered_constraints?.length) {
|
|
953
|
+
const policy = getPolicy();
|
|
954
|
+
const maxPerRound = policy.evolution?.max_discovered_constraints_per_round ?? 5;
|
|
955
|
+
const maxActive = policy.evolution?.max_active_constraints ?? 15;
|
|
956
|
+
const newConstraints = lastRoundResult.discovered_constraints
|
|
957
|
+
.slice(0, maxPerRound)
|
|
958
|
+
.filter((c) => !cs.includes(c));
|
|
959
|
+
cs = [...cs, ...newConstraints].slice(0, maxActive);
|
|
960
|
+
}
|
|
961
|
+
// P5a: Retracted constraints
|
|
962
|
+
if (lastRoundResult?.retracted_constraints?.length) {
|
|
963
|
+
for (const rc of lastRoundResult.retracted_constraints) {
|
|
964
|
+
const idx = cs.indexOf(rc);
|
|
965
|
+
if (idx >= 0) {
|
|
966
|
+
cs.splice(idx, 1);
|
|
967
|
+
retired.push(`[agent-retracted] ${rc}`);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
// P5b: Revised success criteria
|
|
972
|
+
if (lastRoundResult?.revised_success_criteria?.length && lo) {
|
|
973
|
+
const sc = [...lo.success_criteria];
|
|
974
|
+
for (const rev of lastRoundResult.revised_success_criteria) {
|
|
975
|
+
const idx = sc.indexOf(rev.old);
|
|
976
|
+
if (idx >= 0)
|
|
977
|
+
sc[idx] = rev.new;
|
|
978
|
+
else
|
|
979
|
+
sc.push(rev.new);
|
|
980
|
+
}
|
|
981
|
+
const version = (lo.version ?? 1) + 1;
|
|
982
|
+
const history = [
|
|
983
|
+
...(lo.refinement_history ?? []),
|
|
984
|
+
`R${currentRound}: revised ${lastRoundResult.revised_success_criteria.length} success criteria`,
|
|
985
|
+
];
|
|
986
|
+
const updatedLo = makeLoopObjective({
|
|
987
|
+
...lo,
|
|
988
|
+
success_criteria: sc,
|
|
989
|
+
version,
|
|
990
|
+
refinement_history: history,
|
|
991
|
+
});
|
|
992
|
+
// Rebuild constraints to include revised criteria, deduplicate
|
|
993
|
+
cs = [...new Set([
|
|
994
|
+
...cs.filter((c) => !updatedLo.hard_constraints.includes(c) && !updatedLo.success_criteria.includes(c)),
|
|
995
|
+
...updatedLo.hard_constraints,
|
|
996
|
+
...updatedLo.success_criteria,
|
|
997
|
+
])];
|
|
998
|
+
lo = updatedLo;
|
|
999
|
+
}
|
|
1000
|
+
return { constraints: cs, loopObjective: lo, retired };
|
|
1001
|
+
}
|
|
1002
|
+
/** Collect checkpoint summaries from vault entries for a given loop.
|
|
1003
|
+
* Shared between compileL1 and compileL2 — same vault scanning logic. */
|
|
1004
|
+
function collectVaultCheckpoints(vaultContext, loopId, excludeRounds) {
|
|
1005
|
+
const checkpoints = [];
|
|
1006
|
+
if (!vaultContext)
|
|
1007
|
+
return checkpoints;
|
|
1008
|
+
const results = vaultContext.results || [];
|
|
1009
|
+
for (const r of results) {
|
|
1010
|
+
const lineage = (r.loop_lineage || r.lineage || {});
|
|
1011
|
+
if (lineage.loop_id !== loopId)
|
|
1012
|
+
continue;
|
|
1013
|
+
const cp = r.checkpoint_summary;
|
|
1014
|
+
if (cp && typeof cp.declared_at_round === "number") {
|
|
1015
|
+
if (excludeRounds?.has(cp.declared_at_round))
|
|
1016
|
+
continue;
|
|
1017
|
+
if (checkpoints.some((c) => c.declared_at_round === cp.declared_at_round))
|
|
1018
|
+
continue;
|
|
1019
|
+
checkpoints.push(makeCheckpointSummary({
|
|
1020
|
+
label: cp.label ?? "",
|
|
1021
|
+
declared_at_round: cp.declared_at_round,
|
|
1022
|
+
outcome: cp.outcome ?? "",
|
|
1023
|
+
carried_constraints: cp.carried_constraints ?? [],
|
|
1024
|
+
resolved_constraints: cp.resolved_constraints ?? [],
|
|
1025
|
+
}));
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return checkpoints;
|
|
1029
|
+
}
|
|
683
1030
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
684
1031
|
// External Context formatting (v1.7 — memory system integration)
|
|
685
1032
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -721,43 +1068,35 @@ export function decideLevel(request, vaultContext) {
|
|
|
721
1068
|
return request.force_level;
|
|
722
1069
|
}
|
|
723
1070
|
}
|
|
724
|
-
//
|
|
725
|
-
|
|
1071
|
+
// ── L2 triggers: structural restarts ──────────────────────────────────
|
|
1072
|
+
// Gate 2: First call or explicit plan → full rebuild
|
|
1073
|
+
if (request.round === 1 || request.plan_source)
|
|
726
1074
|
return "l2";
|
|
727
|
-
|
|
728
|
-
|
|
1075
|
+
// Gate 3: Checkpoint boundary — subtask completed, re-plan
|
|
1076
|
+
if (request.last_round_result?.compression_checkpoint === true)
|
|
1077
|
+
return "l2";
|
|
1078
|
+
// Gate 4: goal_id stability
|
|
729
1079
|
const goalId = deriveGoalId(request.loop_id, request.task, request.goal_id);
|
|
730
1080
|
const prev = getPreviousRound(request.loop_id, request.round - 1, vaultContext);
|
|
731
1081
|
if (prev === null)
|
|
732
1082
|
return "l2";
|
|
733
|
-
// Gate 3: goal_id stability
|
|
734
1083
|
if (goalId !== prev.goal_id)
|
|
735
1084
|
return "l2";
|
|
736
|
-
//
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
}
|
|
752
|
-
// Gate 5: Explicit failures or new constraints → patch
|
|
753
|
-
const hasNewConstraints = request.constraints_from_plan.length > 0;
|
|
754
|
-
const hasNewFailures = request.last_round_result !== null &&
|
|
755
|
-
!request.last_round_result.success;
|
|
756
|
-
const hasRepair = detectsRepairSignal(request);
|
|
757
|
-
if (hasNewConstraints || hasNewFailures || hasRepair)
|
|
758
|
-
return "l1";
|
|
759
|
-
// Nothing triggered → fast path
|
|
760
|
-
return "l0";
|
|
1085
|
+
// ── L0 trigger: honest failure with no new information ────────────────
|
|
1086
|
+
const lr = request.last_round_result;
|
|
1087
|
+
const prevFailed = lr !== null && !lr.success;
|
|
1088
|
+
const hasNoNewInfo = (lr?.discovered_constraints?.length ?? 0) === 0 &&
|
|
1089
|
+
(lr?.objective_refinement?.trim().length ?? 0) === 0 &&
|
|
1090
|
+
(lr?.emerged_subtasks?.length ?? 0) === 0 &&
|
|
1091
|
+
(lr?.retracted_constraints?.length ?? 0) === 0 &&
|
|
1092
|
+
(lr?.revised_success_criteria?.length ?? 0) === 0 &&
|
|
1093
|
+
(lr?.wrong_assumptions?.length ?? 0) === 0 &&
|
|
1094
|
+
request.constraints_from_plan.length === 0 &&
|
|
1095
|
+
!detectsRepairSignal(request);
|
|
1096
|
+
if (prevFailed && hasNoNewInfo)
|
|
1097
|
+
return "l0";
|
|
1098
|
+
// ── L1: default path — all P0-P5 state changes handled here ───────────
|
|
1099
|
+
return "l1";
|
|
761
1100
|
}
|
|
762
1101
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
763
1102
|
// LAYER 2 — Soft Advisories (NEVER change compile level)
|
|
@@ -853,7 +1192,7 @@ export function checkLoopHealth(loopId, request, vaultContext) {
|
|
|
853
1192
|
// 4. strategy_stability
|
|
854
1193
|
const recent = getRecentRounds(loopId, 3, vaultContext);
|
|
855
1194
|
const strategyStability = recent.length > 0
|
|
856
|
-
? recent.every((r) => r.
|
|
1195
|
+
? recent.every((r) => r.success === true)
|
|
857
1196
|
: true;
|
|
858
1197
|
// 5. task_continuity
|
|
859
1198
|
let taskContinuity = 1.0;
|
|
@@ -890,15 +1229,22 @@ function computeSuggestedNextTask(loopId, vaultContext) {
|
|
|
890
1229
|
if (vaultContext === null)
|
|
891
1230
|
return "";
|
|
892
1231
|
const results = vaultContext.results || [];
|
|
1232
|
+
let bestTask = "";
|
|
1233
|
+
let bestRound = -1;
|
|
893
1234
|
for (const r of results) {
|
|
894
1235
|
const lineage = (r.loop_lineage || r.lineage || {});
|
|
895
|
-
if (lineage.loop_id
|
|
1236
|
+
if (lineage.loop_id !== loopId)
|
|
1237
|
+
continue;
|
|
1238
|
+
const rnd = lineage.round ?? 0;
|
|
1239
|
+
if (rnd > bestRound) {
|
|
896
1240
|
const task = r.task ?? r.user_intent ?? "";
|
|
897
|
-
if (task)
|
|
898
|
-
|
|
1241
|
+
if (task) {
|
|
1242
|
+
bestTask = task;
|
|
1243
|
+
bestRound = rnd;
|
|
1244
|
+
}
|
|
899
1245
|
}
|
|
900
1246
|
}
|
|
901
|
-
return "";
|
|
1247
|
+
return bestTask ? `[R${bestRound}] Previous task: ${bestTask.slice(0, 120)}` : "";
|
|
902
1248
|
}
|
|
903
1249
|
export function computeAdvisories(request, vaultContext) {
|
|
904
1250
|
const warnings = [];
|
|
@@ -912,11 +1258,6 @@ export function computeAdvisories(request, vaultContext) {
|
|
|
912
1258
|
warnings.push(`goal_text_hash changed (${prev.goal_text_hash} → ${currentHash}) ` +
|
|
913
1259
|
"but goal_id matched — wording drift detected");
|
|
914
1260
|
}
|
|
915
|
-
// Strategy collapse check
|
|
916
|
-
if (strategyCollapse(request.loop_id, vaultContext)) {
|
|
917
|
-
warnings.push("strategy_collapse: 3 consecutive low-quality rounds — " +
|
|
918
|
-
"consider force_level=L2 rebuild");
|
|
919
|
-
}
|
|
920
1261
|
// Repair cue detection
|
|
921
1262
|
if (detectsRepairSignal(request)) {
|
|
922
1263
|
warnings.push("repair signal detected — L1 patch applied");
|
|
@@ -1105,6 +1446,96 @@ function computeLoopObjectiveFromTask(request, _vaultContext) {
|
|
|
1105
1446
|
});
|
|
1106
1447
|
}
|
|
1107
1448
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1449
|
+
// P4: Shared consistency checks and progress dashboard (used by L1 & L2)
|
|
1450
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1451
|
+
/** Run P4 consistency validation checks on agent execution evidence.
|
|
1452
|
+
* Returns warning strings. Called by both compileL1 and compileL2.
|
|
1453
|
+
* Three checks:
|
|
1454
|
+
* 1. Agent claims file changes but files_changed is empty
|
|
1455
|
+
* 2. Agent reports success but test results show failures
|
|
1456
|
+
* 3. Progress estimate mismatch (objective vs subjective) */
|
|
1457
|
+
function runP4ConsistencyChecks(evidence, outputSummary, success, loopObjective) {
|
|
1458
|
+
const warnings = [];
|
|
1459
|
+
// Check 1: Agent claims action but reports no files changed
|
|
1460
|
+
if (evidence.files_changed.length === 0 && outputSummary) {
|
|
1461
|
+
const actionWords = /\b(fix|modif|chang|updat|refactor|implement|add|remov|delet|rewrit|修补|修改|更改|更新|重构|实现|添加|删除|重写)\b/i;
|
|
1462
|
+
if (actionWords.test(outputSummary)) {
|
|
1463
|
+
warnings.push("P4: Agent output claims changes but execution_evidence.files_changed is empty — possible oversight");
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
// Check 2: Agent reports success but tests failed
|
|
1467
|
+
if (evidence.test_results && evidence.test_results.failed > 0 && success) {
|
|
1468
|
+
warnings.push(`P4: Agent reports success=true but ${evidence.test_results.failed} tests failed — claims may not match reality`);
|
|
1469
|
+
}
|
|
1470
|
+
// Check 3: Progress estimate mismatch with criteria tracking
|
|
1471
|
+
if (loopObjective && loopObjective.success_criteria.length > 0) {
|
|
1472
|
+
const metCount = evidence.success_criteria_met.length;
|
|
1473
|
+
const totalCount = loopObjective.success_criteria.length;
|
|
1474
|
+
const objectiveProgress = totalCount > 0 ? metCount / totalCount : 0;
|
|
1475
|
+
const subjectiveProgress = evidence.progress_estimate;
|
|
1476
|
+
const policy = getPolicy();
|
|
1477
|
+
const mismatchThreshold = policy.evolution?.progress_mismatch_threshold ?? 0.3;
|
|
1478
|
+
if (Math.abs(objectiveProgress - subjectiveProgress) > mismatchThreshold) {
|
|
1479
|
+
warnings.push(`P4: Progress estimate mismatch — agent estimates ${(subjectiveProgress * 100).toFixed(0)}% but ${metCount}/${totalCount} criteria met (${(objectiveProgress * 100).toFixed(0)}%)`);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
return warnings;
|
|
1483
|
+
}
|
|
1484
|
+
/** Build a progress dashboard string for prompt or state file injection.
|
|
1485
|
+
* Used by both compileL1 and compileL2. When showTrend is true, also
|
|
1486
|
+
* computes a trend arrow from vault history (L2 path). */
|
|
1487
|
+
function buildProgressDashboard(evidence, loopObjective, vaultContext, loopId, currentRound, showTrend) {
|
|
1488
|
+
const totalCriteria = loopObjective?.success_criteria.length ?? 0;
|
|
1489
|
+
const metCriteria = evidence.success_criteria_met.length;
|
|
1490
|
+
const remainingCriteria = evidence.success_criteria_remaining;
|
|
1491
|
+
const objProgress = totalCriteria > 0 ? metCriteria / totalCriteria : evidence.progress_estimate;
|
|
1492
|
+
const subjProgress = evidence.progress_estimate;
|
|
1493
|
+
const lines = [
|
|
1494
|
+
"",
|
|
1495
|
+
"### Progress Dashboard",
|
|
1496
|
+
"",
|
|
1497
|
+
`**Overall**: ${metCriteria}/${totalCriteria} criteria met (${(objProgress * 100).toFixed(0)}%)`,
|
|
1498
|
+
];
|
|
1499
|
+
if (Math.abs(objProgress - subjProgress) > 0.05) {
|
|
1500
|
+
lines.push(`**Agent estimate**: ${(subjProgress * 100).toFixed(0)}%`);
|
|
1501
|
+
}
|
|
1502
|
+
if (evidence.files_changed.length) {
|
|
1503
|
+
lines.push(`**Files changed**: ${evidence.files_changed.join(", ")}`);
|
|
1504
|
+
}
|
|
1505
|
+
if (evidence.test_results) {
|
|
1506
|
+
const tr = evidence.test_results;
|
|
1507
|
+
lines.push(`**Tests**: ${tr.passed} passed, ${tr.failed} failed, ${tr.skipped} skipped`);
|
|
1508
|
+
}
|
|
1509
|
+
if (remainingCriteria.length) {
|
|
1510
|
+
lines.push("**Remaining criteria**:");
|
|
1511
|
+
for (const rc of remainingCriteria.slice(0, 5)) {
|
|
1512
|
+
lines.push(`- ${rc}`);
|
|
1513
|
+
}
|
|
1514
|
+
if (remainingCriteria.length > 5) {
|
|
1515
|
+
lines.push(`- ... and ${remainingCriteria.length - 5} more`);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
lines.push("");
|
|
1519
|
+
// Trend arrow — only computed in L2 path where full vault history is available
|
|
1520
|
+
if (showTrend && vaultContext && loopObjective) {
|
|
1521
|
+
const prevEvidence = getPreviousRoundEvidence(loopId, currentRound - 1, vaultContext);
|
|
1522
|
+
if (prevEvidence) {
|
|
1523
|
+
const trend = objProgress - (prevEvidence.success_criteria_met.length / Math.max(totalCriteria, 1));
|
|
1524
|
+
if (trend > 0.03) {
|
|
1525
|
+
lines.push(`**Trend**: ↑ advancing (+${(trend * 100).toFixed(0)}% this round)`);
|
|
1526
|
+
}
|
|
1527
|
+
else if (trend > -0.03) {
|
|
1528
|
+
lines.push("**Trend**: → stable");
|
|
1529
|
+
}
|
|
1530
|
+
else {
|
|
1531
|
+
lines.push(`**Trend**: ↓ regressing (${(trend * 100).toFixed(0)}% this round)`);
|
|
1532
|
+
}
|
|
1533
|
+
lines.push("");
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
return lines.join("\n");
|
|
1537
|
+
}
|
|
1538
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1108
1539
|
// Compilation — L0 / L1 / L2
|
|
1109
1540
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1110
1541
|
function compileL0(request, vaultContext, prevRound) {
|
|
@@ -1137,108 +1568,196 @@ function compileL0(request, vaultContext, prevRound) {
|
|
|
1137
1568
|
return l2Response;
|
|
1138
1569
|
}
|
|
1139
1570
|
function compileL1(request, vaultContext, prevRound) {
|
|
1140
|
-
const prev = prevRound ??
|
|
1141
|
-
getPreviousRound(request.loop_id, request.round - 1, vaultContext);
|
|
1142
1571
|
const goalId = deriveGoalId(request.loop_id, request.task, request.goal_id);
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1145
|
-
//
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1572
|
+
const lr = request.last_round_result;
|
|
1573
|
+
// ── v1.14: Technique routing (Tier 1 only — 4 techniques) ────────────
|
|
1574
|
+
// L1 is the default path: technique evolves within Tier 1 unless
|
|
1575
|
+
// decideLevel already escalated to L2 (strategy collapse / checkpoint).
|
|
1576
|
+
const analysis = routeTechniqueAdaptive(request.task, vaultContext, request.loop_id,
|
|
1577
|
+
/* isCheckpoint */ false);
|
|
1578
|
+
const technique = analysis.technique;
|
|
1579
|
+
const referenceFile = analysis.reference_file;
|
|
1580
|
+
// Determine if technique changed from last round
|
|
1581
|
+
const techChanged = techniqueChanged(request.loop_id, technique, request.round, vaultContext);
|
|
1582
|
+
// ── Loop Objective: load from vault, apply P1 refinement ─────────────
|
|
1583
|
+
let loopObjective = null;
|
|
1584
|
+
const vaultLo = vaultGetLoopObjective(request.loop_id, vaultContext);
|
|
1585
|
+
if (vaultLo) {
|
|
1586
|
+
const loData = vaultLo.loop_objective;
|
|
1587
|
+
if (loData) {
|
|
1588
|
+
loopObjective = makeLoopObjective({
|
|
1589
|
+
objective: loData.objective ?? "",
|
|
1590
|
+
success_criteria: loData.success_criteria ?? [],
|
|
1591
|
+
hard_constraints: loData.hard_constraints ?? [],
|
|
1592
|
+
created_at_round: loData.created_at_round ?? 1,
|
|
1593
|
+
loop_id: loData.loop_id ?? request.loop_id,
|
|
1594
|
+
version: loData.version ?? 1,
|
|
1595
|
+
refinement_history: loData.refinement_history ?? [],
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
if (!loopObjective && request.loop_objective) {
|
|
1600
|
+
loopObjective = request.loop_objective;
|
|
1601
|
+
}
|
|
1602
|
+
// P1: Apply objective refinement (shared)
|
|
1603
|
+
loopObjective = applyObjectiveRefinement(loopObjective, lr, request.round);
|
|
1604
|
+
// ── Build active constraints (base + LO) ─────────────────────────────
|
|
1605
|
+
let constraints = [...request.constraints_from_plan];
|
|
1606
|
+
if (loopObjective) {
|
|
1607
|
+
constraints = [
|
|
1608
|
+
...new Set([
|
|
1609
|
+
...constraints,
|
|
1610
|
+
...loopObjective.hard_constraints,
|
|
1611
|
+
...loopObjective.success_criteria,
|
|
1612
|
+
]),
|
|
1613
|
+
];
|
|
1614
|
+
}
|
|
1615
|
+
// ── P0/P5: Constraint evolution (shared) ─────────────────────────────
|
|
1616
|
+
const { constraints: evolvedCs, loopObjective: evolvedLo, retired } = applyConstraintEvolution(constraints, loopObjective, lr, request.round);
|
|
1617
|
+
constraints = evolvedCs;
|
|
1618
|
+
loopObjective = evolvedLo;
|
|
1619
|
+
// ── Constraint retirement ────────────────────────────────────────────
|
|
1620
|
+
const { active, retired: autoRetired } = computeConstraintRetirement(constraints, request.loop_id, request.round, vaultContext);
|
|
1621
|
+
for (const r of autoRetired) {
|
|
1622
|
+
if (!retired.includes(r))
|
|
1623
|
+
retired.push(r);
|
|
1624
|
+
}
|
|
1625
|
+
// ── Rolling summary + P5c wrong assumptions ──────────────────────────
|
|
1150
1626
|
const rollingSummary = buildRollingSummary(request.loop_id, request.round, vaultContext);
|
|
1627
|
+
if (lr?.wrong_assumptions?.length) {
|
|
1628
|
+
if (rollingSummary) {
|
|
1629
|
+
rollingSummary.key_outcomes = [
|
|
1630
|
+
...rollingSummary.key_outcomes,
|
|
1631
|
+
...lr.wrong_assumptions.map((a) => `Wrong assumption corrected: ${a}`),
|
|
1632
|
+
];
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1151
1635
|
const rollingText = formatRollingSummaryForPrompt(rollingSummary);
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
if (
|
|
1155
|
-
|
|
1636
|
+
// ── P2: Emerged subtasks → suggested next task ───────────────────────
|
|
1637
|
+
let suggestedNextTask = "";
|
|
1638
|
+
if (lr?.emerged_subtasks?.length) {
|
|
1639
|
+
suggestedNextTask = lr.emerged_subtasks.slice(0, 3).join("; ");
|
|
1156
1640
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1641
|
+
// ── P4: Consistency validation ───────────────────────────────────────
|
|
1642
|
+
const evidence = lr?.execution_evidence;
|
|
1643
|
+
const warnings = evidence
|
|
1644
|
+
? runP4ConsistencyChecks(evidence, lr.output_summary, lr.success, loopObjective)
|
|
1645
|
+
: [];
|
|
1646
|
+
// ── Route to specialist (all 4 techniques available via Tier 1) ──────
|
|
1647
|
+
let prompt;
|
|
1648
|
+
if (technique === "step-back") {
|
|
1649
|
+
prompt = compileStepBack(request, goalId, active, loopObjective, rollingText, analysis, { embedSkeleton: techChanged, level: "l1" });
|
|
1159
1650
|
}
|
|
1160
|
-
if (
|
|
1161
|
-
|
|
1651
|
+
else if (technique === "least-to-most") {
|
|
1652
|
+
prompt = compileLeastToMost(request, goalId, active, loopObjective, rollingText, analysis, { embedSkeleton: techChanged, level: "l1" });
|
|
1162
1653
|
}
|
|
1163
|
-
if (
|
|
1164
|
-
|
|
1654
|
+
else if (technique === "tree-of-thought") {
|
|
1655
|
+
prompt = compileToT(request, goalId, active, loopObjective, rollingText, analysis, { embedSkeleton: techChanged, level: "l1" });
|
|
1165
1656
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
`## Loop Round ${request.round} — L1 Patch`,
|
|
1169
|
-
"",
|
|
1170
|
-
`**Goal**: ${request.task}`,
|
|
1171
|
-
`**Loop ID**: ${request.loop_id}`,
|
|
1172
|
-
`**Goal ID**: ${goalId}`,
|
|
1173
|
-
"",
|
|
1174
|
-
];
|
|
1175
|
-
if (rollingText) {
|
|
1176
|
-
lines.push(rollingText);
|
|
1177
|
-
lines.push("");
|
|
1657
|
+
else {
|
|
1658
|
+
prompt = compileGeneric(request, goalId, active, loopObjective, rollingText, analysis, referenceFile, technique, { embedSkeleton: techChanged, level: "l1" });
|
|
1178
1659
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1660
|
+
// ── P4: Progress Dashboard ───────────────────────────────────────────
|
|
1661
|
+
const progressDashboard = (evidence && loopObjective)
|
|
1662
|
+
? buildProgressDashboard(evidence, loopObjective, vaultContext, request.loop_id, request.round, /* showTrend */ false)
|
|
1663
|
+
: "";
|
|
1664
|
+
// ── v1.14: Render state file ─────────────────────────────────────────
|
|
1665
|
+
const sfPolicy = getPolicy().state_file;
|
|
1666
|
+
let stateFileContent;
|
|
1667
|
+
if (sfPolicy.enabled) {
|
|
1668
|
+
const allCheckpoints = collectVaultCheckpoints(vaultContext, request.loop_id);
|
|
1669
|
+
const delegationSummary = vaultContext
|
|
1670
|
+
? buildDelegationSummary(vaultContext)
|
|
1671
|
+
: "";
|
|
1672
|
+
stateFileContent = renderStateFile({
|
|
1673
|
+
loopId: request.loop_id,
|
|
1674
|
+
currentRound: request.round,
|
|
1675
|
+
maxRounds: request.max_rounds ?? sfPolicy.max_summary_rounds * 2,
|
|
1676
|
+
loopObjective,
|
|
1677
|
+
rollingSummary,
|
|
1678
|
+
constraints: active,
|
|
1679
|
+
retiredConstraints: retired,
|
|
1680
|
+
checkpoints: allCheckpoints,
|
|
1681
|
+
delegationSummary,
|
|
1682
|
+
progressDashboard,
|
|
1683
|
+
externalContext: request.external_context,
|
|
1684
|
+
});
|
|
1685
|
+
prompt = renderStateFilePointer(request.loop_id) + "\n" + prompt;
|
|
1686
|
+
}
|
|
1687
|
+
// ── Build thin diff ──────────────────────────────────────────────────
|
|
1688
|
+
const violations = lr?.constraint_violations ?? [];
|
|
1689
|
+
const diffParts = [];
|
|
1690
|
+
if (request.constraints_from_plan.length) {
|
|
1691
|
+
diffParts.push(`new constraints: [${request.constraints_from_plan.join(", ")}]`);
|
|
1184
1692
|
}
|
|
1185
1693
|
if (retired.length) {
|
|
1186
|
-
|
|
1187
|
-
for (const c of retired)
|
|
1188
|
-
lines.push(`- ~${c}~`);
|
|
1189
|
-
lines.push("");
|
|
1694
|
+
diffParts.push(`retired constraints: [${retired.join(", ")}]`);
|
|
1190
1695
|
}
|
|
1191
1696
|
if (violations.length) {
|
|
1192
|
-
|
|
1193
|
-
for (const v of violations)
|
|
1194
|
-
lines.push(`- ${v}`);
|
|
1195
|
-
lines.push("");
|
|
1697
|
+
diffParts.push(`violations: [${violations.join(", ")}]`);
|
|
1196
1698
|
}
|
|
1197
1699
|
if (request.new_since_last_round) {
|
|
1198
|
-
|
|
1199
|
-
lines.push(request.new_since_last_round);
|
|
1200
|
-
lines.push("");
|
|
1201
|
-
}
|
|
1202
|
-
if (request.last_round_result?.output_summary) {
|
|
1203
|
-
lines.push("### Last Round Summary");
|
|
1204
|
-
lines.push(request.last_round_result.output_summary);
|
|
1205
|
-
lines.push("");
|
|
1700
|
+
diffParts.push(`delta: ${request.new_since_last_round.slice(0, 200)}`);
|
|
1206
1701
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1702
|
+
// v1.14: When state file is enabled, progressDashboard lives in the state
|
|
1703
|
+
// file (see renderStateFile above). Only inline it in the prompt when the
|
|
1704
|
+
// state file is disabled — this keeps the prompt truly thin.
|
|
1705
|
+
const finalPrompt = sfPolicy.enabled ? prompt : prompt + progressDashboard;
|
|
1209
1706
|
return makeLoopCompileResponse({
|
|
1210
1707
|
status: AgentStatus.OK,
|
|
1211
|
-
prompt:
|
|
1708
|
+
prompt: finalPrompt,
|
|
1212
1709
|
recompile_level: "l1",
|
|
1213
|
-
diff_from_previous: diffParts.length ? diffParts.join("; ") : "
|
|
1710
|
+
diff_from_previous: diffParts.length ? diffParts.join("; ") : "L1 continue — state updated in file.",
|
|
1214
1711
|
lineage: [`${request.loop_id}:r${request.round}`],
|
|
1215
1712
|
constraints_active: active,
|
|
1216
1713
|
constraints_retired: retired,
|
|
1217
|
-
technique_used:
|
|
1714
|
+
technique_used: technique,
|
|
1715
|
+
reference_file: referenceFile,
|
|
1218
1716
|
rolling_summary: rollingSummary,
|
|
1219
1717
|
loop_id: request.loop_id,
|
|
1220
1718
|
round: request.round,
|
|
1221
1719
|
goal_id: goalId,
|
|
1222
1720
|
goal_text_hash: computeGoalTextHash(request.task),
|
|
1721
|
+
loop_objective: loopObjective,
|
|
1223
1722
|
plan_source: request.plan_source,
|
|
1224
|
-
|
|
1723
|
+
suggested_next_task: suggestedNextTask,
|
|
1724
|
+
warnings,
|
|
1725
|
+
state_file_content: stateFileContent,
|
|
1225
1726
|
});
|
|
1226
1727
|
}
|
|
1227
1728
|
export function compileL2(request, vaultContext) {
|
|
1228
1729
|
const goalId = deriveGoalId(request.loop_id, request.task, request.goal_id);
|
|
1229
|
-
//
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
//
|
|
1234
|
-
|
|
1730
|
+
// v1.15: L2 no longer auto-selects a technique. The Agent reads the technique
|
|
1731
|
+
// catalog (skills/prompt-techniques/SKILL.md), freely chooses the best fit
|
|
1732
|
+
// based on loop state, reads the corresponding reference file, and applies
|
|
1733
|
+
// it directly to the task.
|
|
1734
|
+
// v1.10: Checkpoint — load existing or build new from last round's self-eval
|
|
1735
|
+
let checkpointSummary = null;
|
|
1736
|
+
let checkpointRound = 0;
|
|
1737
|
+
// Check if the last round declared a checkpoint
|
|
1738
|
+
const lr = request.last_round_result;
|
|
1739
|
+
if (lr?.compression_checkpoint === true) {
|
|
1740
|
+
// Build fresh checkpoint from the last round's data
|
|
1741
|
+
// Constraints haven't been updated yet at this point, but we snapshot the
|
|
1742
|
+
// current state as seen by this compile call (pre-P5 correction).
|
|
1743
|
+
// We defer building until constraints are finalized below.
|
|
1744
|
+
checkpointRound = request.round - 1;
|
|
1745
|
+
}
|
|
1746
|
+
else if (request.round > 1) {
|
|
1747
|
+
// No new checkpoint — try loading the previous one from vault
|
|
1748
|
+
checkpointSummary = loadLatestCheckpoint(request.loop_id, request.round, vaultContext);
|
|
1749
|
+
if (checkpointSummary) {
|
|
1750
|
+
checkpointRound = checkpointSummary.declared_at_round;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
// v3.5: Rolling summary — optionally scoped to post-checkpoint rounds
|
|
1754
|
+
const rollingSummary = buildRollingSummary(request.loop_id, request.round, vaultContext, checkpointRound > 0 ? checkpointRound : undefined);
|
|
1235
1755
|
// P5c: Wrong assumptions — must be applied BEFORE formatting so they appear
|
|
1236
1756
|
// in the rendered cross-round summary text injected into the prompt.
|
|
1237
|
-
const lr = request.last_round_result;
|
|
1238
1757
|
if (lr?.wrong_assumptions?.length) {
|
|
1239
1758
|
if (rollingSummary) {
|
|
1240
|
-
rollingSummary.
|
|
1241
|
-
...rollingSummary.
|
|
1759
|
+
rollingSummary.key_outcomes = [
|
|
1760
|
+
...rollingSummary.key_outcomes,
|
|
1242
1761
|
...lr.wrong_assumptions.map((a) => `Wrong assumption corrected: ${a}`),
|
|
1243
1762
|
];
|
|
1244
1763
|
}
|
|
@@ -1275,26 +1794,9 @@ export function compileL2(request, vaultContext) {
|
|
|
1275
1794
|
loopObjective = request.loop_objective;
|
|
1276
1795
|
}
|
|
1277
1796
|
}
|
|
1278
|
-
// ── P1: Apply
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
if (refinement) {
|
|
1282
|
-
const version = (loopObjective.version ?? 1) + 1;
|
|
1283
|
-
const history = [...(loopObjective.refinement_history ?? []), refinement];
|
|
1284
|
-
// Enforce max versions from policy
|
|
1285
|
-
const policy = getPolicy();
|
|
1286
|
-
const maxVersions = policy.evolution?.max_objective_versions ?? 10;
|
|
1287
|
-
const prunedHistory = history.slice(-maxVersions);
|
|
1288
|
-
const refinedObjective = `${loopObjective.objective} [R${request.round}: ${refinement}]`;
|
|
1289
|
-
loopObjective = makeLoopObjective({
|
|
1290
|
-
...loopObjective,
|
|
1291
|
-
objective: refinedObjective,
|
|
1292
|
-
version,
|
|
1293
|
-
refinement_history: prunedHistory,
|
|
1294
|
-
});
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
// ── Build active constraints (base + LO + P0 discovered) ──
|
|
1797
|
+
// ── P1: Apply objective refinement (shared) ──
|
|
1798
|
+
loopObjective = applyObjectiveRefinement(loopObjective, lr, request.round);
|
|
1799
|
+
// ── Build active constraints (base + LO) ──
|
|
1298
1800
|
let constraints = [...request.constraints_from_plan];
|
|
1299
1801
|
if (loopObjective) {
|
|
1300
1802
|
constraints = [
|
|
@@ -1305,76 +1807,39 @@ export function compileL2(request, vaultContext) {
|
|
|
1305
1807
|
]),
|
|
1306
1808
|
];
|
|
1307
1809
|
}
|
|
1308
|
-
// P0: Merge discovered constraints from last round
|
|
1309
|
-
if (request.last_round_result?.discovered_constraints?.length) {
|
|
1310
|
-
const policy = getPolicy();
|
|
1311
|
-
const maxPerRound = policy.evolution?.max_discovered_constraints_per_round ?? 5;
|
|
1312
|
-
const maxActive = policy.evolution?.max_active_constraints ?? 15;
|
|
1313
|
-
const newConstraints = request.last_round_result.discovered_constraints
|
|
1314
|
-
.slice(0, maxPerRound)
|
|
1315
|
-
.filter((c) => !constraints.includes(c));
|
|
1316
|
-
constraints = [...constraints, ...newConstraints].slice(0, maxActive);
|
|
1317
|
-
}
|
|
1318
1810
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1319
|
-
// P5:
|
|
1320
|
-
// so the technician compiler sees the corrected constraints and loop objective.
|
|
1811
|
+
// P0/P5: Constraint evolution (shared) — retractions/revisions BEFORE prompt
|
|
1321
1812
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1322
1813
|
const warnings = [];
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
...loopObjective,
|
|
1353
|
-
success_criteria: sc,
|
|
1354
|
-
version,
|
|
1355
|
-
refinement_history: history,
|
|
1356
|
-
});
|
|
1357
|
-
// Rebuild constraints to include revised criteria
|
|
1358
|
-
constraints = [...new Set([
|
|
1359
|
-
...constraints.filter((c) => !updatedLo.hard_constraints.includes(c) && !updatedLo.success_criteria.includes(c)),
|
|
1360
|
-
...updatedLo.hard_constraints,
|
|
1361
|
-
...updatedLo.success_criteria,
|
|
1362
|
-
])];
|
|
1363
|
-
loopObjective = updatedLo;
|
|
1364
|
-
}
|
|
1365
|
-
// ── Route to technique-specific specialist (v1.1 deep integration) ──
|
|
1366
|
-
let prompt;
|
|
1367
|
-
if (technique === "step-back") {
|
|
1368
|
-
prompt = compileStepBack(request, goalId, constraints, loopObjective, rollingText, analysis);
|
|
1369
|
-
}
|
|
1370
|
-
else if (technique === "least-to-most") {
|
|
1371
|
-
prompt = compileLeastToMost(request, goalId, constraints, loopObjective, rollingText, analysis);
|
|
1372
|
-
}
|
|
1373
|
-
else if (technique === "tree-of-thought") {
|
|
1374
|
-
prompt = compileToT(request, goalId, constraints, loopObjective, rollingText, analysis);
|
|
1375
|
-
}
|
|
1376
|
-
else {
|
|
1377
|
-
prompt = compileGeneric(request, goalId, constraints, loopObjective, rollingText, analysis, referenceFile, technique);
|
|
1814
|
+
const evoResult = applyConstraintEvolution(constraints, loopObjective, lr, request.round);
|
|
1815
|
+
constraints = evoResult.constraints;
|
|
1816
|
+
loopObjective = evoResult.loopObjective;
|
|
1817
|
+
const retired = evoResult.retired;
|
|
1818
|
+
// ── v1.10: Build checkpoint summary if last round declared one ──
|
|
1819
|
+
if (checkpointRound > 0 && !checkpointSummary && lr) {
|
|
1820
|
+
// checkpointRound was set but no summary yet → fresh declaration
|
|
1821
|
+
// We need the self-eval from last round. It's embedded in lr fields.
|
|
1822
|
+
// Build a synthetic SelfEvaluation from the last_round_result for the builder.
|
|
1823
|
+
const synthEval = {
|
|
1824
|
+
success: lr.success,
|
|
1825
|
+
output_summary: lr.output_summary,
|
|
1826
|
+
constraint_violations: lr.constraint_violations,
|
|
1827
|
+
should_continue: true,
|
|
1828
|
+
compression_checkpoint: true,
|
|
1829
|
+
checkpoint_label: lr.checkpoint_label ?? "",
|
|
1830
|
+
};
|
|
1831
|
+
checkpointSummary = buildCheckpointSummary(synthEval, checkpointRound, constraints, retired);
|
|
1832
|
+
}
|
|
1833
|
+
// ── v1.15: Build L2 prompt with Technique Selection block ──
|
|
1834
|
+
// The Agent freely chooses a technique by reading the catalog and reference
|
|
1835
|
+
// files. LoopForge provides the loop state (objective, constraints, rolling
|
|
1836
|
+
// summary) — the Agent provides the reasoning strategy.
|
|
1837
|
+
let prompt = buildL2Prompt(request, goalId, constraints, loopObjective, rollingText);
|
|
1838
|
+
// ── v1.10: Inject checkpoint block (before rolling summary in prompt) ──
|
|
1839
|
+
// The checkpoint is rendered before the external context so it appears
|
|
1840
|
+
// in a natural position: after Loop Objective, before Cross-Round Summary.
|
|
1841
|
+
if (checkpointSummary) {
|
|
1842
|
+
prompt += "\n" + formatCheckpointForPrompt(checkpointSummary);
|
|
1378
1843
|
}
|
|
1379
1844
|
// ── v1.7: Inject external memory context (L2 only) ──
|
|
1380
1845
|
const policy = getPolicy();
|
|
@@ -1393,29 +1858,7 @@ export function compileL2(request, vaultContext) {
|
|
|
1393
1858
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1394
1859
|
const evidence = lr?.execution_evidence;
|
|
1395
1860
|
if (evidence) {
|
|
1396
|
-
|
|
1397
|
-
if (evidence.files_changed.length === 0 && lr.output_summary) {
|
|
1398
|
-
const actionWords = /\b(fix|modif|chang|updat|refactor|implement|add|remov|delet|rewrit|修补|修改|更改|更新|重构|实现|添加|删除|重写)\b/i;
|
|
1399
|
-
if (actionWords.test(lr.output_summary)) {
|
|
1400
|
-
warnings.push("P4: Agent output claims changes but execution_evidence.files_changed is empty — possible oversight");
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
// Check 2: Agent reports success but tests failed
|
|
1404
|
-
if (evidence.test_results && evidence.test_results.failed > 0 && lr.success) {
|
|
1405
|
-
warnings.push(`P4: Agent reports success=true but ${evidence.test_results.failed} tests failed — claims may not match reality`);
|
|
1406
|
-
}
|
|
1407
|
-
// Check 3: Progress estimate mismatch with criteria tracking
|
|
1408
|
-
if (loopObjective && loopObjective.success_criteria.length > 0) {
|
|
1409
|
-
const metCount = evidence.success_criteria_met.length;
|
|
1410
|
-
const totalCount = loopObjective.success_criteria.length;
|
|
1411
|
-
const objectiveProgress = totalCount > 0 ? metCount / totalCount : 0;
|
|
1412
|
-
const subjectiveProgress = evidence.progress_estimate;
|
|
1413
|
-
const policy = getPolicy();
|
|
1414
|
-
const mismatchThreshold = policy.evolution?.progress_mismatch_threshold ?? 0.3;
|
|
1415
|
-
if (Math.abs(objectiveProgress - subjectiveProgress) > mismatchThreshold) {
|
|
1416
|
-
warnings.push(`P4: Progress estimate mismatch — agent estimates ${(subjectiveProgress * 100).toFixed(0)}% but ${metCount}/${totalCount} criteria met (${(objectiveProgress * 100).toFixed(0)}%)`);
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1861
|
+
warnings.push(...runP4ConsistencyChecks(evidence, lr.output_summary, lr.success, loopObjective));
|
|
1419
1862
|
}
|
|
1420
1863
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1421
1864
|
// P4: Progress gradient — early stall detection
|
|
@@ -1439,71 +1882,58 @@ export function compileL2(request, vaultContext) {
|
|
|
1439
1882
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1440
1883
|
// P4: Build Progress Dashboard for prompt injection
|
|
1441
1884
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
if (
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
// Compute trend from previous round
|
|
1476
|
-
if (vaultContext) {
|
|
1477
|
-
const prevEvidence = getPreviousRoundEvidence(request.loop_id, request.round - 1, vaultContext);
|
|
1478
|
-
if (prevEvidence) {
|
|
1479
|
-
const trend = objProgress - (prevEvidence.success_criteria_met.length / Math.max(totalCriteria, 1));
|
|
1480
|
-
if (trend > 0.03) {
|
|
1481
|
-
lines.push(`**Trend**: ↑ advancing (+${(trend * 100).toFixed(0)}% this round)`);
|
|
1482
|
-
}
|
|
1483
|
-
else if (trend > -0.03) {
|
|
1484
|
-
lines.push("**Trend**: → stable");
|
|
1485
|
-
}
|
|
1486
|
-
else {
|
|
1487
|
-
lines.push(`**Trend**: ↓ regressing (${(trend * 100).toFixed(0)}% this round)`);
|
|
1488
|
-
}
|
|
1489
|
-
lines.push("");
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
progressDashboard = lines.join("\n");
|
|
1885
|
+
const progressDashboard = (evidence && loopObjective)
|
|
1886
|
+
? buildProgressDashboard(evidence, loopObjective, vaultContext, request.loop_id, request.round, /* showTrend */ true)
|
|
1887
|
+
: "";
|
|
1888
|
+
// ── v1.14: Render state file ──────────────────────────────────────────
|
|
1889
|
+
// Collect all accumulated state and render it as a markdown snapshot.
|
|
1890
|
+
// The caller writes this to .loopforge/state/{loopId}-state.md.
|
|
1891
|
+
const sfPolicy = getPolicy().state_file;
|
|
1892
|
+
let stateFileContent;
|
|
1893
|
+
if (sfPolicy.enabled) {
|
|
1894
|
+
const excludeRounds = checkpointSummary
|
|
1895
|
+
? new Set([checkpointSummary.declared_at_round])
|
|
1896
|
+
: undefined;
|
|
1897
|
+
const allCheckpoints = collectVaultCheckpoints(vaultContext, request.loop_id, excludeRounds);
|
|
1898
|
+
if (checkpointSummary)
|
|
1899
|
+
allCheckpoints.unshift(checkpointSummary);
|
|
1900
|
+
const delegationSummary = vaultContext
|
|
1901
|
+
? buildDelegationSummary(vaultContext)
|
|
1902
|
+
: "";
|
|
1903
|
+
stateFileContent = renderStateFile({
|
|
1904
|
+
loopId: request.loop_id,
|
|
1905
|
+
currentRound: request.round,
|
|
1906
|
+
maxRounds: request.max_rounds ?? sfPolicy.max_summary_rounds * 2,
|
|
1907
|
+
loopObjective,
|
|
1908
|
+
rollingSummary,
|
|
1909
|
+
constraints,
|
|
1910
|
+
retiredConstraints: retired,
|
|
1911
|
+
checkpoints: allCheckpoints,
|
|
1912
|
+
delegationSummary,
|
|
1913
|
+
progressDashboard,
|
|
1914
|
+
externalContext: request.external_context,
|
|
1915
|
+
});
|
|
1916
|
+
// Prepend state file pointer to the prompt
|
|
1917
|
+
prompt = renderStateFilePointer(request.loop_id) + "\n" + prompt;
|
|
1493
1918
|
}
|
|
1919
|
+
// v1.14: When state file is enabled, progressDashboard lives in the state
|
|
1920
|
+
// file (see renderStateFile above). Only inline it in the prompt when the
|
|
1921
|
+
// state file is disabled — this keeps the prompt truly thin.
|
|
1922
|
+
const finalPrompt = sfPolicy.enabled ? prompt : prompt + progressDashboard;
|
|
1494
1923
|
return makeLoopCompileResponse({
|
|
1495
1924
|
status: AgentStatus.OK,
|
|
1496
|
-
prompt:
|
|
1925
|
+
prompt: finalPrompt,
|
|
1497
1926
|
recompile_level: "l2",
|
|
1498
1927
|
diff_from_previous: request.round === 1 || request.plan_source
|
|
1499
1928
|
? "Full recompile — new goal or first call."
|
|
1500
|
-
: "Full recompile — goal_id changed or
|
|
1929
|
+
: "Full recompile — goal_id changed or checkpoint boundary.",
|
|
1501
1930
|
lineage: [`${request.loop_id}:r${request.round}`],
|
|
1502
1931
|
constraints_active: constraints,
|
|
1503
1932
|
constraints_retired: retired,
|
|
1504
|
-
technique_used:
|
|
1505
|
-
reference_file:
|
|
1933
|
+
technique_used: "agent-selected",
|
|
1934
|
+
reference_file: `${SKILLS_DIR}/prompt-techniques/SKILL.md`,
|
|
1506
1935
|
rolling_summary: rollingSummary,
|
|
1936
|
+
checkpoint_summary: checkpointSummary,
|
|
1507
1937
|
loop_id: request.loop_id,
|
|
1508
1938
|
round: request.round,
|
|
1509
1939
|
goal_id: goalId,
|
|
@@ -1512,11 +1942,51 @@ export function compileL2(request, vaultContext) {
|
|
|
1512
1942
|
plan_source: request.plan_source,
|
|
1513
1943
|
suggested_next_task: suggestedNextTask,
|
|
1514
1944
|
warnings,
|
|
1945
|
+
state_file_content: stateFileContent,
|
|
1515
1946
|
});
|
|
1516
1947
|
}
|
|
1948
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1949
|
+
// L2 Prompt Builder (v1.15 — Agent technique autonomy)
|
|
1950
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1951
|
+
/** Build the L2 restart prompt with a Technique Selection block.
|
|
1952
|
+
* The Agent reads the technique catalog (SKILL.md), freely chooses the best
|
|
1953
|
+
* technique based on loop state, reads the corresponding reference file, and
|
|
1954
|
+
* applies it directly to the task. LoopForge provides context — the Agent
|
|
1955
|
+
* provides the reasoning strategy. */
|
|
1956
|
+
function buildL2Prompt(request, goalId, constraints, loopObjective, rollingText) {
|
|
1957
|
+
const ctx = {
|
|
1958
|
+
request, goalId, constraints, loopObjective, rollingText, level: "l2",
|
|
1959
|
+
};
|
|
1960
|
+
const lines = [
|
|
1961
|
+
`## LoopForge L2 Compile — Round ${request.round}`,
|
|
1962
|
+
"",
|
|
1963
|
+
];
|
|
1964
|
+
// Cross-round summary
|
|
1965
|
+
if (rollingText) {
|
|
1966
|
+
lines.push(rollingText);
|
|
1967
|
+
lines.push("");
|
|
1968
|
+
}
|
|
1969
|
+
// Technique Selection — Agent freely chooses
|
|
1970
|
+
lines.push("### Technique Selection", "", "This is a strategy restart point. Read the technique catalog to choose", "the best reasoning approach for this round:", "", ` \`${SKILLS_DIR}/prompt-techniques/SKILL.md\``, "", "Based on the loop state (state file + context below), freely select", "the technique that best fits this round's challenge. Then read the", "full reference for your chosen technique:", "", ` \`${SKILLS_DIR}/prompt-techniques/references/<chosen>.md\``, "", "Apply the technique's method directly to the task — do NOT generate", "an intermediate prompt. Execute the task using the chosen technique", "as your thinking framework.", "");
|
|
1971
|
+
// Loop Objective
|
|
1972
|
+
lines.push(...buildLoopObjectiveBlock(loopObjective));
|
|
1973
|
+
// Active Constraints
|
|
1974
|
+
lines.push(...buildConstraintsBlock(constraints));
|
|
1975
|
+
// Plan Source
|
|
1976
|
+
if (request.plan_source) {
|
|
1977
|
+
lines.push(`**Plan Source**: ${request.plan_source}`);
|
|
1978
|
+
lines.push("");
|
|
1979
|
+
}
|
|
1980
|
+
// Task
|
|
1981
|
+
lines.push(...buildTaskBlock(ctx));
|
|
1982
|
+
// Loop Identity
|
|
1983
|
+
lines.push(...buildIdentityBlock(ctx));
|
|
1984
|
+
return lines.join("\n");
|
|
1985
|
+
}
|
|
1517
1986
|
function buildHeader(ctx, technique) {
|
|
1987
|
+
const levelLabel = ctx.level.toUpperCase();
|
|
1518
1988
|
const lines = [
|
|
1519
|
-
`## LoopForge
|
|
1989
|
+
`## LoopForge ${levelLabel} Compile — Round ${ctx.request.round}`,
|
|
1520
1990
|
`**Technique**: ${technique} (embedded)`,
|
|
1521
1991
|
"",
|
|
1522
1992
|
];
|
|
@@ -1577,267 +2047,85 @@ function buildIdentityBlock(ctx) {
|
|
|
1577
2047
|
];
|
|
1578
2048
|
}
|
|
1579
2049
|
/** Generic compiler — reads technique reference file (fallback for zero-shot/few-shot/cot). */
|
|
1580
|
-
function compileGeneric(request, goalId, constraints, loopObjective, rollingText, analysis, referenceFile, technique) {
|
|
1581
|
-
const
|
|
2050
|
+
function compileGeneric(request, goalId, constraints, loopObjective, rollingText, analysis, referenceFile, technique, opts = {}) {
|
|
2051
|
+
const embedSkeleton = opts.embedSkeleton !== false; // default true
|
|
2052
|
+
const ctx = { request, goalId, constraints, loopObjective, rollingText, level: opts.level ?? "l2" };
|
|
1582
2053
|
const lines = [
|
|
1583
2054
|
...buildHeader(ctx, technique),
|
|
1584
|
-
`Read the technique reference BEFORE generating the prompt:`,
|
|
1585
|
-
` Technique: ${technique}`,
|
|
1586
|
-
` Reference: ${referenceFile}`,
|
|
1587
|
-
` Rationale: ${analysis.rationale}`,
|
|
1588
|
-
"",
|
|
1589
|
-
...buildLoopObjectiveBlock(loopObjective),
|
|
1590
|
-
...buildConstraintsBlock(constraints),
|
|
1591
|
-
...(request.plan_source ? [`**Plan Source**: ${request.plan_source}`, ""] : []),
|
|
1592
|
-
...buildTaskBlock(ctx),
|
|
1593
|
-
...buildIdentityBlock(ctx),
|
|
1594
|
-
"### Generation Instructions",
|
|
1595
|
-
`1. Read \`${referenceFile}\` — study its structure rules, section count, and format requirements`,
|
|
1596
|
-
"2. Generate a complete prompt following that technique's structure",
|
|
1597
|
-
"3. Inject all hard constraints and the loop objective into the prompt",
|
|
1598
|
-
"4. If Cross-Round Summary is present above, incorporate its recurring issues and key lessons",
|
|
1599
|
-
"5. The prompt must be self-contained — ready for a coding agent to execute",
|
|
1600
|
-
"6. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1601
2055
|
];
|
|
2056
|
+
if (embedSkeleton) {
|
|
2057
|
+
lines.push(`Read the technique reference BEFORE generating the prompt:`, ` Technique: ${technique}`, ` Reference: ${referenceFile}`, ` Rationale: ${analysis.rationale}`, "");
|
|
2058
|
+
}
|
|
2059
|
+
else {
|
|
2060
|
+
lines.push(`**Technique**: ${technique} (see state file for full reference)`, `**Rationale**: ${analysis.rationale}`, "");
|
|
2061
|
+
}
|
|
2062
|
+
lines.push(...buildLoopObjectiveBlock(loopObjective), ...buildConstraintsBlock(constraints), ...(request.plan_source ? [`**Plan Source**: ${request.plan_source}`, ""] : []), ...buildTaskBlock(ctx), ...buildIdentityBlock(ctx));
|
|
2063
|
+
if (embedSkeleton) {
|
|
2064
|
+
lines.push("### Generation Instructions", `1. Read \`${referenceFile}\` — study its structure rules, section count, and format requirements`, "2. Generate a complete prompt following that technique's structure", "3. The prompt must be self-contained — ready for a coding agent to execute", "4. Output only the generated prompt — no preamble, no meta-commentary");
|
|
2065
|
+
}
|
|
1602
2066
|
return lines.join("\n");
|
|
1603
2067
|
}
|
|
1604
2068
|
// ── Step-Back specialist ───────────────────────────────────────────────────────
|
|
1605
|
-
function compileStepBack(request, goalId, constraints, loopObjective, rollingText, analysis) {
|
|
1606
|
-
const
|
|
2069
|
+
function compileStepBack(request, goalId, constraints, loopObjective, rollingText, analysis, opts = {}) {
|
|
2070
|
+
const embedSkeleton = opts.embedSkeleton !== false; // default true
|
|
2071
|
+
const ctx = { request, goalId, constraints, loopObjective, rollingText, level: opts.level ?? "l2" };
|
|
1607
2072
|
const lines = [
|
|
1608
2073
|
...buildHeader(ctx, "step-back"),
|
|
1609
2074
|
`**Rationale**: ${analysis.rationale}`,
|
|
1610
2075
|
"",
|
|
1611
|
-
"Generate a complete prompt using the **Step-Back** technique. Follow the 8-section skeleton below.",
|
|
1612
|
-
"Embed all structural constraints — do NOT reference an external file.",
|
|
1613
|
-
"",
|
|
1614
|
-
...buildLoopObjectiveBlock(loopObjective),
|
|
1615
|
-
...buildConstraintsBlock(constraints),
|
|
1616
|
-
...(request.plan_source ? [`**Plan Source**: ${request.plan_source}`, ""] : []),
|
|
1617
|
-
...buildTaskBlock(ctx),
|
|
1618
|
-
...buildIdentityBlock(ctx),
|
|
1619
|
-
"",
|
|
1620
|
-
"### 8-Section Skeleton (REQUIRED)",
|
|
1621
|
-
"",
|
|
1622
|
-
"| # | Section | Required | Notes |",
|
|
1623
|
-
"|---|---------|----------|-------|",
|
|
1624
|
-
"| 1 | 角色 | ✓ | Domain expert role appropriate to the task |",
|
|
1625
|
-
"| 2 | 任务 | ✓ | One-sentence task summary |",
|
|
1626
|
-
"| 3 | 输入 | ✓ | Target module/problem to analyse |",
|
|
1627
|
-
"| 4 | 输出格式 | ✓ | Numbered list of expected outputs |",
|
|
1628
|
-
"| 5 | **Step-Back 抽象框架** | ✓ | See format rules below |",
|
|
1629
|
-
"| 6 | 具体实现要求 | ✓ | MUST start with: **\"基于上述抽象框架,实现以下所有功能\"** |",
|
|
1630
|
-
"| 7 | 硬约束 | ✓ | Numbered list of hard constraints |",
|
|
1631
|
-
"| 8 | 生成要求 | ✓ | Acceptance criteria |",
|
|
1632
|
-
"",
|
|
1633
|
-
"### Section 5 — Step-Back 抽象框架 (CRITICAL)",
|
|
1634
|
-
"",
|
|
1635
|
-
"Must contain **2-3 abstract frameworks**. Each framework is an independent ASCII diagram.",
|
|
1636
|
-
"Frameworks are PARALLEL (peer-level), not sequential.",
|
|
1637
|
-
"",
|
|
1638
|
-
"Each framework format:",
|
|
1639
|
-
"```",
|
|
1640
|
-
"### 框架N:[Framework Name]",
|
|
1641
|
-
"",
|
|
1642
|
-
"[ASCII diagram with principles/formulas/classification tables/rule tables]",
|
|
1643
|
-
"",
|
|
1644
|
-
"Diagram content requirements:",
|
|
1645
|
-
"- Core principle/formula (e.g. \"Total = Σ(dimension × weight)\")",
|
|
1646
|
-
"- Component table (e.g. weight allocation table, type mapping table)",
|
|
1647
|
-
"- Rule description (e.g. \"Missing data dimensions use neutral value\")",
|
|
1648
|
-
"```",
|
|
1649
|
-
"",
|
|
1650
|
-
"**Tightening rules**:",
|
|
1651
|
-
"- The step-back question must be MORE ABSTRACT than the original problem, but cover all required information",
|
|
1652
|
-
"- Abstract ≠ vague. Use precise formulas, principles, standards, definitions, or causal mechanisms",
|
|
1653
|
-
"- Tighten to the MINIMUM generalisation layer that still covers the original problem",
|
|
1654
|
-
"- Reasoning must RETURN to the original problem — do not stay at the principle level",
|
|
1655
|
-
"",
|
|
1656
|
-
"**Section 6 transition sentence (MANDATORY)**:",
|
|
1657
|
-
"\"基于上述抽象框架,实现以下所有功能。\"",
|
|
1658
|
-
"",
|
|
1659
|
-
"### Quality Checklist",
|
|
1660
|
-
"- [ ] Frameworks are abstract concepts/formulas/principles, NOT concrete code",
|
|
1661
|
-
"- [ ] Section 6 starts with the mandatory transition sentence",
|
|
1662
|
-
"- [ ] Abstraction level is consistent across frameworks",
|
|
1663
|
-
"- [ ] Step-back question is tightened to minimum necessary generalisation",
|
|
1664
|
-
"",
|
|
1665
|
-
"### Generation Instructions",
|
|
1666
|
-
"1. Extract 2-3 abstract principles/frameworks from the task domain",
|
|
1667
|
-
"2. Build ASCII diagrams for each framework (formulas, tables, rules)",
|
|
1668
|
-
"3. Apply the abstraction back to the concrete task",
|
|
1669
|
-
"4. Inject all hard constraints and the loop objective into the prompt",
|
|
1670
|
-
"5. If Cross-Round Summary is present above, incorporate its recurring issues and key lessons",
|
|
1671
|
-
"6. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1672
2076
|
];
|
|
2077
|
+
if (embedSkeleton) {
|
|
2078
|
+
lines.push("Generate a complete prompt using the **Step-Back** technique. Follow the 8-section skeleton below.", "Embed all structural constraints — do NOT reference an external file.", "");
|
|
2079
|
+
}
|
|
2080
|
+
else {
|
|
2081
|
+
lines.push("**Technique**: step-back (see state file for the full skeleton).", "Generate a complete prompt following this technique.", "");
|
|
2082
|
+
}
|
|
2083
|
+
lines.push(...buildLoopObjectiveBlock(loopObjective), ...buildConstraintsBlock(constraints), ...(request.plan_source ? [`**Plan Source**: ${request.plan_source}`, ""] : []), ...buildTaskBlock(ctx), ...buildIdentityBlock(ctx));
|
|
2084
|
+
if (embedSkeleton) {
|
|
2085
|
+
lines.push("", "### 8-Section Skeleton (REQUIRED)", "", "| # | Section | Required | Notes |", "|---|---------|----------|-------|", "| 1 | 角色 | ✓ | Domain expert role appropriate to the task |", "| 2 | 任务 | ✓ | One-sentence task summary |", "| 3 | 输入 | ✓ | Target module/problem to analyse |", "| 4 | 输出格式 | ✓ | Numbered list of expected outputs |", "| 5 | **Step-Back 抽象框架** | ✓ | See format rules below |", "| 6 | 具体实现要求 | ✓ | MUST start with: **\"基于上述抽象框架,实现以下所有功能\"** |", "| 7 | 硬约束 | ✓ | Numbered list of hard constraints |", "| 8 | 生成要求 | ✓ | Acceptance criteria |", "", "### Section 5 — Step-Back 抽象框架 (CRITICAL)", "", "Must contain **2-3 abstract frameworks**. Each framework is an independent ASCII diagram.", "Frameworks are PARALLEL (peer-level), not sequential.", "", "Each framework format:", "```", "### 框架N:[Framework Name]", "", "[ASCII diagram with principles/formulas/classification tables/rule tables]", "", "Diagram content requirements:", "- Core principle/formula (e.g. \"Total = Σ(dimension × weight)\")", "- Component table (e.g. weight allocation table, type mapping table)", "- Rule description (e.g. \"Missing data dimensions use neutral value\")", "```", "", "**Tightening rules**:", "- The step-back question must be MORE ABSTRACT than the original problem, but cover all required information", "- Abstract ≠ vague. Use precise formulas, principles, standards, definitions, or causal mechanisms", "- Tighten to the MINIMUM generalisation layer that still covers the original problem", "- Reasoning must RETURN to the original problem — do not stay at the principle level", "", "**Section 6 transition sentence (MANDATORY)**:", "\"基于上述抽象框架,实现以下所有功能。\"", "", "### Generation Instructions", "1. Extract 2-3 abstract principles/frameworks from the task domain", "2. Build ASCII diagrams for each framework (formulas, tables, rules)", "3. Apply the abstraction back to the concrete task", "4. Verify: the generated prompt satisfies all structural rules in the skeleton above", "5. Output only the generated prompt — no preamble, no meta-commentary");
|
|
2086
|
+
}
|
|
1673
2087
|
return lines.join("\n");
|
|
1674
2088
|
}
|
|
1675
2089
|
// ── Least-to-Most specialist ───────────────────────────────────────────────────
|
|
1676
|
-
function compileLeastToMost(request, goalId, constraints, loopObjective, rollingText, analysis) {
|
|
1677
|
-
const
|
|
2090
|
+
function compileLeastToMost(request, goalId, constraints, loopObjective, rollingText, analysis, opts = {}) {
|
|
2091
|
+
const embedSkeleton = opts.embedSkeleton !== false; // default true
|
|
2092
|
+
const ctx = { request, goalId, constraints, loopObjective, rollingText, level: opts.level ?? "l2" };
|
|
1678
2093
|
const lines = [
|
|
1679
2094
|
...buildHeader(ctx, "least-to-most"),
|
|
1680
2095
|
`**Rationale**: ${analysis.rationale}`,
|
|
1681
2096
|
"",
|
|
1682
|
-
"Generate a complete prompt using the **Least-to-Most** technique. Follow the 8-section skeleton below.",
|
|
1683
|
-
"Embed all structural constraints — do NOT reference an external file.",
|
|
1684
|
-
"",
|
|
1685
|
-
...buildLoopObjectiveBlock(loopObjective),
|
|
1686
|
-
...buildConstraintsBlock(constraints),
|
|
1687
|
-
...(request.plan_source ? [`**Plan Source**: ${request.plan_source}`, ""] : []),
|
|
1688
|
-
...buildTaskBlock(ctx),
|
|
1689
|
-
...buildIdentityBlock(ctx),
|
|
1690
|
-
"",
|
|
1691
|
-
"### 8-Section Skeleton (REQUIRED)",
|
|
1692
|
-
"",
|
|
1693
|
-
"| # | Section | Required | Notes |",
|
|
1694
|
-
"|---|---------|----------|-------|",
|
|
1695
|
-
"| 1 | 角色 | ✓ | Domain expert role appropriate to the task |",
|
|
1696
|
-
"| 2 | 任务 | ✓ | One-sentence task summary |",
|
|
1697
|
-
"| 3 | 输入 | ✓ | Target module |",
|
|
1698
|
-
"| 4 | 输出格式 | ✓ | Numbered list (e.g. \"1. DDL, 2. API, 3. Enum...\") |",
|
|
1699
|
-
"| 5 | **Least-to-Most 逐步推理框架** | ✓ | See format rules below |",
|
|
1700
|
-
"| 6 | 具体实现要求 | ✓ | Expand per output format list (NOT per sub-problem — sub-problems fully expanded in §5) |",
|
|
1701
|
-
"| 7 | 硬约束 | ✓ | Numbered list |",
|
|
1702
|
-
"| 8 | 生成要求 | ✓ | Acceptance criteria, must include: \"严格按照子问题顺序逐步实现\" |",
|
|
1703
|
-
"",
|
|
1704
|
-
"### Section 5 — Least-to-Most 逐步推理框架 (CRITICAL)",
|
|
1705
|
-
"",
|
|
1706
|
-
"Must contain **4-6 ordered sub-problems**. Each sub-problem format:",
|
|
1707
|
-
"",
|
|
1708
|
-
"```",
|
|
1709
|
-
"### 子问题 N:[Sub-problem Name]",
|
|
1710
|
-
"",
|
|
1711
|
-
"**目标:** [What this sub-problem solves, which prior sub-problem(s) it depends on]",
|
|
1712
|
-
"",
|
|
1713
|
-
"**要求:**",
|
|
1714
|
-
"- Specific requirement list",
|
|
1715
|
-
"- [If sub-problem involves enums/mapping tables, list them]",
|
|
1716
|
-
"",
|
|
1717
|
-
"---",
|
|
1718
|
-
"```",
|
|
1719
|
-
"",
|
|
1720
|
-
"**Critical rules**:",
|
|
1721
|
-
"- Sub-problem 1 is the SIMPLEST (e.g. \"Define enums and base data structures\")",
|
|
1722
|
-
"- Each sub-problem declares: \"基于子问题 N-1 的结论\"",
|
|
1723
|
-
"- The LAST sub-problem MUST be: **\"综合实现完整模块\"** — list all components to integrate",
|
|
1724
|
-
"- Sub-problems separated by `---`",
|
|
1725
|
-
"- Order goes from LEAST complex → MOST complex, dependency chain must be explicit",
|
|
1726
|
-
"- Each sub-problem must serve the original task — no unrelated steps",
|
|
1727
|
-
"- The final sub-problem must be equivalent to or directly address the original task",
|
|
1728
|
-
"",
|
|
1729
|
-
"### Quality Checklist",
|
|
1730
|
-
"- [ ] Sub-problems ordered simplest → most complex, dependency chain explicit",
|
|
1731
|
-
"- [ ] Each sub-problem declares its prerequisite sub-problems",
|
|
1732
|
-
"- [ ] Last sub-problem is \"综合实现完整模块\" with integration list",
|
|
1733
|
-
"- [ ] Section 6 expands per output format list (NOT per sub-problem)",
|
|
1734
|
-
"- [ ] Sub-problem count: 4-6, no more, no less",
|
|
1735
|
-
"",
|
|
1736
|
-
"### Generation Instructions",
|
|
1737
|
-
"1. Decompose the task into 4-6 ordered sub-problems with explicit dependencies",
|
|
1738
|
-
"2. Sub-problem 1 starts with the simplest building block (data structures, enums, base config)",
|
|
1739
|
-
"3. Each subsequent sub-problem builds on prior results",
|
|
1740
|
-
"4. Final sub-problem integrates all components into the complete module",
|
|
1741
|
-
"5. Inject all hard constraints and the loop objective into the prompt",
|
|
1742
|
-
"6. If Cross-Round Summary is present above, incorporate its recurring issues and key lessons",
|
|
1743
|
-
"7. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1744
2097
|
];
|
|
2098
|
+
if (embedSkeleton) {
|
|
2099
|
+
lines.push("Generate a complete prompt using the **Least-to-Most** technique. Follow the 8-section skeleton below.", "Embed all structural constraints — do NOT reference an external file.", "");
|
|
2100
|
+
}
|
|
2101
|
+
else {
|
|
2102
|
+
lines.push("**Technique**: least-to-most (see state file for the full skeleton).", "Generate a complete prompt following this technique.", "");
|
|
2103
|
+
}
|
|
2104
|
+
lines.push(...buildLoopObjectiveBlock(loopObjective), ...buildConstraintsBlock(constraints), ...(request.plan_source ? [`**Plan Source**: ${request.plan_source}`, ""] : []), ...buildTaskBlock(ctx), ...buildIdentityBlock(ctx));
|
|
2105
|
+
if (embedSkeleton) {
|
|
2106
|
+
lines.push("", "### 8-Section Skeleton (REQUIRED)", "", "| # | Section | Required | Notes |", "|---|---------|----------|-------|", "| 1 | 角色 | ✓ | Domain expert role appropriate to the task |", "| 2 | 任务 | ✓ | One-sentence task summary |", "| 3 | 输入 | ✓ | Target module |", "| 4 | 输出格式 | ✓ | Numbered list (e.g. \"1. DDL, 2. API, 3. Enum...\") |", "| 5 | **Least-to-Most 逐步推理框架** | ✓ | See format rules below |", "| 6 | 具体实现要求 | ✓ | Expand per output format list (NOT per sub-problem — sub-problems fully expanded in §5) |", "| 7 | 硬约束 | ✓ | Numbered list |", "| 8 | 生成要求 | ✓ | Acceptance criteria, must include: \"严格按照子问题顺序逐步实现\" |", "", "### Section 5 — Least-to-Most 逐步推理框架 (CRITICAL)", "", "Must contain **4-6 ordered sub-problems**. Each sub-problem format:", "", "```", "### 子问题 N:[Sub-problem Name]", "", "**目标:** [What this sub-problem solves, which prior sub-problem(s) it depends on]", "", "**要求:**", "- Specific requirement list", "- [If sub-problem involves enums/mapping tables, list them]", "", "---", "```", "", "**Critical rules**:", "- Sub-problem 1 is the SIMPLEST (e.g. \"Define enums and base data structures\")", "- Each sub-problem declares: \"基于子问题 N-1 的结论\"", "- The LAST sub-problem MUST be: **\"综合实现完整模块\"** — list all components to integrate", "- Sub-problems separated by `---`", "- Order goes from LEAST complex → MOST complex, dependency chain must be explicit", "- Each sub-problem must serve the original task — no unrelated steps", "- The final sub-problem must be equivalent to or directly address the original task", "", "### Generation Instructions", "1. Decompose the task into 4-6 ordered sub-problems with explicit dependencies", "2. Sub-problem 1 starts with the simplest building block (data structures, enums, base config)", "3. Each subsequent sub-problem builds on prior results", "4. Final sub-problem integrates all components into the complete module", "5. Verify: the generated prompt satisfies all structural rules in the skeleton above", "6. Output only the generated prompt — no preamble, no meta-commentary");
|
|
2107
|
+
}
|
|
1745
2108
|
return lines.join("\n");
|
|
1746
2109
|
}
|
|
1747
2110
|
// ── Tree-of-Thought specialist ──────────────────────────────────────────────────
|
|
1748
|
-
function compileToT(request, goalId, constraints, loopObjective, rollingText, analysis) {
|
|
1749
|
-
const
|
|
2111
|
+
function compileToT(request, goalId, constraints, loopObjective, rollingText, analysis, opts = {}) {
|
|
2112
|
+
const embedSkeleton = opts.embedSkeleton !== false; // default true
|
|
2113
|
+
const ctx = { request, goalId, constraints, loopObjective, rollingText, level: opts.level ?? "l2" };
|
|
1750
2114
|
const lines = [
|
|
1751
2115
|
...buildHeader(ctx, "tree-of-thought"),
|
|
1752
2116
|
`**Rationale**: ${analysis.rationale}`,
|
|
1753
2117
|
"",
|
|
1754
|
-
"Generate a complete prompt using the **Tree-of-Thought** technique. Follow the 8-section skeleton below.",
|
|
1755
|
-
"Embed all structural constraints — do NOT reference an external file.",
|
|
1756
|
-
"",
|
|
1757
|
-
...buildLoopObjectiveBlock(loopObjective),
|
|
1758
|
-
...buildConstraintsBlock(constraints),
|
|
1759
|
-
...(request.plan_source ? [`**Plan Source**: ${request.plan_source}`, ""] : []),
|
|
1760
|
-
...buildTaskBlock(ctx),
|
|
1761
|
-
...buildIdentityBlock(ctx),
|
|
1762
|
-
"",
|
|
1763
|
-
"### 8-Section Skeleton (REQUIRED)",
|
|
1764
|
-
"",
|
|
1765
|
-
"| # | Section | Required | Notes |",
|
|
1766
|
-
"|---|---------|----------|-------|",
|
|
1767
|
-
"| 1 | 角色 | ✓ | Multi-path problem-solving expert; may introduce 3 expert personas |",
|
|
1768
|
-
"| 2 | 任务 | ✓ | One-sentence task summary |",
|
|
1769
|
-
"| 3 | 输入 | ✓ | High-risk complex problem |",
|
|
1770
|
-
"| 4 | 输出格式 | ✓ | \"先输出思维树过程,再输出最终答案/方案\" |",
|
|
1771
|
-
"| 5 | **思维树探索框架** | ✓ | See 3 sub-blocks below (REQUIRED) |",
|
|
1772
|
-
"| 6 | 具体实现要求 | ✓ | Expand per output format |",
|
|
1773
|
-
"| 7 | 硬约束 | ✓ | Include branch_count, max_depth, pruning rules, safety/performance constraints |",
|
|
1774
|
-
"| 8 | 生成要求 | ✓ | \"先探索多路径,再选择最优方案\" |",
|
|
1775
|
-
"",
|
|
1776
|
-
"### Section 5 — 思维树探索框架 (CRITICAL — 3 sub-blocks REQUIRED)",
|
|
1777
|
-
"",
|
|
1778
|
-
"**Sub-block A: Search Strategy Declaration**",
|
|
1779
|
-
"```",
|
|
1780
|
-
"搜索策略: [beam / dfs / expert-panel]",
|
|
1781
|
-
"分支数(branch_count): 2-4",
|
|
1782
|
-
"最大深度(max_depth): ≤3",
|
|
1783
|
-
"每轮保留数(keep_count): 1-2",
|
|
1784
|
-
"```",
|
|
1785
|
-
"Strategy selection guide:",
|
|
1786
|
-
"- **beam** (default): planning, creative, math search — generate multiple candidates per turn, keep top b",
|
|
1787
|
-
"- **dfs**: puzzles, constraint satisfaction, debugging — go deep on highest-score branch, backtrack on failure",
|
|
1788
|
-
"- **expert-panel**: 3+ experts each generate candidates, then cross-evaluate and revise",
|
|
1789
|
-
"",
|
|
1790
|
-
"**Sub-block B: Evaluation Criteria Table**",
|
|
1791
|
-
"| 标准 | 权重 | 说明 |",
|
|
1792
|
-
"|------|------|------|",
|
|
1793
|
-
"| 正确性 | 最高 | Logic/math correctness |",
|
|
1794
|
-
"| 可行性 | 高 | Can be actually implemented |",
|
|
1795
|
-
"| 约束匹配 | 高 | Satisfies hard constraints |",
|
|
1796
|
-
"| 性能 | 中 | Time/space efficiency |",
|
|
1797
|
-
"| 安全性 | 最高 | No vulnerabilities/privilege escalation |",
|
|
1798
|
-
"",
|
|
1799
|
-
"**Sub-block C: Thought Tree State Table Format**",
|
|
1800
|
-
"Require the model to output in this table format:",
|
|
1801
|
-
"```",
|
|
1802
|
-
"| 轮次 | 分支 | 候选方案 | 评估 | 决策 |",
|
|
1803
|
-
"|------|------|---------|------|------|",
|
|
1804
|
-
"| 1 | A | [方案描述] | 评分/判断 | 保留/剪枝 |",
|
|
1805
|
-
"| 1 | B | [方案描述] | 评分/判断 | 保留/剪枝 |",
|
|
1806
|
-
"| 1 | C | [方案描述] | 评分/判断 | 保留/剪枝 |",
|
|
1807
|
-
"| 2 | A1 | [深入展开] | ... | ... |",
|
|
1808
|
-
"| ... | ... | ... | ... | ... |",
|
|
1809
|
-
"",
|
|
1810
|
-
"最终选择: [最优方案 + 选择理由]",
|
|
1811
|
-
"```",
|
|
1812
|
-
"",
|
|
1813
|
-
"### Critical Rules",
|
|
1814
|
-
"- Hard constraints MUST be ranked FIRST in evaluation criteria",
|
|
1815
|
-
"- Branch count: 2-4, depth: ≤3 (prevents token explosion)",
|
|
1816
|
-
"- Every turn: generate multiple candidates → evaluate → keep/prune — never single-path",
|
|
1817
|
-
"- If all branches are low quality: backtrack to previous turn and regenerate",
|
|
1818
|
-
"- The core of ToT is NOT \"3 experts chatting\" — it's: candidate generation → state evaluation → search/prune → final selection",
|
|
1819
|
-
"- thought is a PUBLIC intermediate semantic unit, not hidden chain-of-thought",
|
|
1820
|
-
"- State table MUST have: round, branch ID, candidate description, evaluation score/judgment, keep/prune decision",
|
|
1821
|
-
"",
|
|
1822
|
-
"### Quality Checklist",
|
|
1823
|
-
"- [ ] Branch count 2-4, depth ≤3 (prevents token explosion)",
|
|
1824
|
-
"- [ ] Evaluation criteria table has ≥3 dimensions including correctness, feasibility, constraint matching",
|
|
1825
|
-
"- [ ] State table format explicit (round/branch/candidate/evaluation/decision)",
|
|
1826
|
-
"- [ ] Search strategy matches task type (beam/dfs/expert-panel)",
|
|
1827
|
-
"- [ ] Hard constraints listed FIRST in evaluation criteria",
|
|
1828
|
-
"- [ ] Not \"3 experts casually discussing\" — has explicit branch count, depth, scoring, and pruning rules",
|
|
1829
|
-
"- [ ] Ends with model outputting final selection and rationale",
|
|
1830
|
-
"",
|
|
1831
|
-
"### Generation Instructions",
|
|
1832
|
-
"1. Select the appropriate search strategy (beam/dfs/expert-panel) based on task type",
|
|
1833
|
-
"2. Define 2-4 candidate approaches as initial branches",
|
|
1834
|
-
"3. Build evaluation criteria table with hard constraints ranked first",
|
|
1835
|
-
"4. Define the state table format for tracking exploration",
|
|
1836
|
-
"5. Set branch count and max depth to prevent token explosion",
|
|
1837
|
-
"6. Inject all hard constraints and the loop objective into the prompt",
|
|
1838
|
-
"7. If Cross-Round Summary is present above, incorporate its recurring issues and key lessons",
|
|
1839
|
-
"8. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1840
2118
|
];
|
|
2119
|
+
if (embedSkeleton) {
|
|
2120
|
+
lines.push("Generate a complete prompt using the **Tree-of-Thought** technique. Follow the 8-section skeleton below.", "Embed all structural constraints — do NOT reference an external file.", "");
|
|
2121
|
+
}
|
|
2122
|
+
else {
|
|
2123
|
+
lines.push("**Technique**: tree-of-thought (see state file for the full skeleton).", "Generate a complete prompt following this technique.", "");
|
|
2124
|
+
}
|
|
2125
|
+
lines.push(...buildLoopObjectiveBlock(loopObjective), ...buildConstraintsBlock(constraints), ...(request.plan_source ? [`**Plan Source**: ${request.plan_source}`, ""] : []), ...buildTaskBlock(ctx), ...buildIdentityBlock(ctx));
|
|
2126
|
+
if (embedSkeleton) {
|
|
2127
|
+
lines.push("", "### 8-Section Skeleton (REQUIRED)", "", "| # | Section | Required | Notes |", "|---|---------|----------|-------|", "| 1 | 角色 | ✓ | Multi-path problem-solving expert; may introduce 3 expert personas |", "| 2 | 任务 | ✓ | One-sentence task summary |", "| 3 | 输入 | ✓ | High-risk complex problem |", "| 4 | 输出格式 | ✓ | \"先输出思维树过程,再输出最终答案/方案\" |", "| 5 | **思维树探索框架** | ✓ | See 3 sub-blocks below (REQUIRED) |", "| 6 | 具体实现要求 | ✓ | Expand per output format |", "| 7 | 硬约束 | ✓ | Include branch_count, max_depth, pruning rules, safety/performance constraints |", "| 8 | 生成要求 | ✓ | \"先探索多路径,再选择最优方案\" |", "", "### Section 5 — 思维树探索框架 (CRITICAL — 3 sub-blocks REQUIRED)", "", "**Sub-block A: Search Strategy Declaration**", "```", "搜索策略: [beam / dfs / expert-panel]", "分支数(branch_count): 2-4", "最大深度(max_depth): ≤3", "每轮保留数(keep_count): 1-2", "```", "Strategy selection guide:", "- **beam** (default): planning, creative, math search — generate multiple candidates per turn, keep top b", "- **dfs**: puzzles, constraint satisfaction, debugging — go deep on highest-score branch, backtrack on failure", "- **expert-panel**: 3+ experts each generate candidates, then cross-evaluate and revise", "", "**Sub-block B: Evaluation Criteria Table**", "| 标准 | 权重 | 说明 |", "|------|------|------|", "| 正确性 | 最高 | Logic/math correctness |", "| 可行性 | 高 | Can be actually implemented |", "| 约束匹配 | 高 | Satisfies hard constraints |", "| 性能 | 中 | Time/space efficiency |", "| 安全性 | 最高 | No vulnerabilities/privilege escalation |", "", "**Sub-block C: Thought Tree State Table Format**", "Require the model to output in this table format:", "```", "| 轮次 | 分支 | 候选方案 | 评估 | 决策 |", "|------|------|---------|------|------|", "| 1 | A | [方案描述] | 评分/判断 | 保留/剪枝 |", "| 1 | B | [方案描述] | 评分/判断 | 保留/剪枝 |", "| 1 | C | [方案描述] | 评分/判断 | 保留/剪枝 |", "| 2 | A1 | [深入展开] | ... | ... |", "| ... | ... | ... | ... | ... |", "", "最终选择: [最优方案 + 选择理由]", "```", "", "### Critical Rules", "- Hard constraints MUST be ranked FIRST in evaluation criteria", "- Branch count: 2-4, depth: ≤3 (prevents token explosion)", "- Every turn: generate multiple candidates → evaluate → keep/prune — never single-path", "- If all branches are low quality: backtrack to previous turn and regenerate", "- The core of ToT is NOT \"3 experts chatting\" — it's: candidate generation → state evaluation → search/prune → final selection", "- thought is a PUBLIC intermediate semantic unit, not hidden chain-of-thought", "- State table MUST have: round, branch ID, candidate description, evaluation score/judgment, keep/prune decision", "", "### Generation Instructions", "1. Select the appropriate search strategy (beam/dfs/expert-panel) based on task type", "2. Define 2-4 candidate approaches as initial branches", "3. Build evaluation criteria table with hard constraints ranked first", "4. Define the state table format for tracking exploration", "5. Set branch count and max depth to prevent token explosion", "6. Verify: the generated prompt satisfies all structural rules in the skeleton above", "7. Output only the generated prompt — no preamble, no meta-commentary");
|
|
2128
|
+
}
|
|
1841
2129
|
return lines.join("\n");
|
|
1842
2130
|
}
|
|
1843
2131
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -1874,7 +2162,9 @@ export function buildSelfEvalBlock(round) {
|
|
|
1874
2162
|
' },',
|
|
1875
2163
|
' "retracted_constraints": [],',
|
|
1876
2164
|
' "revised_success_criteria": [],',
|
|
1877
|
-
' "wrong_assumptions": []',
|
|
2165
|
+
' "wrong_assumptions": [],',
|
|
2166
|
+
' "compression_checkpoint": false,',
|
|
2167
|
+
' "checkpoint_label": ""',
|
|
1878
2168
|
"}",
|
|
1879
2169
|
"---end-loopforge-eval",
|
|
1880
2170
|
"```",
|
|
@@ -1907,6 +2197,15 @@ export function buildSelfEvalBlock(round) {
|
|
|
1907
2197
|
`Array of {old, new} objects. Applied to the Loop Objective. Omit or [] if none.`,
|
|
1908
2198
|
`- wrong_assumptions (P5 — optional): Assumptions from earlier rounds that turned ` +
|
|
1909
2199
|
`out to be incorrect. Recorded as key lessons. Omit or [] if none.`,
|
|
2200
|
+
`- compression_checkpoint (v1.10 — optional): Set to true when you have COMPLETED a ` +
|
|
2201
|
+
`major subtask. The compiler will snapshot the current state (constraints, outcome) ` +
|
|
2202
|
+
`into a persistent checkpoint that survives context-window eviction. ` +
|
|
2203
|
+
`Use sparingly — only at genuine subtask boundaries, not every round. ` +
|
|
2204
|
+
`Omit or false otherwise.`,
|
|
2205
|
+
`- checkpoint_label (v1.10 — optional): Human-readable label for this checkpoint. ` +
|
|
2206
|
+
`e.g. "数据模型层完成", "API layer complete", "Auth module done". ` +
|
|
2207
|
+
`Used as the checkpoint heading in subsequent prompts. Only meaningful when ` +
|
|
2208
|
+
`compression_checkpoint is true. Omit or "" otherwise.`,
|
|
1910
2209
|
`- The JSON MUST appear between the ---loopforge-eval and ---end-loopforge-eval markers`,
|
|
1911
2210
|
`- Do NOT wrap the markers in code fences — output them as raw text`,
|
|
1912
2211
|
"",
|