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/enforcement-gate.js
DELETED
|
@@ -1,744 +0,0 @@
|
|
|
1
|
-
/** Enforcement Gate — Layer 2 round-boundary runtime enforcement (v1.13).
|
|
2
|
-
*
|
|
3
|
-
* Pure-function module. Receives the verification gate's findings plus
|
|
4
|
-
* round-level context and decides whether to accept the round, reject it
|
|
5
|
-
* (force the agent to redo), or terminate the loop.
|
|
6
|
-
*
|
|
7
|
-
* This is the "runtime" that prompt-only constraint systems lack.
|
|
8
|
-
* Verification gate detects WHAT is wrong; enforcement gate decides
|
|
9
|
-
* what to DO about it.
|
|
10
|
-
*
|
|
11
|
-
* Decision semantics:
|
|
12
|
-
* - accept: round passes all checks; advance to next round as normal.
|
|
13
|
-
* - reject: agent's self-evaluation or round output is invalid; the
|
|
14
|
-
* agent receives a rejection prompt and must redo the SAME
|
|
15
|
-
* round. Round counter does NOT increment.
|
|
16
|
-
* - terminate: loop has reached an unrecoverable state; stop immediately
|
|
17
|
-
* with stopReason "enforcement_terminated".
|
|
18
|
-
*/
|
|
19
|
-
import { makeEnforcementResult } from "./protocol.js";
|
|
20
|
-
import { entryRound } from "./verification-gate.js";
|
|
21
|
-
import { getPolicy } from "./policy.js";
|
|
22
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
23
|
-
// Helpers
|
|
24
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
25
|
-
/** Extract progress_estimate from a vault entry's execution_evidence.
|
|
26
|
-
* Returns null if no execution evidence is available. */
|
|
27
|
-
function entryProgress(entry) {
|
|
28
|
-
const evidence = entry.execution_evidence;
|
|
29
|
-
if (!evidence)
|
|
30
|
-
return null;
|
|
31
|
-
const pe = evidence.progress_estimate;
|
|
32
|
-
return typeof pe === "number" ? pe : null;
|
|
33
|
-
}
|
|
34
|
-
/** Collect progress estimates from vault entries into a round→estimate map.
|
|
35
|
-
* Shared by R4 (progress_stall) and R5 (progress_stall_terminal). */
|
|
36
|
-
function collectProgressByRound(vaultEntries, currentRound) {
|
|
37
|
-
const progressByRound = new Map();
|
|
38
|
-
for (const entry of vaultEntries) {
|
|
39
|
-
const rnd = entryRound(entry);
|
|
40
|
-
if (rnd < 1 || rnd >= currentRound)
|
|
41
|
-
continue;
|
|
42
|
-
const pe = entryProgress(entry);
|
|
43
|
-
if (pe !== null)
|
|
44
|
-
progressByRound.set(rnd, pe);
|
|
45
|
-
}
|
|
46
|
-
return progressByRound;
|
|
47
|
-
}
|
|
48
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
49
|
-
// v2.10: Backtrack helpers
|
|
50
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
51
|
-
/** Check whether a vault entry represents a "clean" round that can serve
|
|
52
|
-
* as a safe restore point. A clean round was accepted (not rejected)
|
|
53
|
-
* and had no error-level verification flags. */
|
|
54
|
-
function isCleanRound(entry) {
|
|
55
|
-
const lineage = entry.loop_lineage;
|
|
56
|
-
// Rejected rounds are not clean
|
|
57
|
-
const success = typeof entry.success === "boolean"
|
|
58
|
-
? entry.success
|
|
59
|
-
: typeof lineage?.success === "boolean" ? lineage.success : undefined;
|
|
60
|
-
if (success === false)
|
|
61
|
-
return false;
|
|
62
|
-
// Rounds with error-level verification flags are not clean
|
|
63
|
-
const flags = entry.verification_flags;
|
|
64
|
-
if (Array.isArray(flags)) {
|
|
65
|
-
for (const f of flags) {
|
|
66
|
-
if (f !== null && typeof f === "object" &&
|
|
67
|
-
f.severity === "error")
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
/** Scan backwards from currentRound to find the most recent clean round.
|
|
74
|
-
* Collects discovered_constraints from skipped rounds along the way.
|
|
75
|
-
* Returns null when no clean round is found within maxDepth. */
|
|
76
|
-
export function findSafeRestorePoint(currentRound, vaultEntries, maxDepth) {
|
|
77
|
-
const completed = vaultEntries
|
|
78
|
-
.filter((e) => {
|
|
79
|
-
const rnd = entryRound(e);
|
|
80
|
-
return rnd >= 1 && rnd < currentRound;
|
|
81
|
-
})
|
|
82
|
-
.sort((a, b) => entryRound(b) - entryRound(a)); // newest first
|
|
83
|
-
const skippedDiscoveries = [];
|
|
84
|
-
for (let depth = 1; depth <= maxDepth; depth++) {
|
|
85
|
-
const targetRound = currentRound - depth;
|
|
86
|
-
if (targetRound < 1)
|
|
87
|
-
break;
|
|
88
|
-
const entry = completed.find((e) => entryRound(e) === targetRound);
|
|
89
|
-
if (!entry)
|
|
90
|
-
continue;
|
|
91
|
-
// Collect discoveries from rounds we're about to skip (depth > 1)
|
|
92
|
-
if (depth > 1) {
|
|
93
|
-
for (let d = 1; d < depth; d++) {
|
|
94
|
-
const skippedRound = currentRound - d;
|
|
95
|
-
const skippedEntry = completed.find((e) => entryRound(e) === skippedRound);
|
|
96
|
-
if (skippedEntry) {
|
|
97
|
-
const discovered = Array.isArray(skippedEntry.discovered_constraints)
|
|
98
|
-
? skippedEntry.discovered_constraints.filter((v) => typeof v === "string")
|
|
99
|
-
: [];
|
|
100
|
-
for (const item of discovered) {
|
|
101
|
-
if (!skippedDiscoveries.includes(item))
|
|
102
|
-
skippedDiscoveries.push(item);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (isCleanRound(entry)) {
|
|
108
|
-
return { round: targetRound, skippedDiscoveries };
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
/** Build the backtrack prompt injected at the top of the restored round.
|
|
114
|
-
*
|
|
115
|
-
* v2.13: Includes concrete workspace restore instructions with affected
|
|
116
|
-
* file lists from skipped rounds. The agent must restore the working tree
|
|
117
|
-
* to the clean round's state before proceeding. */
|
|
118
|
-
export function buildBacktrackPrompt(fromRound, toRound, triggerRule, skippedDiscoveries,
|
|
119
|
-
/** v2.13: Files changed in the skipped rounds (from evidence snapshots).
|
|
120
|
-
* Used to show the agent exactly what needs to be reverted. */
|
|
121
|
-
skippedFiles = [],
|
|
122
|
-
/** v2.13: Git HEAD commit hash at the backtrack point (current HEAD).
|
|
123
|
-
* The agent must discard work back to the clean round's state. */
|
|
124
|
-
_gitHead) {
|
|
125
|
-
const lines = [
|
|
126
|
-
`## ⛔ Backtrack — Round ${fromRound} → Restored to Round ${toRound}`,
|
|
127
|
-
"",
|
|
128
|
-
];
|
|
129
|
-
// ── Why This Happened ────────────────────────────────────────────────
|
|
130
|
-
if (triggerRule === "progress_stall") {
|
|
131
|
-
lines.push("### Why This Happened", "", `Progress stalled over rounds ${toRound + 1}–${fromRound}. ` +
|
|
132
|
-
`The work done in those rounds produced no verifiable progress — ` +
|
|
133
|
-
`no new files changed, no criteria met.`, "", "The approach used in those rounds **did not work**. It should not be repeated.", "");
|
|
134
|
-
}
|
|
135
|
-
else if (triggerRule === "progress_stall_terminal") {
|
|
136
|
-
lines.push("### Why This Happened", "", `Progress was completely flat for multiple rounds leading up to Round ${fromRound}. ` +
|
|
137
|
-
`The agent made **zero forward motion** — the task is not advancing.`, "", "A **radically different** strategy is needed. The previous approach produced nothing.", "");
|
|
138
|
-
}
|
|
139
|
-
// ── v2.13: Workspace Restore ──────────────────────────────────────────
|
|
140
|
-
lines.push("### ⚠️ Workspace Restore Required", "", `Your working directory still contains changes from the **failed** ` +
|
|
141
|
-
`rounds (${toRound + 1}–${fromRound}). You MUST discard those changes ` +
|
|
142
|
-
`before starting Round ${toRound + 1}. Working on top of stale changes ` +
|
|
143
|
-
`will cause the verification gate to reject your next submission.`, "");
|
|
144
|
-
if (skippedFiles.length > 0) {
|
|
145
|
-
lines.push("**Files modified in skipped rounds (must be reverted):**", "");
|
|
146
|
-
for (const f of skippedFiles.slice(0, 15)) {
|
|
147
|
-
lines.push(`- \`${f}\``);
|
|
148
|
-
}
|
|
149
|
-
if (skippedFiles.length > 15) {
|
|
150
|
-
lines.push(`- … and ${skippedFiles.length - 15} more files`);
|
|
151
|
-
}
|
|
152
|
-
lines.push("");
|
|
153
|
-
}
|
|
154
|
-
lines.push("**To restore a clean workspace, run:**", "", "```bash", "# Option A: Discard all uncommitted changes (recommended)", "git checkout -- .", "git clean -fd", "", "# Option B: If you have unrelated work to keep", `git stash push -m "backtrack-safety-net-round-${fromRound}"`, "git checkout -- .", "git clean -fd", "```", "", `**If git is unavailable**, manually revert the files listed above ` +
|
|
155
|
-
`to their state at Round ${toRound}. Re-read the state file below — ` +
|
|
156
|
-
`it reflects the correct state at Round ${toRound}.`, "", "> 🛡️ **Verification:** The next round's evidence check will verify that " +
|
|
157
|
-
"> your workspace no longer contains the failed changes. Submissions " +
|
|
158
|
-
"> with unrestored files will be **rejected**.", "");
|
|
159
|
-
// ── What Must Change ──────────────────────────────────────────────────
|
|
160
|
-
lines.push("### What Must Change", "", "- Do **NOT** repeat the approach used in the skipped rounds.", "- Try a **different** task decomposition or technique.", "- If the current sub-goal is stuck, consider canceling it " +
|
|
161
|
-
"(`canceled_subtasks`) and working on a different one.", "- Use the `prompt_requests.confusion_points` field in your self-evaluation " +
|
|
162
|
-
"to flag anything you don't understand about the task or the blockage.", "");
|
|
163
|
-
// ── Preserved Discoveries ─────────────────────────────────────────────
|
|
164
|
-
if (skippedDiscoveries.length > 0) {
|
|
165
|
-
lines.push("### Preserved Discoveries", "", "These were discovered in the skipped rounds and remain valid:", "");
|
|
166
|
-
for (const d of skippedDiscoveries) {
|
|
167
|
-
lines.push(`- ${d}`);
|
|
168
|
-
}
|
|
169
|
-
lines.push("");
|
|
170
|
-
}
|
|
171
|
-
// ── Instructions ──────────────────────────────────────────────────────
|
|
172
|
-
lines.push("### Instructions", "", `You have been rolled back to Round ${toRound}. ` +
|
|
173
|
-
`You are now starting **Round ${toRound + 1}** with the lessons above.`, "", "1. **First**: restore your workspace using the commands above.", "2. Read the restored state below — it reflects Round " + toRound + ".", "3. The discoveries from skipped rounds (if any) have been merged " +
|
|
174
|
-
"into the active constraints.", "4. Do NOT retrace the steps that led to the stall. Choose a new path.", "");
|
|
175
|
-
return lines.join("\n");
|
|
176
|
-
}
|
|
177
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
178
|
-
// Individual enforcement rules — each returns EnforcementResult | null
|
|
179
|
-
// Rules are ordered by priority. The first non-null result wins.
|
|
180
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
181
|
-
/** R1: Agent claims success but success_criteria_remaining has items.
|
|
182
|
-
* This is a lie — the agent must either finish the criteria or
|
|
183
|
-
* set success=false honestly. Triggered by the verification gate's
|
|
184
|
-
* "success_with_remaining_criteria" error flag. */
|
|
185
|
-
function enforceSuccessWithRemainingCriteria(flags) {
|
|
186
|
-
const flag = flags.find((f) => f.check === "success_with_remaining_criteria" && f.severity === "error");
|
|
187
|
-
if (!flag)
|
|
188
|
-
return null;
|
|
189
|
-
return makeEnforcementResult({
|
|
190
|
-
action: "reject",
|
|
191
|
-
reason: flag.detail,
|
|
192
|
-
fix_instructions: "You set success=true but success criteria remain unmet. " +
|
|
193
|
-
"Either: (a) complete the remaining criteria and re-submit your self-evaluation, " +
|
|
194
|
-
"or (b) set success=false and honestly report what remains to be done.",
|
|
195
|
-
check: "success_with_remaining_criteria",
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
/** R2: Same constraint violation appears in 3 consecutive rounds.
|
|
199
|
-
* The agent is repeating the same mistake. Triggered by the verification
|
|
200
|
-
* gate's "recurring_violation" error flag. */
|
|
201
|
-
function enforceRecurringViolation(flags) {
|
|
202
|
-
const flag = flags.find((f) => f.check === "recurring_violation" && f.severity === "error");
|
|
203
|
-
if (!flag)
|
|
204
|
-
return null;
|
|
205
|
-
return makeEnforcementResult({
|
|
206
|
-
action: "reject",
|
|
207
|
-
reason: flag.detail,
|
|
208
|
-
fix_instructions: "The same constraint violation has appeared in 3 consecutive rounds. " +
|
|
209
|
-
"You must: (a) explain WHY this violation keeps occurring, " +
|
|
210
|
-
"and (b) propose a DIFFERENT approach than what you used in the last 3 rounds. " +
|
|
211
|
-
"Do NOT retry the same strategy — it has failed 3 times.",
|
|
212
|
-
check: "recurring_violation",
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
/** R3: Agent claims success but did nothing verifiable.
|
|
216
|
-
* v1.17: execution_evidence is now MANDATORY for structured self-evaluations.
|
|
217
|
-
* Missing evidence when success=true → reject (agent must provide evidence).
|
|
218
|
-
* Empty evidence (no files + no tests) when success=true → reject. */
|
|
219
|
-
function enforceEmptySuccess(selfEval, _flags, skipEvidenceRules = false) {
|
|
220
|
-
if (skipEvidenceRules)
|
|
221
|
-
return null; // Requires execution_evidence
|
|
222
|
-
if (!selfEval.success)
|
|
223
|
-
return null;
|
|
224
|
-
const ev = selfEval.execution_evidence;
|
|
225
|
-
// v1.17: Missing evidence when claiming success → reject.
|
|
226
|
-
// The agent MUST provide execution_evidence to back up a success claim.
|
|
227
|
-
if (!ev) {
|
|
228
|
-
return makeEnforcementResult({
|
|
229
|
-
action: "reject",
|
|
230
|
-
reason: "Agent claims success but provided no execution_evidence. " +
|
|
231
|
-
"Every successful round MUST include execution_evidence with " +
|
|
232
|
-
"files_changed, test_results, and progress_estimate.",
|
|
233
|
-
fix_instructions: "You must provide execution_evidence in your self-evaluation: " +
|
|
234
|
-
"(a) list the files you changed in execution_evidence.files_changed, " +
|
|
235
|
-
"(b) run tests and report results in execution_evidence.test_results, " +
|
|
236
|
-
"(c) estimate your progress in execution_evidence.progress_estimate. " +
|
|
237
|
-
"If you genuinely completed the task without file changes or tests, " +
|
|
238
|
-
"explain why in detail in your output_summary.",
|
|
239
|
-
check: "empty_success",
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
const filesEmpty = ev.files_changed.length === 0;
|
|
243
|
-
const testsNotRun = ev.test_results === null;
|
|
244
|
-
if (!filesEmpty || !testsNotRun)
|
|
245
|
-
return null;
|
|
246
|
-
return makeEnforcementResult({
|
|
247
|
-
action: "reject",
|
|
248
|
-
reason: "Agent claims success but execution_evidence shows no files changed " +
|
|
249
|
-
"and no tests were run. There is no verifiable evidence of work.",
|
|
250
|
-
fix_instructions: "You must provide verifiable evidence: " +
|
|
251
|
-
"(a) list the files you changed in execution_evidence.files_changed, " +
|
|
252
|
-
"and (b) run tests and report results in execution_evidence.test_results. " +
|
|
253
|
-
"If you genuinely completed the task without file changes or tests, " +
|
|
254
|
-
"explain why in detail in your output_summary.",
|
|
255
|
-
check: "empty_success",
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
/** R4: Progress has stalled for 3+ consecutive rounds.
|
|
259
|
-
* Detected by checking progress_estimate deltas across vault entries.
|
|
260
|
-
* First occurrence → REJECT (agent must change approach).
|
|
261
|
-
* Second consecutive occurrence → TERMINATE (agent cannot recover). */
|
|
262
|
-
function enforceProgressStall(_selfEval, _flags, currentRound, vaultEntries, consecutiveRejections, skipEvidenceRules = false) {
|
|
263
|
-
if (skipEvidenceRules)
|
|
264
|
-
return null; // Requires execution_evidence
|
|
265
|
-
// Need at least 3 rounds of history
|
|
266
|
-
if (currentRound < 3)
|
|
267
|
-
return null;
|
|
268
|
-
const progressByRound = collectProgressByRound(vaultEntries, currentRound);
|
|
269
|
-
if (progressByRound.size < 3)
|
|
270
|
-
return null;
|
|
271
|
-
const sortedRounds = [...progressByRound.keys()].sort((a, b) => a - b);
|
|
272
|
-
const last3 = sortedRounds.slice(-3);
|
|
273
|
-
// Require all three data points to be within [currentRound-3, currentRound-1].
|
|
274
|
-
const expectedMin = currentRound - 3;
|
|
275
|
-
if (last3[0] < expectedMin || last3[2] > currentRound - 1)
|
|
276
|
-
return null;
|
|
277
|
-
const p1 = progressByRound.get(last3[0]);
|
|
278
|
-
const p2 = progressByRound.get(last3[1]);
|
|
279
|
-
const p3 = progressByRound.get(last3[2]);
|
|
280
|
-
const stallThreshold = getPolicy().evolution.progress_stall_threshold;
|
|
281
|
-
const delta12 = p2 - p1;
|
|
282
|
-
const delta23 = p3 - p2;
|
|
283
|
-
// Both deltas must be below threshold AND not near completion
|
|
284
|
-
const isStalling = delta12 < stallThreshold &&
|
|
285
|
-
delta23 < stallThreshold &&
|
|
286
|
-
p3 < 0.95;
|
|
287
|
-
if (!isStalling)
|
|
288
|
-
return null;
|
|
289
|
-
const escalation = getPolicy().engine.enforcement_escalation_enabled;
|
|
290
|
-
// v2.7: When escalation is enabled, give one extra rejection round with a
|
|
291
|
-
// "Seek Human Guidance" notice before terminating. This lets the agent
|
|
292
|
-
// course-correct with human input rather than being killed immediately.
|
|
293
|
-
if (consecutiveRejections >= 2) {
|
|
294
|
-
return makeEnforcementResult({
|
|
295
|
-
action: "terminate",
|
|
296
|
-
reason: `Progress stalled for 3+ rounds ` +
|
|
297
|
-
`(${(p1 * 100).toFixed(0)}% → ${(p2 * 100).toFixed(0)}% → ${(p3 * 100).toFixed(0)}%) ` +
|
|
298
|
-
`and agent did not resolve after escalation. Terminating loop.`,
|
|
299
|
-
check: "progress_stall",
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
if (consecutiveRejections >= 1 && escalation) {
|
|
303
|
-
// v2.10: When backtrack is enabled, escalate to backtrack instead of
|
|
304
|
-
// reject. The agent is rolled back to the last clean round with lessons
|
|
305
|
-
// injected, rather than redoing the same round with an escalation notice.
|
|
306
|
-
if (getPolicy().engine.backtrack_enabled) {
|
|
307
|
-
return makeEnforcementResult({
|
|
308
|
-
action: "backtrack",
|
|
309
|
-
reason: `Progress has stalled: ${(p1 * 100).toFixed(0)}% → ` +
|
|
310
|
-
`${(p2 * 100).toFixed(0)}% → ${(p3 * 100).toFixed(0)}% over the ` +
|
|
311
|
-
`last 3 rounds (delta < ${(stallThreshold * 100).toFixed(0)}% each round).`,
|
|
312
|
-
fix_instructions: "Your progress has been flat for 3 rounds. You are being rolled back " +
|
|
313
|
-
"to the last clean round. Do NOT repeat the approach that led to the stall. " +
|
|
314
|
-
"Choose a different technique or task decomposition.",
|
|
315
|
-
check: "progress_stall",
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
return makeEnforcementResult({
|
|
319
|
-
action: "reject",
|
|
320
|
-
reason: `Progress has stalled: ${(p1 * 100).toFixed(0)}% → ` +
|
|
321
|
-
`${(p2 * 100).toFixed(0)}% → ${(p3 * 100).toFixed(0)}% over the ` +
|
|
322
|
-
`last 3 rounds (delta < ${(stallThreshold * 100).toFixed(0)}% each round).`,
|
|
323
|
-
fix_instructions: "Your progress has been flat for 3 rounds. You must: " +
|
|
324
|
-
"(a) explain what is blocking progress, " +
|
|
325
|
-
"(b) propose a DIFFERENT technique or task decomposition, and " +
|
|
326
|
-
"(c) set a concrete, verifiable goal for the redo of this round. " +
|
|
327
|
-
"Do NOT repeat the same approach — it has not moved progress forward." +
|
|
328
|
-
buildEscalationNotice(),
|
|
329
|
-
check: "progress_stall",
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
if (consecutiveRejections >= 1) {
|
|
333
|
-
return makeEnforcementResult({
|
|
334
|
-
action: "terminate",
|
|
335
|
-
reason: `Progress stalled for 3+ rounds ` +
|
|
336
|
-
`(${(p1 * 100).toFixed(0)}% → ${(p2 * 100).toFixed(0)}% → ${(p3 * 100).toFixed(0)}%) ` +
|
|
337
|
-
`and agent did not resolve after previous rejection. Terminating loop.`,
|
|
338
|
-
check: "progress_stall",
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
return makeEnforcementResult({
|
|
342
|
-
action: "reject",
|
|
343
|
-
reason: `Progress has stalled: ${(p1 * 100).toFixed(0)}% → ` +
|
|
344
|
-
`${(p2 * 100).toFixed(0)}% → ${(p3 * 100).toFixed(0)}% over the ` +
|
|
345
|
-
`last 3 rounds (delta < ${(stallThreshold * 100).toFixed(0)}% each round).`,
|
|
346
|
-
fix_instructions: "Your progress has been flat for 3 rounds. You must: " +
|
|
347
|
-
"(a) explain what is blocking progress, " +
|
|
348
|
-
"(b) propose a DIFFERENT technique or task decomposition, and " +
|
|
349
|
-
"(c) set a concrete, verifiable goal for the redo of this round. " +
|
|
350
|
-
"Do NOT repeat the same approach — it has not moved progress forward.",
|
|
351
|
-
check: "progress_stall",
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
/** R5: Progress completely stalled (delta = 0) for N consecutive rounds.
|
|
355
|
-
* Stricter than R4 (which triggers on delta < 5%). R4 gives the agent a chance
|
|
356
|
-
* to change approach via reject; this rule fires when progress has been exactly
|
|
357
|
-
* flat — the agent is making zero forward motion.
|
|
358
|
-
*
|
|
359
|
-
* v2.7: When enforcement_escalation_enabled is true, the first occurrence
|
|
360
|
-
* issues a rejection with an escalation notice instead of terminating.
|
|
361
|
-
* Termination only happens on the second consecutive flatline detection.
|
|
362
|
-
*
|
|
363
|
-
* Uses the engine.max_circuit_breaker policy value as the lookback window. */
|
|
364
|
-
function enforceProgressStallTerminal(_selfEval, _flags, currentRound, vaultEntries, consecutiveRejections, skipEvidenceRules = false) {
|
|
365
|
-
if (skipEvidenceRules)
|
|
366
|
-
return null; // Requires execution_evidence
|
|
367
|
-
const breakerSize = getPolicy().engine.max_circuit_breaker;
|
|
368
|
-
if (currentRound < breakerSize || vaultEntries.length < breakerSize)
|
|
369
|
-
return null;
|
|
370
|
-
const progressByRound = collectProgressByRound(vaultEntries, currentRound);
|
|
371
|
-
if (progressByRound.size < breakerSize)
|
|
372
|
-
return null;
|
|
373
|
-
const sortedRounds = [...progressByRound.keys()].sort((a, b) => a - b);
|
|
374
|
-
const recent = sortedRounds.slice(-breakerSize);
|
|
375
|
-
// Check that progress values are ALL identical (delta = 0).
|
|
376
|
-
// Use epsilon comparison — JSON round-trips can produce float drift
|
|
377
|
-
// (e.g. 0.3 vs 0.30000000000000004) that would defeat strict === .
|
|
378
|
-
const EPSILON = 1e-10;
|
|
379
|
-
const first = progressByRound.get(recent[0]);
|
|
380
|
-
const allFlat = recent.every((r) => Math.abs((progressByRound.get(r) ?? 0) - first) < EPSILON);
|
|
381
|
-
if (!allFlat || first >= 0.95)
|
|
382
|
-
return null;
|
|
383
|
-
const escalation = getPolicy().engine.enforcement_escalation_enabled;
|
|
384
|
-
// v2.7: When escalation is enabled, give one rejection round with a
|
|
385
|
-
// "Seek Human Guidance" notice before terminating.
|
|
386
|
-
if (consecutiveRejections >= 1) {
|
|
387
|
-
return makeEnforcementResult({
|
|
388
|
-
action: "terminate",
|
|
389
|
-
reason: `Progress has been completely flat at ${(first * 100).toFixed(0)}% ` +
|
|
390
|
-
`for ${breakerSize} consecutive rounds and agent did not resolve ` +
|
|
391
|
-
`after escalation. The agent is making zero forward motion.`,
|
|
392
|
-
check: "progress_stall_terminal",
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
if (escalation) {
|
|
396
|
-
// v2.10: When backtrack is enabled, escalate to backtrack instead of
|
|
397
|
-
// reject. The agent is rolled back to the last clean round with a
|
|
398
|
-
// "radically different strategy" lesson injected.
|
|
399
|
-
if (getPolicy().engine.backtrack_enabled) {
|
|
400
|
-
return makeEnforcementResult({
|
|
401
|
-
action: "backtrack",
|
|
402
|
-
reason: `Progress has been completely flat at ${(first * 100).toFixed(0)}% ` +
|
|
403
|
-
`for ${breakerSize} consecutive rounds. The agent is making zero ` +
|
|
404
|
-
`forward motion.`,
|
|
405
|
-
fix_instructions: "Your progress has been exactly flat — zero forward motion. " +
|
|
406
|
-
"You are being rolled back to the last clean round. " +
|
|
407
|
-
"Choose a RADICALLY different approach. The previous one produced nothing.",
|
|
408
|
-
check: "progress_stall_terminal",
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
return makeEnforcementResult({
|
|
412
|
-
action: "reject",
|
|
413
|
-
reason: `Progress has been completely flat at ${(first * 100).toFixed(0)}% ` +
|
|
414
|
-
`for ${breakerSize} consecutive rounds. The agent is making zero ` +
|
|
415
|
-
`forward motion.`,
|
|
416
|
-
fix_instructions: "Your progress has been exactly flat — you are making zero forward " +
|
|
417
|
-
"motion. You must: (a) explain what is fundamentally blocking you, " +
|
|
418
|
-
"(b) propose a radically different approach, and " +
|
|
419
|
-
"(c) set a concrete verifiable goal. " +
|
|
420
|
-
"Do NOT resubmit the same output." +
|
|
421
|
-
buildEscalationNotice(),
|
|
422
|
-
check: "progress_stall_terminal",
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
return makeEnforcementResult({
|
|
426
|
-
action: "terminate",
|
|
427
|
-
reason: `Progress has been completely flat at ${(first * 100).toFixed(0)}% ` +
|
|
428
|
-
`for ${breakerSize} consecutive rounds. The agent is making zero ` +
|
|
429
|
-
`forward motion and cannot recover.`,
|
|
430
|
-
check: "progress_stall_terminal",
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
/** R6: Two consecutive rejections → escalate (v2.7) or terminate.
|
|
434
|
-
*
|
|
435
|
-
* v2.7: When enforcement_escalation_enabled is true, the second consecutive
|
|
436
|
-
* rejection issues an escalated rejection with a "Seek Human Guidance" notice
|
|
437
|
-
* instead of terminating immediately. Termination occurs on the third
|
|
438
|
-
* consecutive rejection. When the flag is disabled, behavior is unchanged
|
|
439
|
-
* (terminate at ≥2 rejections). */
|
|
440
|
-
function enforceMaxRejections(consecutiveRejections) {
|
|
441
|
-
const escalation = getPolicy().engine.enforcement_escalation_enabled;
|
|
442
|
-
if (escalation) {
|
|
443
|
-
if (consecutiveRejections >= 3) {
|
|
444
|
-
return makeEnforcementResult({
|
|
445
|
-
action: "terminate",
|
|
446
|
-
reason: `${consecutiveRejections} consecutive enforcement rejections for the ` +
|
|
447
|
-
`same issue without resolution. The agent has been unable to correct ` +
|
|
448
|
-
`the identified issue even after escalation. Terminating loop.`,
|
|
449
|
-
check: "max_rejections",
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
if (consecutiveRejections >= 2) {
|
|
453
|
-
return makeEnforcementResult({
|
|
454
|
-
action: "reject",
|
|
455
|
-
reason: `${consecutiveRejections} consecutive enforcement rejections for the ` +
|
|
456
|
-
`same issue without resolution.`,
|
|
457
|
-
fix_instructions: "You have been rejected multiple times for the same issue. " +
|
|
458
|
-
"You must address ALL items in the Evidence Gap section above. " +
|
|
459
|
-
"If you do not understand why you are being rejected, explain " +
|
|
460
|
-
"your confusion explicitly and ask for clarification." +
|
|
461
|
-
buildEscalationNotice(),
|
|
462
|
-
check: "max_rejections",
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
return null;
|
|
466
|
-
}
|
|
467
|
-
if (consecutiveRejections < 2)
|
|
468
|
-
return null;
|
|
469
|
-
return makeEnforcementResult({
|
|
470
|
-
action: "terminate",
|
|
471
|
-
reason: `${consecutiveRejections} consecutive enforcement rejections for the ` +
|
|
472
|
-
`same issue without resolution. The agent has been unable to correct the identified issue.`,
|
|
473
|
-
check: "max_rejections",
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
/** v2.12: Check if a drift_clarification contains at least one semantic
|
|
477
|
-
* anchor — a constraint/criterion/sub-goal ID (v2.11) or a file-path-like
|
|
478
|
-
* reference. This distinguishes genuine explanations from filler text that
|
|
479
|
-
* just happens to be ≥ 20 characters. */
|
|
480
|
-
function hasSemanticAnchor(clarification) {
|
|
481
|
-
// v2.11: Constraint / criterion / sub-goal ID patterns
|
|
482
|
-
if (/[c]r?-[a-f0-9]{8}/.test(clarification))
|
|
483
|
-
return true;
|
|
484
|
-
if (/sg-[a-f0-9]{8}/.test(clarification))
|
|
485
|
-
return true;
|
|
486
|
-
// File path references (e.g. "src/auth/login.ts", "lib/utils.go")
|
|
487
|
-
if (/[\w./-]+\.[a-z]{2,6}\b/i.test(clarification))
|
|
488
|
-
return true;
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
/** Build a stronger rejection notice as the clarification streak grows. */
|
|
492
|
-
function buildClarificationRejectionNotice(streak) {
|
|
493
|
-
const base = "Your declared next_action from the previous round does not match " +
|
|
494
|
-
"what you actually did this round. You must either: " +
|
|
495
|
-
"(a) explain why you pivoted in the drift_clarification field, " +
|
|
496
|
-
"mentioning specific constraint/criterion/sub-goal IDs or file paths " +
|
|
497
|
-
"that prompted the change, or " +
|
|
498
|
-
"(b) redo this round and do what you said you would do.";
|
|
499
|
-
if (streak >= 2) {
|
|
500
|
-
return (base +
|
|
501
|
-
"\n\n### ⚠️ Repeated Weak Clarification\n\n" +
|
|
502
|
-
`You have submitted drift_clarification without concrete references ` +
|
|
503
|
-
`for ${streak} consecutive rounds. Your explanation MUST include at ` +
|
|
504
|
-
`least one of: a constraint ID (c-XXXXXXXX), criterion ID (cr-XXXXXXXX), ` +
|
|
505
|
-
`sub-goal ID (sg-XXXXXXXX), or a file path you changed.\n\n` +
|
|
506
|
-
`🚨 If the next clarification also lacks concrete anchors, the loop ` +
|
|
507
|
-
`will be terminated.`);
|
|
508
|
-
}
|
|
509
|
-
return base;
|
|
510
|
-
}
|
|
511
|
-
/** R7: Agent declared a next_action but did something unrelated.
|
|
512
|
-
*
|
|
513
|
-
* v2.8: When the agent provides a substantive drift_clarification (≥ 20
|
|
514
|
-
* characters), the rejection is waived — the agent has acknowledged and
|
|
515
|
-
* explained the pivot. An empty or absent clarification still triggers
|
|
516
|
-
* the normal reject/terminate path.
|
|
517
|
-
*
|
|
518
|
-
* v2.12: Clarification now requires a semantic anchor (constraint/criterion/
|
|
519
|
-
* sub-goal ID or file path) in addition to the ≥ 20 char minimum. Weak
|
|
520
|
-
* clarifications (no anchors) increment a streak counter; 3 consecutive
|
|
521
|
-
* weak clarifications terminate the loop.
|
|
522
|
-
*
|
|
523
|
-
* First occurrence → reject (agent must explain the gap or realign).
|
|
524
|
-
* Second consecutive → terminate (agent cannot/will not follow its own plan). */
|
|
525
|
-
function enforceIntentDrift(flags, selfEval, consecutiveRejections, driftClarificationStreak = 0) {
|
|
526
|
-
const flag = flags.find((f) => f.check === "intent_drift");
|
|
527
|
-
if (!flag)
|
|
528
|
-
return null;
|
|
529
|
-
const clarification = selfEval.drift_clarification?.trim();
|
|
530
|
-
// v2.12: Semantic anchor detection — requires concrete references
|
|
531
|
-
const hasAnchor = clarification ? hasSemanticAnchor(clarification) : false;
|
|
532
|
-
// v2.12: Substantive clarification = length check + semantic anchor
|
|
533
|
-
const isSubstantive = clarification
|
|
534
|
-
&& clarification.length >= 20
|
|
535
|
-
&& hasAnchor;
|
|
536
|
-
if (isSubstantive) {
|
|
537
|
-
// Genuine explanation with concrete references → accept the pivot
|
|
538
|
-
return makeEnforcementResult({
|
|
539
|
-
action: "accept",
|
|
540
|
-
reason: "",
|
|
541
|
-
fix_instructions: "",
|
|
542
|
-
check: "intent_drift",
|
|
543
|
-
clarification_accepted: true,
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
// v2.12: Clarification present but weak (no semantic anchors)
|
|
547
|
-
if (clarification && clarification.length >= 20 && !hasAnchor) {
|
|
548
|
-
const maxStreak = getPolicy().engine.drift_clarification_max_streak;
|
|
549
|
-
// When maxStreak is 0, disable the check (pre-v2.12 behavior)
|
|
550
|
-
if (maxStreak <= 0) {
|
|
551
|
-
return makeEnforcementResult({
|
|
552
|
-
action: "accept",
|
|
553
|
-
reason: "",
|
|
554
|
-
fix_instructions: "",
|
|
555
|
-
check: "intent_drift",
|
|
556
|
-
clarification_accepted: true,
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
const newStreak = driftClarificationStreak + 1;
|
|
560
|
-
if (newStreak >= maxStreak) {
|
|
561
|
-
return makeEnforcementResult({
|
|
562
|
-
action: "terminate",
|
|
563
|
-
reason: `Agent has submitted weak drift_clarification (no concrete IDs or ` +
|
|
564
|
-
`file references) for ${newStreak} consecutive rounds. The agent ` +
|
|
565
|
-
`systematically avoids following its own plan without substantive ` +
|
|
566
|
-
`explanation.`,
|
|
567
|
-
check: "intent_drift",
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
return makeEnforcementResult({
|
|
571
|
-
action: "reject",
|
|
572
|
-
reason: flag.detail,
|
|
573
|
-
fix_instructions: buildClarificationRejectionNotice(newStreak),
|
|
574
|
-
check: "intent_drift",
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
// No clarification or too short — original behavior
|
|
578
|
-
if (consecutiveRejections >= 1) {
|
|
579
|
-
return makeEnforcementResult({
|
|
580
|
-
action: "terminate",
|
|
581
|
-
reason: `Agent was warned about intent-action drift but continues to do ` +
|
|
582
|
-
`work unrelated to its declared next_action without explanation. ` +
|
|
583
|
-
`The agent cannot or will not follow its own plan — terminating loop.`,
|
|
584
|
-
check: "intent_drift",
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
return makeEnforcementResult({
|
|
588
|
-
action: "reject",
|
|
589
|
-
reason: flag.detail,
|
|
590
|
-
fix_instructions: "Your declared next_action from the previous round does not match " +
|
|
591
|
-
"what you actually did this round. You must either: " +
|
|
592
|
-
"(a) explain why you pivoted and what you learned in the " +
|
|
593
|
-
"drift_clarification field, or " +
|
|
594
|
-
"(b) redo this round and do what you said you would do. " +
|
|
595
|
-
"If you intentionally changed direction, update your objective_refinement " +
|
|
596
|
-
"and set a new next_action that reflects the new plan.",
|
|
597
|
-
check: "intent_drift",
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
601
|
-
// Escalation notice builder (v2.7)
|
|
602
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
603
|
-
/** Build an escalation notice urging the agent to seek human guidance.
|
|
604
|
-
*
|
|
605
|
-
* Appended to fix_instructions when a rule would otherwise terminate.
|
|
606
|
-
* Gives the agent one final chance to correct course with human input
|
|
607
|
-
* before the loop is irreversibly stopped. */
|
|
608
|
-
function buildEscalationNotice() {
|
|
609
|
-
return [
|
|
610
|
-
"",
|
|
611
|
-
"### 🆘 Escalation — Seek Human Guidance",
|
|
612
|
-
"",
|
|
613
|
-
"You have been rejected multiple times for the same issue. " +
|
|
614
|
-
"Before retrying, pause and ask a human for help:",
|
|
615
|
-
"",
|
|
616
|
-
"1. Explain what you've tried so far and why each approach failed.",
|
|
617
|
-
"2. Ask the human for specific guidance on what to try next.",
|
|
618
|
-
"3. Incorporate their feedback before re-submitting your self-evaluation.",
|
|
619
|
-
"",
|
|
620
|
-
"⚠️ If this round is rejected again, the loop will be **terminated**. " +
|
|
621
|
-
"Do NOT resubmit without meaningful changes.",
|
|
622
|
-
].join("\n");
|
|
623
|
-
}
|
|
624
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
625
|
-
// Diagnostic gap builder (v2.7)
|
|
626
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
627
|
-
/** Build a diagnostic gap section from verification flags.
|
|
628
|
-
*
|
|
629
|
-
* Translates each error/warn-level VerificationFlag into a concrete
|
|
630
|
-
* claim-vs-evidence mismatch statement. Info-level flags are excluded
|
|
631
|
-
* because they are purely informational and do not represent gaps.
|
|
632
|
-
*
|
|
633
|
-
* Returns an empty string when there are no diagnostic flags to report. */
|
|
634
|
-
function buildDiagnosticGap(flags) {
|
|
635
|
-
const diagnosticFlags = flags.filter((f) => f.severity === "error" || f.severity === "warn");
|
|
636
|
-
if (diagnosticFlags.length === 0)
|
|
637
|
-
return "";
|
|
638
|
-
const lines = [
|
|
639
|
-
"### Evidence Gap",
|
|
640
|
-
"",
|
|
641
|
-
"The enforcement gate detected mismatches between your claims and the evidence. " +
|
|
642
|
-
"Each item below shows a specific discrepancy — address every one before re-submitting.",
|
|
643
|
-
"",
|
|
644
|
-
];
|
|
645
|
-
for (const flag of diagnosticFlags) {
|
|
646
|
-
const icon = flag.severity === "error" ? "🚫" : "⚠️";
|
|
647
|
-
lines.push(`- ${icon} **[${flag.check}]** \`${flag.field}\` — ${flag.detail}`);
|
|
648
|
-
}
|
|
649
|
-
lines.push("");
|
|
650
|
-
return lines.join("\n");
|
|
651
|
-
}
|
|
652
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
653
|
-
// Main entry point
|
|
654
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
655
|
-
/** Enforce round-boundary rules based on the verification gate's findings
|
|
656
|
-
* and the agent's self-evaluation integrity.
|
|
657
|
-
*
|
|
658
|
-
* Rules run in priority order (R1 → R5). The first rule that fires wins.
|
|
659
|
-
*
|
|
660
|
-
* @param selfEval The agent's self-evaluation for the current round.
|
|
661
|
-
* @param verifyResult The verification gate's output (from verifySelfEvaluation).
|
|
662
|
-
* @param currentRound Current round number (1-based, BEFORE increment).
|
|
663
|
-
* @param vaultEntries Vault entries for this loop (used for progress tracking).
|
|
664
|
-
* @param consecutiveRejections How many consecutive rounds have already been rejected.
|
|
665
|
-
* Starts at 0; increments on each reject; resets on accept.
|
|
666
|
-
*/
|
|
667
|
-
export function enforceRound(selfEval, verifyResult, currentRound, vaultEntries, consecutiveRejections = 0, skipEvidenceRules = false,
|
|
668
|
-
/** v2.12: Current clarification streak for R7 escalation. */
|
|
669
|
-
driftClarificationStreak = 0) {
|
|
670
|
-
const { flags } = verifyResult;
|
|
671
|
-
// Run enforcement rules in priority order.
|
|
672
|
-
// Earlier rules take higher precedence.
|
|
673
|
-
const rules = [
|
|
674
|
-
() => enforceSuccessWithRemainingCriteria(flags),
|
|
675
|
-
() => enforceRecurringViolation(flags),
|
|
676
|
-
() => enforceEmptySuccess(selfEval, flags, skipEvidenceRules),
|
|
677
|
-
() => enforceProgressStall(selfEval, flags, currentRound, vaultEntries, consecutiveRejections, skipEvidenceRules),
|
|
678
|
-
() => enforceProgressStallTerminal(selfEval, flags, currentRound, vaultEntries, consecutiveRejections, skipEvidenceRules),
|
|
679
|
-
() => enforceMaxRejections(consecutiveRejections),
|
|
680
|
-
() => enforceIntentDrift(flags, selfEval, consecutiveRejections, driftClarificationStreak),
|
|
681
|
-
];
|
|
682
|
-
for (const rule of rules) {
|
|
683
|
-
const result = rule();
|
|
684
|
-
if (result)
|
|
685
|
-
return result;
|
|
686
|
-
}
|
|
687
|
-
// All rules passed — accept the round
|
|
688
|
-
return makeEnforcementResult({
|
|
689
|
-
action: "accept",
|
|
690
|
-
reason: "",
|
|
691
|
-
fix_instructions: "",
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
/** Build a rejection prompt for the agent.
|
|
695
|
-
*
|
|
696
|
-
* The prompt clearly states the round was rejected, why, what the agent
|
|
697
|
-
* must fix, and that the agent must redo the SAME round (not advance).
|
|
698
|
-
*
|
|
699
|
-
* v2.7: Accepts verificationFlags to render a diagnostic "Evidence Gap"
|
|
700
|
-
* section. Each error/warn flag becomes a concrete claim-vs-evidence
|
|
701
|
-
* mismatch statement so the agent knows exactly what to correct rather
|
|
702
|
-
* than retrying blindly.
|
|
703
|
-
*
|
|
704
|
-
* @param currentRound The round number that was rejected (NOT incremented).
|
|
705
|
-
* @param task The original loop task description.
|
|
706
|
-
* @param enforceResult The enforcement decision with reason and fix instructions.
|
|
707
|
-
* @param verificationFlags The verification gate's findings for this round.
|
|
708
|
-
* Used to build the Evidence Gap section. */
|
|
709
|
-
export function buildRejectionPrompt(currentRound, task, enforceResult, verificationFlags = []) {
|
|
710
|
-
const lines = [
|
|
711
|
-
"## ⛔ Round " + currentRound + " — REJECTED",
|
|
712
|
-
"",
|
|
713
|
-
"Your self-evaluation for Round " + currentRound +
|
|
714
|
-
" was **rejected** by the enforcement gate.",
|
|
715
|
-
"",
|
|
716
|
-
"### Reason",
|
|
717
|
-
"",
|
|
718
|
-
enforceResult.reason,
|
|
719
|
-
"",
|
|
720
|
-
];
|
|
721
|
-
// v2.7: Diagnostic gap — show the agent exactly what doesn't match.
|
|
722
|
-
const gap = buildDiagnosticGap(verificationFlags);
|
|
723
|
-
if (gap) {
|
|
724
|
-
lines.push(gap);
|
|
725
|
-
}
|
|
726
|
-
if (enforceResult.fix_instructions) {
|
|
727
|
-
lines.push("### Required Fix");
|
|
728
|
-
lines.push("");
|
|
729
|
-
lines.push(enforceResult.fix_instructions);
|
|
730
|
-
lines.push("");
|
|
731
|
-
}
|
|
732
|
-
lines.push("### Your Task (Round " + currentRound + " — Retry)");
|
|
733
|
-
lines.push("");
|
|
734
|
-
lines.push(task);
|
|
735
|
-
lines.push("");
|
|
736
|
-
lines.push("### Instructions");
|
|
737
|
-
lines.push("");
|
|
738
|
-
lines.push("1. Read and address each issue in **Required Fix** above.", "2. Re-execute **Round " + currentRound +
|
|
739
|
-
"** — do NOT advance to the next round.", "3. Submit a corrected self-evaluation via `loopforge_next`.", "4. Be honest in your self-evaluation — " +
|
|
740
|
-
"claiming success when criteria are unmet will be rejected again.", "5. If you believe this rejection is incorrect, " +
|
|
741
|
-
"explain why in your output_summary and the enforcement gate will re-evaluate.");
|
|
742
|
-
return lines.join("\n");
|
|
743
|
-
}
|
|
744
|
-
//# sourceMappingURL=enforcement-gate.js.map
|