loopforge 1.12.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -95
- package/dist/backends/interface.d.ts +3 -9
- package/dist/backends/interface.d.ts.map +1 -1
- package/dist/backends/interface.js +1 -5
- package/dist/backends/interface.js.map +1 -1
- package/dist/canonical-state.d.ts +53 -0
- package/dist/canonical-state.d.ts.map +1 -0
- package/dist/canonical-state.js +161 -0
- package/dist/canonical-state.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +227 -0
- package/dist/cli.js.map +1 -0
- package/dist/enforcement-gate.d.ts +44 -0
- package/dist/enforcement-gate.d.ts.map +1 -0
- package/dist/enforcement-gate.js +271 -0
- package/dist/enforcement-gate.js.map +1 -0
- package/dist/engine.d.ts +17 -7
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +158 -249
- package/dist/engine.js.map +1 -1
- package/dist/evidence-provider.d.ts +113 -0
- package/dist/evidence-provider.d.ts.map +1 -0
- package/dist/evidence-provider.js +419 -0
- package/dist/evidence-provider.js.map +1 -0
- package/dist/index.d.ts +19 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -10
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +15 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +49 -0
- package/dist/init.js.map +1 -0
- package/dist/interop.d.ts +30 -0
- package/dist/interop.d.ts.map +1 -0
- package/dist/interop.js +29 -0
- package/dist/interop.js.map +1 -0
- package/dist/loop-compiler.d.ts +14 -64
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +307 -2001
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-store.d.ts +82 -0
- package/dist/loop-store.d.ts.map +1 -0
- package/dist/loop-store.js +332 -0
- package/dist/loop-store.js.map +1 -0
- package/dist/mcp/server.d.ts +9 -6
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +109 -73
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +84 -20
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +718 -316
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts +18 -268
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +225 -12
- package/dist/mcp/tools.js.map +1 -1
- package/dist/observability.d.ts +36 -16
- package/dist/observability.d.ts.map +1 -1
- package/dist/observability.js +82 -23
- package/dist/observability.js.map +1 -1
- package/dist/policy-metrics.d.ts +46 -0
- package/dist/policy-metrics.d.ts.map +1 -0
- package/dist/policy-metrics.js +146 -0
- package/dist/policy-metrics.js.map +1 -0
- package/dist/policy.d.ts +58 -75
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +122 -93
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts +30 -0
- package/dist/prompt-assembler.d.ts.map +1 -0
- package/dist/prompt-assembler.js +257 -0
- package/dist/prompt-assembler.js.map +1 -0
- package/dist/prompt-policy.d.ts +29 -0
- package/dist/prompt-policy.d.ts.map +1 -0
- package/dist/prompt-policy.js +51 -0
- package/dist/prompt-policy.js.map +1 -0
- package/dist/protocol.d.ts +117 -91
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +40 -36
- package/dist/protocol.js.map +1 -1
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +1 -27
- package/dist/replay.js.map +1 -1
- package/dist/round-coordinator.d.ts +90 -0
- package/dist/round-coordinator.d.ts.map +1 -0
- package/dist/round-coordinator.js +186 -0
- package/dist/round-coordinator.js.map +1 -0
- package/dist/round-driver.d.ts +53 -0
- package/dist/round-driver.d.ts.map +1 -0
- package/dist/round-driver.js +101 -0
- package/dist/round-driver.js.map +1 -0
- package/dist/round-transaction.d.ts +70 -0
- package/dist/round-transaction.d.ts.map +1 -0
- package/dist/round-transaction.js +290 -0
- package/dist/round-transaction.js.map +1 -0
- package/dist/runtime.d.ts +35 -17
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +357 -344
- package/dist/runtime.js.map +1 -1
- package/dist/storage.d.ts +38 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +201 -0
- package/dist/storage.js.map +1 -0
- package/dist/verification-gate.d.ts +39 -7
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +198 -9
- package/dist/verification-gate.js.map +1 -1
- package/loop_policy.json +26 -47
- package/package.json +13 -10
- package/skills/perception/SKILL.md +80 -249
- package/dist/backends/fs.d.ts +0 -36
- package/dist/backends/fs.d.ts.map +0 -1
- package/dist/backends/fs.js +0 -358
- package/dist/backends/fs.js.map +0 -1
- package/dist/builder.d.ts +0 -12
- package/dist/builder.d.ts.map +0 -1
- package/dist/builder.js +0 -259
- package/dist/builder.js.map +0 -1
- package/dist/mcp-server.d.ts +0 -12
- package/dist/mcp-server.d.ts.map +0 -1
- package/dist/mcp-server.js +0 -14
- package/dist/mcp-server.js.map +0 -1
- package/dist/memory-bridge.d.ts +0 -47
- package/dist/memory-bridge.d.ts.map +0 -1
- package/dist/memory-bridge.js +0 -290
- package/dist/memory-bridge.js.map +0 -1
- package/dist/tests/_helpers.d.ts +0 -25
- package/dist/tests/_helpers.d.ts.map +0 -1
- package/dist/tests/_helpers.js +0 -47
- package/dist/tests/_helpers.js.map +0 -1
- package/dist/tests/builder.test.d.ts +0 -2
- package/dist/tests/builder.test.d.ts.map +0 -1
- package/dist/tests/builder.test.js +0 -110
- package/dist/tests/builder.test.js.map +0 -1
- package/dist/tests/engine.test.d.ts +0 -2
- package/dist/tests/engine.test.d.ts.map +0 -1
- package/dist/tests/engine.test.js +0 -371
- package/dist/tests/engine.test.js.map +0 -1
- package/dist/tests/generate-schema.test.d.ts +0 -8
- package/dist/tests/generate-schema.test.d.ts.map +0 -1
- package/dist/tests/generate-schema.test.js +0 -199
- package/dist/tests/generate-schema.test.js.map +0 -1
- package/dist/tests/loop-compiler.test.d.ts +0 -2
- package/dist/tests/loop-compiler.test.d.ts.map +0 -1
- package/dist/tests/loop-compiler.test.js +0 -1051
- package/dist/tests/loop-compiler.test.js.map +0 -1
- package/dist/tests/mcp.test.d.ts +0 -7
- package/dist/tests/mcp.test.d.ts.map +0 -1
- package/dist/tests/mcp.test.js +0 -485
- package/dist/tests/mcp.test.js.map +0 -1
- package/dist/tests/memory-bridge.test.d.ts +0 -8
- package/dist/tests/memory-bridge.test.d.ts.map +0 -1
- package/dist/tests/memory-bridge.test.js +0 -383
- package/dist/tests/memory-bridge.test.js.map +0 -1
- package/dist/tests/policy.test.d.ts +0 -2
- package/dist/tests/policy.test.d.ts.map +0 -1
- package/dist/tests/policy.test.js +0 -49
- package/dist/tests/policy.test.js.map +0 -1
- package/dist/tests/protocol.test.d.ts +0 -2
- package/dist/tests/protocol.test.d.ts.map +0 -1
- package/dist/tests/protocol.test.js +0 -73
- package/dist/tests/protocol.test.js.map +0 -1
- package/dist/tests/replay.test.d.ts +0 -2
- package/dist/tests/replay.test.d.ts.map +0 -1
- package/dist/tests/replay.test.js +0 -222
- package/dist/tests/replay.test.js.map +0 -1
- package/dist/tests/runtime.test.d.ts +0 -7
- package/dist/tests/runtime.test.d.ts.map +0 -1
- package/dist/tests/runtime.test.js +0 -323
- package/dist/tests/runtime.test.js.map +0 -1
- package/dist/tests/verification-gate.test.d.ts +0 -2
- package/dist/tests/verification-gate.test.d.ts.map +0 -1
- package/dist/tests/verification-gate.test.js +0 -407
- package/dist/tests/verification-gate.test.js.map +0 -1
- package/skills/prompt-techniques/SKILL.md +0 -52
- package/skills/prompt-techniques/references/chain-of-thought.md +0 -207
- package/skills/prompt-techniques/references/few-shot.md +0 -174
- package/skills/prompt-techniques/references/least-to-most.md +0 -182
- package/skills/prompt-techniques/references/step-back.md +0 -160
- package/skills/prompt-techniques/references/tree-of-thought.md +0 -191
- package/skills/prompt-techniques/references/zero-shot.md +0 -97
package/dist/loop-compiler.js
CHANGED
|
@@ -1,2045 +1,351 @@
|
|
|
1
|
-
/** LoopForge-
|
|
1
|
+
/** LoopForge cognitive-state compiler.
|
|
2
2
|
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Two layers:
|
|
6
|
-
* Layer 1 (Hard Gates): decideLevel() — 4-gate routing that CAN change compile level.
|
|
7
|
-
* Layer 2 (Soft Advisories): computeAdvisories() — warnings/alignment/health, NEVER
|
|
8
|
-
* change compile level directly.
|
|
9
|
-
*
|
|
10
|
-
* Compilation: compileL0() / compileL1() / compileL2() produce the actual prompt.
|
|
3
|
+
* The compiler evolves structured state and renders one prompt artifact.
|
|
4
|
+
* L0/L1/L2 control state density only; the external Agent owns reasoning.
|
|
11
5
|
*/
|
|
12
6
|
import { createHash } from "node:crypto";
|
|
13
|
-
import { readFileSync } from "node:fs";
|
|
14
7
|
import { getPolicy } from "./policy.js";
|
|
15
8
|
import { AgentStatus, makeCheckpointSummary, makeLoopCompileResponse, makeLoopHealth, makeLoopObjective, makeRollingSummary, makeTaskAlignment, } from "./protocol.js";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
text += " " + (request.last_round_result.output_summary || "").toLowerCase();
|
|
28
|
-
if (request.last_round_result.manual_fixes_needed) {
|
|
29
|
-
text += " " + request.last_round_result.manual_fixes_needed.toLowerCase();
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return REPAIR_CUES.some((cue) => text.includes(cue));
|
|
33
|
-
}
|
|
34
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
35
|
-
// Tokenization helpers
|
|
36
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
37
|
-
export function tokenize(text) {
|
|
38
|
-
const tokens = text.split(/\s+/);
|
|
39
|
-
const result = new Set();
|
|
40
|
-
for (let token of tokens) {
|
|
41
|
-
token = token.trim().replace(/^[.,;:!?()[\]{}'"]+|[.,;:!?()[\]{}'"]+$/g, "");
|
|
42
|
-
if (token.length >= 2) {
|
|
43
|
-
result.add(/^[\x00-\x7F]*$/.test(token) ? token.toLowerCase() : token);
|
|
44
|
-
}
|
|
45
|
-
// Add individual CJK chars as standalone tokens
|
|
46
|
-
for (const ch of token) {
|
|
47
|
-
if ((ch >= "一" && ch <= "鿿") ||
|
|
48
|
-
(ch >= "" && ch <= "ヿ")) {
|
|
49
|
-
result.add(ch);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return result;
|
|
54
|
-
}
|
|
55
|
-
export function jaccard(a, b) {
|
|
56
|
-
if (a.size === 0 || b.size === 0)
|
|
57
|
-
return 0.0;
|
|
58
|
-
let intersection = 0;
|
|
59
|
-
for (const item of a) {
|
|
60
|
-
if (b.has(item))
|
|
61
|
-
intersection++;
|
|
62
|
-
}
|
|
63
|
-
const union = a.size + b.size - intersection;
|
|
64
|
-
return union > 0 ? intersection / union : 0.0;
|
|
65
|
-
}
|
|
66
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
67
|
-
// Delegation helpers (v1.9 — AgentTool mode lightweight utilities)
|
|
68
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
69
|
-
/** Filter relevant constraints for a sub-agent task using Jaccard token similarity.
|
|
70
|
-
* Returns constraints whose token overlap with the subTask exceeds the threshold.
|
|
71
|
-
* Default threshold 0.15 is intentionally lower than the 0.3/0.5 alignment thresholds
|
|
72
|
-
* — constraint filtering should err on the side of inclusion. */
|
|
73
|
-
export function filterConstraintsForSubTask(allConstraints, subTask, threshold = 0.15) {
|
|
74
|
-
if (!allConstraints.length || !subTask.trim())
|
|
75
|
-
return [];
|
|
76
|
-
const taskTokens = tokenize(subTask);
|
|
77
|
-
if (taskTokens.size === 0)
|
|
78
|
-
return [];
|
|
79
|
-
return allConstraints.filter((c) => {
|
|
80
|
-
const cTokens = tokenize(c);
|
|
81
|
-
return jaccard(taskTokens, cTokens) >= threshold;
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
/** Format a self-contained delegation prompt for a sub-agent.
|
|
85
|
-
* Produces a prompt that stands alone — no references to parent conversation,
|
|
86
|
-
* no "based on above", no "continue from previous". This matches the AgentTool
|
|
87
|
-
* contract: "Workers can't see your conversation." */
|
|
88
|
-
export function formatDelegationPrompt(subTask, subAgentType, relevantConstraints, options) {
|
|
89
|
-
const lines = [];
|
|
90
|
-
// Header
|
|
91
|
-
lines.push("### Delegated Task");
|
|
92
|
-
lines.push("");
|
|
93
|
-
lines.push(subTask);
|
|
94
|
-
lines.push("");
|
|
95
|
-
// Context (optional — e.g. file paths, module names)
|
|
96
|
-
if (options?.context) {
|
|
97
|
-
lines.push("### Context");
|
|
98
|
-
lines.push("");
|
|
99
|
-
lines.push(options.context);
|
|
100
|
-
lines.push("");
|
|
101
|
-
}
|
|
102
|
-
// Constraints (only for types that need them — Explore is read-only)
|
|
103
|
-
if (relevantConstraints.length && subAgentType !== "explore") {
|
|
104
|
-
lines.push("### Relevant Constraints");
|
|
105
|
-
for (const c of relevantConstraints)
|
|
106
|
-
lines.push(`- ${c}`);
|
|
107
|
-
lines.push("");
|
|
108
|
-
}
|
|
109
|
-
// Output format (for Explore and Plan agents)
|
|
110
|
-
if (subAgentType === "explore") {
|
|
111
|
-
if (options?.outputFormat) {
|
|
112
|
-
lines.push("### Output Format");
|
|
113
|
-
lines.push("");
|
|
114
|
-
lines.push(options.outputFormat);
|
|
115
|
-
lines.push("");
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
lines.push("### Output Format");
|
|
119
|
-
lines.push("");
|
|
120
|
-
lines.push("Report findings as a structured list with file paths and line numbers.");
|
|
121
|
-
lines.push("");
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (subAgentType === "plan") {
|
|
125
|
-
lines.push("### Instructions");
|
|
126
|
-
lines.push("");
|
|
127
|
-
lines.push("Design a concrete implementation plan. Output must include:");
|
|
128
|
-
lines.push("- Step-by-step approach");
|
|
129
|
-
lines.push("- Critical files for implementation (3-5 files with paths)");
|
|
130
|
-
lines.push("- Key design decisions and tradeoffs");
|
|
131
|
-
lines.push("");
|
|
132
|
-
}
|
|
133
|
-
if (subAgentType === "general-purpose") {
|
|
134
|
-
lines.push("### Instructions");
|
|
135
|
-
lines.push("");
|
|
136
|
-
lines.push("Complete the task fully — don't gold-plate, but don't leave it half-done.");
|
|
137
|
-
lines.push("Report what was done, what files were changed, and any issues encountered.");
|
|
138
|
-
lines.push("");
|
|
139
|
-
}
|
|
140
|
-
if (!["explore", "general-purpose", "plan"].includes(subAgentType)) {
|
|
141
|
-
// Custom sub-agent type — generic template
|
|
142
|
-
lines.push("### Instructions");
|
|
143
|
-
lines.push("");
|
|
144
|
-
lines.push("Complete the task above and report results concisely.");
|
|
145
|
-
lines.push("");
|
|
146
|
-
}
|
|
147
|
-
return lines.join("\n");
|
|
148
|
-
}
|
|
149
|
-
/** Build a delegation history summary from vault context (v1.9 — multi-agent).
|
|
150
|
-
* Scans vault for delegation_journal entries and formats them as a table.
|
|
151
|
-
* Returns empty string if no delegation history exists. */
|
|
152
|
-
export function buildDelegationSummary(vaultContext) {
|
|
153
|
-
if (!vaultContext)
|
|
154
|
-
return "";
|
|
155
|
-
const results = vaultContext.results;
|
|
156
|
-
if (!results?.length)
|
|
157
|
-
return "";
|
|
158
|
-
const delegations = results.filter((r) => r?.task_type === "delegation_journal");
|
|
159
|
-
if (!delegations.length)
|
|
160
|
-
return "";
|
|
161
|
-
// Helper: escape pipe characters so they don't break the markdown table
|
|
162
|
-
const esc = (s) => s.replace(/\|/g, "\\|");
|
|
163
|
-
// Collect all delegation entries across rounds
|
|
164
|
-
const entries = [];
|
|
165
|
-
for (const d of delegations) {
|
|
166
|
-
const lineage = d.loop_lineage;
|
|
167
|
-
const round = typeof lineage?.round === "number" ? lineage.round : 0;
|
|
168
|
-
const dels = lineage?.delegations;
|
|
169
|
-
if (!dels?.length)
|
|
170
|
-
continue;
|
|
171
|
-
for (const e of dels) {
|
|
172
|
-
entries.push({
|
|
173
|
-
round,
|
|
174
|
-
agentId: typeof e.agentId === "string" ? e.agentId : (typeof e.index === "number" ? `worker-${e.index}` : "?"),
|
|
175
|
-
type: typeof e.subAgentType === "string" ? e.subAgentType : "?",
|
|
176
|
-
task: typeof e.subTask === "string" ? e.subTask : "?",
|
|
177
|
-
result: typeof e.resultSummary === "string" ? e.resultSummary : "?",
|
|
178
|
-
success: typeof e.success === "boolean" ? e.success : true,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
if (!entries.length)
|
|
183
|
-
return "";
|
|
184
|
-
const lines = [
|
|
185
|
-
"",
|
|
186
|
-
"### Delegation History (Multi-Agent)",
|
|
187
|
-
"",
|
|
188
|
-
"| Round | Agent | Type | Task | Result | ✓/✗ |",
|
|
189
|
-
"|-------|-------|------|------|--------|-----|",
|
|
190
|
-
];
|
|
191
|
-
for (const e of entries) {
|
|
192
|
-
const status = e.success ? "✓" : "✗";
|
|
193
|
-
const shortAgent = e.agentId.length > 24 ? e.agentId.slice(0, 21) + "..." : e.agentId;
|
|
194
|
-
const shortTask = e.task.length > 50 ? e.task.slice(0, 47) + "..." : e.task;
|
|
195
|
-
const shortResult = e.result.length > 40 ? e.result.slice(0, 37) + "..." : e.result;
|
|
196
|
-
lines.push(`| R${e.round} | ${esc(shortAgent)} | ${e.type} | ${esc(shortTask)} | ${esc(shortResult)} | ${status} |`);
|
|
197
|
-
}
|
|
198
|
-
lines.push("");
|
|
199
|
-
return lines.join("\n");
|
|
200
|
-
}
|
|
201
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
202
|
-
// Goal identity
|
|
203
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
204
|
-
export function computeGoalTextHash(task) {
|
|
205
|
-
const normalized = (task || "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
206
|
-
return createHash("sha256").update(normalized, "utf-8").digest("hex").slice(0, 12);
|
|
207
|
-
}
|
|
208
|
-
export function deriveGoalId(loopId, task, explicitGoalId = "") {
|
|
209
|
-
if (explicitGoalId)
|
|
210
|
-
return explicitGoalId;
|
|
211
|
-
let taskPrefix = (task || "unnamed").slice(0, 60).trim().toLowerCase();
|
|
212
|
-
taskPrefix = taskPrefix.replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
213
|
-
return loopId ? `${loopId}:${taskPrefix}` : taskPrefix;
|
|
214
|
-
}
|
|
215
|
-
export function getPreviousRound(loopId, roundNum, vaultContext) {
|
|
216
|
-
if (vaultContext === null)
|
|
217
|
-
return null;
|
|
218
|
-
const results = vaultContext.results || [];
|
|
219
|
-
if (!results.length)
|
|
220
|
-
return null;
|
|
221
|
-
for (const r of results) {
|
|
222
|
-
const lineage = (r.loop_lineage || r.lineage || {});
|
|
223
|
-
if (lineage.loop_id === loopId && lineage.round === roundNum) {
|
|
224
|
-
return {
|
|
225
|
-
goal_id: lineage.goal_id ?? "",
|
|
226
|
-
goal_text_hash: lineage.goal_text_hash ?? "",
|
|
227
|
-
success: r.success ?? lineage.success ?? true,
|
|
228
|
-
task: r.task ?? r.user_intent ?? "",
|
|
229
|
-
constraints_active: lineage.constraints_active ?? [],
|
|
230
|
-
prompt_text: r.full_prompt ?? "",
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
function getRecentRounds(loopId, n, vaultContext) {
|
|
237
|
-
if (vaultContext === null)
|
|
9
|
+
import { createCanonicalLoopState, renderCanonicalStateMarkdown, } from "./canonical-state.js";
|
|
10
|
+
import { assemblePromptArtifact } from "./prompt-assembler.js";
|
|
11
|
+
import { decidePromptLevel, } from "./prompt-policy.js";
|
|
12
|
+
function unique(values) {
|
|
13
|
+
return [...new Set(values
|
|
14
|
+
.filter((value) => typeof value === "string")
|
|
15
|
+
.map((value) => value.trim())
|
|
16
|
+
.filter(Boolean))];
|
|
17
|
+
}
|
|
18
|
+
function contextEntries(context) {
|
|
19
|
+
if (!context || !Array.isArray(context.results))
|
|
238
20
|
return [];
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
21
|
+
return context.results.filter((value) => value !== null && typeof value === "object" && !Array.isArray(value));
|
|
22
|
+
}
|
|
23
|
+
function lineage(entry) {
|
|
24
|
+
const value = entry.loop_lineage ?? entry.lineage;
|
|
25
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
26
|
+
? value
|
|
27
|
+
: {};
|
|
28
|
+
}
|
|
29
|
+
function entryRound(entry) {
|
|
30
|
+
const value = lineage(entry).round;
|
|
31
|
+
return typeof value === "number" ? value : 0;
|
|
32
|
+
}
|
|
33
|
+
function loopEntries(loopId, context) {
|
|
34
|
+
return contextEntries(context)
|
|
35
|
+
.filter((entry) => entry.loop_id === loopId || lineage(entry).loop_id === loopId)
|
|
36
|
+
.sort((a, b) => entryRound(a) - entryRound(b));
|
|
37
|
+
}
|
|
38
|
+
export function computeGoalTextHash(text) {
|
|
39
|
+
const normalized = text.trim().replace(/\s+/g, " ").toLowerCase();
|
|
40
|
+
return createHash("sha256").update(normalized).digest("hex").slice(0, 12);
|
|
41
|
+
}
|
|
42
|
+
export function deriveGoalId(loopId, task, explicit = "") {
|
|
43
|
+
return explicit.trim() || `${loopId}:${task.trim().toLowerCase()
|
|
44
|
+
.replace(/[^a-z0-9\u4e00-\u9fff]+/g, "-")
|
|
45
|
+
.replace(/^-|-$/g, "")
|
|
46
|
+
.slice(0, 48) || computeGoalTextHash(task)}`;
|
|
47
|
+
}
|
|
48
|
+
export function getPreviousRound(loopId, round, context) {
|
|
49
|
+
const entry = [...loopEntries(loopId, context)].reverse()
|
|
50
|
+
.find((candidate) => entryRound(candidate) === round);
|
|
51
|
+
if (!entry)
|
|
260
52
|
return null;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
function countConsecutiveHashMismatches(loopId, vaultContext) {
|
|
280
|
-
if (vaultContext === null)
|
|
281
|
-
return 0;
|
|
282
|
-
const rounds = getRecentRounds(loopId, 20, vaultContext);
|
|
283
|
-
if (rounds.length < 2)
|
|
284
|
-
return 0;
|
|
285
|
-
let count = 0;
|
|
286
|
-
for (let i = 0; i < rounds.length - 1; i++) {
|
|
287
|
-
const currHash = rounds[i].goal_text_hash;
|
|
288
|
-
const prevHash = rounds[i + 1].goal_text_hash;
|
|
289
|
-
if (currHash && prevHash && currHash !== prevHash) {
|
|
290
|
-
count++;
|
|
291
|
-
}
|
|
292
|
-
else {
|
|
293
|
-
break;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return count;
|
|
297
|
-
}
|
|
298
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
299
|
-
// Strategy collapse detection
|
|
300
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
301
|
-
function strategyCollapse(loopId, vaultContext) {
|
|
302
|
-
const recent = getRecentRounds(loopId, 3, vaultContext);
|
|
303
|
-
if (recent.length < 3)
|
|
304
|
-
return false;
|
|
305
|
-
return recent.every((r) => r.success === false);
|
|
306
|
-
}
|
|
307
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
308
|
-
// Constraint Retirement (v3.5)
|
|
309
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
310
|
-
function computeConstraintRetirement(activeConstraints, loopId, currentRound, vaultContext) {
|
|
311
|
-
if (!activeConstraints.length || vaultContext === null) {
|
|
312
|
-
return { active: [...activeConstraints], retired: [] };
|
|
313
|
-
}
|
|
314
|
-
const results = vaultContext.results || [];
|
|
315
|
-
if (!results.length) {
|
|
316
|
-
return { active: [...activeConstraints], retired: [] };
|
|
317
|
-
}
|
|
318
|
-
const policy = getPolicy();
|
|
319
|
-
const window = policy.constraints.retire_window;
|
|
320
|
-
const targetRounds = new Set();
|
|
321
|
-
for (let r = currentRound - window; r < currentRound; r++) {
|
|
322
|
-
targetRounds.add(r);
|
|
323
|
-
}
|
|
324
|
-
// Collect all text per target round for activity detection
|
|
325
|
-
const roundTexts = new Map();
|
|
326
|
-
for (const r of results) {
|
|
327
|
-
const lineage = (r.loop_lineage || r.lineage || {});
|
|
328
|
-
if (lineage.loop_id !== loopId)
|
|
329
|
-
continue;
|
|
330
|
-
const rnd = lineage.round;
|
|
331
|
-
if (!targetRounds.has(rnd))
|
|
332
|
-
continue;
|
|
333
|
-
let text = ((r.task ?? r.user_intent ?? "") + " " +
|
|
334
|
-
(lineage.task ?? "") + " " +
|
|
335
|
-
(r.output_summary ?? "")).toLowerCase();
|
|
336
|
-
for (const v of r.constraint_violations || []) {
|
|
337
|
-
text += " " + String(v).toLowerCase();
|
|
338
|
-
}
|
|
339
|
-
roundTexts.set(rnd, text);
|
|
340
|
-
}
|
|
341
|
-
const retired = [];
|
|
342
|
-
const pruned = [];
|
|
343
|
-
for (const constraint of activeConstraints) {
|
|
344
|
-
const cLower = constraint.toLowerCase();
|
|
345
|
-
const cNormalized = cLower.replace(/-/g, " ");
|
|
346
|
-
let isActive = false;
|
|
347
|
-
for (const text of roundTexts.values()) {
|
|
348
|
-
if (text.includes(cLower) || text.includes(cNormalized)) {
|
|
349
|
-
isActive = true;
|
|
350
|
-
break;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (isActive || roundTexts.size < window) {
|
|
354
|
-
pruned.push(constraint);
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
retired.push(constraint);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
return { active: pruned, retired };
|
|
53
|
+
const data = lineage(entry);
|
|
54
|
+
return {
|
|
55
|
+
round,
|
|
56
|
+
goal_id: typeof data.goal_id === "string" ? data.goal_id : "",
|
|
57
|
+
goal_text_hash: typeof data.goal_text_hash === "string" ? data.goal_text_hash : "",
|
|
58
|
+
success: typeof entry.success === "boolean"
|
|
59
|
+
? entry.success
|
|
60
|
+
: typeof data.success === "boolean" ? data.success : false,
|
|
61
|
+
task: typeof data.task === "string"
|
|
62
|
+
? data.task
|
|
63
|
+
: typeof entry.task === "string" ? entry.task : "",
|
|
64
|
+
constraints_active: Array.isArray(data.constraints_active)
|
|
65
|
+
? data.constraints_active.filter((value) => typeof value === "string")
|
|
66
|
+
: [],
|
|
67
|
+
output_summary: typeof entry.output_summary === "string"
|
|
68
|
+
? entry.output_summary
|
|
69
|
+
: typeof data.output_summary === "string" ? data.output_summary : "",
|
|
70
|
+
};
|
|
361
71
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
function getPreviousRoundEvidence(loopId, roundNum, vaultContext) {
|
|
368
|
-
if (vaultContext === null)
|
|
369
|
-
return null;
|
|
370
|
-
const results = vaultContext.results || [];
|
|
371
|
-
for (const r of results) {
|
|
372
|
-
const lineage = (r.loop_lineage || r.lineage || {});
|
|
373
|
-
if (lineage.loop_id !== loopId)
|
|
374
|
-
continue;
|
|
375
|
-
if (lineage.round !== roundNum)
|
|
72
|
+
function latestObjective(loopId, context) {
|
|
73
|
+
const candidates = loopEntries(loopId, context).reverse();
|
|
74
|
+
for (const entry of candidates) {
|
|
75
|
+
const raw = entry.loop_objective;
|
|
76
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
376
77
|
continue;
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
78
|
+
const value = raw;
|
|
79
|
+
return makeLoopObjective({
|
|
80
|
+
objective: typeof value.objective === "string" ? value.objective : "",
|
|
81
|
+
success_criteria: Array.isArray(value.success_criteria)
|
|
82
|
+
? value.success_criteria.filter((item) => typeof item === "string")
|
|
83
|
+
: [],
|
|
84
|
+
hard_constraints: Array.isArray(value.hard_constraints)
|
|
85
|
+
? value.hard_constraints.filter((item) => typeof item === "string")
|
|
86
|
+
: [],
|
|
87
|
+
created_at_round: typeof value.created_at_round === "number" ? value.created_at_round : 1,
|
|
88
|
+
loop_id: loopId,
|
|
89
|
+
version: typeof value.version === "number" ? value.version : 1,
|
|
90
|
+
refinement_history: Array.isArray(value.refinement_history)
|
|
91
|
+
? value.refinement_history.filter((item) => typeof item === "string")
|
|
92
|
+
: [],
|
|
93
|
+
});
|
|
385
94
|
}
|
|
386
95
|
return null;
|
|
387
96
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const prev = getPreviousRoundEvidence(loopId, r - 1, vaultContext);
|
|
397
|
-
if (curr && prev) {
|
|
398
|
-
const delta = curr.progress_estimate - prev.progress_estimate;
|
|
399
|
-
if (delta < threshold && curr.progress_estimate < 0.95) {
|
|
400
|
-
count++;
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
break;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
break;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
return count;
|
|
411
|
-
}
|
|
412
|
-
/** Detect repeated failure patterns in sampled rounds.
|
|
413
|
-
* A failure pattern = 2+ consecutive rounds where:
|
|
414
|
-
* 1. success === false (failed)
|
|
415
|
-
* 2. same technique was used
|
|
416
|
-
* 3. task text is similar (Jaccard > 0.4)
|
|
417
|
-
*
|
|
418
|
-
* Returns human-readable descriptions for injection into the prompt. */
|
|
419
|
-
function detectFailurePatterns(sampled) {
|
|
420
|
-
if (sampled.length < 2)
|
|
421
|
-
return [];
|
|
422
|
-
const patterns = [];
|
|
423
|
-
let runStart = -1;
|
|
424
|
-
for (let i = 0; i < sampled.length; i++) {
|
|
425
|
-
const r = sampled[i];
|
|
426
|
-
const isFailed = r.success === false;
|
|
427
|
-
if (isFailed && runStart === -1) {
|
|
428
|
-
runStart = i;
|
|
429
|
-
}
|
|
430
|
-
else if (!isFailed && runStart !== -1) {
|
|
431
|
-
if (i - runStart >= 2) {
|
|
432
|
-
const pattern = classifyRun(sampled.slice(runStart, i));
|
|
433
|
-
if (pattern)
|
|
434
|
-
patterns.push(pattern);
|
|
435
|
-
}
|
|
436
|
-
runStart = -1;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
// Handle run at end of array
|
|
440
|
-
if (runStart !== -1 && sampled.length - runStart >= 2) {
|
|
441
|
-
const pattern = classifyRun(sampled.slice(runStart));
|
|
442
|
-
if (pattern)
|
|
443
|
-
patterns.push(pattern);
|
|
444
|
-
}
|
|
445
|
-
return patterns;
|
|
446
|
-
}
|
|
447
|
-
/** Classify a run of consecutive failed rounds as a failure pattern.
|
|
448
|
-
* Returns null if the rounds are too dissimilar to be the same pattern. */
|
|
449
|
-
function classifyRun(run) {
|
|
450
|
-
if (run.length < 2)
|
|
451
|
-
return null;
|
|
452
|
-
// Must all use the same technique
|
|
453
|
-
const technique = run[0].technique_used || "unknown";
|
|
454
|
-
if (!run.every((r) => (r.technique_used || "unknown") === technique)) {
|
|
455
|
-
return null;
|
|
456
|
-
}
|
|
457
|
-
// Task text must be similar across the run (pairwise Jaccard)
|
|
458
|
-
const tokensList = run.map((r) => tokenize(r.task.toLowerCase()));
|
|
459
|
-
for (let i = 0; i < tokensList.length - 1; i++) {
|
|
460
|
-
for (let j = i + 1; j < tokensList.length; j++) {
|
|
461
|
-
if (jaccard(tokensList[i], tokensList[j]) < 0.4)
|
|
462
|
-
return null;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
const rounds = `R${run[0].round}-R${run[run.length - 1].round}`;
|
|
466
|
-
const taskPreview = run[0].task.slice(0, 80);
|
|
467
|
-
const failureRate = `${run.length}/${run.length}`;
|
|
468
|
-
return (`technique '${technique}' on task '${taskPreview}' ` +
|
|
469
|
-
`failed ${run.length} consecutive rounds (${rounds}) — ` +
|
|
470
|
-
`consider strategy change`);
|
|
471
|
-
}
|
|
472
|
-
/** Extract round numbers referenced in failure pattern descriptions.
|
|
473
|
-
* Used by buildRollingSummary to identify which rounds to demote. */
|
|
474
|
-
function extractFailureRoundNums(patterns) {
|
|
475
|
-
const nums = new Set();
|
|
476
|
-
for (const p of patterns) {
|
|
477
|
-
// Match "R2-R4" or "R5"
|
|
478
|
-
const rangeMatch = p.match(/R(\d+)-R(\d+)/);
|
|
479
|
-
if (rangeMatch) {
|
|
480
|
-
const start = parseInt(rangeMatch[1], 10);
|
|
481
|
-
const end = parseInt(rangeMatch[2], 10);
|
|
482
|
-
for (let r = start; r <= end; r++)
|
|
483
|
-
nums.add(r);
|
|
484
|
-
}
|
|
485
|
-
else {
|
|
486
|
-
const singleMatch = p.match(/R(\d+)/);
|
|
487
|
-
if (singleMatch)
|
|
488
|
-
nums.add(parseInt(singleMatch[1], 10));
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
return nums;
|
|
492
|
-
}
|
|
493
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
494
|
-
// Rolling Summary (v3.5)
|
|
495
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
496
|
-
export function buildRollingSummary(loopId, currentRound, vaultContext, sinceRound) {
|
|
497
|
-
if (vaultContext === null)
|
|
498
|
-
return null;
|
|
499
|
-
const results = vaultContext.results || [];
|
|
500
|
-
if (!results.length)
|
|
501
|
-
return null;
|
|
502
|
-
const policy = getPolicy();
|
|
503
|
-
const window = policy.summary.window;
|
|
504
|
-
const minRound = sinceRound ?? 0;
|
|
505
|
-
// Collect rounds matching this loop_id, excluding current
|
|
506
|
-
const rounds = [];
|
|
507
|
-
for (const r of results) {
|
|
508
|
-
const lineage = (r.loop_lineage || r.lineage || {});
|
|
509
|
-
if (lineage.loop_id !== loopId)
|
|
510
|
-
continue;
|
|
511
|
-
const rnd = lineage.round;
|
|
512
|
-
if (rnd >= currentRound)
|
|
513
|
-
continue;
|
|
514
|
-
if (rnd < minRound)
|
|
515
|
-
continue;
|
|
516
|
-
rounds.push({
|
|
517
|
-
round: rnd,
|
|
518
|
-
success: r.success ?? lineage.success ?? false,
|
|
519
|
-
task: r.task ?? r.user_intent ?? "",
|
|
520
|
-
output_summary: r.output_summary ?? "",
|
|
521
|
-
constraint_violations: r.constraint_violations || [],
|
|
522
|
-
technique_used: r.technique_used ?? "",
|
|
97
|
+
function evolveObjective(request, context) {
|
|
98
|
+
const current = request.loop_objective ?? latestObjective(request.loop_id, context) ??
|
|
99
|
+
makeLoopObjective({
|
|
100
|
+
objective: request.task,
|
|
101
|
+
success_criteria: ["Task goal achieved with verifiable evidence"],
|
|
102
|
+
hard_constraints: unique(request.constraints_from_plan),
|
|
103
|
+
created_at_round: 1,
|
|
104
|
+
loop_id: request.loop_id,
|
|
523
105
|
});
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
for (const v of r.constraint_violations || []) {
|
|
544
|
-
const vNorm = String(v).trim().toLowerCase();
|
|
545
|
-
if (vNorm && !seen.has(vNorm)) {
|
|
546
|
-
violationCounts.set(vNorm, (violationCounts.get(vNorm) || 0) + 1);
|
|
547
|
-
seen.add(vNorm);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
let recurringIssues = [...violationCounts.entries()]
|
|
552
|
-
.filter(([, count]) => count >= 2)
|
|
553
|
-
.sort((a, b) => b[1] - a[1])
|
|
554
|
-
.map(([v, count]) => `${v} (appeared in ${count} rounds)`);
|
|
555
|
-
// v1.7: Failure lineage weighting — detect and demote failure patterns
|
|
556
|
-
const sampledRounds = sampled.map((r) => ({
|
|
557
|
-
round: r.round,
|
|
558
|
-
success: r.success,
|
|
559
|
-
task: r.task,
|
|
560
|
-
output_summary: r.output_summary,
|
|
561
|
-
constraint_violations: r.constraint_violations,
|
|
562
|
-
technique_used: r.technique_used,
|
|
563
|
-
}));
|
|
564
|
-
const failurePatterns = detectFailurePatterns(sampledRounds);
|
|
565
|
-
const failedRoundNums = extractFailureRoundNums(failurePatterns);
|
|
566
|
-
// Re-weight key_outcomes: outcomes from failure-pattern rounds go to end
|
|
567
|
-
if (failedRoundNums.size > 0 && keyOutcomes.length > 0) {
|
|
568
|
-
const promoted = [];
|
|
569
|
-
const demoted = [];
|
|
570
|
-
for (const ko of keyOutcomes) {
|
|
571
|
-
const rndMatch = ko.match(/^\[R(\d+)\]/);
|
|
572
|
-
if (rndMatch && failedRoundNums.has(parseInt(rndMatch[1], 10))) {
|
|
573
|
-
demoted.push(ko.replace(/^\[R\d+\]/, "$& [Consider alternatives]"));
|
|
574
|
-
}
|
|
575
|
-
else {
|
|
576
|
-
promoted.push(ko);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
keyOutcomes.length = 0;
|
|
580
|
-
keyOutcomes.push(...promoted, ...demoted);
|
|
581
|
-
}
|
|
582
|
-
// Re-weight recurring_issues: mark issues that ONLY appear in failure-pattern rounds
|
|
583
|
-
if (failedRoundNums.size > 0 && recurringIssues.length > 0) {
|
|
584
|
-
// Build per-round violation sets
|
|
585
|
-
const failureViolations = new Set();
|
|
586
|
-
const successViolations = new Set();
|
|
587
|
-
for (const r of sampledRounds) {
|
|
588
|
-
const target = failedRoundNums.has(r.round) ? failureViolations : successViolations;
|
|
589
|
-
for (const v of r.constraint_violations) {
|
|
590
|
-
target.add(String(v).trim().toLowerCase());
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
recurringIssues.length = 0;
|
|
594
|
-
recurringIssues.push(...[...violationCounts.entries()]
|
|
595
|
-
.filter(([, count]) => count >= 2)
|
|
596
|
-
.sort((a, b) => b[1] - a[1])
|
|
597
|
-
.map(([v, count]) => {
|
|
598
|
-
const vNorm = v.trim().toLowerCase();
|
|
599
|
-
// Mark if this issue only appears in failure rounds (possible dead end)
|
|
600
|
-
if (failureViolations.has(vNorm) && !successViolations.has(vNorm)) {
|
|
601
|
-
return `${v} (appeared in ${count} rounds) ⚠️ [Possible dead end — only in failed rounds]`;
|
|
602
|
-
}
|
|
603
|
-
return `${v} (appeared in ${count} rounds)`;
|
|
604
|
-
}));
|
|
605
|
-
}
|
|
606
|
-
return makeRollingSummary({
|
|
607
|
-
key_outcomes: keyOutcomes,
|
|
608
|
-
recurring_issues: recurringIssues,
|
|
609
|
-
rounds_sampled: sampled.length,
|
|
610
|
-
generated_at_round: currentRound,
|
|
611
|
-
failed_patterns: failurePatterns.length ? failurePatterns : [],
|
|
106
|
+
const last = request.last_round_result;
|
|
107
|
+
const history = [...(current.refinement_history ?? [])];
|
|
108
|
+
let objective = current.objective || request.task;
|
|
109
|
+
let version = current.version ?? 1;
|
|
110
|
+
if (last?.objective_refinement?.trim()) {
|
|
111
|
+
const refinement = last.objective_refinement.trim();
|
|
112
|
+
history.push(refinement);
|
|
113
|
+
objective = `${objective}\nRefinement: ${refinement}`;
|
|
114
|
+
version++;
|
|
115
|
+
}
|
|
116
|
+
const revisions = new Map((last?.revised_success_criteria ?? []).map((item) => [item.old, item.new]));
|
|
117
|
+
return makeLoopObjective({
|
|
118
|
+
...current,
|
|
119
|
+
objective,
|
|
120
|
+
version,
|
|
121
|
+
refinement_history: history.slice(-getPolicy().evolution.max_objective_versions),
|
|
122
|
+
success_criteria: unique(current.success_criteria.map((item) => revisions.get(item) ?? item)),
|
|
123
|
+
hard_constraints: unique(current.hard_constraints),
|
|
124
|
+
loop_id: request.loop_id,
|
|
612
125
|
});
|
|
613
126
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
lines.push("");
|
|
639
|
-
lines.push("The following approaches failed repeatedly. The compiler has demoted " +
|
|
640
|
-
"their influence on key outcomes. Consider a different technique or " +
|
|
641
|
-
"narrower scope.");
|
|
642
|
-
for (const fp of rs.failed_patterns) {
|
|
643
|
-
lines.push(`- 🚫 ${fp}`);
|
|
644
|
-
}
|
|
645
|
-
lines.push("");
|
|
646
|
-
}
|
|
647
|
-
return lines.join("\n");
|
|
648
|
-
}
|
|
649
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
650
|
-
// Checkpoint compression (v1.10 — subtask boundary snapshots)
|
|
651
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
652
|
-
/** Build a CheckpointSummary from the current round's self-evaluation state.
|
|
653
|
-
* Called when the Agent sets compression_checkpoint: true in its self-eval.
|
|
654
|
-
* Snapshots the current constraints and outcome so they survive rolling-window
|
|
655
|
-
* eviction in subsequent rounds. */
|
|
656
|
-
export function buildCheckpointSummary(selfEval, round, activeConstraints, retiredConstraints) {
|
|
657
|
-
const policy = getPolicy();
|
|
658
|
-
const cpCfg = policy.checkpoint;
|
|
659
|
-
const maxCarried = cpCfg?.max_carried_constraints ?? 10;
|
|
660
|
-
const outcomeMaxChars = cpCfg?.outcome_max_chars ?? 200;
|
|
661
|
-
const label = selfEval.checkpoint_label?.trim() ||
|
|
662
|
-
`Subtask completed at round ${round}`;
|
|
663
|
-
const outcome = (selfEval.output_summary || "").trim().slice(0, outcomeMaxChars);
|
|
664
|
-
// Carried: constraints still active, capped
|
|
665
|
-
const carried = activeConstraints.slice(0, maxCarried);
|
|
666
|
-
// Resolved: constraints retired this round (retracted + the retire delta)
|
|
667
|
-
const resolved = retiredConstraints.slice(0, maxCarried);
|
|
668
|
-
return {
|
|
669
|
-
label,
|
|
670
|
-
declared_at_round: round,
|
|
671
|
-
outcome,
|
|
672
|
-
carried_constraints: carried,
|
|
673
|
-
resolved_constraints: resolved,
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
/** Format a CheckpointSummary as a prompt block.
|
|
677
|
-
* Renders as a fixed section that persists across rolling-window eviction.
|
|
678
|
-
* Returns empty string if the summary is null or has no meaningful data. */
|
|
679
|
-
export function formatCheckpointForPrompt(cs) {
|
|
680
|
-
if (!cs)
|
|
681
|
-
return "";
|
|
682
|
-
// Must have at least a label and some content to be meaningful
|
|
683
|
-
if (!cs.label && !cs.outcome && !cs.carried_constraints.length)
|
|
684
|
-
return "";
|
|
685
|
-
const lines = [
|
|
686
|
-
`### Checkpoint: ${cs.label} (Round ${cs.declared_at_round})`,
|
|
687
|
-
"",
|
|
688
|
-
];
|
|
689
|
-
if (cs.outcome) {
|
|
690
|
-
lines.push(`**Outcome**: ${cs.outcome}`);
|
|
691
|
-
lines.push("");
|
|
692
|
-
}
|
|
693
|
-
if (cs.carried_constraints.length) {
|
|
694
|
-
lines.push("**Carried Constraints** (still active):");
|
|
695
|
-
for (const c of cs.carried_constraints) {
|
|
696
|
-
lines.push(`- ${c}`);
|
|
697
|
-
}
|
|
698
|
-
lines.push("");
|
|
699
|
-
}
|
|
700
|
-
if (cs.resolved_constraints.length) {
|
|
701
|
-
lines.push("**Resolved Constraints** (closed with this checkpoint):");
|
|
702
|
-
for (const c of cs.resolved_constraints) {
|
|
703
|
-
lines.push(`- ${c}`);
|
|
704
|
-
}
|
|
705
|
-
lines.push("");
|
|
706
|
-
}
|
|
707
|
-
return lines.join("\n");
|
|
708
|
-
}
|
|
709
|
-
/** Query the vault for the most recent checkpoint entry before the current
|
|
710
|
-
* round. Returns null if no checkpoint has been declared yet. */
|
|
711
|
-
function loadLatestCheckpoint(loopId, currentRound, vaultContext) {
|
|
712
|
-
if (!vaultContext)
|
|
127
|
+
function evolveConstraints(request, objective, previous) {
|
|
128
|
+
const retired = unique(request.last_round_result?.retracted_constraints ?? []);
|
|
129
|
+
const retiredSet = new Set(retired);
|
|
130
|
+
const discovered = unique(request.last_round_result?.discovered_constraints ?? [])
|
|
131
|
+
.slice(0, getPolicy().evolution.max_discovered_constraints_per_round);
|
|
132
|
+
const active = unique([
|
|
133
|
+
...(previous?.constraints_active ?? []),
|
|
134
|
+
...request.constraints_from_plan,
|
|
135
|
+
...objective.hard_constraints,
|
|
136
|
+
...objective.success_criteria,
|
|
137
|
+
...discovered,
|
|
138
|
+
]).filter((item) => !retiredSet.has(item))
|
|
139
|
+
.slice(0, getPolicy().evolution.max_active_constraints);
|
|
140
|
+
return { active, retired };
|
|
141
|
+
}
|
|
142
|
+
export function buildRollingSummary(loopId, currentRound, context, sinceRound = 0) {
|
|
143
|
+
const window = getPolicy().summary.window;
|
|
144
|
+
const entries = loopEntries(loopId, context)
|
|
145
|
+
.filter((entry) => {
|
|
146
|
+
const round = entryRound(entry);
|
|
147
|
+
return round >= sinceRound && round < currentRound;
|
|
148
|
+
})
|
|
149
|
+
.slice(-window);
|
|
150
|
+
if (entries.length === 0)
|
|
713
151
|
return null;
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
}
|
|
152
|
+
const outcomes = [];
|
|
153
|
+
const issues = [];
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
const data = lineage(entry);
|
|
156
|
+
const round = entryRound(entry);
|
|
157
|
+
const success = entry.success ?? data.success;
|
|
158
|
+
const summary = typeof entry.output_summary === "string"
|
|
159
|
+
? entry.output_summary
|
|
160
|
+
: typeof data.output_summary === "string" ? data.output_summary : "";
|
|
161
|
+
if (summary)
|
|
162
|
+
outcomes.push(`[R${round}] ${success === false ? "failed" : "accepted"}: ${summary}`);
|
|
163
|
+
const violations = Array.isArray(entry.constraint_violations)
|
|
164
|
+
? entry.constraint_violations
|
|
165
|
+
: Array.isArray(data.constraint_violations) ? data.constraint_violations : [];
|
|
166
|
+
issues.push(...violations.filter((item) => typeof item === "string"));
|
|
730
167
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
carried_constraints: latest.carried_constraints ?? [],
|
|
738
|
-
resolved_constraints: latest.resolved_constraints ?? [],
|
|
168
|
+
return makeRollingSummary({
|
|
169
|
+
key_outcomes: unique(outcomes),
|
|
170
|
+
recurring_issues: unique(issues),
|
|
171
|
+
rounds_sampled: entries.length,
|
|
172
|
+
generated_at_round: currentRound,
|
|
173
|
+
failed_patterns: [],
|
|
739
174
|
});
|
|
740
175
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
744
|
-
/** Format an external context string for injection into an L2 prompt.
|
|
745
|
-
* Wraps the raw context in a marked section with a priority disclaimer.
|
|
746
|
-
* Returns empty string if context is empty or injection is disabled by policy. */
|
|
747
|
-
export function formatExternalContext(externalContext, sectionTitle, maxLength) {
|
|
748
|
-
if (!externalContext || externalContext.trim().length === 0)
|
|
749
|
-
return "";
|
|
750
|
-
const trimmed = externalContext.trim().slice(0, maxLength);
|
|
751
|
-
const truncationNote = trimmed.length < externalContext.trim().length
|
|
752
|
-
? `\n> ⚠️ Context was truncated to ${maxLength} characters.`
|
|
753
|
-
: "";
|
|
754
|
-
return [
|
|
755
|
-
"",
|
|
756
|
-
sectionTitle,
|
|
757
|
-
"",
|
|
758
|
-
"> ⚠️ **Role**: Situational awareness only. These are point-in-time",
|
|
759
|
-
"> observations from past sessions — claims about code locations or",
|
|
760
|
-
"> behaviour may be outdated. If any insight contradicts the Loop",
|
|
761
|
-
"> Objective or Active Constraints above, the LoopForge specification",
|
|
762
|
-
"> takes absolute precedence.",
|
|
763
|
-
"",
|
|
764
|
-
trimmed,
|
|
765
|
-
truncationNote,
|
|
766
|
-
"",
|
|
767
|
-
"---",
|
|
768
|
-
"",
|
|
769
|
-
].join("\n");
|
|
176
|
+
function tokens(text) {
|
|
177
|
+
return new Set(text.toLowerCase().match(/[a-z0-9\u4e00-\u9fff]+/g) ?? []);
|
|
770
178
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
if (
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
782
|
-
// Gate 2: First call or explicit plan input → full rebuild
|
|
783
|
-
if (request.round === 1 || request.plan_source) {
|
|
784
|
-
return "l2";
|
|
785
|
-
}
|
|
786
|
-
// Derive goal_id
|
|
787
|
-
const goalId = deriveGoalId(request.loop_id, request.task, request.goal_id);
|
|
788
|
-
const prev = getPreviousRound(request.loop_id, request.round - 1, vaultContext);
|
|
789
|
-
if (prev === null)
|
|
790
|
-
return "l2";
|
|
791
|
-
// Gate 3: goal_id stability
|
|
792
|
-
if (goalId !== prev.goal_id)
|
|
793
|
-
return "l2";
|
|
794
|
-
// Gate 4: P0–P5 cognitive evolution signals → full recompile (v1.7)
|
|
795
|
-
// Check BEFORE the simple repair/new-constraint gates because evolution signals
|
|
796
|
-
// (constraint discovery, objective refinement, self-correction) need full L2
|
|
797
|
-
// processing. Only structural evolution signals trigger L2 — execution_evidence
|
|
798
|
-
// alone does not (it is processed via the rolling summary at L1).
|
|
799
|
-
if (request.last_round_result) {
|
|
800
|
-
const lr = request.last_round_result;
|
|
801
|
-
if ((lr.discovered_constraints?.length ?? 0) > 0 ||
|
|
802
|
-
(lr.objective_refinement?.trim().length ?? 0) > 0 ||
|
|
803
|
-
(lr.emerged_subtasks?.length ?? 0) > 0 ||
|
|
804
|
-
(lr.retracted_constraints?.length ?? 0) > 0 ||
|
|
805
|
-
(lr.revised_success_criteria?.length ?? 0) > 0 ||
|
|
806
|
-
(lr.wrong_assumptions?.length ?? 0) > 0) {
|
|
807
|
-
return "l2";
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
// Gate 5: Explicit failures or new constraints → patch
|
|
811
|
-
const hasNewConstraints = request.constraints_from_plan.length > 0;
|
|
812
|
-
const hasNewFailures = request.last_round_result !== null &&
|
|
813
|
-
!request.last_round_result.success;
|
|
814
|
-
const hasRepair = detectsRepairSignal(request);
|
|
815
|
-
if (hasNewConstraints || hasNewFailures || hasRepair)
|
|
816
|
-
return "l1";
|
|
817
|
-
// Nothing triggered → fast path
|
|
818
|
-
return "l0";
|
|
179
|
+
function similarity(left, right) {
|
|
180
|
+
const a = tokens(left);
|
|
181
|
+
const b = tokens(right);
|
|
182
|
+
if (a.size === 0 || b.size === 0)
|
|
183
|
+
return 1;
|
|
184
|
+
let intersection = 0;
|
|
185
|
+
for (const value of a)
|
|
186
|
+
if (b.has(value))
|
|
187
|
+
intersection++;
|
|
188
|
+
return intersection / new Set([...a, ...b]).size;
|
|
819
189
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
export function alignTask(proposedTask, request, vaultContext) {
|
|
824
|
-
const objectiveEntry = vaultGetLoopObjective(request.loop_id, vaultContext);
|
|
825
|
-
let objData = null;
|
|
826
|
-
if (request.loop_objective) {
|
|
827
|
-
objData = request.loop_objective;
|
|
828
|
-
}
|
|
829
|
-
else if (objectiveEntry) {
|
|
830
|
-
const lo = objectiveEntry.loop_objective;
|
|
831
|
-
if (lo && typeof lo === "object") {
|
|
832
|
-
objData = lo;
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
if (objData === null)
|
|
190
|
+
export function alignTask(proposedTask, request, context) {
|
|
191
|
+
const objective = request.loop_objective ?? latestObjective(request.loop_id, context);
|
|
192
|
+
if (!objective)
|
|
836
193
|
return makeTaskAlignment();
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
? jaccard(proposedTokens, objTokens)
|
|
845
|
-
: 1.0;
|
|
846
|
-
if (score >= 0.5) {
|
|
847
|
-
return makeTaskAlignment({
|
|
848
|
-
is_aligned: true,
|
|
849
|
-
alignment_score: Math.round(score * 100) / 100,
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
else if (score >= 0.3) {
|
|
853
|
-
return makeTaskAlignment({
|
|
854
|
-
is_aligned: true,
|
|
855
|
-
alignment_score: Math.round(score * 100) / 100,
|
|
856
|
-
warning: `Proposed task '${proposedTask.slice(0, 80)}' may be drifting from ` +
|
|
857
|
-
`loop objective '${objective}'. Consider narrowing scope.`,
|
|
858
|
-
escalation: "warn",
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
else {
|
|
862
|
-
return makeTaskAlignment({
|
|
863
|
-
is_aligned: false,
|
|
864
|
-
alignment_score: Math.round(score * 100) / 100,
|
|
865
|
-
warning: `Proposed task '${proposedTask.slice(0, 80)}' is OFF-OBJECTIVE. ` +
|
|
866
|
-
`Loop objective: '${objective}'. Full realignment recommended.`,
|
|
867
|
-
escalation: "block",
|
|
868
|
-
});
|
|
869
|
-
}
|
|
194
|
+
const score = similarity(proposedTask, [objective.objective, ...objective.success_criteria, ...objective.hard_constraints].join(" "));
|
|
195
|
+
return makeTaskAlignment({
|
|
196
|
+
is_aligned: score >= 0.3,
|
|
197
|
+
alignment_score: Number(score.toFixed(2)),
|
|
198
|
+
warning: score < 0.3 ? "Current task may be drifting from the loop objective." : "",
|
|
199
|
+
escalation: score < 0.3 ? "warn" : "none",
|
|
200
|
+
});
|
|
870
201
|
}
|
|
871
|
-
export function checkLoopHealth(loopId, request,
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
const lo = objectiveEntry.loop_objective;
|
|
879
|
-
if (lo && typeof lo === "object") {
|
|
880
|
-
obj = lo;
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
if (obj === null)
|
|
884
|
-
return makeLoopHealth();
|
|
885
|
-
const objective = obj.objective ?? "";
|
|
886
|
-
const successCriteria = obj.success_criteria ?? [];
|
|
887
|
-
const hardConstraints = obj.hard_constraints ?? [];
|
|
888
|
-
// 1. goal_alignment
|
|
889
|
-
let goalAlignment = 1.0;
|
|
890
|
-
if (request.task) {
|
|
891
|
-
const taskTokens = tokenize(request.task.toLowerCase());
|
|
892
|
-
const objText = `${objective} ${successCriteria.join(" ")} ${hardConstraints.join(" ")}`.toLowerCase();
|
|
893
|
-
const objTokens = tokenize(objText);
|
|
894
|
-
goalAlignment =
|
|
895
|
-
taskTokens.size && objTokens.size
|
|
896
|
-
? jaccard(taskTokens, objTokens)
|
|
897
|
-
: 1.0;
|
|
898
|
-
}
|
|
899
|
-
// 2. constraint_integrity
|
|
900
|
-
let constraintIntegrity = 1.0;
|
|
901
|
-
if (request.last_round_result && hardConstraints.length) {
|
|
902
|
-
const outputText = request.last_round_result.output_summary.toLowerCase();
|
|
903
|
-
const present = hardConstraints.filter((c) => c
|
|
904
|
-
.toLowerCase()
|
|
905
|
-
.split(/\s+/)
|
|
906
|
-
.some((word) => outputText.includes(word))).length;
|
|
907
|
-
constraintIntegrity = present / hardConstraints.length;
|
|
908
|
-
}
|
|
909
|
-
// 3. drift_detected
|
|
910
|
-
const driftDetected = countConsecutiveHashMismatches(loopId, vaultContext) >= 3;
|
|
911
|
-
// 4. strategy_stability
|
|
912
|
-
const recent = getRecentRounds(loopId, 3, vaultContext);
|
|
913
|
-
const strategyStability = recent.length > 0
|
|
914
|
-
? recent.every((r) => r.success === true)
|
|
915
|
-
: true;
|
|
916
|
-
// 5. task_continuity
|
|
917
|
-
let taskContinuity = 1.0;
|
|
918
|
-
const prevTask = getPreviousRoundTask(loopId, request.round - 1, vaultContext);
|
|
919
|
-
if (prevTask && request.task) {
|
|
920
|
-
const currTokens = tokenize(request.task.toLowerCase());
|
|
921
|
-
const prevTokens = tokenize(prevTask.toLowerCase());
|
|
922
|
-
taskContinuity =
|
|
923
|
-
currTokens.size && prevTokens.size
|
|
924
|
-
? jaccard(currTokens, prevTokens)
|
|
925
|
-
: 1.0;
|
|
926
|
-
}
|
|
927
|
-
// Escalation recommendation
|
|
928
|
-
let escalation = "none";
|
|
929
|
-
if (goalAlignment < 0.5) {
|
|
930
|
-
escalation = "l2";
|
|
931
|
-
}
|
|
932
|
-
else if (constraintIntegrity < 0.7) {
|
|
933
|
-
escalation = "l1";
|
|
934
|
-
}
|
|
935
|
-
else if (driftDetected) {
|
|
936
|
-
escalation = "l2";
|
|
937
|
-
}
|
|
202
|
+
export function checkLoopHealth(loopId, request, context) {
|
|
203
|
+
const previous = getPreviousRound(loopId, request.round - 1, context);
|
|
204
|
+
const alignment = alignTask(request.task, request, context);
|
|
205
|
+
const violations = request.last_round_result?.constraint_violations.length ?? 0;
|
|
206
|
+
const integrity = Math.max(0, 1 - violations * 0.2);
|
|
207
|
+
const continuity = previous ? similarity(previous.task, request.task) : 1;
|
|
208
|
+
const drift = alignment.alignment_score < 0.3 || continuity < 0.2;
|
|
938
209
|
return makeLoopHealth({
|
|
939
|
-
goal_alignment:
|
|
940
|
-
constraint_integrity:
|
|
941
|
-
drift_detected:
|
|
942
|
-
strategy_stability:
|
|
943
|
-
task_continuity:
|
|
944
|
-
escalation_recommended:
|
|
210
|
+
goal_alignment: alignment.alignment_score,
|
|
211
|
+
constraint_integrity: integrity,
|
|
212
|
+
drift_detected: drift,
|
|
213
|
+
strategy_stability: true,
|
|
214
|
+
task_continuity: Number(continuity.toFixed(2)),
|
|
215
|
+
escalation_recommended: drift || integrity < 0.6 ? "l2" : "none",
|
|
945
216
|
});
|
|
946
217
|
}
|
|
947
|
-
function
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
if (prev && currentHash !== prev.goal_text_hash && prev.goal_text_hash) {
|
|
977
|
-
warnings.push(`goal_text_hash changed (${prev.goal_text_hash} → ${currentHash}) ` +
|
|
978
|
-
"but goal_id matched — wording drift detected");
|
|
979
|
-
}
|
|
980
|
-
// Strategy collapse check
|
|
981
|
-
if (strategyCollapse(request.loop_id, vaultContext)) {
|
|
982
|
-
warnings.push("strategy_collapse: 3 consecutive failed rounds — " +
|
|
983
|
-
"consider force_level=L2 rebuild");
|
|
984
|
-
}
|
|
985
|
-
// Repair cue detection
|
|
986
|
-
if (detectsRepairSignal(request)) {
|
|
987
|
-
warnings.push("repair signal detected — L1 patch applied");
|
|
988
|
-
}
|
|
989
|
-
// Task alignment
|
|
990
|
-
if (request.next_task_proposal) {
|
|
991
|
-
alignment = alignTask(request.next_task_proposal, request, vaultContext);
|
|
992
|
-
if (alignment.escalation !== "none") {
|
|
993
|
-
warnings.push(`task_alignment: ${alignment.warning}`);
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
// Loop health
|
|
997
|
-
const interval = Math.max(request.health_check_interval, 1);
|
|
998
|
-
if (request.round % interval === 0) {
|
|
999
|
-
health = checkLoopHealth(request.loop_id, request, vaultContext);
|
|
1000
|
-
if (health.escalation_recommended !== "none") {
|
|
1001
|
-
warnings.push(`loop_health recommends ${health.escalation_recommended}: ` +
|
|
1002
|
-
`goal_alignment=${health.goal_alignment.toFixed(2)}, ` +
|
|
1003
|
-
`constraint_integrity=${health.constraint_integrity.toFixed(2)}, ` +
|
|
1004
|
-
`task_continuity=${health.task_continuity.toFixed(2)}`);
|
|
1005
|
-
}
|
|
1006
|
-
if (health.drift_detected) {
|
|
1007
|
-
warnings.push("drift_detected: goal_text_hash diverged 3+ consecutive rounds");
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
// Forward hint
|
|
1011
|
-
suggested = computeSuggestedNextTask(request.loop_id, vaultContext);
|
|
1012
|
-
return { warnings, suggestedNextTask: suggested, alignment, health };
|
|
1013
|
-
}
|
|
1014
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1015
|
-
// Plan extraction
|
|
1016
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1017
|
-
function extractObjectiveFromPlan(planPath) {
|
|
1018
|
-
let text;
|
|
1019
|
-
try {
|
|
1020
|
-
text = readFileSync(planPath, "utf-8");
|
|
1021
|
-
}
|
|
1022
|
-
catch {
|
|
1023
|
-
return null;
|
|
1024
|
-
}
|
|
1025
|
-
const sections = {
|
|
1026
|
-
goal: [],
|
|
1027
|
-
success: [],
|
|
1028
|
-
constraints: [],
|
|
1029
|
-
};
|
|
1030
|
-
let currentSection = null;
|
|
1031
|
-
const goalPatterns = ["goal", "objective", "目标", "目的", "意图"];
|
|
1032
|
-
const successPatterns = [
|
|
1033
|
-
"success criteria", "acceptance criteria", "验收标准", "成功标准",
|
|
1034
|
-
"done when", "完成标准", "交付标准", "outcome", "deliverable",
|
|
1035
|
-
"结果", "产出", "交付物",
|
|
1036
|
-
];
|
|
1037
|
-
const constraintPatterns = [
|
|
1038
|
-
"hard constraint", "constraint", "non-goal", "out of scope",
|
|
1039
|
-
"硬约束", "约束", "非目标", "不做什么", "限制",
|
|
1040
|
-
];
|
|
1041
|
-
for (const line of text.split("\n")) {
|
|
1042
|
-
const stripped = line.trim();
|
|
1043
|
-
const low = stripped
|
|
1044
|
-
.toLowerCase()
|
|
1045
|
-
.replace(/^#+\s*/, "");
|
|
1046
|
-
if (stripped.startsWith("#")) {
|
|
1047
|
-
if (goalPatterns.some((p) => low.includes(p))) {
|
|
1048
|
-
currentSection = "goal";
|
|
1049
|
-
continue;
|
|
1050
|
-
}
|
|
1051
|
-
if (successPatterns.some((p) => low.includes(p))) {
|
|
1052
|
-
currentSection = "success";
|
|
1053
|
-
continue;
|
|
1054
|
-
}
|
|
1055
|
-
if (constraintPatterns.some((p) => low.includes(p))) {
|
|
1056
|
-
currentSection = "constraints";
|
|
1057
|
-
continue;
|
|
1058
|
-
}
|
|
1059
|
-
currentSection = null;
|
|
1060
|
-
continue;
|
|
1061
|
-
}
|
|
1062
|
-
if (!currentSection)
|
|
1063
|
-
continue;
|
|
1064
|
-
if (/^[-*•]/.test(stripped)) {
|
|
1065
|
-
const item = stripped.replace(/^[-*•]\s*/, "").trim();
|
|
1066
|
-
if (item && item.length > 3) {
|
|
1067
|
-
sections[currentSection].push(item);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
else if (stripped && currentSection === "goal") {
|
|
1071
|
-
if (!sections.goal.length && stripped.length > 10) {
|
|
1072
|
-
sections.goal.push(stripped);
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
else if (stripped && currentSection === "success" && stripped.length > 10) {
|
|
1076
|
-
// P3: Also capture free-text paragraphs under success criteria headers
|
|
1077
|
-
if (!/^#/.test(stripped)) {
|
|
1078
|
-
sections.success.push(stripped);
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
if (!Object.values(sections).some((v) => v.length))
|
|
1083
|
-
return null;
|
|
1084
|
-
return {
|
|
1085
|
-
objective: sections.goal[0] ?? "",
|
|
1086
|
-
success_criteria: sections.success,
|
|
1087
|
-
hard_constraints: sections.constraints,
|
|
1088
|
-
};
|
|
1089
|
-
}
|
|
1090
|
-
function computeLoopObjectiveFromTask(request, _vaultContext) {
|
|
1091
|
-
const task = request.task || "";
|
|
1092
|
-
const constraints = [...request.constraints_from_plan];
|
|
1093
|
-
let objective = task.trim().slice(0, 200);
|
|
1094
|
-
const successCriteria = [];
|
|
1095
|
-
const hardConstraints = [...constraints];
|
|
1096
|
-
// P3: Outcome-oriented default success criteria — describe the RESULT,
|
|
1097
|
-
// not the process. The process emerges from the loop.
|
|
1098
|
-
if (/test|测试/i.test(task)) {
|
|
1099
|
-
successCriteria.push("All tests pass with no regressions");
|
|
1100
|
-
}
|
|
1101
|
-
if (/compat|兼容/i.test(task)) {
|
|
1102
|
-
successCriteria.push("Existing API contracts and integrations unchanged");
|
|
1103
|
-
}
|
|
1104
|
-
if (/security|安全|audit/i.test(task)) {
|
|
1105
|
-
successCriteria.push("All identified vulnerabilities resolved or documented");
|
|
1106
|
-
}
|
|
1107
|
-
if (/fix|修复|bug/i.test(task)) {
|
|
1108
|
-
successCriteria.push("Root cause addressed and verified, not just symptoms");
|
|
1109
|
-
}
|
|
1110
|
-
if (/build|create|implement|开发|实现|构建/i.test(task)) {
|
|
1111
|
-
successCriteria.push("Deliverable matches specification and passes review");
|
|
1112
|
-
}
|
|
1113
|
-
if (/refactor|重构/i.test(task)) {
|
|
1114
|
-
successCriteria.push("Behavior preserved, structure improved, no new failures");
|
|
1115
|
-
}
|
|
1116
|
-
if (/migrate|迁移|upgrade|升级/i.test(task)) {
|
|
1117
|
-
successCriteria.push("Migration completes without data loss or downtime");
|
|
1118
|
-
}
|
|
1119
|
-
if (/perf|performance|optimize|性能|优化/i.test(task)) {
|
|
1120
|
-
successCriteria.push("Measurable improvement verified by benchmarks");
|
|
1121
|
-
}
|
|
1122
|
-
// P3: Try to extract richer objective from plan_source
|
|
1123
|
-
if (request.plan_source) {
|
|
1124
|
-
const planExtracted = extractObjectiveFromPlan(request.plan_source);
|
|
1125
|
-
if (planExtracted) {
|
|
1126
|
-
if (planExtracted.objective) {
|
|
1127
|
-
objective = planExtracted.objective;
|
|
1128
|
-
}
|
|
1129
|
-
if (planExtracted.success_criteria) {
|
|
1130
|
-
successCriteria.push(...planExtracted.success_criteria);
|
|
1131
|
-
}
|
|
1132
|
-
if (planExtracted.hard_constraints) {
|
|
1133
|
-
hardConstraints.push(...planExtracted.hard_constraints);
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
else {
|
|
1137
|
-
hardConstraints.push(`Follow plan: ${request.plan_source}`);
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
// P3: Derive domain from task content for context-aware defaults
|
|
1141
|
-
if (!successCriteria.length) {
|
|
1142
|
-
if (/\.sol|solidity|contract|智能合约/i.test(task)) {
|
|
1143
|
-
successCriteria.push("All functions respect check-effects-interactions pattern");
|
|
1144
|
-
successCriteria.push("No unchecked external calls or reentrancy vectors");
|
|
1145
|
-
}
|
|
1146
|
-
else if (/\.tsx?|\.jsx?|react|component|组件/i.test(task)) {
|
|
1147
|
-
successCriteria.push("Component renders correctly across all states (loading, empty, error)");
|
|
1148
|
-
successCriteria.push("No prop-type or accessibility regressions");
|
|
1149
|
-
}
|
|
1150
|
-
else if (/api|endpoint|接口|route/i.test(task)) {
|
|
1151
|
-
successCriteria.push("Endpoint responds correctly for all defined status codes");
|
|
1152
|
-
successCriteria.push("Error responses follow the project's error schema");
|
|
1153
|
-
}
|
|
1154
|
-
else {
|
|
1155
|
-
successCriteria.push("Task goal achieved with verifiable evidence");
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
if (!hardConstraints.length) {
|
|
1159
|
-
hardConstraints.push("Do not modify files outside the stated scope");
|
|
1160
|
-
hardConstraints.push("Preserve existing test coverage — no test deletions without replacement");
|
|
1161
|
-
}
|
|
1162
|
-
return makeLoopObjective({
|
|
1163
|
-
objective,
|
|
1164
|
-
success_criteria: successCriteria,
|
|
1165
|
-
hard_constraints: hardConstraints,
|
|
1166
|
-
created_at_round: 1,
|
|
1167
|
-
loop_id: request.loop_id,
|
|
1168
|
-
version: 1,
|
|
1169
|
-
refinement_history: [],
|
|
218
|
+
function levelDecision(request, context) {
|
|
219
|
+
const previous = getPreviousRound(request.loop_id, request.round - 1, context);
|
|
220
|
+
const last = request.last_round_result;
|
|
221
|
+
const hasNewInformation = Boolean(request.new_since_last_round.trim() ||
|
|
222
|
+
last?.discovered_constraints?.length ||
|
|
223
|
+
last?.objective_refinement?.trim() ||
|
|
224
|
+
last?.emerged_subtasks?.length ||
|
|
225
|
+
last?.retracted_constraints?.length ||
|
|
226
|
+
last?.revised_success_criteria?.length ||
|
|
227
|
+
last?.wrong_assumptions?.length);
|
|
228
|
+
const lastFullRound = loopEntries(request.loop_id, context)
|
|
229
|
+
.filter((entry) => lineage(entry).recompile_level === "l2")
|
|
230
|
+
.map(entryRound)
|
|
231
|
+
.filter((round) => round < request.round)
|
|
232
|
+
.at(-1) ?? 1;
|
|
233
|
+
return decidePromptLevel({
|
|
234
|
+
round: request.round,
|
|
235
|
+
attempt: request.attempt,
|
|
236
|
+
forceLevel: request.force_level,
|
|
237
|
+
hasPlanSource: Boolean(request.plan_source),
|
|
238
|
+
checkpointBoundary: last?.compression_checkpoint === true,
|
|
239
|
+
goalChanged: previous !== null && previous.goal_id !== deriveGoalId(request.loop_id, request.task, request.goal_id),
|
|
240
|
+
previousStateMissing: request.round > 1 && previous === null,
|
|
241
|
+
previousFailedWithoutNewInformation: last?.success === false && !hasNewInformation,
|
|
242
|
+
verificationContradicted: (request.verification_flags ?? [])
|
|
243
|
+
.some((flag) => flag.severity === "error"),
|
|
244
|
+
consecutiveRejections: request.consecutive_rejections,
|
|
245
|
+
fullRefreshInterval: getPolicy().prompt.full_refresh_interval,
|
|
246
|
+
lastFullRound,
|
|
1170
247
|
});
|
|
1171
248
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1175
|
-
function compileL0(request, vaultContext, prevRound) {
|
|
1176
|
-
const prev = prevRound ??
|
|
1177
|
-
getPreviousRound(request.loop_id, request.round - 1, vaultContext);
|
|
1178
|
-
const cachedPrompt = prev?.prompt_text ?? "";
|
|
1179
|
-
if (cachedPrompt) {
|
|
1180
|
-
return makeLoopCompileResponse({
|
|
1181
|
-
status: AgentStatus.OK,
|
|
1182
|
-
prompt: cachedPrompt,
|
|
1183
|
-
recompile_level: "l0",
|
|
1184
|
-
diff_from_previous: `L0 cache hit — reusing prompt from round ${request.round - 1}`,
|
|
1185
|
-
lineage: [`${request.loop_id}:r${request.round}`],
|
|
1186
|
-
constraints_active: prev?.constraints_active ?? [],
|
|
1187
|
-
constraints_retired: [],
|
|
1188
|
-
technique_used: "cached",
|
|
1189
|
-
loop_id: request.loop_id,
|
|
1190
|
-
round: request.round,
|
|
1191
|
-
goal_id: deriveGoalId(request.loop_id, request.task, request.goal_id),
|
|
1192
|
-
goal_text_hash: computeGoalTextHash(request.task),
|
|
1193
|
-
plan_source: request.plan_source,
|
|
1194
|
-
warnings: [],
|
|
1195
|
-
});
|
|
1196
|
-
}
|
|
1197
|
-
// Auto-escalate: no cached prompt → delegate to L2
|
|
1198
|
-
const l2Response = compileL2(request, vaultContext);
|
|
1199
|
-
l2Response.recompile_level = "l0";
|
|
1200
|
-
l2Response.diff_from_previous =
|
|
1201
|
-
`L0 auto-escalated to L2 — no cached prompt available from round ${request.round - 1}`;
|
|
1202
|
-
return l2Response;
|
|
249
|
+
export function decideLevel(request, context) {
|
|
250
|
+
return levelDecision(request, context).level;
|
|
1203
251
|
}
|
|
1204
|
-
function
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
const goalId = deriveGoalId(request.loop_id, request.task, request.goal_id);
|
|
1208
|
-
const newConstraints = [...request.constraints_from_plan];
|
|
1209
|
-
let activeRaw = [...(prev?.constraints_active ?? []), ...newConstraints];
|
|
1210
|
-
// Deduplicate preserving order
|
|
1211
|
-
activeRaw = [...new Set(activeRaw)];
|
|
1212
|
-
// v3.5: Constraint retirement
|
|
1213
|
-
const { active, retired } = computeConstraintRetirement(activeRaw, request.loop_id, request.round, vaultContext);
|
|
1214
|
-
// v3.5: Rolling summary
|
|
1215
|
-
const rollingSummary = buildRollingSummary(request.loop_id, request.round, vaultContext);
|
|
1216
|
-
const rollingText = formatRollingSummaryForPrompt(rollingSummary);
|
|
1217
|
-
const violations = request.last_round_result?.constraint_violations ?? [];
|
|
1218
|
-
const diffParts = [];
|
|
1219
|
-
if (newConstraints.length) {
|
|
1220
|
-
diffParts.push(`new constraints: [${newConstraints.join(", ")}]`);
|
|
1221
|
-
}
|
|
1222
|
-
if (retired.length) {
|
|
1223
|
-
diffParts.push(`retired constraints: [${retired.join(", ")}]`);
|
|
1224
|
-
}
|
|
1225
|
-
if (violations.length) {
|
|
1226
|
-
diffParts.push(`violations from last round: [${violations.join(", ")}]`);
|
|
1227
|
-
}
|
|
1228
|
-
if (request.new_since_last_round) {
|
|
1229
|
-
diffParts.push(`delta: ${request.new_since_last_round.slice(0, 200)}`);
|
|
1230
|
-
}
|
|
1231
|
-
// Build patched prompt
|
|
1232
|
-
const lines = [
|
|
1233
|
-
`## Loop Round ${request.round} — L1 Patch`,
|
|
252
|
+
export function buildSelfEvalBlock(round) {
|
|
253
|
+
return [
|
|
254
|
+
"### LoopForge Evaluation (Required)",
|
|
1234
255
|
"",
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
256
|
+
`After completing Round ${round}, submit JSON between these markers:`,
|
|
257
|
+
"---loopforge-eval",
|
|
258
|
+
JSON.stringify({
|
|
259
|
+
success: false,
|
|
260
|
+
output_summary: `<verified result of round ${round}>`,
|
|
261
|
+
constraint_violations: [],
|
|
262
|
+
should_continue: true,
|
|
263
|
+
discovered_constraints: [],
|
|
264
|
+
emerged_subtasks: [],
|
|
265
|
+
execution_evidence: {
|
|
266
|
+
files_changed: [],
|
|
267
|
+
test_results: { passed: 0, failed: 0, skipped: 0 },
|
|
268
|
+
success_criteria_met: [],
|
|
269
|
+
success_criteria_remaining: [],
|
|
270
|
+
progress_estimate: 0,
|
|
271
|
+
},
|
|
272
|
+
wrong_assumptions: [],
|
|
273
|
+
next_action: "<next concrete action, or empty when complete>",
|
|
274
|
+
}, null, 2),
|
|
275
|
+
"---end-loopforge-eval",
|
|
1238
276
|
"",
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
if (
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
lines.push(`- ~${c}~`);
|
|
1254
|
-
lines.push("");
|
|
1255
|
-
}
|
|
1256
|
-
if (violations.length) {
|
|
1257
|
-
lines.push("### Violations From Last Round (must fix)");
|
|
1258
|
-
for (const v of violations)
|
|
1259
|
-
lines.push(`- ${v}`);
|
|
1260
|
-
lines.push("");
|
|
1261
|
-
}
|
|
1262
|
-
if (request.new_since_last_round) {
|
|
1263
|
-
lines.push("### What Changed Since Last Round");
|
|
1264
|
-
lines.push(request.new_since_last_round);
|
|
1265
|
-
lines.push("");
|
|
1266
|
-
}
|
|
1267
|
-
if (request.last_round_result?.output_summary) {
|
|
1268
|
-
lines.push("### Last Round Summary");
|
|
1269
|
-
lines.push(request.last_round_result.output_summary);
|
|
1270
|
-
lines.push("");
|
|
1271
|
-
}
|
|
1272
|
-
lines.push("### Task");
|
|
1273
|
-
lines.push(request.task);
|
|
1274
|
-
return makeLoopCompileResponse({
|
|
1275
|
-
status: AgentStatus.OK,
|
|
1276
|
-
prompt: lines.join("\n"),
|
|
1277
|
-
recompile_level: "l1",
|
|
1278
|
-
diff_from_previous: diffParts.length ? diffParts.join("; ") : "Patch applied.",
|
|
1279
|
-
lineage: [`${request.loop_id}:r${request.round}`],
|
|
1280
|
-
constraints_active: active,
|
|
1281
|
-
constraints_retired: retired,
|
|
1282
|
-
technique_used: "patch",
|
|
1283
|
-
rolling_summary: rollingSummary,
|
|
1284
|
-
loop_id: request.loop_id,
|
|
1285
|
-
round: request.round,
|
|
1286
|
-
goal_id: goalId,
|
|
1287
|
-
goal_text_hash: computeGoalTextHash(request.task),
|
|
1288
|
-
plan_source: request.plan_source,
|
|
1289
|
-
warnings: [],
|
|
277
|
+
"Set success=true only when the full goal and hard constraints are verified.",
|
|
278
|
+
].join("\n");
|
|
279
|
+
}
|
|
280
|
+
function checkpoint(request, active, retired) {
|
|
281
|
+
const last = request.last_round_result;
|
|
282
|
+
if (!last?.compression_checkpoint)
|
|
283
|
+
return null;
|
|
284
|
+
const policy = getPolicy().checkpoint;
|
|
285
|
+
return makeCheckpointSummary({
|
|
286
|
+
label: last.checkpoint_label?.trim() || `Round ${last.round}`,
|
|
287
|
+
declared_at_round: last.round,
|
|
288
|
+
outcome: last.output_summary.slice(0, policy.outcome_max_chars),
|
|
289
|
+
carried_constraints: active.slice(0, policy.max_carried_constraints),
|
|
290
|
+
resolved_constraints: retired,
|
|
1290
291
|
});
|
|
1291
292
|
}
|
|
1292
|
-
export function
|
|
1293
|
-
const
|
|
1294
|
-
|
|
1295
|
-
const
|
|
1296
|
-
const
|
|
1297
|
-
const
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
// current state as seen by this compile call (pre-P5 correction).
|
|
1308
|
-
// We defer building until constraints are finalized below.
|
|
1309
|
-
checkpointRound = request.round - 1;
|
|
1310
|
-
}
|
|
1311
|
-
else if (request.round > 1) {
|
|
1312
|
-
// No new checkpoint — try loading the previous one from vault
|
|
1313
|
-
checkpointSummary = loadLatestCheckpoint(request.loop_id, request.round, vaultContext);
|
|
1314
|
-
if (checkpointSummary) {
|
|
1315
|
-
checkpointRound = checkpointSummary.declared_at_round;
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
// v3.5: Rolling summary — optionally scoped to post-checkpoint rounds
|
|
1319
|
-
const rollingSummary = buildRollingSummary(request.loop_id, request.round, vaultContext, checkpointRound > 0 ? checkpointRound : undefined);
|
|
1320
|
-
// P5c: Wrong assumptions — must be applied BEFORE formatting so they appear
|
|
1321
|
-
// in the rendered cross-round summary text injected into the prompt.
|
|
1322
|
-
if (lr?.wrong_assumptions?.length) {
|
|
1323
|
-
if (rollingSummary) {
|
|
1324
|
-
rollingSummary.key_outcomes = [
|
|
1325
|
-
...rollingSummary.key_outcomes,
|
|
1326
|
-
...lr.wrong_assumptions.map((a) => `Wrong assumption corrected: ${a}`),
|
|
1327
|
-
];
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
const rollingText = formatRollingSummaryForPrompt(rollingSummary);
|
|
1331
|
-
// ── Loop Objective: create at round 1, load from vault at later rounds ──
|
|
1332
|
-
let loopObjective = null;
|
|
1333
|
-
if (request.round === 1) {
|
|
1334
|
-
if (request.loop_objective) {
|
|
1335
|
-
loopObjective = request.loop_objective;
|
|
1336
|
-
}
|
|
1337
|
-
else {
|
|
1338
|
-
loopObjective = computeLoopObjectiveFromTask(request, vaultContext);
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
else {
|
|
1342
|
-
// P1 fix: Load loop objective from vault for round > 1
|
|
1343
|
-
const vaultLo = vaultGetLoopObjective(request.loop_id, vaultContext);
|
|
1344
|
-
if (vaultLo) {
|
|
1345
|
-
const loData = vaultLo.loop_objective;
|
|
1346
|
-
if (loData) {
|
|
1347
|
-
loopObjective = makeLoopObjective({
|
|
1348
|
-
objective: loData.objective ?? "",
|
|
1349
|
-
success_criteria: loData.success_criteria ?? [],
|
|
1350
|
-
hard_constraints: loData.hard_constraints ?? [],
|
|
1351
|
-
created_at_round: loData.created_at_round ?? 1,
|
|
1352
|
-
loop_id: loData.loop_id ?? request.loop_id,
|
|
1353
|
-
version: loData.version ?? 1,
|
|
1354
|
-
refinement_history: loData.refinement_history ?? [],
|
|
1355
|
-
});
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
if (!loopObjective && request.loop_objective) {
|
|
1359
|
-
loopObjective = request.loop_objective;
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
// ── P1: Apply objective_refinement from last round ──
|
|
1363
|
-
if (loopObjective && request.last_round_result?.objective_refinement) {
|
|
1364
|
-
const refinement = request.last_round_result.objective_refinement.trim();
|
|
1365
|
-
if (refinement) {
|
|
1366
|
-
const version = (loopObjective.version ?? 1) + 1;
|
|
1367
|
-
const history = [...(loopObjective.refinement_history ?? []), refinement];
|
|
1368
|
-
// Enforce max versions from policy
|
|
1369
|
-
const policy = getPolicy();
|
|
1370
|
-
const maxVersions = policy.evolution?.max_objective_versions ?? 10;
|
|
1371
|
-
const prunedHistory = history.slice(-maxVersions);
|
|
1372
|
-
const refinedObjective = `${loopObjective.objective} [R${request.round}: ${refinement}]`;
|
|
1373
|
-
loopObjective = makeLoopObjective({
|
|
1374
|
-
...loopObjective,
|
|
1375
|
-
objective: refinedObjective,
|
|
1376
|
-
version,
|
|
1377
|
-
refinement_history: prunedHistory,
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
// ── Build active constraints (base + LO + P0 discovered) ──
|
|
1382
|
-
let constraints = [...request.constraints_from_plan];
|
|
1383
|
-
if (loopObjective) {
|
|
1384
|
-
constraints = [
|
|
1385
|
-
...new Set([
|
|
1386
|
-
...constraints,
|
|
1387
|
-
...loopObjective.hard_constraints,
|
|
1388
|
-
...loopObjective.success_criteria,
|
|
1389
|
-
]),
|
|
1390
|
-
];
|
|
1391
|
-
}
|
|
1392
|
-
// P0: Merge discovered constraints from last round
|
|
1393
|
-
if (request.last_round_result?.discovered_constraints?.length) {
|
|
1394
|
-
const policy = getPolicy();
|
|
1395
|
-
const maxPerRound = policy.evolution?.max_discovered_constraints_per_round ?? 5;
|
|
1396
|
-
const maxActive = policy.evolution?.max_active_constraints ?? 15;
|
|
1397
|
-
const newConstraints = request.last_round_result.discovered_constraints
|
|
1398
|
-
.slice(0, maxPerRound)
|
|
1399
|
-
.filter((c) => !constraints.includes(c));
|
|
1400
|
-
constraints = [...constraints, ...newConstraints].slice(0, maxActive);
|
|
1401
|
-
}
|
|
1402
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1403
|
-
// P5: Self-correction — MUST process retractions/revisions BEFORE prompt build
|
|
1404
|
-
// so the technician compiler sees the corrected constraints and loop objective.
|
|
1405
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1406
|
-
const warnings = [];
|
|
1407
|
-
const retired = [];
|
|
1408
|
-
// P5a: Retracted constraints — remove from active set
|
|
1409
|
-
if (lr?.retracted_constraints?.length) {
|
|
1410
|
-
for (const rc of lr.retracted_constraints) {
|
|
1411
|
-
const idx = constraints.indexOf(rc);
|
|
1412
|
-
if (idx >= 0) {
|
|
1413
|
-
constraints.splice(idx, 1);
|
|
1414
|
-
retired.push(`[agent-retracted] ${rc}`);
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
// P5b: Revised success criteria — update Loop Objective
|
|
1419
|
-
if (lr?.revised_success_criteria?.length && loopObjective) {
|
|
1420
|
-
const sc = [...loopObjective.success_criteria];
|
|
1421
|
-
for (const rev of lr.revised_success_criteria) {
|
|
1422
|
-
const idx = sc.indexOf(rev.old);
|
|
1423
|
-
if (idx >= 0) {
|
|
1424
|
-
sc[idx] = rev.new;
|
|
1425
|
-
}
|
|
1426
|
-
else {
|
|
1427
|
-
sc.push(rev.new);
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
const version = (loopObjective.version ?? 1) + 1;
|
|
1431
|
-
const history = [
|
|
1432
|
-
...(loopObjective.refinement_history ?? []),
|
|
1433
|
-
`R${request.round}: revised ${lr.revised_success_criteria.length} success criteria`,
|
|
1434
|
-
];
|
|
1435
|
-
const updatedLo = makeLoopObjective({
|
|
1436
|
-
...loopObjective,
|
|
1437
|
-
success_criteria: sc,
|
|
1438
|
-
version,
|
|
1439
|
-
refinement_history: history,
|
|
1440
|
-
});
|
|
1441
|
-
// Rebuild constraints to include revised criteria
|
|
1442
|
-
constraints = [...new Set([
|
|
1443
|
-
...constraints.filter((c) => !updatedLo.hard_constraints.includes(c) && !updatedLo.success_criteria.includes(c)),
|
|
1444
|
-
...updatedLo.hard_constraints,
|
|
1445
|
-
...updatedLo.success_criteria,
|
|
1446
|
-
])];
|
|
1447
|
-
loopObjective = updatedLo;
|
|
1448
|
-
}
|
|
1449
|
-
// ── v1.10: Build checkpoint summary if last round declared one ──
|
|
1450
|
-
if (checkpointRound > 0 && !checkpointSummary && lr) {
|
|
1451
|
-
// checkpointRound was set but no summary yet → fresh declaration
|
|
1452
|
-
// We need the self-eval from last round. It's embedded in lr fields.
|
|
1453
|
-
// Build a synthetic SelfEvaluation from the last_round_result for the builder.
|
|
1454
|
-
const synthEval = {
|
|
1455
|
-
success: lr.success,
|
|
1456
|
-
output_summary: lr.output_summary,
|
|
1457
|
-
constraint_violations: lr.constraint_violations,
|
|
1458
|
-
should_continue: true,
|
|
1459
|
-
compression_checkpoint: true,
|
|
1460
|
-
checkpoint_label: lr.checkpoint_label ?? "",
|
|
1461
|
-
};
|
|
1462
|
-
checkpointSummary = buildCheckpointSummary(synthEval, checkpointRound, constraints, retired);
|
|
1463
|
-
}
|
|
1464
|
-
// ── Route to technique-specific specialist (v1.1 deep integration) ──
|
|
1465
|
-
let prompt;
|
|
1466
|
-
if (technique === "step-back") {
|
|
1467
|
-
prompt = compileStepBack(request, goalId, constraints, loopObjective, rollingText, analysis);
|
|
1468
|
-
}
|
|
1469
|
-
else if (technique === "least-to-most") {
|
|
1470
|
-
prompt = compileLeastToMost(request, goalId, constraints, loopObjective, rollingText, analysis);
|
|
1471
|
-
}
|
|
1472
|
-
else if (technique === "tree-of-thought") {
|
|
1473
|
-
prompt = compileToT(request, goalId, constraints, loopObjective, rollingText, analysis);
|
|
1474
|
-
}
|
|
1475
|
-
else {
|
|
1476
|
-
prompt = compileGeneric(request, goalId, constraints, loopObjective, rollingText, analysis, referenceFile, technique);
|
|
1477
|
-
}
|
|
1478
|
-
// ── v1.10: Inject checkpoint block (before rolling summary in prompt) ──
|
|
1479
|
-
// The checkpoint is rendered before the external context so it appears
|
|
1480
|
-
// in a natural position: after Loop Objective, before Cross-Round Summary.
|
|
1481
|
-
if (checkpointSummary) {
|
|
1482
|
-
prompt += "\n" + formatCheckpointForPrompt(checkpointSummary);
|
|
1483
|
-
}
|
|
1484
|
-
// ── v1.7: Inject external memory context (L2 only) ──
|
|
1485
|
-
const policy = getPolicy();
|
|
1486
|
-
if (policy.memory_injection.enabled && request.external_context) {
|
|
1487
|
-
prompt += formatExternalContext(request.external_context, policy.memory_injection.section_title, policy.memory_injection.max_context_length);
|
|
1488
|
-
}
|
|
1489
|
-
// ── P2: Emerged subtasks → suggested next task ──
|
|
1490
|
-
let suggestedNextTask = "";
|
|
1491
|
-
if (request.last_round_result?.emerged_subtasks?.length) {
|
|
1492
|
-
suggestedNextTask = request.last_round_result.emerged_subtasks
|
|
1493
|
-
.slice(0, 3)
|
|
1494
|
-
.join("; ");
|
|
1495
|
-
}
|
|
1496
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1497
|
-
// P4: Consistency validation — cross-check agent claims
|
|
1498
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1499
|
-
const evidence = lr?.execution_evidence;
|
|
1500
|
-
if (evidence) {
|
|
1501
|
-
// Check 1: Agent claims action but reports no files changed
|
|
1502
|
-
if (evidence.files_changed.length === 0 && lr.output_summary) {
|
|
1503
|
-
const actionWords = /\b(fix|modif|chang|updat|refactor|implement|add|remov|delet|rewrit|修补|修改|更改|更新|重构|实现|添加|删除|重写)\b/i;
|
|
1504
|
-
if (actionWords.test(lr.output_summary)) {
|
|
1505
|
-
warnings.push("P4: Agent output claims changes but execution_evidence.files_changed is empty — possible oversight");
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
// Check 2: Agent reports success but tests failed
|
|
1509
|
-
if (evidence.test_results && evidence.test_results.failed > 0 && lr.success) {
|
|
1510
|
-
warnings.push(`P4: Agent reports success=true but ${evidence.test_results.failed} tests failed — claims may not match reality`);
|
|
1511
|
-
}
|
|
1512
|
-
// Check 3: Progress estimate mismatch with criteria tracking
|
|
1513
|
-
if (loopObjective && loopObjective.success_criteria.length > 0) {
|
|
1514
|
-
const metCount = evidence.success_criteria_met.length;
|
|
1515
|
-
const totalCount = loopObjective.success_criteria.length;
|
|
1516
|
-
const objectiveProgress = totalCount > 0 ? metCount / totalCount : 0;
|
|
1517
|
-
const subjectiveProgress = evidence.progress_estimate;
|
|
1518
|
-
const policy = getPolicy();
|
|
1519
|
-
const mismatchThreshold = policy.evolution?.progress_mismatch_threshold ?? 0.3;
|
|
1520
|
-
if (Math.abs(objectiveProgress - subjectiveProgress) > mismatchThreshold) {
|
|
1521
|
-
warnings.push(`P4: Progress estimate mismatch — agent estimates ${(subjectiveProgress * 100).toFixed(0)}% but ${metCount}/${totalCount} criteria met (${(objectiveProgress * 100).toFixed(0)}%)`);
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1526
|
-
// P4: Progress gradient — early stall detection
|
|
1527
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1528
|
-
if (evidence && vaultContext) {
|
|
1529
|
-
const prevEvidence = getPreviousRoundEvidence(request.loop_id, request.round - 1, vaultContext);
|
|
1530
|
-
if (prevEvidence) {
|
|
1531
|
-
const delta = evidence.progress_estimate - prevEvidence.progress_estimate;
|
|
1532
|
-
const policy = getPolicy();
|
|
1533
|
-
const stallThreshold = policy.evolution?.progress_stall_threshold ?? 0.05;
|
|
1534
|
-
const stallRounds = policy.evolution?.progress_stall_rounds ?? 2;
|
|
1535
|
-
if (delta < stallThreshold && evidence.progress_estimate < 0.95) {
|
|
1536
|
-
// Check how many consecutive rounds have been below threshold
|
|
1537
|
-
const stallCount = countProgressStallRounds(request.loop_id, request.round, vaultContext, stallThreshold);
|
|
1538
|
-
if (stallCount >= stallRounds) {
|
|
1539
|
-
warnings.push(`P4: Progress has stalled — <${(stallThreshold * 100).toFixed(0)}% improvement for ${stallCount} consecutive rounds. Current: ${(evidence.progress_estimate * 100).toFixed(0)}%`);
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1545
|
-
// P4: Build Progress Dashboard for prompt injection
|
|
1546
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1547
|
-
let progressDashboard = "";
|
|
1548
|
-
if (evidence && loopObjective) {
|
|
1549
|
-
const totalCriteria = loopObjective.success_criteria.length;
|
|
1550
|
-
const metCriteria = evidence.success_criteria_met.length;
|
|
1551
|
-
const remainingCriteria = evidence.success_criteria_remaining;
|
|
1552
|
-
const objProgress = totalCriteria > 0 ? metCriteria / totalCriteria : evidence.progress_estimate;
|
|
1553
|
-
const subjProgress = evidence.progress_estimate;
|
|
1554
|
-
const lines = [
|
|
1555
|
-
"",
|
|
1556
|
-
"### Progress Dashboard",
|
|
1557
|
-
"",
|
|
1558
|
-
`**Overall**: ${metCriteria}/${totalCriteria} criteria met (${(objProgress * 100).toFixed(0)}%)`,
|
|
1559
|
-
];
|
|
1560
|
-
if (Math.abs(objProgress - subjProgress) > 0.05) {
|
|
1561
|
-
lines.push(`**Agent estimate**: ${(subjProgress * 100).toFixed(0)}%`);
|
|
1562
|
-
}
|
|
1563
|
-
if (evidence.files_changed.length) {
|
|
1564
|
-
lines.push(`**Files changed**: ${evidence.files_changed.join(", ")}`);
|
|
1565
|
-
}
|
|
1566
|
-
if (evidence.test_results) {
|
|
1567
|
-
const tr = evidence.test_results;
|
|
1568
|
-
lines.push(`**Tests**: ${tr.passed} passed, ${tr.failed} failed, ${tr.skipped} skipped`);
|
|
1569
|
-
}
|
|
1570
|
-
if (remainingCriteria.length) {
|
|
1571
|
-
lines.push("**Remaining criteria**:");
|
|
1572
|
-
for (const rc of remainingCriteria.slice(0, 5)) {
|
|
1573
|
-
lines.push(`- ${rc}`);
|
|
1574
|
-
}
|
|
1575
|
-
if (remainingCriteria.length > 5) {
|
|
1576
|
-
lines.push(`- ... and ${remainingCriteria.length - 5} more`);
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
lines.push("");
|
|
1580
|
-
// Compute trend from previous round
|
|
1581
|
-
if (vaultContext) {
|
|
1582
|
-
const prevEvidence = getPreviousRoundEvidence(request.loop_id, request.round - 1, vaultContext);
|
|
1583
|
-
if (prevEvidence) {
|
|
1584
|
-
const trend = objProgress - (prevEvidence.success_criteria_met.length / Math.max(totalCriteria, 1));
|
|
1585
|
-
if (trend > 0.03) {
|
|
1586
|
-
lines.push(`**Trend**: ↑ advancing (+${(trend * 100).toFixed(0)}% this round)`);
|
|
1587
|
-
}
|
|
1588
|
-
else if (trend > -0.03) {
|
|
1589
|
-
lines.push("**Trend**: → stable");
|
|
1590
|
-
}
|
|
1591
|
-
else {
|
|
1592
|
-
lines.push(`**Trend**: ↓ regressing (${(trend * 100).toFixed(0)}% this round)`);
|
|
1593
|
-
}
|
|
1594
|
-
lines.push("");
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
progressDashboard = lines.join("\n");
|
|
1598
|
-
}
|
|
1599
|
-
return makeLoopCompileResponse({
|
|
293
|
+
export function compileLoop(request, context) {
|
|
294
|
+
const decision = levelDecision(request, context);
|
|
295
|
+
const previous = getPreviousRound(request.loop_id, request.round - 1, context);
|
|
296
|
+
const objective = evolveObjective(request, context);
|
|
297
|
+
const constraints = evolveConstraints(request, objective, previous);
|
|
298
|
+
const rolling = buildRollingSummary(request.loop_id, request.round, context);
|
|
299
|
+
const alignment = alignTask(request.task, request, context);
|
|
300
|
+
const health = checkLoopHealth(request.loop_id, { ...request, loop_objective: objective }, context);
|
|
301
|
+
const warnings = unique([
|
|
302
|
+
alignment.warning,
|
|
303
|
+
health.escalation_recommended !== "none"
|
|
304
|
+
? `Loop health recommends ${health.escalation_recommended}.`
|
|
305
|
+
: "",
|
|
306
|
+
]);
|
|
307
|
+
const response = makeLoopCompileResponse({
|
|
1600
308
|
status: AgentStatus.OK,
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
diff_from_previous: request.round === 1 || request.plan_source
|
|
1604
|
-
? "Full recompile — new goal or first call."
|
|
1605
|
-
: "Full recompile — goal_id changed or strategy collapse.",
|
|
309
|
+
recompile_level: decision.level,
|
|
310
|
+
diff_from_previous: decision.reasons.join(","),
|
|
1606
311
|
lineage: [`${request.loop_id}:r${request.round}`],
|
|
1607
|
-
constraints_active: constraints,
|
|
1608
|
-
constraints_retired: retired,
|
|
1609
|
-
technique_used: technique,
|
|
1610
|
-
reference_file: referenceFile,
|
|
1611
|
-
rolling_summary: rollingSummary,
|
|
1612
|
-
checkpoint_summary: checkpointSummary,
|
|
312
|
+
constraints_active: constraints.active,
|
|
313
|
+
constraints_retired: constraints.retired,
|
|
1613
314
|
loop_id: request.loop_id,
|
|
1614
315
|
round: request.round,
|
|
1615
|
-
goal_id:
|
|
316
|
+
goal_id: deriveGoalId(request.loop_id, request.task, request.goal_id),
|
|
1616
317
|
goal_text_hash: computeGoalTextHash(request.task),
|
|
1617
|
-
loop_objective:
|
|
318
|
+
loop_objective: objective,
|
|
319
|
+
loop_health: health,
|
|
320
|
+
task_alignment: alignment,
|
|
321
|
+
rolling_summary: rolling,
|
|
322
|
+
checkpoint_summary: checkpoint(request, constraints.active, constraints.retired),
|
|
323
|
+
suggested_next_task: request.last_round_result?.next_action?.trim() ||
|
|
324
|
+
request.last_round_result?.emerged_subtasks?.[0] || "",
|
|
1618
325
|
plan_source: request.plan_source,
|
|
1619
|
-
suggested_next_task: suggestedNextTask,
|
|
1620
326
|
warnings,
|
|
1621
327
|
});
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
const
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
lines.push("**Success Criteria**:");
|
|
1644
|
-
for (const sc of lo.success_criteria)
|
|
1645
|
-
lines.push(`- ${sc}`);
|
|
1646
|
-
}
|
|
1647
|
-
if (lo.hard_constraints.length) {
|
|
1648
|
-
lines.push("**Hard Constraints**:");
|
|
1649
|
-
for (const hc of lo.hard_constraints)
|
|
1650
|
-
lines.push(`- ${hc}`);
|
|
1651
|
-
}
|
|
1652
|
-
lines.push("");
|
|
1653
|
-
return lines;
|
|
1654
|
-
}
|
|
1655
|
-
function buildConstraintsBlock(constraints) {
|
|
1656
|
-
if (!constraints.length)
|
|
1657
|
-
return [];
|
|
1658
|
-
const lines = ["### Active Constraints"];
|
|
1659
|
-
for (const c of constraints)
|
|
1660
|
-
lines.push(`- ${c}`);
|
|
1661
|
-
lines.push("");
|
|
1662
|
-
return lines;
|
|
1663
|
-
}
|
|
1664
|
-
function buildTaskBlock(ctx) {
|
|
1665
|
-
const lines = [
|
|
1666
|
-
"### Task",
|
|
1667
|
-
ctx.request.task,
|
|
1668
|
-
"",
|
|
1669
|
-
];
|
|
1670
|
-
if (ctx.request.domain) {
|
|
1671
|
-
lines.push(`**Domain**: ${ctx.request.domain}`);
|
|
1672
|
-
lines.push("");
|
|
1673
|
-
}
|
|
1674
|
-
return lines;
|
|
1675
|
-
}
|
|
1676
|
-
function buildIdentityBlock(ctx) {
|
|
1677
|
-
return [
|
|
1678
|
-
"### Loop Identity",
|
|
1679
|
-
`- Loop ID: \`${ctx.request.loop_id}\``,
|
|
1680
|
-
`- Goal ID: \`${ctx.goalId}\``,
|
|
1681
|
-
`- Round: ${ctx.request.round}`,
|
|
1682
|
-
"",
|
|
1683
|
-
];
|
|
1684
|
-
}
|
|
1685
|
-
/** Generic compiler — reads technique reference file (fallback for zero-shot/few-shot/cot). */
|
|
1686
|
-
function compileGeneric(request, goalId, constraints, loopObjective, rollingText, analysis, referenceFile, technique) {
|
|
1687
|
-
const ctx = { request, goalId, constraints, loopObjective, rollingText };
|
|
1688
|
-
const lines = [
|
|
1689
|
-
...buildHeader(ctx, technique),
|
|
1690
|
-
`Read the technique reference BEFORE generating the prompt:`,
|
|
1691
|
-
` Technique: ${technique}`,
|
|
1692
|
-
` Reference: ${referenceFile}`,
|
|
1693
|
-
` Rationale: ${analysis.rationale}`,
|
|
1694
|
-
"",
|
|
1695
|
-
...buildLoopObjectiveBlock(loopObjective),
|
|
1696
|
-
...buildConstraintsBlock(constraints),
|
|
1697
|
-
...(request.plan_source ? [`**Plan Source**: ${request.plan_source}`, ""] : []),
|
|
1698
|
-
...buildTaskBlock(ctx),
|
|
1699
|
-
...buildIdentityBlock(ctx),
|
|
1700
|
-
"### Generation Instructions",
|
|
1701
|
-
`1. Read \`${referenceFile}\` — study its structure rules, section count, and format requirements`,
|
|
1702
|
-
"2. Generate a complete prompt following that technique's structure",
|
|
1703
|
-
"3. The prompt must be self-contained — ready for a coding agent to execute",
|
|
1704
|
-
"4. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1705
|
-
];
|
|
1706
|
-
return lines.join("\n");
|
|
1707
|
-
}
|
|
1708
|
-
// ── Step-Back specialist ───────────────────────────────────────────────────────
|
|
1709
|
-
function compileStepBack(request, goalId, constraints, loopObjective, rollingText, analysis) {
|
|
1710
|
-
const ctx = { request, goalId, constraints, loopObjective, rollingText };
|
|
1711
|
-
const lines = [
|
|
1712
|
-
...buildHeader(ctx, "step-back"),
|
|
1713
|
-
`**Rationale**: ${analysis.rationale}`,
|
|
1714
|
-
"",
|
|
1715
|
-
"Generate a complete prompt using the **Step-Back** technique. Follow the 8-section skeleton below.",
|
|
1716
|
-
"Embed all structural constraints — do NOT reference an external file.",
|
|
1717
|
-
"",
|
|
1718
|
-
...buildLoopObjectiveBlock(loopObjective),
|
|
1719
|
-
...buildConstraintsBlock(constraints),
|
|
1720
|
-
...(request.plan_source ? [`**Plan Source**: ${request.plan_source}`, ""] : []),
|
|
1721
|
-
...buildTaskBlock(ctx),
|
|
1722
|
-
...buildIdentityBlock(ctx),
|
|
1723
|
-
"",
|
|
1724
|
-
"### 8-Section Skeleton (REQUIRED)",
|
|
1725
|
-
"",
|
|
1726
|
-
"| # | Section | Required | Notes |",
|
|
1727
|
-
"|---|---------|----------|-------|",
|
|
1728
|
-
"| 1 | 角色 | ✓ | Domain expert role appropriate to the task |",
|
|
1729
|
-
"| 2 | 任务 | ✓ | One-sentence task summary |",
|
|
1730
|
-
"| 3 | 输入 | ✓ | Target module/problem to analyse |",
|
|
1731
|
-
"| 4 | 输出格式 | ✓ | Numbered list of expected outputs |",
|
|
1732
|
-
"| 5 | **Step-Back 抽象框架** | ✓ | See format rules below |",
|
|
1733
|
-
"| 6 | 具体实现要求 | ✓ | MUST start with: **\"基于上述抽象框架,实现以下所有功能\"** |",
|
|
1734
|
-
"| 7 | 硬约束 | ✓ | Numbered list of hard constraints |",
|
|
1735
|
-
"| 8 | 生成要求 | ✓ | Acceptance criteria |",
|
|
1736
|
-
"",
|
|
1737
|
-
"### Section 5 — Step-Back 抽象框架 (CRITICAL)",
|
|
1738
|
-
"",
|
|
1739
|
-
"Must contain **2-3 abstract frameworks**. Each framework is an independent ASCII diagram.",
|
|
1740
|
-
"Frameworks are PARALLEL (peer-level), not sequential.",
|
|
1741
|
-
"",
|
|
1742
|
-
"Each framework format:",
|
|
1743
|
-
"```",
|
|
1744
|
-
"### 框架N:[Framework Name]",
|
|
1745
|
-
"",
|
|
1746
|
-
"[ASCII diagram with principles/formulas/classification tables/rule tables]",
|
|
1747
|
-
"",
|
|
1748
|
-
"Diagram content requirements:",
|
|
1749
|
-
"- Core principle/formula (e.g. \"Total = Σ(dimension × weight)\")",
|
|
1750
|
-
"- Component table (e.g. weight allocation table, type mapping table)",
|
|
1751
|
-
"- Rule description (e.g. \"Missing data dimensions use neutral value\")",
|
|
1752
|
-
"```",
|
|
1753
|
-
"",
|
|
1754
|
-
"**Tightening rules**:",
|
|
1755
|
-
"- The step-back question must be MORE ABSTRACT than the original problem, but cover all required information",
|
|
1756
|
-
"- Abstract ≠ vague. Use precise formulas, principles, standards, definitions, or causal mechanisms",
|
|
1757
|
-
"- Tighten to the MINIMUM generalisation layer that still covers the original problem",
|
|
1758
|
-
"- Reasoning must RETURN to the original problem — do not stay at the principle level",
|
|
1759
|
-
"",
|
|
1760
|
-
"**Section 6 transition sentence (MANDATORY)**:",
|
|
1761
|
-
"\"基于上述抽象框架,实现以下所有功能。\"",
|
|
1762
|
-
"",
|
|
1763
|
-
"### Generation Instructions",
|
|
1764
|
-
"1. Extract 2-3 abstract principles/frameworks from the task domain",
|
|
1765
|
-
"2. Build ASCII diagrams for each framework (formulas, tables, rules)",
|
|
1766
|
-
"3. Apply the abstraction back to the concrete task",
|
|
1767
|
-
"4. Verify: the generated prompt satisfies all structural rules in the skeleton above",
|
|
1768
|
-
"5. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1769
|
-
];
|
|
1770
|
-
return lines.join("\n");
|
|
1771
|
-
}
|
|
1772
|
-
// ── Least-to-Most specialist ───────────────────────────────────────────────────
|
|
1773
|
-
function compileLeastToMost(request, goalId, constraints, loopObjective, rollingText, analysis) {
|
|
1774
|
-
const ctx = { request, goalId, constraints, loopObjective, rollingText };
|
|
1775
|
-
const lines = [
|
|
1776
|
-
...buildHeader(ctx, "least-to-most"),
|
|
1777
|
-
`**Rationale**: ${analysis.rationale}`,
|
|
1778
|
-
"",
|
|
1779
|
-
"Generate a complete prompt using the **Least-to-Most** technique. Follow the 8-section skeleton below.",
|
|
1780
|
-
"Embed all structural constraints — do NOT reference an external file.",
|
|
1781
|
-
"",
|
|
1782
|
-
...buildLoopObjectiveBlock(loopObjective),
|
|
1783
|
-
...buildConstraintsBlock(constraints),
|
|
1784
|
-
...(request.plan_source ? [`**Plan Source**: ${request.plan_source}`, ""] : []),
|
|
1785
|
-
...buildTaskBlock(ctx),
|
|
1786
|
-
...buildIdentityBlock(ctx),
|
|
1787
|
-
"",
|
|
1788
|
-
"### 8-Section Skeleton (REQUIRED)",
|
|
1789
|
-
"",
|
|
1790
|
-
"| # | Section | Required | Notes |",
|
|
1791
|
-
"|---|---------|----------|-------|",
|
|
1792
|
-
"| 1 | 角色 | ✓ | Domain expert role appropriate to the task |",
|
|
1793
|
-
"| 2 | 任务 | ✓ | One-sentence task summary |",
|
|
1794
|
-
"| 3 | 输入 | ✓ | Target module |",
|
|
1795
|
-
"| 4 | 输出格式 | ✓ | Numbered list (e.g. \"1. DDL, 2. API, 3. Enum...\") |",
|
|
1796
|
-
"| 5 | **Least-to-Most 逐步推理框架** | ✓ | See format rules below |",
|
|
1797
|
-
"| 6 | 具体实现要求 | ✓ | Expand per output format list (NOT per sub-problem — sub-problems fully expanded in §5) |",
|
|
1798
|
-
"| 7 | 硬约束 | ✓ | Numbered list |",
|
|
1799
|
-
"| 8 | 生成要求 | ✓ | Acceptance criteria, must include: \"严格按照子问题顺序逐步实现\" |",
|
|
1800
|
-
"",
|
|
1801
|
-
"### Section 5 — Least-to-Most 逐步推理框架 (CRITICAL)",
|
|
1802
|
-
"",
|
|
1803
|
-
"Must contain **4-6 ordered sub-problems**. Each sub-problem format:",
|
|
1804
|
-
"",
|
|
1805
|
-
"```",
|
|
1806
|
-
"### 子问题 N:[Sub-problem Name]",
|
|
1807
|
-
"",
|
|
1808
|
-
"**目标:** [What this sub-problem solves, which prior sub-problem(s) it depends on]",
|
|
1809
|
-
"",
|
|
1810
|
-
"**要求:**",
|
|
1811
|
-
"- Specific requirement list",
|
|
1812
|
-
"- [If sub-problem involves enums/mapping tables, list them]",
|
|
1813
|
-
"",
|
|
1814
|
-
"---",
|
|
1815
|
-
"```",
|
|
1816
|
-
"",
|
|
1817
|
-
"**Critical rules**:",
|
|
1818
|
-
"- Sub-problem 1 is the SIMPLEST (e.g. \"Define enums and base data structures\")",
|
|
1819
|
-
"- Each sub-problem declares: \"基于子问题 N-1 的结论\"",
|
|
1820
|
-
"- The LAST sub-problem MUST be: **\"综合实现完整模块\"** — list all components to integrate",
|
|
1821
|
-
"- Sub-problems separated by `---`",
|
|
1822
|
-
"- Order goes from LEAST complex → MOST complex, dependency chain must be explicit",
|
|
1823
|
-
"- Each sub-problem must serve the original task — no unrelated steps",
|
|
1824
|
-
"- The final sub-problem must be equivalent to or directly address the original task",
|
|
1825
|
-
"",
|
|
1826
|
-
"### Generation Instructions",
|
|
1827
|
-
"1. Decompose the task into 4-6 ordered sub-problems with explicit dependencies",
|
|
1828
|
-
"2. Sub-problem 1 starts with the simplest building block (data structures, enums, base config)",
|
|
1829
|
-
"3. Each subsequent sub-problem builds on prior results",
|
|
1830
|
-
"4. Final sub-problem integrates all components into the complete module",
|
|
1831
|
-
"5. Verify: the generated prompt satisfies all structural rules in the skeleton above",
|
|
1832
|
-
"6. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1833
|
-
];
|
|
1834
|
-
return lines.join("\n");
|
|
1835
|
-
}
|
|
1836
|
-
// ── Tree-of-Thought specialist ──────────────────────────────────────────────────
|
|
1837
|
-
function compileToT(request, goalId, constraints, loopObjective, rollingText, analysis) {
|
|
1838
|
-
const ctx = { request, goalId, constraints, loopObjective, rollingText };
|
|
1839
|
-
const lines = [
|
|
1840
|
-
...buildHeader(ctx, "tree-of-thought"),
|
|
1841
|
-
`**Rationale**: ${analysis.rationale}`,
|
|
1842
|
-
"",
|
|
1843
|
-
"Generate a complete prompt using the **Tree-of-Thought** technique. Follow the 8-section skeleton below.",
|
|
1844
|
-
"Embed all structural constraints — do NOT reference an external file.",
|
|
1845
|
-
"",
|
|
1846
|
-
...buildLoopObjectiveBlock(loopObjective),
|
|
1847
|
-
...buildConstraintsBlock(constraints),
|
|
1848
|
-
...(request.plan_source ? [`**Plan Source**: ${request.plan_source}`, ""] : []),
|
|
1849
|
-
...buildTaskBlock(ctx),
|
|
1850
|
-
...buildIdentityBlock(ctx),
|
|
1851
|
-
"",
|
|
1852
|
-
"### 8-Section Skeleton (REQUIRED)",
|
|
1853
|
-
"",
|
|
1854
|
-
"| # | Section | Required | Notes |",
|
|
1855
|
-
"|---|---------|----------|-------|",
|
|
1856
|
-
"| 1 | 角色 | ✓ | Multi-path problem-solving expert; may introduce 3 expert personas |",
|
|
1857
|
-
"| 2 | 任务 | ✓ | One-sentence task summary |",
|
|
1858
|
-
"| 3 | 输入 | ✓ | High-risk complex problem |",
|
|
1859
|
-
"| 4 | 输出格式 | ✓ | \"先输出思维树过程,再输出最终答案/方案\" |",
|
|
1860
|
-
"| 5 | **思维树探索框架** | ✓ | See 3 sub-blocks below (REQUIRED) |",
|
|
1861
|
-
"| 6 | 具体实现要求 | ✓ | Expand per output format |",
|
|
1862
|
-
"| 7 | 硬约束 | ✓ | Include branch_count, max_depth, pruning rules, safety/performance constraints |",
|
|
1863
|
-
"| 8 | 生成要求 | ✓ | \"先探索多路径,再选择最优方案\" |",
|
|
1864
|
-
"",
|
|
1865
|
-
"### Section 5 — 思维树探索框架 (CRITICAL — 3 sub-blocks REQUIRED)",
|
|
1866
|
-
"",
|
|
1867
|
-
"**Sub-block A: Search Strategy Declaration**",
|
|
1868
|
-
"```",
|
|
1869
|
-
"搜索策略: [beam / dfs / expert-panel]",
|
|
1870
|
-
"分支数(branch_count): 2-4",
|
|
1871
|
-
"最大深度(max_depth): ≤3",
|
|
1872
|
-
"每轮保留数(keep_count): 1-2",
|
|
1873
|
-
"```",
|
|
1874
|
-
"Strategy selection guide:",
|
|
1875
|
-
"- **beam** (default): planning, creative, math search — generate multiple candidates per turn, keep top b",
|
|
1876
|
-
"- **dfs**: puzzles, constraint satisfaction, debugging — go deep on highest-score branch, backtrack on failure",
|
|
1877
|
-
"- **expert-panel**: 3+ experts each generate candidates, then cross-evaluate and revise",
|
|
1878
|
-
"",
|
|
1879
|
-
"**Sub-block B: Evaluation Criteria Table**",
|
|
1880
|
-
"| 标准 | 权重 | 说明 |",
|
|
1881
|
-
"|------|------|------|",
|
|
1882
|
-
"| 正确性 | 最高 | Logic/math correctness |",
|
|
1883
|
-
"| 可行性 | 高 | Can be actually implemented |",
|
|
1884
|
-
"| 约束匹配 | 高 | Satisfies hard constraints |",
|
|
1885
|
-
"| 性能 | 中 | Time/space efficiency |",
|
|
1886
|
-
"| 安全性 | 最高 | No vulnerabilities/privilege escalation |",
|
|
1887
|
-
"",
|
|
1888
|
-
"**Sub-block C: Thought Tree State Table Format**",
|
|
1889
|
-
"Require the model to output in this table format:",
|
|
1890
|
-
"```",
|
|
1891
|
-
"| 轮次 | 分支 | 候选方案 | 评估 | 决策 |",
|
|
1892
|
-
"|------|------|---------|------|------|",
|
|
1893
|
-
"| 1 | A | [方案描述] | 评分/判断 | 保留/剪枝 |",
|
|
1894
|
-
"| 1 | B | [方案描述] | 评分/判断 | 保留/剪枝 |",
|
|
1895
|
-
"| 1 | C | [方案描述] | 评分/判断 | 保留/剪枝 |",
|
|
1896
|
-
"| 2 | A1 | [深入展开] | ... | ... |",
|
|
1897
|
-
"| ... | ... | ... | ... | ... |",
|
|
1898
|
-
"",
|
|
1899
|
-
"最终选择: [最优方案 + 选择理由]",
|
|
1900
|
-
"```",
|
|
1901
|
-
"",
|
|
1902
|
-
"### Critical Rules",
|
|
1903
|
-
"- Hard constraints MUST be ranked FIRST in evaluation criteria",
|
|
1904
|
-
"- Branch count: 2-4, depth: ≤3 (prevents token explosion)",
|
|
1905
|
-
"- Every turn: generate multiple candidates → evaluate → keep/prune — never single-path",
|
|
1906
|
-
"- If all branches are low quality: backtrack to previous turn and regenerate",
|
|
1907
|
-
"- The core of ToT is NOT \"3 experts chatting\" — it's: candidate generation → state evaluation → search/prune → final selection",
|
|
1908
|
-
"- thought is a PUBLIC intermediate semantic unit, not hidden chain-of-thought",
|
|
1909
|
-
"- State table MUST have: round, branch ID, candidate description, evaluation score/judgment, keep/prune decision",
|
|
1910
|
-
"",
|
|
1911
|
-
"### Generation Instructions",
|
|
1912
|
-
"1. Select the appropriate search strategy (beam/dfs/expert-panel) based on task type",
|
|
1913
|
-
"2. Define 2-4 candidate approaches as initial branches",
|
|
1914
|
-
"3. Build evaluation criteria table with hard constraints ranked first",
|
|
1915
|
-
"4. Define the state table format for tracking exploration",
|
|
1916
|
-
"5. Set branch count and max depth to prevent token explosion",
|
|
1917
|
-
"6. Verify: the generated prompt satisfies all structural rules in the skeleton above",
|
|
1918
|
-
"7. Output only the generated prompt — no preamble, no meta-commentary",
|
|
1919
|
-
];
|
|
1920
|
-
return lines.join("\n");
|
|
1921
|
-
}
|
|
1922
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1923
|
-
// Self-Evaluation block (v1.1 — autonomous loop feedback)
|
|
1924
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1925
|
-
/** Build the standardized self-evaluation block appended to every compiled prompt.
|
|
1926
|
-
* The agent MUST output a JSON self-evaluation between the delimiters.
|
|
1927
|
-
* 4 required fields + 3 optional evolution fields (P0–P2) —
|
|
1928
|
-
* each consumed by at least one downstream function. */
|
|
1929
|
-
export function buildSelfEvalBlock(round) {
|
|
1930
|
-
return [
|
|
1931
|
-
"",
|
|
1932
|
-
"### LoopForge Self-Evaluation (REQUIRED)",
|
|
1933
|
-
"",
|
|
1934
|
-
`You are completing Round ${round}. After finishing the task above, ` +
|
|
1935
|
-
"you MUST output a self-evaluation in this exact format:",
|
|
1936
|
-
"",
|
|
1937
|
-
"```",
|
|
1938
|
-
"---loopforge-eval",
|
|
1939
|
-
"{",
|
|
1940
|
-
' "success": true,',
|
|
1941
|
-
` "output_summary": "<one paragraph — what was DONE in round ${round}, be specific>",`,
|
|
1942
|
-
' "constraint_violations": [],',
|
|
1943
|
-
' "should_continue": true,',
|
|
1944
|
-
' "discovered_constraints": [],',
|
|
1945
|
-
' "objective_refinement": "",',
|
|
1946
|
-
' "emerged_subtasks": [],',
|
|
1947
|
-
' "execution_evidence": {',
|
|
1948
|
-
' "files_changed": [],',
|
|
1949
|
-
' "test_results": {"passed": 0, "failed": 0, "skipped": 0},',
|
|
1950
|
-
' "success_criteria_met": [],',
|
|
1951
|
-
' "success_criteria_remaining": [],',
|
|
1952
|
-
' "progress_estimate": 0.0',
|
|
1953
|
-
' },',
|
|
1954
|
-
' "retracted_constraints": [],',
|
|
1955
|
-
' "revised_success_criteria": [],',
|
|
1956
|
-
' "wrong_assumptions": [],',
|
|
1957
|
-
' "compression_checkpoint": false,',
|
|
1958
|
-
' "checkpoint_label": ""',
|
|
1959
|
-
"}",
|
|
1960
|
-
"---end-loopforge-eval",
|
|
1961
|
-
"```",
|
|
1962
|
-
"",
|
|
1963
|
-
"Field rules:",
|
|
1964
|
-
`- success: true ONLY if all hard constraints were met and the task goal was achieved`,
|
|
1965
|
-
`- output_summary: Be specific about what was PRODUCED, not what you "tried". ` +
|
|
1966
|
-
`Bad: "worked on audit". Good: "Found 3 vulns: reentrancy in withdraw(), ` +
|
|
1967
|
-
`integer overflow in transfer(), missing access control in mint()". ` +
|
|
1968
|
-
`This feeds cross-round knowledge distillation.`,
|
|
1969
|
-
`- constraint_violations: List ONLY constraints you actually broke. Empty array [] if none. ` +
|
|
1970
|
-
`This directly affects constraint-integrity scoring and retirement decisions.`,
|
|
1971
|
-
`- should_continue: false ONLY when the ENTIRE task is complete. ` +
|
|
1972
|
-
`If there is more to audit/implement/test, say true. ` +
|
|
1973
|
-
`This tells the autonomous runner when to stop.`,
|
|
1974
|
-
`- discovered_constraints (P0 — optional): New constraints you discovered this round. ` +
|
|
1975
|
-
`Omit or [] if none.`,
|
|
1976
|
-
`- objective_refinement (P1 — optional): Deepened understanding of the task goal. ` +
|
|
1977
|
-
`APPENDED to the original objective. Omit or "" if unchanged.`,
|
|
1978
|
-
`- emerged_subtasks (P2 — optional): Sub-problems that surfaced during execution. ` +
|
|
1979
|
-
`Omit or [] if none.`,
|
|
1980
|
-
`- execution_evidence (P4 — recommended): Structured record of what you actually did. ` +
|
|
1981
|
-
`files_changed: paths relative to project root. test_results: null if no tests run. ` +
|
|
1982
|
-
`success_criteria_met/remaining: track against the Loop Objective. ` +
|
|
1983
|
-
`progress_estimate: your honest estimate of overall completion (0.0 to 1.0). ` +
|
|
1984
|
-
`This enables real progress tracking and early stall detection.`,
|
|
1985
|
-
`- retracted_constraints (P5 — optional): Constraints you now believe are WRONG. ` +
|
|
1986
|
-
`Removed from active guardrails. Only retract if you have evidence. Omit or [] if none.`,
|
|
1987
|
-
`- revised_success_criteria (P5 — optional): Success criteria that need reformulation. ` +
|
|
1988
|
-
`Array of {old, new} objects. Applied to the Loop Objective. Omit or [] if none.`,
|
|
1989
|
-
`- wrong_assumptions (P5 — optional): Assumptions from earlier rounds that turned ` +
|
|
1990
|
-
`out to be incorrect. Recorded as key lessons. Omit or [] if none.`,
|
|
1991
|
-
`- compression_checkpoint (v1.10 — optional): Set to true when you have COMPLETED a ` +
|
|
1992
|
-
`major subtask. The compiler will snapshot the current state (constraints, outcome) ` +
|
|
1993
|
-
`into a persistent checkpoint that survives context-window eviction. ` +
|
|
1994
|
-
`Use sparingly — only at genuine subtask boundaries, not every round. ` +
|
|
1995
|
-
`Omit or false otherwise.`,
|
|
1996
|
-
`- checkpoint_label (v1.10 — optional): Human-readable label for this checkpoint. ` +
|
|
1997
|
-
`e.g. "数据模型层完成", "API layer complete", "Auth module done". ` +
|
|
1998
|
-
`Used as the checkpoint heading in subsequent prompts. Only meaningful when ` +
|
|
1999
|
-
`compression_checkpoint is true. Omit or "" otherwise.`,
|
|
2000
|
-
`- The JSON MUST appear between the ---loopforge-eval and ---end-loopforge-eval markers`,
|
|
2001
|
-
`- Do NOT wrap the markers in code fences — output them as raw text`,
|
|
2002
|
-
"",
|
|
2003
|
-
].join("\n");
|
|
2004
|
-
}
|
|
2005
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
2006
|
-
// Top-level compile — ties layers together
|
|
2007
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
2008
|
-
export function compileLoop(request, vaultContext = null) {
|
|
2009
|
-
// Layer 1: Decide compile level
|
|
2010
|
-
const level = decideLevel(request, vaultContext);
|
|
2011
|
-
// Compile at the decided level
|
|
2012
|
-
let response;
|
|
2013
|
-
if (level === "l0") {
|
|
2014
|
-
response = compileL0(request, vaultContext);
|
|
2015
|
-
}
|
|
2016
|
-
else if (level === "l1") {
|
|
2017
|
-
response = compileL1(request, vaultContext);
|
|
2018
|
-
}
|
|
2019
|
-
else {
|
|
2020
|
-
response = compileL2(request, vaultContext);
|
|
2021
|
-
}
|
|
2022
|
-
// Layer 2: Compute advisories
|
|
2023
|
-
const { warnings, suggestedNextTask, alignment, health } = computeAdvisories(request, vaultContext);
|
|
2024
|
-
// Merge advisories into response (preserve P4 consistency warnings from L2)
|
|
2025
|
-
response.warnings = [...(response.warnings || []), ...warnings];
|
|
2026
|
-
// Only overwrite suggested_next_task from advisories if the compile function
|
|
2027
|
-
// didn't already set one (e.g. L2 sets it from emerged_subtasks)
|
|
2028
|
-
if (!response.suggested_next_task) {
|
|
2029
|
-
response.suggested_next_task = suggestedNextTask;
|
|
2030
|
-
}
|
|
2031
|
-
response.task_alignment = alignment;
|
|
2032
|
-
response.loop_health = health;
|
|
2033
|
-
response.recompile_level = level;
|
|
2034
|
-
// v1.1: Append self-evaluation block for autonomous loop feedback
|
|
2035
|
-
response.prompt += buildSelfEvalBlock(request.round);
|
|
2036
|
-
// Multi-agent: Inject delegation history if available
|
|
2037
|
-
if (vaultContext) {
|
|
2038
|
-
const delegationText = buildDelegationSummary(vaultContext);
|
|
2039
|
-
if (delegationText) {
|
|
2040
|
-
response.prompt += delegationText;
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
328
|
+
const policy = getPolicy();
|
|
329
|
+
const statePath = `${policy.state_file.directory}/${request.loop_id}-state.md`;
|
|
330
|
+
const state = createCanonicalLoopState(request, response, statePath);
|
|
331
|
+
const markdown = renderCanonicalStateMarkdown(state);
|
|
332
|
+
const artifact = assemblePromptArtifact({
|
|
333
|
+
state,
|
|
334
|
+
level: decision.level,
|
|
335
|
+
reasons: decision.reasons,
|
|
336
|
+
mode: policy.prompt.injection_mode,
|
|
337
|
+
budgets: {
|
|
338
|
+
l0: policy.prompt.l0_max_chars,
|
|
339
|
+
l1: policy.prompt.l1_max_chars,
|
|
340
|
+
l2: policy.prompt.l2_max_chars,
|
|
341
|
+
},
|
|
342
|
+
attempt: request.attempt,
|
|
343
|
+
selfEvaluationBlock: buildSelfEvalBlock(request.round),
|
|
344
|
+
fullStateMarkdown: markdown,
|
|
345
|
+
});
|
|
346
|
+
response.prompt = artifact.renderedPrompt;
|
|
347
|
+
response.prompt_artifact = artifact;
|
|
348
|
+
response.state_file_content = policy.state_file.enabled ? markdown : undefined;
|
|
2043
349
|
return response;
|
|
2044
350
|
}
|
|
2045
351
|
//# sourceMappingURL=loop-compiler.js.map
|