loopforge 2.11.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -128
- package/dist/canonical-event-writer.d.ts +20 -0
- package/dist/canonical-event-writer.d.ts.map +1 -0
- package/dist/canonical-event-writer.js +131 -0
- package/dist/canonical-event-writer.js.map +1 -0
- package/dist/canonical-state.d.ts +20 -16
- package/dist/canonical-state.d.ts.map +1 -1
- package/dist/canonical-state.js +170 -61
- package/dist/canonical-state.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -10
- package/dist/cli.js.map +1 -1
- package/dist/cognitive-governance.d.ts +48 -0
- package/dist/cognitive-governance.d.ts.map +1 -0
- package/dist/cognitive-governance.js +226 -0
- package/dist/cognitive-governance.js.map +1 -0
- package/dist/compiler-subgoals.d.ts +7 -0
- package/dist/compiler-subgoals.d.ts.map +1 -0
- package/dist/compiler-subgoals.js +93 -0
- package/dist/compiler-subgoals.js.map +1 -0
- package/dist/derive-id.d.ts +12 -0
- package/dist/derive-id.d.ts.map +1 -0
- package/dist/derive-id.js +31 -0
- package/dist/derive-id.js.map +1 -0
- package/dist/evidence-provider.d.ts +23 -21
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +34 -74
- package/dist/evidence-provider.js.map +1 -1
- package/dist/generate-schema.js +90 -2
- package/dist/generate-schema.js.map +1 -1
- package/dist/index.d.ts +22 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -16
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +1 -1
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +250 -337
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-extras-parser.d.ts +2 -2
- package/dist/loop-extras-parser.d.ts.map +1 -1
- package/dist/loop-extras-parser.js +2 -2
- package/dist/loop-extras-parser.js.map +1 -1
- package/dist/loop-store.d.ts +20 -58
- package/dist/loop-store.d.ts.map +1 -1
- package/dist/loop-store.js +232 -309
- package/dist/loop-store.js.map +1 -1
- package/dist/mcp/server.d.ts +3 -3
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +83 -13
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session-projection.d.ts +5 -0
- package/dist/mcp/session-projection.d.ts.map +1 -0
- package/dist/mcp/session-projection.js +18 -0
- package/dist/mcp/session-projection.js.map +1 -0
- package/dist/mcp/session-queue.d.ts +3 -0
- package/dist/mcp/session-queue.d.ts.map +1 -0
- package/dist/mcp/session-queue.js +18 -0
- package/dist/mcp/session-queue.js.map +1 -0
- package/dist/mcp/session-types.d.ts +72 -0
- package/dist/mcp/session-types.d.ts.map +1 -0
- package/dist/mcp/session-types.js +2 -0
- package/dist/mcp/session-types.js.map +1 -0
- package/dist/mcp/session.d.ts +49 -93
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +544 -245
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts +26 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +399 -274
- package/dist/mcp/tools.js.map +1 -1
- package/dist/policy-metrics.d.ts +17 -1
- package/dist/policy-metrics.d.ts.map +1 -1
- package/dist/policy-metrics.js +164 -1
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +34 -18
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +113 -18
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts +5 -3
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +126 -77
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/prompt-policy.d.ts +8 -1
- package/dist/prompt-policy.d.ts.map +1 -1
- package/dist/prompt-policy.js +26 -8
- package/dist/prompt-policy.js.map +1 -1
- package/dist/protocol.d.ts +232 -192
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +15 -100
- package/dist/protocol.js.map +1 -1
- package/dist/replay.d.ts +11 -3
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +51 -14
- package/dist/replay.js.map +1 -1
- package/dist/round-commit-store.d.ts +12 -0
- package/dist/round-commit-store.d.ts.map +1 -0
- package/dist/round-commit-store.js +13 -0
- package/dist/round-commit-store.js.map +1 -0
- package/dist/round-coordinator.d.ts +25 -18
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +83 -71
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-driver.d.ts +14 -10
- package/dist/round-driver.d.ts.map +1 -1
- package/dist/round-driver.js +9 -13
- package/dist/round-driver.js.map +1 -1
- package/dist/round-gate.d.ts +31 -0
- package/dist/round-gate.d.ts.map +1 -0
- package/dist/round-gate.js +259 -0
- package/dist/round-gate.js.map +1 -0
- package/dist/round-recovery.d.ts +17 -0
- package/dist/round-recovery.d.ts.map +1 -0
- package/dist/round-recovery.js +117 -0
- package/dist/round-recovery.js.map +1 -0
- package/dist/round-report.d.ts +16 -0
- package/dist/round-report.d.ts.map +1 -0
- package/dist/round-report.js +401 -0
- package/dist/round-report.js.map +1 -0
- package/dist/round-transaction.d.ts +19 -12
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +102 -88
- package/dist/round-transaction.js.map +1 -1
- package/dist/runtime-services.d.ts +59 -0
- package/dist/runtime-services.d.ts.map +1 -0
- package/dist/runtime-services.js +460 -0
- package/dist/runtime-services.js.map +1 -0
- package/dist/storage.d.ts +3 -9
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +6 -17
- package/dist/storage.js.map +1 -1
- package/dist/token-utils.d.ts +9 -8
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +50 -10
- package/dist/token-utils.js.map +1 -1
- package/dist/vault-format.d.ts +4 -0
- package/dist/vault-format.d.ts.map +1 -0
- package/dist/vault-format.js +4 -0
- package/dist/vault-format.js.map +1 -0
- package/dist/vault-types.d.ts +24 -0
- package/dist/vault-types.d.ts.map +1 -0
- package/dist/vault-types.js +2 -0
- package/dist/vault-types.js.map +1 -0
- package/loop_policy.json +10 -12
- package/loopforge-policy.json +340 -0
- package/package.json +12 -6
- package/skills/perception/SKILL.md +199 -164
- package/dist/backends/interface.d.ts +0 -39
- package/dist/backends/interface.d.ts.map +0 -1
- package/dist/backends/interface.js +0 -6
- package/dist/backends/interface.js.map +0 -1
- package/dist/enforcement-gate.d.ts +0 -71
- package/dist/enforcement-gate.d.ts.map +0 -1
- package/dist/enforcement-gate.js +0 -744
- package/dist/enforcement-gate.js.map +0 -1
- package/dist/engine.d.ts +0 -59
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js +0 -596
- package/dist/engine.js.map +0 -1
- package/dist/self-eval.d.ts +0 -23
- package/dist/self-eval.d.ts.map +0 -1
- package/dist/self-eval.js +0 -181
- package/dist/self-eval.js.map +0 -1
- package/dist/verification-gate.d.ts +0 -56
- package/dist/verification-gate.d.ts.map +0 -1
- package/dist/verification-gate.js +0 -563
- package/dist/verification-gate.js.map +0 -1
package/dist/loop-compiler.js
CHANGED
|
@@ -3,20 +3,23 @@
|
|
|
3
3
|
* The compiler evolves structured state and renders one prompt artifact.
|
|
4
4
|
* L0/L1/L2 control state density only; the external Agent owns reasoning.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { deriveConstraintId, deriveCriterionId, deriveTextHash, } from "./derive-id.js";
|
|
7
7
|
import { getPolicy } from "./policy.js";
|
|
8
|
-
import { AgentStatus, makeLoopCompileResponse, makeLoopHealth, makeLoopObjective, makeConstraintMeta, makeMilestoneSummary, makeRollingSummary,
|
|
8
|
+
import { AgentStatus, makeLoopCompileResponse, makeLoopCompileProjection, makeLoopHealth, makeLoopObjective, makeConstraintMeta, makeMilestoneSummary, makeRollingSummary, makeTaskAlignment, } from "./protocol.js";
|
|
9
9
|
import { createCanonicalLoopState, renderCanonicalStateMarkdown, } from "./canonical-state.js";
|
|
10
|
+
import { auditEntries, deriveGate, deriveStructuredGate } from "./cognitive-governance.js";
|
|
11
|
+
import { parseRoundReport, deriveCompilerProjection } from "./round-report.js";
|
|
10
12
|
import { assemblePromptArtifact } from "./prompt-assembler.js";
|
|
11
13
|
import { decidePromptLevel, } from "./prompt-policy.js";
|
|
12
14
|
import { jaccardSimilarity, unique, entryRound } from "./token-utils.js";
|
|
15
|
+
import { manageSubGoals as deriveSubGoals } from "./compiler-subgoals.js";
|
|
13
16
|
function contextEntries(context) {
|
|
14
17
|
if (!context || !Array.isArray(context.results))
|
|
15
18
|
return [];
|
|
16
19
|
return context.results.filter((value) => value !== null && typeof value === "object" && !Array.isArray(value));
|
|
17
20
|
}
|
|
18
21
|
function lineage(entry) {
|
|
19
|
-
const value = entry.loop_lineage
|
|
22
|
+
const value = entry.loop_lineage;
|
|
20
23
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
21
24
|
? value
|
|
22
25
|
: {};
|
|
@@ -26,9 +29,12 @@ function loopEntries(loopId, context) {
|
|
|
26
29
|
.filter((entry) => entry.loop_id === loopId || lineage(entry).loop_id === loopId)
|
|
27
30
|
.sort((a, b) => entryRound(a) - entryRound(b));
|
|
28
31
|
}
|
|
32
|
+
function entryProjection(entry) {
|
|
33
|
+
const report = parseRoundReport(entry.round_report);
|
|
34
|
+
return report ? deriveCompilerProjection(report, entryRound(entry)) : null;
|
|
35
|
+
}
|
|
29
36
|
export function computeGoalTextHash(text) {
|
|
30
|
-
|
|
31
|
-
return createHash("sha256").update(normalized).digest("hex").slice(0, 12);
|
|
37
|
+
return deriveTextHash(text, 12);
|
|
32
38
|
}
|
|
33
39
|
export function deriveGoalId(loopId, task, explicit = "") {
|
|
34
40
|
return explicit.trim() || `${loopId}:${task.trim().toLowerCase()
|
|
@@ -55,9 +61,7 @@ export function getPreviousRound(loopId, round, context) {
|
|
|
55
61
|
constraints_active: Array.isArray(data.constraints_active)
|
|
56
62
|
? data.constraints_active.filter((value) => typeof value === "string")
|
|
57
63
|
: [],
|
|
58
|
-
output_summary:
|
|
59
|
-
? entry.output_summary
|
|
60
|
-
: typeof data.output_summary === "string" ? data.output_summary : "",
|
|
64
|
+
output_summary: entryProjection(entry)?.summary ?? "",
|
|
61
65
|
};
|
|
62
66
|
}
|
|
63
67
|
function latestObjective(loopId, context) {
|
|
@@ -94,17 +98,17 @@ function evolveObjective(request, context) {
|
|
|
94
98
|
created_at_round: 1,
|
|
95
99
|
loop_id: request.loop_id,
|
|
96
100
|
});
|
|
97
|
-
const last = request.
|
|
101
|
+
const last = request.last_round_view;
|
|
98
102
|
const history = [...(current.refinement_history ?? [])];
|
|
99
103
|
let objective = current.objective || request.task;
|
|
100
104
|
let version = current.version ?? 1;
|
|
101
|
-
if (last?.
|
|
102
|
-
const refinement = last.
|
|
105
|
+
if (last?.objectiveRefinement?.trim()) {
|
|
106
|
+
const refinement = last.objectiveRefinement.trim();
|
|
103
107
|
history.push(refinement);
|
|
104
108
|
objective = `${objective}\nRefinement: ${refinement}`;
|
|
105
109
|
version++;
|
|
106
110
|
}
|
|
107
|
-
const revisions = new Map((last?.
|
|
111
|
+
const revisions = new Map((last?.criterionRevisions ?? []).map((item) => [item.old, item.new]));
|
|
108
112
|
return makeLoopObjective({
|
|
109
113
|
...current,
|
|
110
114
|
objective,
|
|
@@ -116,9 +120,9 @@ function evolveObjective(request, context) {
|
|
|
116
120
|
});
|
|
117
121
|
}
|
|
118
122
|
function evolveConstraints(request, objective, previous) {
|
|
119
|
-
const retired = unique(request.
|
|
123
|
+
const retired = unique(request.last_round_view?.constraintEvents?.retired ?? []);
|
|
120
124
|
const retiredSet = new Set(retired);
|
|
121
|
-
const discovered = unique(request.
|
|
125
|
+
const discovered = unique(request.last_round_view?.discoveries?.newConstraints ?? [])
|
|
122
126
|
.slice(0, getPolicy().evolution.max_discovered_constraints_per_round);
|
|
123
127
|
const active = unique([
|
|
124
128
|
...(previous?.constraints_active ?? []),
|
|
@@ -133,27 +137,48 @@ function evolveConstraints(request, objective, previous) {
|
|
|
133
137
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
134
138
|
// Hierarchical Summary (v2.1)
|
|
135
139
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
136
|
-
/** Extract execution_evidence from a vault entry, handling both direct-field
|
|
137
|
-
* and lineage-nested shapes. Returns null when absent. */
|
|
138
|
-
function entryExecutionEvidence(entry) {
|
|
139
|
-
const direct = entry.execution_evidence;
|
|
140
|
-
if (direct && typeof direct === "object" && !Array.isArray(direct)) {
|
|
141
|
-
return direct;
|
|
142
|
-
}
|
|
143
|
-
const lin = lineage(entry);
|
|
144
|
-
const nested = lin.execution_evidence;
|
|
145
|
-
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
146
|
-
return nested;
|
|
147
|
-
}
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
/** Read success_criteria_met from an entry's execution_evidence. */
|
|
151
140
|
function entryCriteriaMet(entry) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
141
|
+
return entryProjection(entry)?.evidence?.verifiedClaims ?? [];
|
|
142
|
+
}
|
|
143
|
+
function verifiedClaims(loopId, context) {
|
|
144
|
+
const facts = new Set();
|
|
145
|
+
const refs = new Set();
|
|
146
|
+
for (const entry of loopEntries(loopId, context)) {
|
|
147
|
+
const report = entry.round_report;
|
|
148
|
+
if (!report || typeof report !== "object" || Array.isArray(report))
|
|
149
|
+
continue;
|
|
150
|
+
const evidence = report.evidence;
|
|
151
|
+
if (!evidence || typeof evidence !== "object" || Array.isArray(evidence))
|
|
152
|
+
continue;
|
|
153
|
+
const envelope = evidence;
|
|
154
|
+
const provenance = envelope.provenance;
|
|
155
|
+
if (!provenance || typeof provenance !== "object" || Array.isArray(provenance))
|
|
156
|
+
continue;
|
|
157
|
+
const provenanceMap = provenance;
|
|
158
|
+
if (!Array.isArray(envelope.claims))
|
|
159
|
+
continue;
|
|
160
|
+
for (const raw of envelope.claims) {
|
|
161
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
162
|
+
continue;
|
|
163
|
+
const claim = raw;
|
|
164
|
+
if (typeof claim.targetId !== "string" || !Array.isArray(claim.evidenceRefs))
|
|
165
|
+
continue;
|
|
166
|
+
const verifiedRefs = claim.evidenceRefs.filter((ref) => {
|
|
167
|
+
if (typeof ref !== "string")
|
|
168
|
+
return false;
|
|
169
|
+
const value = provenanceMap[ref];
|
|
170
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) &&
|
|
171
|
+
value.status === "verified" &&
|
|
172
|
+
value.source !== "agent");
|
|
173
|
+
});
|
|
174
|
+
if (verifiedRefs.length === 0)
|
|
175
|
+
continue;
|
|
176
|
+
facts.add(claim.targetId);
|
|
177
|
+
for (const ref of verifiedRefs)
|
|
178
|
+
refs.add(ref);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { facts: [...facts].sort(), refs: [...refs].sort() };
|
|
157
182
|
}
|
|
158
183
|
/** v2.11: Match two criterion references for deduplication.
|
|
159
184
|
* If either is a criterion ID (cr-XXXXXXXX), uses exact ID comparison.
|
|
@@ -172,6 +197,9 @@ function criteriaMatch(a, b) {
|
|
|
172
197
|
// Neither is an ID — Jaccard fallback
|
|
173
198
|
return jaccardSimilarity(a, b) >= getPolicy().evolution.criteria_dedup_threshold;
|
|
174
199
|
}
|
|
200
|
+
function isCriterionId(ref) {
|
|
201
|
+
return /^cr-[a-f0-9]{8}$/.test(ref);
|
|
202
|
+
}
|
|
175
203
|
/** Detect newly met criteria by comparing the current entry's
|
|
176
204
|
* success_criteria_met against the previous entry's.
|
|
177
205
|
* v2.11: ID-first matching (cr-XXXXXXXX) with Jaccard similarity
|
|
@@ -199,29 +227,15 @@ function entryActiveConstraints(entry) {
|
|
|
199
227
|
}
|
|
200
228
|
/** Read retracted_constraints from an entry. */
|
|
201
229
|
function entryRetractedConstraints(entry) {
|
|
202
|
-
|
|
203
|
-
if (Array.isArray(direct))
|
|
204
|
-
return direct.filter((v) => typeof v === "string");
|
|
205
|
-
const lin = lineage(entry);
|
|
206
|
-
const nested = lin.retracted_constraints;
|
|
207
|
-
if (Array.isArray(nested))
|
|
208
|
-
return nested.filter((v) => typeof v === "string");
|
|
209
|
-
return [];
|
|
230
|
+
return entryProjection(entry)?.constraintEvents?.retired ?? [];
|
|
210
231
|
}
|
|
211
|
-
/** Read progress_estimate from an entry's execution_evidence. */
|
|
212
232
|
function entryProgressEstimate(entry) {
|
|
213
|
-
|
|
214
|
-
if (!ev)
|
|
215
|
-
return 0;
|
|
216
|
-
const pe = ev.progress_estimate;
|
|
217
|
-
return typeof pe === "number" ? pe : 0;
|
|
233
|
+
return entryProjection(entry)?.evidence?.progressRatio ?? 0;
|
|
218
234
|
}
|
|
219
235
|
/** Build a single MilestoneSummary from a range of completed round entries. */
|
|
220
236
|
function buildMilestoneFromEntries(phaseEntries, startRound, endRound, label, kind) {
|
|
221
237
|
const last = phaseEntries.at(-1);
|
|
222
|
-
const outcomeRaw = last
|
|
223
|
-
? (typeof last.output_summary === "string" ? last.output_summary : "")
|
|
224
|
-
: "";
|
|
238
|
+
const outcomeRaw = last ? entryProjection(last)?.summary ?? "" : "";
|
|
225
239
|
const outcomeMax = getPolicy().checkpoint.outcome_max_chars;
|
|
226
240
|
const outcome = outcomeRaw.length > outcomeMax
|
|
227
241
|
? outcomeRaw.slice(0, outcomeMax) + "…"
|
|
@@ -256,28 +270,48 @@ export function buildRollingSummary(loopId, currentRound, context, sinceRound =
|
|
|
256
270
|
const data = lineage(entry);
|
|
257
271
|
const round = entryRound(entry);
|
|
258
272
|
const success = entry.success ?? data.success;
|
|
259
|
-
const summary =
|
|
260
|
-
? entry.output_summary
|
|
261
|
-
: typeof data.output_summary === "string" ? data.output_summary : "";
|
|
273
|
+
const summary = entryProjection(entry)?.summary ?? "";
|
|
262
274
|
if (summary)
|
|
263
275
|
outcomes.push(`[R${round}] ${success === false ? "failed" : "accepted"}: ${summary}`);
|
|
264
|
-
const violations =
|
|
265
|
-
? entry.constraint_violations
|
|
266
|
-
: Array.isArray(data.constraint_violations) ? data.constraint_violations : [];
|
|
276
|
+
const violations = entryProjection(entry)?.violations ?? [];
|
|
267
277
|
issues.push(...violations.filter((item) => typeof item === "string"));
|
|
268
278
|
}
|
|
279
|
+
const issueCounts = new Map();
|
|
280
|
+
for (const issue of issues) {
|
|
281
|
+
const key = issue.trim().replace(/\s+/g, " ").toLowerCase();
|
|
282
|
+
if (!key)
|
|
283
|
+
continue;
|
|
284
|
+
const previous = issueCounts.get(key);
|
|
285
|
+
issueCounts.set(key, { text: previous?.text ?? issue.trim(), count: (previous?.count ?? 0) + 1 });
|
|
286
|
+
}
|
|
269
287
|
// ── Phase 2: Milestone Accumulation ──
|
|
270
288
|
// Scan all completed entries in round order. For each entry, check three
|
|
271
289
|
// trigger signals in priority order: agent_declared > criteria_milestone > auto.
|
|
272
290
|
const completedEntries = allEntries.filter((entry) => entryRound(entry) >= 1 && entryRound(entry) < currentRound);
|
|
291
|
+
const failedGroups = [];
|
|
292
|
+
for (const entry of completedEntries) {
|
|
293
|
+
const data = lineage(entry);
|
|
294
|
+
if ((entry.success ?? data.success) !== false)
|
|
295
|
+
continue;
|
|
296
|
+
const text = (entryProjection(entry)?.summary ?? "").trim();
|
|
297
|
+
if (!text)
|
|
298
|
+
continue;
|
|
299
|
+
const group = failedGroups.find((candidate) => jaccardSimilarity(candidate.text, text) >= getPolicy().evolution.failed_pattern_match_threshold);
|
|
300
|
+
if (group)
|
|
301
|
+
group.count++;
|
|
302
|
+
else
|
|
303
|
+
failedGroups.push({ text, count: 1 });
|
|
304
|
+
}
|
|
273
305
|
const milestones = [];
|
|
274
306
|
let lastMilestoneRound = 0;
|
|
275
307
|
for (let i = 0; i < completedEntries.length; i++) {
|
|
276
308
|
const entry = completedEntries[i];
|
|
277
309
|
const rnd = entryRound(entry);
|
|
278
310
|
const lin = lineage(entry);
|
|
311
|
+
const report = parseRoundReport(entry.round_report);
|
|
279
312
|
// Signal 1: Agent-declared checkpoint
|
|
280
|
-
const isAgentCheckpoint =
|
|
313
|
+
const isAgentCheckpoint = Boolean(report?.phaseBoundaryProposal) ||
|
|
314
|
+
lin.compression_checkpoint === true;
|
|
281
315
|
// Signal 2: New criteria met (compare with previous vault entry)
|
|
282
316
|
const prev = i > 0 ? completedEntries[i - 1] : null;
|
|
283
317
|
const newCriteria = detectNewCriteria(entry, prev);
|
|
@@ -291,9 +325,10 @@ export function buildRollingSummary(loopId, currentRound, context, sinceRound =
|
|
|
291
325
|
? "agent_declared"
|
|
292
326
|
: "criteria_milestone";
|
|
293
327
|
const label = isAgentCheckpoint
|
|
294
|
-
? (
|
|
295
|
-
|
|
296
|
-
|
|
328
|
+
? (report?.phaseBoundaryProposal?.label?.trim() ||
|
|
329
|
+
(typeof lin.checkpoint_label === "string" && lin.checkpoint_label.trim()
|
|
330
|
+
? lin.checkpoint_label.trim()
|
|
331
|
+
: `Round ${rnd}`))
|
|
297
332
|
: `Completed: ${newCriteria.join(", ")}`;
|
|
298
333
|
milestones.push(buildMilestoneFromEntries(phaseEntries, startRound, rnd, label, kind));
|
|
299
334
|
lastMilestoneRound = rnd;
|
|
@@ -336,234 +371,20 @@ export function buildRollingSummary(loopId, currentRound, context, sinceRound =
|
|
|
336
371
|
return null;
|
|
337
372
|
return makeRollingSummary({
|
|
338
373
|
key_outcomes: unique(outcomes),
|
|
339
|
-
recurring_issues:
|
|
374
|
+
recurring_issues: [...issueCounts.values()]
|
|
375
|
+
.filter((issue) => issue.count >= 2)
|
|
376
|
+
.map((issue) => `${issue.text} (${issue.count} rounds)`),
|
|
340
377
|
rounds_sampled: windowEntries.length,
|
|
341
378
|
generated_at_round: currentRound,
|
|
342
|
-
failed_patterns:
|
|
379
|
+
failed_patterns: failedGroups
|
|
380
|
+
.filter((failure) => failure.count >= 2)
|
|
381
|
+
.map((failure) => `${failure.text} (${failure.count} attempts)`),
|
|
343
382
|
milestones: cappedMilestones,
|
|
344
383
|
loop_synthesis: loopSynthesis || undefined,
|
|
345
384
|
});
|
|
346
385
|
}
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
350
|
-
/** Derive a stable sub-goal ID from its description hash. */
|
|
351
|
-
function deriveSubGoalId(description) {
|
|
352
|
-
return "sg-" + computeGoalTextHash(description).slice(0, 8);
|
|
353
|
-
}
|
|
354
|
-
/** v2.11: Derive a stable constraint ID from its text hash (c-XXXXXXXX).
|
|
355
|
-
* Same hash strategy as SubGoal — deterministic across rounds. */
|
|
356
|
-
function deriveConstraintId(text) {
|
|
357
|
-
return "c-" + computeGoalTextHash(text).slice(0, 8);
|
|
358
|
-
}
|
|
359
|
-
/** v2.11: Derive a stable criterion ID from its text hash (cr-XXXXXXXX).
|
|
360
|
-
* Same hash strategy as SubGoal — deterministic across rounds. */
|
|
361
|
-
function deriveCriterionId(text) {
|
|
362
|
-
return "cr-" + computeGoalTextHash(text).slice(0, 8);
|
|
363
|
-
}
|
|
364
|
-
/** v2.11: Check whether a user-provided reference looks like a constraint ID.
|
|
365
|
-
* Matches the pattern c-XXXXXXXX where X is a hex digit. */
|
|
366
|
-
function isConstraintId(ref) {
|
|
367
|
-
return /^c-[a-f0-9]{8}$/.test(ref);
|
|
368
|
-
}
|
|
369
|
-
/** v2.11: Check whether a user-provided reference looks like a criterion ID.
|
|
370
|
-
* Matches the pattern cr-XXXXXXXX where X is a hex digit. */
|
|
371
|
-
function isCriterionId(ref) {
|
|
372
|
-
return /^cr-[a-f0-9]{8}$/.test(ref);
|
|
373
|
-
}
|
|
374
|
-
/** Read emerged_subtasks from a vault entry (handles direct + lineage nesting). */
|
|
375
|
-
function entryEmergedSubtasks(entry) {
|
|
376
|
-
const direct = entry.emerged_subtasks;
|
|
377
|
-
if (Array.isArray(direct))
|
|
378
|
-
return direct.filter((v) => typeof v === "string");
|
|
379
|
-
const lin = lineage(entry);
|
|
380
|
-
const nested = lin.emerged_subtasks;
|
|
381
|
-
if (Array.isArray(nested))
|
|
382
|
-
return nested.filter((v) => typeof v === "string");
|
|
383
|
-
return [];
|
|
384
|
-
}
|
|
385
|
-
/** Read completed_subtasks from an entry. */
|
|
386
|
-
function entryCompletedSubtasks(entry) {
|
|
387
|
-
const direct = entry.completed_subtasks;
|
|
388
|
-
if (Array.isArray(direct))
|
|
389
|
-
return direct.filter((v) => typeof v === "string");
|
|
390
|
-
return [];
|
|
391
|
-
}
|
|
392
|
-
/** Read blocked_subtasks from an entry. */
|
|
393
|
-
function entryBlockedSubtasks(entry) {
|
|
394
|
-
const direct = entry.blocked_subtasks;
|
|
395
|
-
if (Array.isArray(direct))
|
|
396
|
-
return direct.filter((v) => typeof v === "string");
|
|
397
|
-
return [];
|
|
398
|
-
}
|
|
399
|
-
/** Read canceled_subtasks from an entry. */
|
|
400
|
-
function entryCanceledSubtasks(entry) {
|
|
401
|
-
const direct = entry.canceled_subtasks;
|
|
402
|
-
if (Array.isArray(direct))
|
|
403
|
-
return direct.filter((v) => typeof v === "string");
|
|
404
|
-
return [];
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Match a user-provided sub-task reference against existing SubGoals.
|
|
408
|
-
*
|
|
409
|
-
* Tries exact SubGoal ID match first (e.g. "sg-a3f2b1c0"), then falls
|
|
410
|
-
* back to Jaccard token similarity on descriptions. Returns the index
|
|
411
|
-
* of the best-matching SubGoal, or -1 when no match reaches threshold.
|
|
412
|
-
*/
|
|
413
|
-
function matchSubGoal(target, subGoals, threshold) {
|
|
414
|
-
const trimmed = target.trim();
|
|
415
|
-
// Phase 1: Exact ID match
|
|
416
|
-
for (let i = 0; i < subGoals.length; i++) {
|
|
417
|
-
if (subGoals[i].id === trimmed)
|
|
418
|
-
return i;
|
|
419
|
-
}
|
|
420
|
-
// Phase 2: Jaccard fallback on description text
|
|
421
|
-
let bestIdx = -1;
|
|
422
|
-
let bestScore = 0;
|
|
423
|
-
for (let i = 0; i < subGoals.length; i++) {
|
|
424
|
-
const score = jaccardSimilarity(trimmed, subGoals[i].description);
|
|
425
|
-
if (score > bestScore) {
|
|
426
|
-
bestScore = score;
|
|
427
|
-
bestIdx = i;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
return bestScore >= threshold ? bestIdx : -1;
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* Manage the full sub-goal lifecycle across rounds.
|
|
434
|
-
*
|
|
435
|
-
* Accumulates sub-goals from all prior vault entries, processes the current
|
|
436
|
-
* round's declarations, derives status changes, and returns the complete
|
|
437
|
-
* structured SubGoal list.
|
|
438
|
-
*
|
|
439
|
-
* Status derivation priority (compiler-driven):
|
|
440
|
-
* 1. Agent declared done/canceled/blocked → explicit status change
|
|
441
|
-
* 2. next_action match → auto in_progress
|
|
442
|
-
* 3. success_criteria_met match → auto done
|
|
443
|
-
*/
|
|
444
|
-
function manageSubGoals(loopId, currentRound, lastRoundResult, vaultContext) {
|
|
445
|
-
const policy = getPolicy().evolution;
|
|
446
|
-
const allEntries = loopEntries(loopId, vaultContext);
|
|
447
|
-
const completedEntries = allEntries.filter((e) => entryRound(e) >= 1 && entryRound(e) < currentRound);
|
|
448
|
-
const subGoals = [];
|
|
449
|
-
// Phase 1 — Accumulate historical sub-goals from vault entries
|
|
450
|
-
for (const entry of completedEntries) {
|
|
451
|
-
const rnd = entryRound(entry);
|
|
452
|
-
const emerged = entryEmergedSubtasks(entry);
|
|
453
|
-
for (const desc of emerged) {
|
|
454
|
-
const id = deriveSubGoalId(desc);
|
|
455
|
-
// Dedup: skip if an existing sub-goal is too similar
|
|
456
|
-
if (matchSubGoal(desc, subGoals, policy.subgoal_dedup_threshold) >= 0)
|
|
457
|
-
continue;
|
|
458
|
-
subGoals.push(makeSubGoal({
|
|
459
|
-
id,
|
|
460
|
-
description: desc.trim(),
|
|
461
|
-
status: "pending",
|
|
462
|
-
declared_at_round: rnd,
|
|
463
|
-
status_changed_at_round: rnd,
|
|
464
|
-
priority: subGoals.length,
|
|
465
|
-
}));
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
// Phase 2 — Process current round's sub-task declarations
|
|
469
|
-
if (lastRoundResult) {
|
|
470
|
-
const rnd = lastRoundResult.round || (currentRound - 1);
|
|
471
|
-
// New sub-goals from this round
|
|
472
|
-
const emerged = lastRoundResult.emerged_subtasks ?? [];
|
|
473
|
-
for (const desc of emerged) {
|
|
474
|
-
if (matchSubGoal(desc, subGoals, policy.subgoal_dedup_threshold) >= 0)
|
|
475
|
-
continue;
|
|
476
|
-
subGoals.push(makeSubGoal({
|
|
477
|
-
id: deriveSubGoalId(desc),
|
|
478
|
-
description: desc.trim(),
|
|
479
|
-
status: "pending",
|
|
480
|
-
declared_at_round: rnd,
|
|
481
|
-
status_changed_at_round: rnd,
|
|
482
|
-
priority: subGoals.length,
|
|
483
|
-
}));
|
|
484
|
-
}
|
|
485
|
-
// Completed sub-goals
|
|
486
|
-
const completed = lastRoundResult.completed_subtasks ?? [];
|
|
487
|
-
for (const desc of completed) {
|
|
488
|
-
const idx = matchSubGoal(desc, subGoals, policy.subgoal_match_threshold);
|
|
489
|
-
if (idx >= 0 && subGoals[idx].status !== "done" && subGoals[idx].status !== "canceled") {
|
|
490
|
-
subGoals[idx].status = "done";
|
|
491
|
-
subGoals[idx].status_changed_at_round = rnd;
|
|
492
|
-
subGoals[idx].completed_at_round = rnd;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
// Blocked sub-goals
|
|
496
|
-
const blocked = lastRoundResult.blocked_subtasks ?? [];
|
|
497
|
-
for (const desc of blocked) {
|
|
498
|
-
const idx = matchSubGoal(desc, subGoals, policy.subgoal_match_threshold);
|
|
499
|
-
if (idx >= 0 && subGoals[idx].status !== "done" && subGoals[idx].status !== "canceled") {
|
|
500
|
-
subGoals[idx].status = "blocked";
|
|
501
|
-
subGoals[idx].status_changed_at_round = rnd;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
// Canceled sub-goals
|
|
505
|
-
const canceled = lastRoundResult.canceled_subtasks ?? [];
|
|
506
|
-
for (const desc of canceled) {
|
|
507
|
-
const idx = matchSubGoal(desc, subGoals, policy.subgoal_match_threshold);
|
|
508
|
-
if (idx >= 0 && subGoals[idx].status !== "done" && subGoals[idx].status !== "canceled") {
|
|
509
|
-
subGoals[idx].status = "canceled";
|
|
510
|
-
subGoals[idx].status_changed_at_round = rnd;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
// Phase 3 — Auto-derive: next_action match → in_progress
|
|
514
|
-
const nextAction = lastRoundResult.next_action?.trim();
|
|
515
|
-
if (nextAction) {
|
|
516
|
-
for (const sg of subGoals) {
|
|
517
|
-
if (sg.status === "pending") {
|
|
518
|
-
if (jaccardSimilarity(nextAction, sg.description) >= policy.subgoal_auto_in_progress_threshold) {
|
|
519
|
-
sg.status = "in_progress";
|
|
520
|
-
sg.status_changed_at_round = rnd;
|
|
521
|
-
break; // Only one sub-goal can be in_progress at a time
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
// Phase 4 — Auto-complete: new criteria match → done
|
|
527
|
-
const newCriteria = lastRoundResult.execution_evidence?.success_criteria_met ?? [];
|
|
528
|
-
if (newCriteria.length > 0) {
|
|
529
|
-
for (const sg of subGoals) {
|
|
530
|
-
if (sg.status === "pending" || sg.status === "in_progress") {
|
|
531
|
-
for (const crit of newCriteria) {
|
|
532
|
-
if (jaccardSimilarity(crit, sg.description) >= policy.subgoal_auto_complete_threshold) {
|
|
533
|
-
sg.status = "done";
|
|
534
|
-
sg.status_changed_at_round = rnd;
|
|
535
|
-
sg.completed_at_round = rnd;
|
|
536
|
-
break;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
// Sort for display: in_progress first, then pending by age desc, then blocked, then done, then canceled
|
|
544
|
-
subGoals.sort((a, b) => {
|
|
545
|
-
const rank = (s) => {
|
|
546
|
-
switch (s.status) {
|
|
547
|
-
case "in_progress": return 0;
|
|
548
|
-
case "pending": return 1;
|
|
549
|
-
case "blocked": return 2;
|
|
550
|
-
case "done": return 3;
|
|
551
|
-
case "canceled": return 4;
|
|
552
|
-
}
|
|
553
|
-
};
|
|
554
|
-
const ra = rank(a), rb = rank(b);
|
|
555
|
-
if (ra !== rb)
|
|
556
|
-
return ra - rb;
|
|
557
|
-
// Within same status: pending by oldest first, done by most recent first
|
|
558
|
-
if (a.status === "pending")
|
|
559
|
-
return (currentRound - a.declared_at_round) - (currentRound - b.declared_at_round);
|
|
560
|
-
if (a.status === "done")
|
|
561
|
-
return (b.completed_at_round ?? 0) - (a.completed_at_round ?? 0);
|
|
562
|
-
return a.declared_at_round - b.declared_at_round;
|
|
563
|
-
});
|
|
564
|
-
return subGoals;
|
|
565
|
-
}
|
|
566
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
386
|
+
// Sub-goal lifecycle is implemented in compiler-subgoals.ts; this facade keeps
|
|
387
|
+
// the compiler's public entry point and projection flow unchanged.
|
|
567
388
|
// Time-Aware Constraint Management (v2.3)
|
|
568
389
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
569
390
|
/** Determine the source of a constraint by matching against known sources. */
|
|
@@ -588,9 +409,7 @@ function findDiscoveredRound(text, vaultEntries) {
|
|
|
588
409
|
const rnd = entryRound(entry);
|
|
589
410
|
if (rnd < 1)
|
|
590
411
|
continue;
|
|
591
|
-
const discovered =
|
|
592
|
-
? entry.discovered_constraints.filter((v) => typeof v === "string")
|
|
593
|
-
: [];
|
|
412
|
+
const discovered = entryProjection(entry)?.discoveries?.newConstraints ?? [];
|
|
594
413
|
for (const d of discovered) {
|
|
595
414
|
// Phase 1: exact ID match (v2.11)
|
|
596
415
|
if (d === targetId || deriveConstraintId(d) === targetId) {
|
|
@@ -619,9 +438,7 @@ function findLastViolatedRound(text, vaultEntries) {
|
|
|
619
438
|
const rnd = entryRound(entry);
|
|
620
439
|
if (rnd < 1)
|
|
621
440
|
continue;
|
|
622
|
-
const violations =
|
|
623
|
-
? entry.constraint_violations.filter((v) => typeof v === "string")
|
|
624
|
-
: [];
|
|
441
|
+
const violations = entryProjection(entry)?.violations ?? [];
|
|
625
442
|
for (const v of violations) {
|
|
626
443
|
// Phase 1: exact ID match (v2.11)
|
|
627
444
|
if (v === targetId || deriveConstraintId(v) === targetId) {
|
|
@@ -707,7 +524,7 @@ export function alignTask(proposedTask, request, context) {
|
|
|
707
524
|
export function checkLoopHealth(loopId, request, context) {
|
|
708
525
|
const previous = getPreviousRound(loopId, request.round - 1, context);
|
|
709
526
|
const alignment = alignTask(request.task, request, context);
|
|
710
|
-
const violations = request.
|
|
527
|
+
const violations = request.last_round_view?.violations?.length ?? 0;
|
|
711
528
|
const integrity = Math.max(0, 1 - violations * 0.2);
|
|
712
529
|
const continuity = previous ? jaccardSimilarity(previous.task, request.task) : 1;
|
|
713
530
|
const drift = alignment.alignment_score < 0.3 || continuity < 0.2;
|
|
@@ -722,61 +539,100 @@ export function checkLoopHealth(loopId, request, context) {
|
|
|
722
539
|
}
|
|
723
540
|
function levelDecision(request, context) {
|
|
724
541
|
const previous = getPreviousRound(request.loop_id, request.round - 1, context);
|
|
725
|
-
const last = request.
|
|
542
|
+
const last = request.last_round_view;
|
|
726
543
|
const hasNewInformation = Boolean(request.new_since_last_round.trim() ||
|
|
727
|
-
last?.
|
|
728
|
-
last?.
|
|
729
|
-
last?.
|
|
730
|
-
last?.
|
|
731
|
-
last?.
|
|
732
|
-
last?.
|
|
544
|
+
last?.discoveries?.newConstraints?.length ||
|
|
545
|
+
last?.objectiveRefinement?.trim() ||
|
|
546
|
+
last?.discoveries?.emergedWork?.length ||
|
|
547
|
+
last?.constraintEvents?.retired?.length ||
|
|
548
|
+
last?.criterionRevisions?.length ||
|
|
549
|
+
last?.discoveries?.wrongAssumptions?.length ||
|
|
550
|
+
last?.evidence?.verifiedClaims.length);
|
|
733
551
|
const lastFullRound = loopEntries(request.loop_id, context)
|
|
734
552
|
.filter((entry) => lineage(entry).recompile_level === "l2")
|
|
735
553
|
.map(entryRound)
|
|
736
554
|
.filter((round) => round < request.round)
|
|
737
555
|
.at(-1) ?? 1;
|
|
556
|
+
const entries = loopEntries(request.loop_id, context)
|
|
557
|
+
.filter((entry) => entryRound(entry) < request.round);
|
|
558
|
+
const lastMeaningfulRound = [...entries].reverse().find((entry) => {
|
|
559
|
+
const data = lineage(entry);
|
|
560
|
+
const evidence = entryProjection(entry)?.evidence;
|
|
561
|
+
return Boolean(entryCriteriaMet(entry).length ||
|
|
562
|
+
evidence?.observedFiles.length ||
|
|
563
|
+
entryProjection(entry)?.discoveries?.newConstraints.length ||
|
|
564
|
+
entryProjection(entry)?.discoveries?.emergedWork.length);
|
|
565
|
+
});
|
|
566
|
+
const meaningfulSections = 2 + [
|
|
567
|
+
request.task.trim(),
|
|
568
|
+
...(last?.violations ?? []),
|
|
569
|
+
...(last?.evidence?.criteriaRemaining ?? []),
|
|
570
|
+
...(last?.evidence?.criteriaMet ?? []),
|
|
571
|
+
...(last?.discoveries?.newConstraints ?? []),
|
|
572
|
+
...(last?.discoveries?.emergedWork ?? []),
|
|
573
|
+
...(last?.evidence?.observedFiles ?? []),
|
|
574
|
+
].filter(Boolean).length;
|
|
575
|
+
const promptPolicy = getPolicy().prompt;
|
|
738
576
|
return decidePromptLevel({
|
|
739
577
|
round: request.round,
|
|
740
578
|
attempt: request.attempt,
|
|
741
579
|
forceLevel: request.force_level,
|
|
742
580
|
hasPlanSource: Boolean(request.plan_source),
|
|
743
|
-
checkpointBoundary: last?.
|
|
744
|
-
goalChanged:
|
|
581
|
+
checkpointBoundary: Boolean(last?.phaseBoundaryProposal),
|
|
582
|
+
goalChanged: Boolean(last?.objectiveRefinement?.trim()) ||
|
|
583
|
+
(previous !== null && previous.goal_id !== deriveGoalId(request.loop_id, request.task, request.goal_id)),
|
|
584
|
+
constraintChanged: Boolean(last?.discoveries?.newConstraints?.length || last?.constraintEvents?.retired?.length),
|
|
745
585
|
previousStateMissing: request.round > 1 && previous === null,
|
|
746
|
-
previousFailedWithoutNewInformation: last?.
|
|
586
|
+
previousFailedWithoutNewInformation: last?.outcome === "failed" && !hasNewInformation,
|
|
747
587
|
verificationContradicted: (request.verification_flags ?? [])
|
|
748
588
|
.some((flag) => flag.severity === "error"),
|
|
749
589
|
consecutiveRejections: request.consecutive_rejections,
|
|
750
590
|
fullRefreshInterval: getPolicy().prompt.full_refresh_interval,
|
|
751
591
|
lastFullRound,
|
|
592
|
+
l1Streak: Math.max(0, request.round - lastFullRound - 1),
|
|
593
|
+
maxL1Streak: promptPolicy.adaptive_refresh ? promptPolicy.max_l1_streak : undefined,
|
|
594
|
+
meaningfulSections: request.round <= 2
|
|
595
|
+
? Math.max(meaningfulSections, promptPolicy.min_meaningful_sections)
|
|
596
|
+
: meaningfulSections,
|
|
597
|
+
minMeaningfulSections: promptPolicy.adaptive_refresh
|
|
598
|
+
? promptPolicy.min_meaningful_sections
|
|
599
|
+
: undefined,
|
|
600
|
+
staleProgressRounds: request.round - (lastMeaningfulRound ? entryRound(lastMeaningfulRound) : 1),
|
|
601
|
+
staleRoundThreshold: promptPolicy.adaptive_refresh
|
|
602
|
+
? promptPolicy.stale_round_threshold
|
|
603
|
+
: undefined,
|
|
752
604
|
});
|
|
753
605
|
}
|
|
754
606
|
export function decideLevel(request, context) {
|
|
755
607
|
return levelDecision(request, context).level;
|
|
756
608
|
}
|
|
757
|
-
export function
|
|
609
|
+
export function buildRoundReportBlock(round, prevDriftFlags) {
|
|
758
610
|
const hasIntentDrift = prevDriftFlags?.some(f => f.check === "intent_drift") ?? false;
|
|
759
611
|
const hasSubGoalDrift = prevDriftFlags?.some(f => f.check === "subgoal_drift") ?? false;
|
|
760
612
|
const needsClarification = hasIntentDrift || hasSubGoalDrift;
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
613
|
+
const report = {
|
|
614
|
+
format: "loopforge.round-report",
|
|
615
|
+
outcome: "partial",
|
|
616
|
+
continuation: "continue",
|
|
617
|
+
summary: `<verified result of round ${round}>`,
|
|
618
|
+
violations: [],
|
|
619
|
+
evidence: {
|
|
620
|
+
files: [],
|
|
621
|
+
checks: [{ name: "<configured provider or command name>", status: "not_run" }],
|
|
622
|
+
claims: [{
|
|
623
|
+
id: "claim-<12 hex chars>",
|
|
624
|
+
targetId: "<cr-XXXXXXXX or criterion text>",
|
|
625
|
+
evidenceRefs: ["provider:<name>"],
|
|
626
|
+
}],
|
|
627
|
+
noChangeReason: "",
|
|
628
|
+
},
|
|
629
|
+
discoveries: {
|
|
630
|
+
wrongAssumptions: [],
|
|
631
|
+
emergedWork: [],
|
|
632
|
+
facts: [],
|
|
633
|
+
newConstraints: [],
|
|
774
634
|
},
|
|
775
|
-
|
|
776
|
-
next_action: "<next concrete action, or empty when complete>",
|
|
777
|
-
completed_subtasks: ["<sg-XXXXXXXX or text>"],
|
|
778
|
-
blocked_subtasks: ["<sg-XXXXXXXX or text>"],
|
|
779
|
-
canceled_subtasks: ["<sg-XXXXXXXX or text>"],
|
|
635
|
+
subGoalEvents: { completed: [], blocked: [], canceled: [] },
|
|
780
636
|
};
|
|
781
637
|
// v2.8: Inject drift clarification field when previous round had drift flags
|
|
782
638
|
if (needsClarification) {
|
|
@@ -784,10 +640,14 @@ export function buildSelfEvalBlock(round, prevDriftFlags) {
|
|
|
784
640
|
hasIntentDrift ? "intent_drift" : "",
|
|
785
641
|
hasSubGoalDrift ? "subgoal_drift" : "",
|
|
786
642
|
].filter(Boolean).join(" / ");
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
643
|
+
report.discoveries = {
|
|
644
|
+
...report.discoveries,
|
|
645
|
+
facts: [`Explain the evidence-backed pivot from ${flagTypes}`],
|
|
646
|
+
};
|
|
647
|
+
report.driftClarification = {
|
|
648
|
+
explanation: "<explain why the current evidence requires this pivot>",
|
|
649
|
+
anchors: ["<criterion ID, constraint ID, or observed file path>"],
|
|
650
|
+
};
|
|
791
651
|
}
|
|
792
652
|
const lines = [
|
|
793
653
|
"### LoopForge Evaluation (Required)",
|
|
@@ -796,14 +656,13 @@ export function buildSelfEvalBlock(round, prevDriftFlags) {
|
|
|
796
656
|
"with your actual results and submit via `loopforge_next`.",
|
|
797
657
|
];
|
|
798
658
|
if (needsClarification) {
|
|
799
|
-
lines.push("", "> ⚠️ **Drift Detected:** The verification gate detected that your previous", "> round's
|
|
659
|
+
lines.push("", "> ⚠️ **Drift Detected:** The verification gate detected that your previous", "> round's previously suggested action did not match your actual output.", "> Explain the evidence-backed pivot in `driftClarification` below and include a runtime-verifiable anchor.", "> An honest explanation helps the enforcement gate distinguish intentional", "> course corrections from unacknowledged drift.");
|
|
800
660
|
}
|
|
801
|
-
lines.push("
|
|
802
|
-
lines.push(JSON.stringify(
|
|
803
|
-
lines.push("---end-loopforge-eval");
|
|
661
|
+
lines.push("Submit this object in the `report` parameter:");
|
|
662
|
+
lines.push(JSON.stringify(report, null, 2));
|
|
804
663
|
lines.push("");
|
|
805
664
|
lines.push("IMPORTANT: Replace every <placeholder> with your actual data.");
|
|
806
|
-
lines.push("Set success
|
|
665
|
+
lines.push("Set outcome=success only when every criterion has runtime-verifiable evidence; set continuation=stop only when no further round is requested.");
|
|
807
666
|
lines.push("Use IDs for exact matching: c-XXXXXXXX, cr-XXXXXXXX, sg-XXXXXXXX.");
|
|
808
667
|
lines.push("For subtasks, use sub-goal IDs from the Sub-Goal Dashboard above.");
|
|
809
668
|
return lines.join("\n");
|
|
@@ -818,7 +677,7 @@ export function compileLoop(request, context) {
|
|
|
818
677
|
const constraints = evolveConstraints(request, objective, previous);
|
|
819
678
|
const constraintLifecycle = manageConstraintLifecycle(constraints.active, objective, request.constraints_from_plan, context, request.round);
|
|
820
679
|
const rolling = buildRollingSummary(request.loop_id, request.round, context);
|
|
821
|
-
const subGoals =
|
|
680
|
+
const subGoals = deriveSubGoals(request.loop_id, request.round, request.last_round_view, context, entryProjection);
|
|
822
681
|
const alignment = alignTask(request.task, request, context);
|
|
823
682
|
const health = checkLoopHealth(request.loop_id, { ...request, loop_objective: objective }, context);
|
|
824
683
|
const warnings = unique([
|
|
@@ -838,13 +697,35 @@ export function compileLoop(request, context) {
|
|
|
838
697
|
.filter((e) => entryRound(e) >= 1 && entryRound(e) < request.round);
|
|
839
698
|
const trend = [];
|
|
840
699
|
for (const entry of completedEntries.slice(-10)) {
|
|
841
|
-
const viols =
|
|
842
|
-
? entry.constraint_violations.filter((v) => typeof v === "string")
|
|
843
|
-
: [];
|
|
700
|
+
const viols = entryProjection(entry)?.violations ?? [];
|
|
844
701
|
// Simple proxy: no violations = perfect trust, each violation = -0.1
|
|
845
702
|
const score = Math.max(0, 1.0 - viols.length * 0.1);
|
|
846
703
|
trend.push(Number(score.toFixed(2)));
|
|
847
704
|
}
|
|
705
|
+
const verified = verifiedClaims(request.loop_id, context);
|
|
706
|
+
const latestReport = [...loopEntries(request.loop_id, context)].reverse()
|
|
707
|
+
.map((entry) => parseRoundReport(entry.round_report))
|
|
708
|
+
.find((report) => report !== null);
|
|
709
|
+
const audit = auditEntries(request.loop_id, loopEntries(request.loop_id, context));
|
|
710
|
+
const regressionObligations = audit.regressionObligations.map((obligation) => ({
|
|
711
|
+
id: obligation.id,
|
|
712
|
+
description: `Re-run ${obligation.check}`,
|
|
713
|
+
sourceRound: obligation.sourceRound,
|
|
714
|
+
evidenceRefs: [`check:${obligation.check}`],
|
|
715
|
+
status: obligation.status,
|
|
716
|
+
}));
|
|
717
|
+
const latestBlocker = latestReport?.outcome === "blocked" && latestReport.blocker;
|
|
718
|
+
const blockerGate = latestBlocker
|
|
719
|
+
? latestBlocker.action
|
|
720
|
+
? deriveStructuredGate(latestBlocker.action)
|
|
721
|
+
: deriveGate(latestBlocker.reason, latestBlocker.references)
|
|
722
|
+
: null;
|
|
723
|
+
const activeBlockers = latestBlocker && blockerGate?.gate.kind === "user" &&
|
|
724
|
+
!audit.unresolvedUserGates.includes(blockerGate.id)
|
|
725
|
+
? []
|
|
726
|
+
: latestBlocker
|
|
727
|
+
? [latestBlocker.reason]
|
|
728
|
+
: [];
|
|
848
729
|
const response = makeLoopCompileResponse({
|
|
849
730
|
status: AgentStatus.OK,
|
|
850
731
|
recompile_level: decision.level,
|
|
@@ -865,8 +746,18 @@ export function compileLoop(request, context) {
|
|
|
865
746
|
sub_goals: subGoals,
|
|
866
747
|
agent_trust_score: request.round > 1 ? Number(trustScore.toFixed(2)) : undefined,
|
|
867
748
|
agent_trust_trend: request.round > 1 ? trend : [],
|
|
868
|
-
|
|
869
|
-
|
|
749
|
+
// Agent intent is advisory only. Runtime suggestions are derived from
|
|
750
|
+
// remaining criteria, active sub-goals and blockers; never copy intent
|
|
751
|
+
// into the canonical next-action projection.
|
|
752
|
+
projection: makeLoopCompileProjection({
|
|
753
|
+
suggestedNextAction: request.last_round_view?.evidence?.criteriaRemaining?.[0] ||
|
|
754
|
+
request.last_round_view?.discoveries?.emergedWork?.[0] || "",
|
|
755
|
+
verifiedFacts: verified.facts,
|
|
756
|
+
verifiedEvidenceRefs: verified.refs,
|
|
757
|
+
regressionObligations,
|
|
758
|
+
discoveredFacts: latestReport?.discoveries?.facts ?? [],
|
|
759
|
+
activeBlockers,
|
|
760
|
+
}),
|
|
870
761
|
plan_source: request.plan_source,
|
|
871
762
|
warnings,
|
|
872
763
|
});
|
|
@@ -896,14 +787,36 @@ export function compileLoop(request, context) {
|
|
|
896
787
|
l2: adaptiveL2,
|
|
897
788
|
},
|
|
898
789
|
attempt: request.attempt,
|
|
899
|
-
|
|
790
|
+
roundReportBlock: buildRoundReportBlock(request.round, request.verification_flags),
|
|
900
791
|
fullStateMarkdown: l2Pointer ? undefined : markdown,
|
|
901
|
-
|
|
902
|
-
promptRequests: request.
|
|
792
|
+
agentIntent: request.last_round_view?.agentIntent,
|
|
793
|
+
promptRequests: request.last_round_view?.contextRequest ?? undefined,
|
|
794
|
+
});
|
|
795
|
+
const priorEntries = loopEntries(request.loop_id, context)
|
|
796
|
+
.filter((entry) => entryRound(entry) < request.round);
|
|
797
|
+
const previousL2 = [...priorEntries].reverse()
|
|
798
|
+
.find((entry) => lineage(entry).recompile_level === "l2");
|
|
799
|
+
artifact.lastL2Round = artifact.level === "l2"
|
|
800
|
+
? request.round
|
|
801
|
+
: previousL2 ? entryRound(previousL2) : 0;
|
|
802
|
+
const meaningfulEntry = [...priorEntries].reverse().find((entry) => {
|
|
803
|
+
const data = lineage(entry);
|
|
804
|
+
const evidence = entryProjection(entry)?.evidence;
|
|
805
|
+
return Boolean(entryCriteriaMet(entry).length ||
|
|
806
|
+
evidence?.observedFiles.length ||
|
|
807
|
+
entryProjection(entry)?.discoveries?.newConstraints.length ||
|
|
808
|
+
entryProjection(entry)?.discoveries?.emergedWork.length);
|
|
903
809
|
});
|
|
810
|
+
artifact.lastMeaningfulProgressRound = meaningfulEntry
|
|
811
|
+
? entryRound(meaningfulEntry)
|
|
812
|
+
: request.round <= 1 ? 0 : 1;
|
|
813
|
+
artifact.stateAgeRounds = artifact.lastMeaningfulProgressRound > 0
|
|
814
|
+
? Math.max(0, request.round - artifact.lastMeaningfulProgressRound)
|
|
815
|
+
: request.round - 1;
|
|
904
816
|
response.prompt = artifact.renderedPrompt;
|
|
905
817
|
response.prompt_artifact = artifact;
|
|
906
818
|
response.state_file_content = policy.state_file.enabled ? markdown : undefined;
|
|
819
|
+
response.canonical_state = state;
|
|
907
820
|
return response;
|
|
908
821
|
}
|
|
909
822
|
//# sourceMappingURL=loop-compiler.js.map
|