loopforge 2.7.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 +136 -182
- 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 +211 -64
- 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 +26 -26
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +45 -71
- 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 +23 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -26
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +2 -2
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +321 -315
- 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 -311
- 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 +123 -19
- 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 +65 -84
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +667 -224
- 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 +401 -254
- package/dist/mcp/tools.js.map +1 -1
- package/dist/policy-metrics.d.ts +24 -0
- package/dist/policy-metrics.d.ts.map +1 -1
- package/dist/policy-metrics.js +174 -0
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +74 -12
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +124 -19
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts +8 -4
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +533 -86
- 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 +259 -173
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +17 -96
- 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 +53 -22
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +140 -44
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-driver.d.ts +18 -10
- package/dist/round-driver.d.ts.map +1 -1
- package/dist/round-driver.js +11 -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 +23 -12
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +104 -91
- 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/{engine.js → runtime-services.js} +157 -275
- 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 +12 -8
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +56 -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 +13 -7
- package/skills/perception/SKILL.md +200 -90
- 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 -50
- package/dist/enforcement-gate.d.ts.map +0 -1
- package/dist/enforcement-gate.js +0 -501
- 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.map +0 -1
- package/dist/interop.d.ts +0 -35
- package/dist/interop.d.ts.map +0 -1
- package/dist/interop.js +0 -34
- package/dist/interop.js.map +0 -1
- package/dist/run.d.ts +0 -51
- package/dist/run.d.ts.map +0 -1
- package/dist/run.js +0 -200
- package/dist/run.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 -151
- package/dist/self-eval.js.map +0 -1
- package/dist/verification-gate.d.ts +0 -52
- package/dist/verification-gate.d.ts.map +0 -1
- package/dist/verification-gate.js +0 -527
- package/dist/verification-gate.js.map +0 -1
package/dist/loop-compiler.js
CHANGED
|
@@ -3,34 +3,38 @@
|
|
|
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";
|
|
13
|
-
|
|
15
|
+
import { manageSubGoals as deriveSubGoals } from "./compiler-subgoals.js";
|
|
14
16
|
function contextEntries(context) {
|
|
15
17
|
if (!context || !Array.isArray(context.results))
|
|
16
18
|
return [];
|
|
17
19
|
return context.results.filter((value) => value !== null && typeof value === "object" && !Array.isArray(value));
|
|
18
20
|
}
|
|
19
21
|
function lineage(entry) {
|
|
20
|
-
const value = entry.loop_lineage
|
|
22
|
+
const value = entry.loop_lineage;
|
|
21
23
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
22
24
|
? value
|
|
23
25
|
: {};
|
|
24
26
|
}
|
|
25
|
-
// entryRound() imported from token-utils.ts
|
|
26
27
|
function loopEntries(loopId, context) {
|
|
27
28
|
return contextEntries(context)
|
|
28
29
|
.filter((entry) => entry.loop_id === loopId || lineage(entry).loop_id === loopId)
|
|
29
30
|
.sort((a, b) => entryRound(a) - entryRound(b));
|
|
30
31
|
}
|
|
32
|
+
function entryProjection(entry) {
|
|
33
|
+
const report = parseRoundReport(entry.round_report);
|
|
34
|
+
return report ? deriveCompilerProjection(report, entryRound(entry)) : null;
|
|
35
|
+
}
|
|
31
36
|
export function computeGoalTextHash(text) {
|
|
32
|
-
|
|
33
|
-
return createHash("sha256").update(normalized).digest("hex").slice(0, 12);
|
|
37
|
+
return deriveTextHash(text, 12);
|
|
34
38
|
}
|
|
35
39
|
export function deriveGoalId(loopId, task, explicit = "") {
|
|
36
40
|
return explicit.trim() || `${loopId}:${task.trim().toLowerCase()
|
|
@@ -57,9 +61,7 @@ export function getPreviousRound(loopId, round, context) {
|
|
|
57
61
|
constraints_active: Array.isArray(data.constraints_active)
|
|
58
62
|
? data.constraints_active.filter((value) => typeof value === "string")
|
|
59
63
|
: [],
|
|
60
|
-
output_summary:
|
|
61
|
-
? entry.output_summary
|
|
62
|
-
: typeof data.output_summary === "string" ? data.output_summary : "",
|
|
64
|
+
output_summary: entryProjection(entry)?.summary ?? "",
|
|
63
65
|
};
|
|
64
66
|
}
|
|
65
67
|
function latestObjective(loopId, context) {
|
|
@@ -96,17 +98,17 @@ function evolveObjective(request, context) {
|
|
|
96
98
|
created_at_round: 1,
|
|
97
99
|
loop_id: request.loop_id,
|
|
98
100
|
});
|
|
99
|
-
const last = request.
|
|
101
|
+
const last = request.last_round_view;
|
|
100
102
|
const history = [...(current.refinement_history ?? [])];
|
|
101
103
|
let objective = current.objective || request.task;
|
|
102
104
|
let version = current.version ?? 1;
|
|
103
|
-
if (last?.
|
|
104
|
-
const refinement = last.
|
|
105
|
+
if (last?.objectiveRefinement?.trim()) {
|
|
106
|
+
const refinement = last.objectiveRefinement.trim();
|
|
105
107
|
history.push(refinement);
|
|
106
108
|
objective = `${objective}\nRefinement: ${refinement}`;
|
|
107
109
|
version++;
|
|
108
110
|
}
|
|
109
|
-
const revisions = new Map((last?.
|
|
111
|
+
const revisions = new Map((last?.criterionRevisions ?? []).map((item) => [item.old, item.new]));
|
|
110
112
|
return makeLoopObjective({
|
|
111
113
|
...current,
|
|
112
114
|
objective,
|
|
@@ -118,9 +120,9 @@ function evolveObjective(request, context) {
|
|
|
118
120
|
});
|
|
119
121
|
}
|
|
120
122
|
function evolveConstraints(request, objective, previous) {
|
|
121
|
-
const retired = unique(request.
|
|
123
|
+
const retired = unique(request.last_round_view?.constraintEvents?.retired ?? []);
|
|
122
124
|
const retiredSet = new Set(retired);
|
|
123
|
-
const discovered = unique(request.
|
|
125
|
+
const discovered = unique(request.last_round_view?.discoveries?.newConstraints ?? [])
|
|
124
126
|
.slice(0, getPolicy().evolution.max_discovered_constraints_per_round);
|
|
125
127
|
const active = unique([
|
|
126
128
|
...(previous?.constraints_active ?? []),
|
|
@@ -135,31 +137,73 @@ function evolveConstraints(request, objective, previous) {
|
|
|
135
137
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
136
138
|
// Hierarchical Summary (v2.1)
|
|
137
139
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
138
|
-
/** Extract execution_evidence from a vault entry, handling both direct-field
|
|
139
|
-
* and lineage-nested shapes. Returns null when absent. */
|
|
140
|
-
function entryExecutionEvidence(entry) {
|
|
141
|
-
const direct = entry.execution_evidence;
|
|
142
|
-
if (direct && typeof direct === "object" && !Array.isArray(direct)) {
|
|
143
|
-
return direct;
|
|
144
|
-
}
|
|
145
|
-
const lin = lineage(entry);
|
|
146
|
-
const nested = lin.execution_evidence;
|
|
147
|
-
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
148
|
-
return nested;
|
|
149
|
-
}
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
/** Read success_criteria_met from an entry's execution_evidence. */
|
|
153
140
|
function entryCriteriaMet(entry) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
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() };
|
|
182
|
+
}
|
|
183
|
+
/** v2.11: Match two criterion references for deduplication.
|
|
184
|
+
* If either is a criterion ID (cr-XXXXXXXX), uses exact ID comparison.
|
|
185
|
+
* Otherwise falls back to Jaccard similarity. */
|
|
186
|
+
function criteriaMatch(a, b) {
|
|
187
|
+
const aIsId = isCriterionId(a);
|
|
188
|
+
const bIsId = isCriterionId(b);
|
|
189
|
+
// Both are IDs — exact string comparison
|
|
190
|
+
if (aIsId && bIsId)
|
|
191
|
+
return a === b;
|
|
192
|
+
// One is an ID — derive ID from the other and compare
|
|
193
|
+
if (aIsId)
|
|
194
|
+
return a === deriveCriterionId(b);
|
|
195
|
+
if (bIsId)
|
|
196
|
+
return deriveCriterionId(a) === b;
|
|
197
|
+
// Neither is an ID — Jaccard fallback
|
|
198
|
+
return jaccardSimilarity(a, b) >= getPolicy().evolution.criteria_dedup_threshold;
|
|
199
|
+
}
|
|
200
|
+
function isCriterionId(ref) {
|
|
201
|
+
return /^cr-[a-f0-9]{8}$/.test(ref);
|
|
159
202
|
}
|
|
160
203
|
/** Detect newly met criteria by comparing the current entry's
|
|
161
|
-
* success_criteria_met against the previous entry's
|
|
162
|
-
*
|
|
204
|
+
* success_criteria_met against the previous entry's.
|
|
205
|
+
* v2.11: ID-first matching (cr-XXXXXXXX) with Jaccard similarity
|
|
206
|
+
* fallback for backward compatibility.
|
|
163
207
|
* Returns empty array when there is no previous entry — the first
|
|
164
208
|
* entry's criteria are the baseline, not a "new" event. */
|
|
165
209
|
function detectNewCriteria(current, previous) {
|
|
@@ -173,7 +217,7 @@ function detectNewCriteria(current, previous) {
|
|
|
173
217
|
const prevMet = entryCriteriaMet(previous);
|
|
174
218
|
if (prevMet.length === 0)
|
|
175
219
|
return currMet;
|
|
176
|
-
return currMet.filter((curr) => prevMet.every((prev) =>
|
|
220
|
+
return currMet.filter((curr) => prevMet.every((prev) => !criteriaMatch(curr, prev)));
|
|
177
221
|
}
|
|
178
222
|
/** Read constraints_active from an entry's lineage. */
|
|
179
223
|
function entryActiveConstraints(entry) {
|
|
@@ -183,29 +227,15 @@ function entryActiveConstraints(entry) {
|
|
|
183
227
|
}
|
|
184
228
|
/** Read retracted_constraints from an entry. */
|
|
185
229
|
function entryRetractedConstraints(entry) {
|
|
186
|
-
|
|
187
|
-
if (Array.isArray(direct))
|
|
188
|
-
return direct.filter((v) => typeof v === "string");
|
|
189
|
-
const lin = lineage(entry);
|
|
190
|
-
const nested = lin.retracted_constraints;
|
|
191
|
-
if (Array.isArray(nested))
|
|
192
|
-
return nested.filter((v) => typeof v === "string");
|
|
193
|
-
return [];
|
|
230
|
+
return entryProjection(entry)?.constraintEvents?.retired ?? [];
|
|
194
231
|
}
|
|
195
|
-
/** Read progress_estimate from an entry's execution_evidence. */
|
|
196
232
|
function entryProgressEstimate(entry) {
|
|
197
|
-
|
|
198
|
-
if (!ev)
|
|
199
|
-
return 0;
|
|
200
|
-
const pe = ev.progress_estimate;
|
|
201
|
-
return typeof pe === "number" ? pe : 0;
|
|
233
|
+
return entryProjection(entry)?.evidence?.progressRatio ?? 0;
|
|
202
234
|
}
|
|
203
235
|
/** Build a single MilestoneSummary from a range of completed round entries. */
|
|
204
236
|
function buildMilestoneFromEntries(phaseEntries, startRound, endRound, label, kind) {
|
|
205
237
|
const last = phaseEntries.at(-1);
|
|
206
|
-
const outcomeRaw = last
|
|
207
|
-
? (typeof last.output_summary === "string" ? last.output_summary : "")
|
|
208
|
-
: "";
|
|
238
|
+
const outcomeRaw = last ? entryProjection(last)?.summary ?? "" : "";
|
|
209
239
|
const outcomeMax = getPolicy().checkpoint.outcome_max_chars;
|
|
210
240
|
const outcome = outcomeRaw.length > outcomeMax
|
|
211
241
|
? outcomeRaw.slice(0, outcomeMax) + "…"
|
|
@@ -240,28 +270,48 @@ export function buildRollingSummary(loopId, currentRound, context, sinceRound =
|
|
|
240
270
|
const data = lineage(entry);
|
|
241
271
|
const round = entryRound(entry);
|
|
242
272
|
const success = entry.success ?? data.success;
|
|
243
|
-
const summary =
|
|
244
|
-
? entry.output_summary
|
|
245
|
-
: typeof data.output_summary === "string" ? data.output_summary : "";
|
|
273
|
+
const summary = entryProjection(entry)?.summary ?? "";
|
|
246
274
|
if (summary)
|
|
247
275
|
outcomes.push(`[R${round}] ${success === false ? "failed" : "accepted"}: ${summary}`);
|
|
248
|
-
const violations =
|
|
249
|
-
? entry.constraint_violations
|
|
250
|
-
: Array.isArray(data.constraint_violations) ? data.constraint_violations : [];
|
|
276
|
+
const violations = entryProjection(entry)?.violations ?? [];
|
|
251
277
|
issues.push(...violations.filter((item) => typeof item === "string"));
|
|
252
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
|
+
}
|
|
253
287
|
// ── Phase 2: Milestone Accumulation ──
|
|
254
288
|
// Scan all completed entries in round order. For each entry, check three
|
|
255
289
|
// trigger signals in priority order: agent_declared > criteria_milestone > auto.
|
|
256
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
|
+
}
|
|
257
305
|
const milestones = [];
|
|
258
306
|
let lastMilestoneRound = 0;
|
|
259
307
|
for (let i = 0; i < completedEntries.length; i++) {
|
|
260
308
|
const entry = completedEntries[i];
|
|
261
309
|
const rnd = entryRound(entry);
|
|
262
310
|
const lin = lineage(entry);
|
|
311
|
+
const report = parseRoundReport(entry.round_report);
|
|
263
312
|
// Signal 1: Agent-declared checkpoint
|
|
264
|
-
const isAgentCheckpoint =
|
|
313
|
+
const isAgentCheckpoint = Boolean(report?.phaseBoundaryProposal) ||
|
|
314
|
+
lin.compression_checkpoint === true;
|
|
265
315
|
// Signal 2: New criteria met (compare with previous vault entry)
|
|
266
316
|
const prev = i > 0 ? completedEntries[i - 1] : null;
|
|
267
317
|
const newCriteria = detectNewCriteria(entry, prev);
|
|
@@ -275,9 +325,10 @@ export function buildRollingSummary(loopId, currentRound, context, sinceRound =
|
|
|
275
325
|
? "agent_declared"
|
|
276
326
|
: "criteria_milestone";
|
|
277
327
|
const label = isAgentCheckpoint
|
|
278
|
-
? (
|
|
279
|
-
|
|
280
|
-
|
|
328
|
+
? (report?.phaseBoundaryProposal?.label?.trim() ||
|
|
329
|
+
(typeof lin.checkpoint_label === "string" && lin.checkpoint_label.trim()
|
|
330
|
+
? lin.checkpoint_label.trim()
|
|
331
|
+
: `Round ${rnd}`))
|
|
281
332
|
: `Completed: ${newCriteria.join(", ")}`;
|
|
282
333
|
milestones.push(buildMilestoneFromEntries(phaseEntries, startRound, rnd, label, kind));
|
|
283
334
|
lastMilestoneRound = rnd;
|
|
@@ -320,205 +371,20 @@ export function buildRollingSummary(loopId, currentRound, context, sinceRound =
|
|
|
320
371
|
return null;
|
|
321
372
|
return makeRollingSummary({
|
|
322
373
|
key_outcomes: unique(outcomes),
|
|
323
|
-
recurring_issues:
|
|
374
|
+
recurring_issues: [...issueCounts.values()]
|
|
375
|
+
.filter((issue) => issue.count >= 2)
|
|
376
|
+
.map((issue) => `${issue.text} (${issue.count} rounds)`),
|
|
324
377
|
rounds_sampled: windowEntries.length,
|
|
325
378
|
generated_at_round: currentRound,
|
|
326
|
-
failed_patterns:
|
|
379
|
+
failed_patterns: failedGroups
|
|
380
|
+
.filter((failure) => failure.count >= 2)
|
|
381
|
+
.map((failure) => `${failure.text} (${failure.count} attempts)`),
|
|
327
382
|
milestones: cappedMilestones,
|
|
328
383
|
loop_synthesis: loopSynthesis || undefined,
|
|
329
384
|
});
|
|
330
385
|
}
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
334
|
-
/** Derive a stable sub-goal ID from its description hash. */
|
|
335
|
-
function deriveSubGoalId(description) {
|
|
336
|
-
return "sg-" + computeGoalTextHash(description).slice(0, 8);
|
|
337
|
-
}
|
|
338
|
-
/** Read emerged_subtasks from a vault entry (handles direct + lineage nesting). */
|
|
339
|
-
function entryEmergedSubtasks(entry) {
|
|
340
|
-
const direct = entry.emerged_subtasks;
|
|
341
|
-
if (Array.isArray(direct))
|
|
342
|
-
return direct.filter((v) => typeof v === "string");
|
|
343
|
-
const lin = lineage(entry);
|
|
344
|
-
const nested = lin.emerged_subtasks;
|
|
345
|
-
if (Array.isArray(nested))
|
|
346
|
-
return nested.filter((v) => typeof v === "string");
|
|
347
|
-
return [];
|
|
348
|
-
}
|
|
349
|
-
/** Read completed_subtasks from an entry. */
|
|
350
|
-
function entryCompletedSubtasks(entry) {
|
|
351
|
-
const direct = entry.completed_subtasks;
|
|
352
|
-
if (Array.isArray(direct))
|
|
353
|
-
return direct.filter((v) => typeof v === "string");
|
|
354
|
-
return [];
|
|
355
|
-
}
|
|
356
|
-
/** Read blocked_subtasks from an entry. */
|
|
357
|
-
function entryBlockedSubtasks(entry) {
|
|
358
|
-
const direct = entry.blocked_subtasks;
|
|
359
|
-
if (Array.isArray(direct))
|
|
360
|
-
return direct.filter((v) => typeof v === "string");
|
|
361
|
-
return [];
|
|
362
|
-
}
|
|
363
|
-
/** Read canceled_subtasks from an entry. */
|
|
364
|
-
function entryCanceledSubtasks(entry) {
|
|
365
|
-
const direct = entry.canceled_subtasks;
|
|
366
|
-
if (Array.isArray(direct))
|
|
367
|
-
return direct.filter((v) => typeof v === "string");
|
|
368
|
-
return [];
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* Match a user-provided sub-task description against existing SubGoals.
|
|
372
|
-
* Returns the index of the best-matching SubGoal with similarity >= threshold,
|
|
373
|
-
* or -1 if no match found.
|
|
374
|
-
*/
|
|
375
|
-
function matchSubGoal(target, subGoals, threshold) {
|
|
376
|
-
let bestIdx = -1;
|
|
377
|
-
let bestScore = 0;
|
|
378
|
-
for (let i = 0; i < subGoals.length; i++) {
|
|
379
|
-
const score = jaccardSimilarity(target, subGoals[i].description);
|
|
380
|
-
if (score > bestScore) {
|
|
381
|
-
bestScore = score;
|
|
382
|
-
bestIdx = i;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
return bestScore >= threshold ? bestIdx : -1;
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Manage the full sub-goal lifecycle across rounds.
|
|
389
|
-
*
|
|
390
|
-
* Accumulates sub-goals from all prior vault entries, processes the current
|
|
391
|
-
* round's declarations, derives status changes, and returns the complete
|
|
392
|
-
* structured SubGoal list.
|
|
393
|
-
*
|
|
394
|
-
* Status derivation priority (compiler-driven):
|
|
395
|
-
* 1. Agent declared done/canceled/blocked → explicit status change
|
|
396
|
-
* 2. next_action match → auto in_progress
|
|
397
|
-
* 3. success_criteria_met match → auto done
|
|
398
|
-
*/
|
|
399
|
-
function manageSubGoals(loopId, currentRound, lastRoundResult, vaultContext) {
|
|
400
|
-
const policy = getPolicy().evolution;
|
|
401
|
-
const allEntries = loopEntries(loopId, vaultContext);
|
|
402
|
-
const completedEntries = allEntries.filter((e) => entryRound(e) >= 1 && entryRound(e) < currentRound);
|
|
403
|
-
const subGoals = [];
|
|
404
|
-
// Phase 1 — Accumulate historical sub-goals from vault entries
|
|
405
|
-
for (const entry of completedEntries) {
|
|
406
|
-
const rnd = entryRound(entry);
|
|
407
|
-
const emerged = entryEmergedSubtasks(entry);
|
|
408
|
-
for (const desc of emerged) {
|
|
409
|
-
const id = deriveSubGoalId(desc);
|
|
410
|
-
// Dedup: skip if an existing sub-goal is too similar
|
|
411
|
-
if (matchSubGoal(desc, subGoals, policy.subgoal_dedup_threshold) >= 0)
|
|
412
|
-
continue;
|
|
413
|
-
subGoals.push(makeSubGoal({
|
|
414
|
-
id,
|
|
415
|
-
description: desc.trim(),
|
|
416
|
-
status: "pending",
|
|
417
|
-
declared_at_round: rnd,
|
|
418
|
-
status_changed_at_round: rnd,
|
|
419
|
-
priority: subGoals.length,
|
|
420
|
-
}));
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
// Phase 2 — Process current round's sub-task declarations
|
|
424
|
-
if (lastRoundResult) {
|
|
425
|
-
const rnd = lastRoundResult.round || (currentRound - 1);
|
|
426
|
-
// New sub-goals from this round
|
|
427
|
-
const emerged = lastRoundResult.emerged_subtasks ?? [];
|
|
428
|
-
for (const desc of emerged) {
|
|
429
|
-
if (matchSubGoal(desc, subGoals, policy.subgoal_dedup_threshold) >= 0)
|
|
430
|
-
continue;
|
|
431
|
-
subGoals.push(makeSubGoal({
|
|
432
|
-
id: deriveSubGoalId(desc),
|
|
433
|
-
description: desc.trim(),
|
|
434
|
-
status: "pending",
|
|
435
|
-
declared_at_round: rnd,
|
|
436
|
-
status_changed_at_round: rnd,
|
|
437
|
-
priority: subGoals.length,
|
|
438
|
-
}));
|
|
439
|
-
}
|
|
440
|
-
// Completed sub-goals
|
|
441
|
-
const completed = lastRoundResult.completed_subtasks ?? [];
|
|
442
|
-
for (const desc of completed) {
|
|
443
|
-
const idx = matchSubGoal(desc, subGoals, policy.subgoal_match_threshold);
|
|
444
|
-
if (idx >= 0 && subGoals[idx].status !== "done" && subGoals[idx].status !== "canceled") {
|
|
445
|
-
subGoals[idx].status = "done";
|
|
446
|
-
subGoals[idx].status_changed_at_round = rnd;
|
|
447
|
-
subGoals[idx].completed_at_round = rnd;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
// Blocked sub-goals
|
|
451
|
-
const blocked = lastRoundResult.blocked_subtasks ?? [];
|
|
452
|
-
for (const desc of blocked) {
|
|
453
|
-
const idx = matchSubGoal(desc, subGoals, policy.subgoal_match_threshold);
|
|
454
|
-
if (idx >= 0 && subGoals[idx].status !== "done" && subGoals[idx].status !== "canceled") {
|
|
455
|
-
subGoals[idx].status = "blocked";
|
|
456
|
-
subGoals[idx].status_changed_at_round = rnd;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
// Canceled sub-goals
|
|
460
|
-
const canceled = lastRoundResult.canceled_subtasks ?? [];
|
|
461
|
-
for (const desc of canceled) {
|
|
462
|
-
const idx = matchSubGoal(desc, subGoals, policy.subgoal_match_threshold);
|
|
463
|
-
if (idx >= 0 && subGoals[idx].status !== "done" && subGoals[idx].status !== "canceled") {
|
|
464
|
-
subGoals[idx].status = "canceled";
|
|
465
|
-
subGoals[idx].status_changed_at_round = rnd;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
// Phase 3 — Auto-derive: next_action match → in_progress
|
|
469
|
-
const nextAction = lastRoundResult.next_action?.trim();
|
|
470
|
-
if (nextAction) {
|
|
471
|
-
for (const sg of subGoals) {
|
|
472
|
-
if (sg.status === "pending") {
|
|
473
|
-
if (jaccardSimilarity(nextAction, sg.description) >= policy.subgoal_auto_in_progress_threshold) {
|
|
474
|
-
sg.status = "in_progress";
|
|
475
|
-
sg.status_changed_at_round = rnd;
|
|
476
|
-
break; // Only one sub-goal can be in_progress at a time
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
// Phase 4 — Auto-complete: new criteria match → done
|
|
482
|
-
const newCriteria = lastRoundResult.execution_evidence?.success_criteria_met ?? [];
|
|
483
|
-
if (newCriteria.length > 0) {
|
|
484
|
-
for (const sg of subGoals) {
|
|
485
|
-
if (sg.status === "pending" || sg.status === "in_progress") {
|
|
486
|
-
for (const crit of newCriteria) {
|
|
487
|
-
if (jaccardSimilarity(crit, sg.description) >= policy.subgoal_auto_complete_threshold) {
|
|
488
|
-
sg.status = "done";
|
|
489
|
-
sg.status_changed_at_round = rnd;
|
|
490
|
-
sg.completed_at_round = rnd;
|
|
491
|
-
break;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
// Sort for display: in_progress first, then pending by age desc, then blocked, then done, then canceled
|
|
499
|
-
subGoals.sort((a, b) => {
|
|
500
|
-
const rank = (s) => {
|
|
501
|
-
switch (s.status) {
|
|
502
|
-
case "in_progress": return 0;
|
|
503
|
-
case "pending": return 1;
|
|
504
|
-
case "blocked": return 2;
|
|
505
|
-
case "done": return 3;
|
|
506
|
-
case "canceled": return 4;
|
|
507
|
-
}
|
|
508
|
-
};
|
|
509
|
-
const ra = rank(a), rb = rank(b);
|
|
510
|
-
if (ra !== rb)
|
|
511
|
-
return ra - rb;
|
|
512
|
-
// Within same status: pending by oldest first, done by most recent first
|
|
513
|
-
if (a.status === "pending")
|
|
514
|
-
return (currentRound - a.declared_at_round) - (currentRound - b.declared_at_round);
|
|
515
|
-
if (a.status === "done")
|
|
516
|
-
return (b.completed_at_round ?? 0) - (a.completed_at_round ?? 0);
|
|
517
|
-
return a.declared_at_round - b.declared_at_round;
|
|
518
|
-
});
|
|
519
|
-
return subGoals;
|
|
520
|
-
}
|
|
521
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
386
|
+
// Sub-goal lifecycle is implemented in compiler-subgoals.ts; this facade keeps
|
|
387
|
+
// the compiler's public entry point and projection flow unchanged.
|
|
522
388
|
// Time-Aware Constraint Management (v2.3)
|
|
523
389
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
524
390
|
/** Determine the source of a constraint by matching against known sources. */
|
|
@@ -533,17 +399,25 @@ function constraintSource(text, objective, constraintsFromPlan) {
|
|
|
533
399
|
return "discovered";
|
|
534
400
|
}
|
|
535
401
|
/** Find the earliest round a constraint was discovered by scanning vault entries.
|
|
536
|
-
*
|
|
402
|
+
* v2.11: ID-first matching. Checks exact constraint ID match (c-XXXXXXXX)
|
|
403
|
+
* against the derived ID of each vault entry's discovered_constraints, then
|
|
404
|
+
* falls back to Jaccard similarity on text. */
|
|
537
405
|
function findDiscoveredRound(text, vaultEntries) {
|
|
406
|
+
const targetId = deriveConstraintId(text);
|
|
538
407
|
let earliest = 0;
|
|
539
408
|
for (const entry of vaultEntries) {
|
|
540
409
|
const rnd = entryRound(entry);
|
|
541
410
|
if (rnd < 1)
|
|
542
411
|
continue;
|
|
543
|
-
const discovered =
|
|
544
|
-
? entry.discovered_constraints.filter((v) => typeof v === "string")
|
|
545
|
-
: [];
|
|
412
|
+
const discovered = entryProjection(entry)?.discoveries?.newConstraints ?? [];
|
|
546
413
|
for (const d of discovered) {
|
|
414
|
+
// Phase 1: exact ID match (v2.11)
|
|
415
|
+
if (d === targetId || deriveConstraintId(d) === targetId) {
|
|
416
|
+
if (earliest === 0 || rnd < earliest)
|
|
417
|
+
earliest = rnd;
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
// Phase 2: Jaccard fallback (backward compatibility)
|
|
547
421
|
if (jaccardSimilarity(text, d) >= getPolicy().evolution.constraint_match_threshold) {
|
|
548
422
|
if (earliest === 0 || rnd < earliest)
|
|
549
423
|
earliest = rnd;
|
|
@@ -554,17 +428,25 @@ function findDiscoveredRound(text, vaultEntries) {
|
|
|
554
428
|
return earliest;
|
|
555
429
|
}
|
|
556
430
|
/** Find the latest round a constraint was violated by scanning vault entries.
|
|
557
|
-
*
|
|
431
|
+
* v2.11: ID-first matching. Checks exact constraint ID match (c-XXXXXXXX)
|
|
432
|
+
* against the derived ID of each vault entry's constraint_violations, then
|
|
433
|
+
* falls back to Jaccard similarity on text. Returns 0 if never violated. */
|
|
558
434
|
function findLastViolatedRound(text, vaultEntries) {
|
|
435
|
+
const targetId = deriveConstraintId(text);
|
|
559
436
|
let latest = 0;
|
|
560
437
|
for (const entry of vaultEntries) {
|
|
561
438
|
const rnd = entryRound(entry);
|
|
562
439
|
if (rnd < 1)
|
|
563
440
|
continue;
|
|
564
|
-
const violations =
|
|
565
|
-
? entry.constraint_violations.filter((v) => typeof v === "string")
|
|
566
|
-
: [];
|
|
441
|
+
const violations = entryProjection(entry)?.violations ?? [];
|
|
567
442
|
for (const v of violations) {
|
|
443
|
+
// Phase 1: exact ID match (v2.11)
|
|
444
|
+
if (v === targetId || deriveConstraintId(v) === targetId) {
|
|
445
|
+
if (rnd > latest)
|
|
446
|
+
latest = rnd;
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
// Phase 2: Jaccard fallback (backward compatibility)
|
|
568
450
|
if (jaccardSimilarity(text, v) >= getPolicy().evolution.constraint_match_threshold) {
|
|
569
451
|
if (rnd > latest)
|
|
570
452
|
latest = rnd;
|
|
@@ -592,6 +474,7 @@ function manageConstraintLifecycle(active, objective, constraintsFromPlan, vault
|
|
|
592
474
|
// Structural constraints never decay
|
|
593
475
|
if (source === "hard" || source === "plan" || source === "criteria") {
|
|
594
476
|
metadata.push(makeConstraintMeta({
|
|
477
|
+
id: deriveConstraintId(text),
|
|
595
478
|
text,
|
|
596
479
|
discovered_at_round: objective.created_at_round || 1,
|
|
597
480
|
last_violated_at_round: findLastViolatedRound(text, completedEntries),
|
|
@@ -610,6 +493,7 @@ function manageConstraintLifecycle(active, objective, constraintsFromPlan, vault
|
|
|
610
493
|
? "inactive"
|
|
611
494
|
: "active";
|
|
612
495
|
metadata.push(makeConstraintMeta({
|
|
496
|
+
id: deriveConstraintId(text),
|
|
613
497
|
text,
|
|
614
498
|
discovered_at_round: discoveredAt || 0,
|
|
615
499
|
last_violated_at_round: lastViolated,
|
|
@@ -625,7 +509,6 @@ function manageConstraintLifecycle(active, objective, constraintsFromPlan, vault
|
|
|
625
509
|
.map((m) => m.text);
|
|
626
510
|
return { active: newActive, inactive, metadata };
|
|
627
511
|
}
|
|
628
|
-
// jaccardSimilarity() imported from token-utils.ts
|
|
629
512
|
export function alignTask(proposedTask, request, context) {
|
|
630
513
|
const objective = request.loop_objective ?? latestObjective(request.loop_id, context);
|
|
631
514
|
if (!objective)
|
|
@@ -641,7 +524,7 @@ export function alignTask(proposedTask, request, context) {
|
|
|
641
524
|
export function checkLoopHealth(loopId, request, context) {
|
|
642
525
|
const previous = getPreviousRound(loopId, request.round - 1, context);
|
|
643
526
|
const alignment = alignTask(request.task, request, context);
|
|
644
|
-
const violations = request.
|
|
527
|
+
const violations = request.last_round_view?.violations?.length ?? 0;
|
|
645
528
|
const integrity = Math.max(0, 1 - violations * 0.2);
|
|
646
529
|
const continuity = previous ? jaccardSimilarity(previous.task, request.task) : 1;
|
|
647
530
|
const drift = alignment.alignment_score < 0.3 || continuity < 0.2;
|
|
@@ -656,70 +539,133 @@ export function checkLoopHealth(loopId, request, context) {
|
|
|
656
539
|
}
|
|
657
540
|
function levelDecision(request, context) {
|
|
658
541
|
const previous = getPreviousRound(request.loop_id, request.round - 1, context);
|
|
659
|
-
const last = request.
|
|
542
|
+
const last = request.last_round_view;
|
|
660
543
|
const hasNewInformation = Boolean(request.new_since_last_round.trim() ||
|
|
661
|
-
last?.
|
|
662
|
-
last?.
|
|
663
|
-
last?.
|
|
664
|
-
last?.
|
|
665
|
-
last?.
|
|
666
|
-
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);
|
|
667
551
|
const lastFullRound = loopEntries(request.loop_id, context)
|
|
668
552
|
.filter((entry) => lineage(entry).recompile_level === "l2")
|
|
669
553
|
.map(entryRound)
|
|
670
554
|
.filter((round) => round < request.round)
|
|
671
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;
|
|
672
576
|
return decidePromptLevel({
|
|
673
577
|
round: request.round,
|
|
674
578
|
attempt: request.attempt,
|
|
675
579
|
forceLevel: request.force_level,
|
|
676
580
|
hasPlanSource: Boolean(request.plan_source),
|
|
677
|
-
checkpointBoundary: last?.
|
|
678
|
-
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),
|
|
679
585
|
previousStateMissing: request.round > 1 && previous === null,
|
|
680
|
-
previousFailedWithoutNewInformation: last?.
|
|
586
|
+
previousFailedWithoutNewInformation: last?.outcome === "failed" && !hasNewInformation,
|
|
681
587
|
verificationContradicted: (request.verification_flags ?? [])
|
|
682
588
|
.some((flag) => flag.severity === "error"),
|
|
683
589
|
consecutiveRejections: request.consecutive_rejections,
|
|
684
590
|
fullRefreshInterval: getPolicy().prompt.full_refresh_interval,
|
|
685
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,
|
|
686
604
|
});
|
|
687
605
|
}
|
|
688
606
|
export function decideLevel(request, context) {
|
|
689
607
|
return levelDecision(request, context).level;
|
|
690
608
|
}
|
|
691
|
-
export function
|
|
692
|
-
|
|
609
|
+
export function buildRoundReportBlock(round, prevDriftFlags) {
|
|
610
|
+
const hasIntentDrift = prevDriftFlags?.some(f => f.check === "intent_drift") ?? false;
|
|
611
|
+
const hasSubGoalDrift = prevDriftFlags?.some(f => f.check === "subgoal_drift") ?? false;
|
|
612
|
+
const needsClarification = hasIntentDrift || hasSubGoalDrift;
|
|
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: [],
|
|
634
|
+
},
|
|
635
|
+
subGoalEvents: { completed: [], blocked: [], canceled: [] },
|
|
636
|
+
};
|
|
637
|
+
// v2.8: Inject drift clarification field when previous round had drift flags
|
|
638
|
+
if (needsClarification) {
|
|
639
|
+
const flagTypes = [
|
|
640
|
+
hasIntentDrift ? "intent_drift" : "",
|
|
641
|
+
hasSubGoalDrift ? "subgoal_drift" : "",
|
|
642
|
+
].filter(Boolean).join(" / ");
|
|
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
|
+
};
|
|
651
|
+
}
|
|
652
|
+
const lines = [
|
|
693
653
|
"### LoopForge Evaluation (Required)",
|
|
694
654
|
"",
|
|
695
655
|
`After completing Round ${round}, replace the placeholder values below`,
|
|
696
656
|
"with your actual results and submit via `loopforge_next`.",
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
success_criteria_remaining: [],
|
|
710
|
-
progress_estimate: 0,
|
|
711
|
-
},
|
|
712
|
-
wrong_assumptions: [],
|
|
713
|
-
next_action: "<next concrete action, or empty when complete>",
|
|
714
|
-
completed_subtasks: [],
|
|
715
|
-
blocked_subtasks: [],
|
|
716
|
-
canceled_subtasks: [],
|
|
717
|
-
}, null, 2),
|
|
718
|
-
"---end-loopforge-eval",
|
|
719
|
-
"",
|
|
720
|
-
"IMPORTANT: Replace every <placeholder> with your actual data.",
|
|
721
|
-
"Set success=true only when the full goal and ALL hard constraints are verified.",
|
|
722
|
-
].join("\n");
|
|
657
|
+
];
|
|
658
|
+
if (needsClarification) {
|
|
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.");
|
|
660
|
+
}
|
|
661
|
+
lines.push("Submit this object in the `report` parameter:");
|
|
662
|
+
lines.push(JSON.stringify(report, null, 2));
|
|
663
|
+
lines.push("");
|
|
664
|
+
lines.push("IMPORTANT: Replace every <placeholder> with your actual data.");
|
|
665
|
+
lines.push("Set outcome=success only when every criterion has runtime-verifiable evidence; set continuation=stop only when no further round is requested.");
|
|
666
|
+
lines.push("Use IDs for exact matching: c-XXXXXXXX, cr-XXXXXXXX, sg-XXXXXXXX.");
|
|
667
|
+
lines.push("For subtasks, use sub-goal IDs from the Sub-Goal Dashboard above.");
|
|
668
|
+
return lines.join("\n");
|
|
723
669
|
}
|
|
724
670
|
// v2.5: checkpoint() removed — agent_declared milestones from
|
|
725
671
|
// compression_checkpoint supersede the separate CheckpointSummary.
|
|
@@ -731,7 +677,7 @@ export function compileLoop(request, context) {
|
|
|
731
677
|
const constraints = evolveConstraints(request, objective, previous);
|
|
732
678
|
const constraintLifecycle = manageConstraintLifecycle(constraints.active, objective, request.constraints_from_plan, context, request.round);
|
|
733
679
|
const rolling = buildRollingSummary(request.loop_id, request.round, context);
|
|
734
|
-
const subGoals =
|
|
680
|
+
const subGoals = deriveSubGoals(request.loop_id, request.round, request.last_round_view, context, entryProjection);
|
|
735
681
|
const alignment = alignTask(request.task, request, context);
|
|
736
682
|
const health = checkLoopHealth(request.loop_id, { ...request, loop_objective: objective }, context);
|
|
737
683
|
const warnings = unique([
|
|
@@ -751,13 +697,35 @@ export function compileLoop(request, context) {
|
|
|
751
697
|
.filter((e) => entryRound(e) >= 1 && entryRound(e) < request.round);
|
|
752
698
|
const trend = [];
|
|
753
699
|
for (const entry of completedEntries.slice(-10)) {
|
|
754
|
-
const viols =
|
|
755
|
-
? entry.constraint_violations.filter((v) => typeof v === "string")
|
|
756
|
-
: [];
|
|
700
|
+
const viols = entryProjection(entry)?.violations ?? [];
|
|
757
701
|
// Simple proxy: no violations = perfect trust, each violation = -0.1
|
|
758
702
|
const score = Math.max(0, 1.0 - viols.length * 0.1);
|
|
759
703
|
trend.push(Number(score.toFixed(2)));
|
|
760
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
|
+
: [];
|
|
761
729
|
const response = makeLoopCompileResponse({
|
|
762
730
|
status: AgentStatus.OK,
|
|
763
731
|
recompile_level: decision.level,
|
|
@@ -778,8 +746,18 @@ export function compileLoop(request, context) {
|
|
|
778
746
|
sub_goals: subGoals,
|
|
779
747
|
agent_trust_score: request.round > 1 ? Number(trustScore.toFixed(2)) : undefined,
|
|
780
748
|
agent_trust_trend: request.round > 1 ? trend : [],
|
|
781
|
-
|
|
782
|
-
|
|
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
|
+
}),
|
|
783
761
|
plan_source: request.plan_source,
|
|
784
762
|
warnings,
|
|
785
763
|
});
|
|
@@ -794,6 +772,10 @@ export function compileLoop(request, context) {
|
|
|
794
772
|
+ ((rolling?.milestones?.length ?? 0) * policy.prompt.l2_adaptive_milestone_factor)
|
|
795
773
|
+ (subGoals.length * policy.prompt.l2_adaptive_subgoal_factor), policy.prompt.l2_adaptive_max_chars)
|
|
796
774
|
: policy.prompt.l2_max_chars;
|
|
775
|
+
// v2.8: L2 pointer mode — when enabled, skip the monolithic markdown blob.
|
|
776
|
+
// Structured L2 sections (milestones, sub-goals, trust, progress) still
|
|
777
|
+
// render. The state file on disk is the durable source of truth.
|
|
778
|
+
const l2Pointer = policy.prompt.l2_pointer_enabled && decision.level === "l2";
|
|
797
779
|
const artifact = assemblePromptArtifact({
|
|
798
780
|
state,
|
|
799
781
|
level: decision.level,
|
|
@@ -805,12 +787,36 @@ export function compileLoop(request, context) {
|
|
|
805
787
|
l2: adaptiveL2,
|
|
806
788
|
},
|
|
807
789
|
attempt: request.attempt,
|
|
808
|
-
|
|
809
|
-
fullStateMarkdown: markdown,
|
|
790
|
+
roundReportBlock: buildRoundReportBlock(request.round, request.verification_flags),
|
|
791
|
+
fullStateMarkdown: l2Pointer ? undefined : markdown,
|
|
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);
|
|
810
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;
|
|
811
816
|
response.prompt = artifact.renderedPrompt;
|
|
812
817
|
response.prompt_artifact = artifact;
|
|
813
818
|
response.state_file_content = policy.state_file.enabled ? markdown : undefined;
|
|
819
|
+
response.canonical_state = state;
|
|
814
820
|
return response;
|
|
815
821
|
}
|
|
816
822
|
//# sourceMappingURL=loop-compiler.js.map
|