loopforge 1.15.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -95
- package/dist/backends/interface.d.ts +5 -10
- package/dist/backends/interface.d.ts.map +1 -1
- package/dist/backends/interface.js +3 -4
- package/dist/backends/interface.js.map +1 -1
- package/dist/canonical-state.d.ts +53 -0
- package/dist/canonical-state.d.ts.map +1 -0
- package/dist/canonical-state.js +161 -0
- package/dist/canonical-state.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +227 -0
- package/dist/cli.js.map +1 -0
- package/dist/enforcement-gate.d.ts.map +1 -1
- package/dist/enforcement-gate.js +28 -10
- package/dist/enforcement-gate.js.map +1 -1
- package/dist/engine.d.ts +9 -28
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +135 -361
- package/dist/engine.js.map +1 -1
- package/dist/evidence-provider.d.ts +146 -0
- package/dist/evidence-provider.d.ts.map +1 -0
- package/dist/evidence-provider.js +512 -0
- package/dist/evidence-provider.js.map +1 -0
- package/dist/index.d.ts +19 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -12
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +15 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +49 -0
- package/dist/init.js.map +1 -0
- package/dist/interop.d.ts +35 -0
- package/dist/interop.d.ts.map +1 -0
- package/dist/interop.js +34 -0
- package/dist/interop.js.map +1 -0
- package/dist/loop-compiler.d.ts +14 -64
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +305 -2206
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-extras-parser.d.ts +82 -0
- package/dist/loop-extras-parser.d.ts.map +1 -0
- package/dist/loop-extras-parser.js +217 -0
- package/dist/loop-extras-parser.js.map +1 -0
- package/dist/loop-store.d.ts +104 -0
- package/dist/loop-store.d.ts.map +1 -0
- package/dist/loop-store.js +421 -0
- package/dist/loop-store.js.map +1 -0
- package/dist/mcp/server.d.ts +9 -6
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +109 -73
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +102 -21
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +795 -400
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts +18 -268
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +226 -12
- package/dist/mcp/tools.js.map +1 -1
- package/dist/observability.d.ts +36 -16
- package/dist/observability.d.ts.map +1 -1
- package/dist/observability.js +82 -23
- package/dist/observability.js.map +1 -1
- package/dist/policy-metrics.d.ts +46 -0
- package/dist/policy-metrics.d.ts.map +1 -0
- package/dist/policy-metrics.js +146 -0
- package/dist/policy-metrics.js.map +1 -0
- package/dist/policy.d.ts +52 -147
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +118 -191
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts +30 -0
- package/dist/prompt-assembler.d.ts.map +1 -0
- package/dist/prompt-assembler.js +278 -0
- package/dist/prompt-assembler.js.map +1 -0
- package/dist/prompt-policy.d.ts +29 -0
- package/dist/prompt-policy.d.ts.map +1 -0
- package/dist/prompt-policy.js +51 -0
- package/dist/prompt-policy.js.map +1 -0
- package/dist/protocol.d.ts +75 -152
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +11 -53
- package/dist/protocol.js.map +1 -1
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +1 -27
- package/dist/replay.js.map +1 -1
- package/dist/round-coordinator.d.ts +90 -0
- package/dist/round-coordinator.d.ts.map +1 -0
- package/dist/round-coordinator.js +186 -0
- package/dist/round-coordinator.js.map +1 -0
- package/dist/round-driver.d.ts +53 -0
- package/dist/round-driver.d.ts.map +1 -0
- package/dist/round-driver.js +101 -0
- package/dist/round-driver.js.map +1 -0
- package/dist/round-transaction.d.ts +70 -0
- package/dist/round-transaction.d.ts.map +1 -0
- package/dist/round-transaction.js +290 -0
- package/dist/round-transaction.js.map +1 -0
- package/dist/self-eval.d.ts +26 -0
- package/dist/self-eval.d.ts.map +1 -0
- package/dist/self-eval.js +154 -0
- package/dist/self-eval.js.map +1 -0
- package/dist/storage.d.ts +39 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +193 -0
- package/dist/storage.js.map +1 -0
- package/dist/verification-gate.d.ts +31 -7
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +250 -7
- package/dist/verification-gate.js.map +1 -1
- package/loop_policy.json +19 -53
- package/package.json +13 -10
- package/skills/perception/SKILL.md +80 -267
- package/dist/backends/fs.d.ts +0 -36
- package/dist/backends/fs.d.ts.map +0 -1
- package/dist/backends/fs.js +0 -362
- package/dist/backends/fs.js.map +0 -1
- package/dist/builder.d.ts +0 -19
- package/dist/builder.d.ts.map +0 -1
- package/dist/builder.js +0 -215
- package/dist/builder.js.map +0 -1
- package/dist/mcp-server.d.ts +0 -12
- package/dist/mcp-server.d.ts.map +0 -1
- package/dist/mcp-server.js +0 -14
- package/dist/mcp-server.js.map +0 -1
- package/dist/memory-bridge.d.ts +0 -47
- package/dist/memory-bridge.d.ts.map +0 -1
- package/dist/memory-bridge.js +0 -290
- package/dist/memory-bridge.js.map +0 -1
- package/dist/runtime.d.ts +0 -80
- package/dist/runtime.d.ts.map +0 -1
- package/dist/runtime.js +0 -699
- package/dist/runtime.js.map +0 -1
- package/dist/tests/_helpers.d.ts +0 -25
- package/dist/tests/_helpers.d.ts.map +0 -1
- package/dist/tests/_helpers.js +0 -47
- package/dist/tests/_helpers.js.map +0 -1
- package/dist/tests/builder.test.d.ts +0 -2
- package/dist/tests/builder.test.d.ts.map +0 -1
- package/dist/tests/builder.test.js +0 -112
- package/dist/tests/builder.test.js.map +0 -1
- package/dist/tests/enforcement-gate.test.d.ts +0 -2
- package/dist/tests/enforcement-gate.test.d.ts.map +0 -1
- package/dist/tests/enforcement-gate.test.js +0 -328
- package/dist/tests/enforcement-gate.test.js.map +0 -1
- package/dist/tests/engine.test.d.ts +0 -2
- package/dist/tests/engine.test.d.ts.map +0 -1
- package/dist/tests/engine.test.js +0 -375
- package/dist/tests/engine.test.js.map +0 -1
- package/dist/tests/generate-schema.test.d.ts +0 -8
- package/dist/tests/generate-schema.test.d.ts.map +0 -1
- package/dist/tests/generate-schema.test.js +0 -199
- package/dist/tests/generate-schema.test.js.map +0 -1
- package/dist/tests/loop-compiler.test.d.ts +0 -2
- package/dist/tests/loop-compiler.test.d.ts.map +0 -1
- package/dist/tests/loop-compiler.test.js +0 -1059
- package/dist/tests/loop-compiler.test.js.map +0 -1
- package/dist/tests/mcp.test.d.ts +0 -7
- package/dist/tests/mcp.test.d.ts.map +0 -1
- package/dist/tests/mcp.test.js +0 -505
- package/dist/tests/mcp.test.js.map +0 -1
- package/dist/tests/memory-bridge.test.d.ts +0 -8
- package/dist/tests/memory-bridge.test.d.ts.map +0 -1
- package/dist/tests/memory-bridge.test.js +0 -383
- package/dist/tests/memory-bridge.test.js.map +0 -1
- package/dist/tests/policy.test.d.ts +0 -2
- package/dist/tests/policy.test.d.ts.map +0 -1
- package/dist/tests/policy.test.js +0 -49
- package/dist/tests/policy.test.js.map +0 -1
- package/dist/tests/protocol.test.d.ts +0 -2
- package/dist/tests/protocol.test.d.ts.map +0 -1
- package/dist/tests/protocol.test.js +0 -73
- package/dist/tests/protocol.test.js.map +0 -1
- package/dist/tests/replay.test.d.ts +0 -2
- package/dist/tests/replay.test.d.ts.map +0 -1
- package/dist/tests/replay.test.js +0 -222
- package/dist/tests/replay.test.js.map +0 -1
- package/dist/tests/runtime.test.d.ts +0 -7
- package/dist/tests/runtime.test.d.ts.map +0 -1
- package/dist/tests/runtime.test.js +0 -337
- package/dist/tests/runtime.test.js.map +0 -1
- package/dist/tests/verification-gate.test.d.ts +0 -2
- package/dist/tests/verification-gate.test.d.ts.map +0 -1
- package/dist/tests/verification-gate.test.js +0 -407
- package/dist/tests/verification-gate.test.js.map +0 -1
- package/skills/prompt-techniques/SKILL.md +0 -52
- package/skills/prompt-techniques/references/chain-of-thought.md +0 -207
- package/skills/prompt-techniques/references/few-shot.md +0 -174
- package/skills/prompt-techniques/references/least-to-most.md +0 -182
- package/skills/prompt-techniques/references/step-back.md +0 -160
- package/skills/prompt-techniques/references/tree-of-thought.md +0 -191
- package/skills/prompt-techniques/references/zero-shot.md +0 -97
package/dist/runtime.js
DELETED
|
@@ -1,699 +0,0 @@
|
|
|
1
|
-
/** LoopForge — Loop Runtime (v1.2).
|
|
2
|
-
*
|
|
3
|
-
* Event-driven loop driver that wraps the compilation engine with
|
|
4
|
-
* heartbeat monitoring, timeout/stall detection, and graceful shutdown.
|
|
5
|
-
*
|
|
6
|
-
* Primary API:
|
|
7
|
-
* import { run } from 'loopforge';
|
|
8
|
-
* const result = await run({ task: '...', execute: myAgent });
|
|
9
|
-
*
|
|
10
|
-
* Advanced API:
|
|
11
|
-
* const rt = new LoopRuntime(config);
|
|
12
|
-
* rt.on('round:complete', (info) => console.log(info));
|
|
13
|
-
* const result = await rt.start();
|
|
14
|
-
*/
|
|
15
|
-
import { EventEmitter } from "node:events";
|
|
16
|
-
import { writeFileSync, mkdirSync } from "node:fs";
|
|
17
|
-
import { join } from "node:path";
|
|
18
|
-
import { extractSelfEvaluation, heuristicSelfEvaluation, createEngine, } from "./engine.js";
|
|
19
|
-
import { tokenize, jaccard } from "./loop-compiler.js";
|
|
20
|
-
import { Mode, RuntimeStatus, makeLoopCompileRequest, makeLoopRoundResult, makeSelfEvaluation, makeVaultConfig, } from "./protocol.js";
|
|
21
|
-
import { getPolicy, resolveAllowedPhases, resolveInjectionPhase, buildAccumulatedMemoryContext, buildBaseMemoryWriteback } from "./policy.js";
|
|
22
|
-
import { verifySelfEvaluation } from "./verification-gate.js";
|
|
23
|
-
import { enforceRound, buildRejectionPrompt } from "./enforcement-gate.js";
|
|
24
|
-
import { logEvent } from "./observability.js";
|
|
25
|
-
import { tryAutoConfigure } from "./memory-bridge.js";
|
|
26
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
27
|
-
// Helpers
|
|
28
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
29
|
-
/** Build a writeback payload from the loop's final state.
|
|
30
|
-
* Uses shared base builder from policy.ts, then layers runtime-specific
|
|
31
|
-
* feedback entries from trajectory analysis on top. */
|
|
32
|
-
function buildWritebackPayload(loopId, task, stopReason, result, discoveries = []) {
|
|
33
|
-
if (result.roundsCompleted < 1)
|
|
34
|
-
return null;
|
|
35
|
-
const wp = getPolicy().memory_writeback;
|
|
36
|
-
const base = buildBaseMemoryWriteback({
|
|
37
|
-
loopId, task, stopReason, roundsCompleted: result.roundsCompleted, discoveries,
|
|
38
|
-
});
|
|
39
|
-
// Build feedback entries from success trajectory patterns (runtime-specific)
|
|
40
|
-
const feedbackEntries = [];
|
|
41
|
-
const trajectory = result.successTrajectory;
|
|
42
|
-
if (trajectory.length >= 3) {
|
|
43
|
-
const firstHalf = trajectory.slice(0, Math.floor(trajectory.length / 2));
|
|
44
|
-
const secondHalf = trajectory.slice(Math.floor(trajectory.length / 2));
|
|
45
|
-
const firstRate = firstHalf.filter(Boolean).length / firstHalf.length;
|
|
46
|
-
const secondRate = secondHalf.filter(Boolean).length / secondHalf.length;
|
|
47
|
-
if (secondRate - firstRate > 0.3) {
|
|
48
|
-
feedbackEntries.push({
|
|
49
|
-
rule: `Success rate improved from ${(firstRate * 100).toFixed(0)}% to ${(secondRate * 100).toFixed(0)}% over ${trajectory.length} rounds`,
|
|
50
|
-
why: `Mid-loop strategy adjustment unlocked progress in task "${task.slice(0, 80)}"`,
|
|
51
|
-
howToApply: "Consider the strategy used in later rounds for similar tasks",
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
if (trajectory.length >= 3) {
|
|
56
|
-
const recent = trajectory.slice(-3);
|
|
57
|
-
if (recent.every((v) => v === false)) {
|
|
58
|
-
feedbackEntries.push({
|
|
59
|
-
rule: `Failed 3 consecutive rounds at end of loop`,
|
|
60
|
-
why: `Task "${task.slice(0, 80)}" stopped with reason '${stopReason}' — strategy may have stagnated`,
|
|
61
|
-
howToApply: "If re-attempting, start with a different technique than the one that produced the failures",
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
...base,
|
|
67
|
-
feedbackEntries: feedbackEntries.slice(0, wp.max_feedback_entries),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
function autoLoopId(task) {
|
|
71
|
-
let slug = task.toLowerCase().trim().slice(0, 60);
|
|
72
|
-
slug = slug.replace(/[^a-z0-9\s-]/g, "");
|
|
73
|
-
slug = slug.replace(/\s+/g, "-");
|
|
74
|
-
return slug || "unnamed-loop";
|
|
75
|
-
}
|
|
76
|
-
function resolveConfig(raw) {
|
|
77
|
-
const policy = getPolicy();
|
|
78
|
-
const rtPolicy = policy.runtime;
|
|
79
|
-
const config = {
|
|
80
|
-
task: raw.task,
|
|
81
|
-
execute: raw.execute,
|
|
82
|
-
loopId: raw.loopId || autoLoopId(raw.task),
|
|
83
|
-
goalId: raw.goalId || "",
|
|
84
|
-
maxRounds: raw.maxRounds ?? rtPolicy.max_rounds,
|
|
85
|
-
roundTimeoutMs: raw.roundTimeoutMs ?? rtPolicy.round_timeout_ms,
|
|
86
|
-
heartbeatIntervalMs: raw.heartbeatIntervalMs ?? rtPolicy.heartbeat_interval_ms,
|
|
87
|
-
stallGraceMs: raw.stallGraceMs ?? rtPolicy.stall_grace_ms,
|
|
88
|
-
maxConsecutiveErrors: raw.maxConsecutiveErrors ?? rtPolicy.max_consecutive_errors,
|
|
89
|
-
interactive: raw.interactive ?? false,
|
|
90
|
-
healthCheckInterval: raw.healthCheckInterval ?? policy.summary.health_check_interval,
|
|
91
|
-
planSource: raw.planSource ?? undefined,
|
|
92
|
-
constraintsFromPlan: raw.constraintsFromPlan ?? [],
|
|
93
|
-
domain: raw.domain ?? "",
|
|
94
|
-
onRoundStart: raw.onRoundStart,
|
|
95
|
-
onRoundComplete: raw.onRoundComplete,
|
|
96
|
-
onHeartbeat: raw.onHeartbeat,
|
|
97
|
-
onTimeout: raw.onTimeout,
|
|
98
|
-
onHealthWarning: raw.onHealthWarning,
|
|
99
|
-
memoryProvider: raw.memoryProvider,
|
|
100
|
-
memoryWriter: raw.memoryWriter,
|
|
101
|
-
};
|
|
102
|
-
// Auto-detect claude-mem if no explicit provider was given
|
|
103
|
-
if (!config.memoryProvider && !config.memoryWriter) {
|
|
104
|
-
const auto = tryAutoConfigure();
|
|
105
|
-
if (auto.memoryProvider)
|
|
106
|
-
config.memoryProvider = auto.memoryProvider;
|
|
107
|
-
if (auto.memoryWriter)
|
|
108
|
-
config.memoryWriter = auto.memoryWriter;
|
|
109
|
-
}
|
|
110
|
-
return config;
|
|
111
|
-
}
|
|
112
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
113
|
-
// LoopRuntime
|
|
114
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
115
|
-
export class LoopRuntime extends EventEmitter {
|
|
116
|
-
config;
|
|
117
|
-
engine;
|
|
118
|
-
_status = RuntimeStatus.IDLE;
|
|
119
|
-
currentRound = 1;
|
|
120
|
-
successTrajectory = [];
|
|
121
|
-
heartbeatTimer = null;
|
|
122
|
-
sigintHandler = null;
|
|
123
|
-
sigtermHandler = null;
|
|
124
|
-
// Verification gate state (v1.6 unified — was MCP-only, now also in runtime)
|
|
125
|
-
lastSelfEval = null;
|
|
126
|
-
pendingVerificationFlags = [];
|
|
127
|
-
// Enforcement gate state (v1.13)
|
|
128
|
-
consecutiveRejections = 0;
|
|
129
|
-
pendingRejectionNotice = "";
|
|
130
|
-
// Memory integration state (v1.7)
|
|
131
|
-
injectionCount = 0;
|
|
132
|
-
lastInjectionRound = 0;
|
|
133
|
-
injectedContexts = [];
|
|
134
|
-
phase2Triggered = false;
|
|
135
|
-
phase3Triggered = false;
|
|
136
|
-
pendingExternalContext = "";
|
|
137
|
-
// Shared mutable state between the main loop and the heartbeat tick
|
|
138
|
-
roundStartTime = 0;
|
|
139
|
-
lastProgressTime = 0;
|
|
140
|
-
activeCtx = null;
|
|
141
|
-
timedOut = false;
|
|
142
|
-
constructor(rawConfig) {
|
|
143
|
-
super();
|
|
144
|
-
this.config = resolveConfig(rawConfig);
|
|
145
|
-
this.engine = createEngine();
|
|
146
|
-
}
|
|
147
|
-
get status() {
|
|
148
|
-
return this._status;
|
|
149
|
-
}
|
|
150
|
-
getCurrentRound() {
|
|
151
|
-
return this.currentRound;
|
|
152
|
-
}
|
|
153
|
-
getSuccessTrajectory() {
|
|
154
|
-
return [...this.successTrajectory];
|
|
155
|
-
}
|
|
156
|
-
// ── Memory Integration (v1.7) ──────────────────────────────────────────
|
|
157
|
-
/** Derive the current progress estimate from the last self evaluation.
|
|
158
|
-
* Returns -1 if no progress data is available. */
|
|
159
|
-
getCurrentProgress() {
|
|
160
|
-
if (!this.lastSelfEval?.execution_evidence)
|
|
161
|
-
return -1;
|
|
162
|
-
const pe = this.lastSelfEval.execution_evidence.progress_estimate;
|
|
163
|
-
return typeof pe === "number" ? pe : -1;
|
|
164
|
-
}
|
|
165
|
-
/** Determine which memory injection phase (0/1/2/3) should fire this round.
|
|
166
|
-
* Returns 0 if no injection should occur. Phase tracking (phase2Triggered /
|
|
167
|
-
* phase3Triggered) is updated by the caller based on the returned phase. */
|
|
168
|
-
getInjectionPhase() {
|
|
169
|
-
const policy = getPolicy();
|
|
170
|
-
const mi = policy.memory_injection;
|
|
171
|
-
if (!mi.enabled)
|
|
172
|
-
return 0;
|
|
173
|
-
if (!this.config.memoryProvider)
|
|
174
|
-
return 0;
|
|
175
|
-
if (this.lastInjectionRound > 0 &&
|
|
176
|
-
this.currentRound - this.lastInjectionRound < mi.min_rounds_between_injections) {
|
|
177
|
-
return 0;
|
|
178
|
-
}
|
|
179
|
-
const allowedPhases = new Set(resolveAllowedPhases(this.config.maxRounds, mi.round_tiers));
|
|
180
|
-
const progress = this.getCurrentProgress();
|
|
181
|
-
return resolveInjectionPhase(this.currentRound, this.injectionCount, allowedPhases, progress, this.phase2Triggered, this.phase3Triggered, {
|
|
182
|
-
phase2: mi.phase_thresholds.phase2.threshold,
|
|
183
|
-
phase3: mi.phase_thresholds.phase3.threshold,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
/** Build the accumulated context for constructing a targeted memory query.
|
|
187
|
-
* Delegates to the shared buildAccumulatedMemoryContext() utility. */
|
|
188
|
-
buildAccumulatedContext() {
|
|
189
|
-
if (!this.lastSelfEval) {
|
|
190
|
-
return { recurringIssues: [], failedPatterns: [], keyLessons: [], remainingCriteria: [] };
|
|
191
|
-
}
|
|
192
|
-
return buildAccumulatedMemoryContext(this.lastSelfEval);
|
|
193
|
-
}
|
|
194
|
-
/** Deduplicate external context against previously injected contexts.
|
|
195
|
-
* Returns empty string if the new context is too similar to any prior. */
|
|
196
|
-
dedupAndStoreContext(newContext) {
|
|
197
|
-
if (!newContext.trim())
|
|
198
|
-
return "";
|
|
199
|
-
const policy = getPolicy();
|
|
200
|
-
const threshold = policy.memory_injection.dedup_threshold;
|
|
201
|
-
const newTokens = tokenize(newContext);
|
|
202
|
-
for (const old of this.injectedContexts) {
|
|
203
|
-
const oldTokens = tokenize(old);
|
|
204
|
-
if (jaccard(newTokens, oldTokens) > threshold) {
|
|
205
|
-
return ""; // Too similar — skip
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
this.injectedContexts.push(newContext);
|
|
209
|
-
return newContext;
|
|
210
|
-
}
|
|
211
|
-
// ── Lifecycle ──────────────────────────────────────────────────────────
|
|
212
|
-
/** Start the loop. Returns when the loop terminates (task complete,
|
|
213
|
-
* circuit breaker, max rounds, stalled, or manual stop). */
|
|
214
|
-
async start() {
|
|
215
|
-
if (this._status !== RuntimeStatus.IDLE) {
|
|
216
|
-
throw new Error(`Cannot start: runtime is ${this._status}`);
|
|
217
|
-
}
|
|
218
|
-
this._status = RuntimeStatus.RUNNING;
|
|
219
|
-
this.registerSignalHandlers();
|
|
220
|
-
this.startHeartbeat();
|
|
221
|
-
this.emit("start");
|
|
222
|
-
let stopReason = "max_rounds";
|
|
223
|
-
let previousAgentOutput = null;
|
|
224
|
-
let consecutiveErrors = 0;
|
|
225
|
-
try {
|
|
226
|
-
for (this.currentRound = 1; this.currentRound <= this.config.maxRounds; this.currentRound++) {
|
|
227
|
-
if (this._status !== RuntimeStatus.RUNNING) {
|
|
228
|
-
stopReason = this._status === RuntimeStatus.STALLED ? "stalled" : "stopped";
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
// ── v1.7: Memory Injection (before compile, only at L2 phases) ──
|
|
232
|
-
this.pendingExternalContext = "";
|
|
233
|
-
const injectionPhase = this.getInjectionPhase();
|
|
234
|
-
if (injectionPhase !== 0) {
|
|
235
|
-
// Track phase triggers
|
|
236
|
-
if (injectionPhase === 2)
|
|
237
|
-
this.phase2Triggered = true;
|
|
238
|
-
if (injectionPhase === 3)
|
|
239
|
-
this.phase3Triggered = true;
|
|
240
|
-
const progress = this.getCurrentProgress();
|
|
241
|
-
try {
|
|
242
|
-
const ctx = {
|
|
243
|
-
loopId: this.config.loopId,
|
|
244
|
-
round: this.currentRound,
|
|
245
|
-
task: this.config.task,
|
|
246
|
-
domain: this.config.domain,
|
|
247
|
-
phase: injectionPhase,
|
|
248
|
-
progressEstimate: progress,
|
|
249
|
-
accumulatedContext: this.buildAccumulatedContext(),
|
|
250
|
-
};
|
|
251
|
-
const rawContext = await this.config.memoryProvider(ctx);
|
|
252
|
-
this.pendingExternalContext = this.dedupAndStoreContext(rawContext);
|
|
253
|
-
if (this.pendingExternalContext) {
|
|
254
|
-
this.injectionCount++;
|
|
255
|
-
this.lastInjectionRound = this.currentRound;
|
|
256
|
-
logEvent("memory_injected", {
|
|
257
|
-
loopId: this.config.loopId,
|
|
258
|
-
round: this.currentRound,
|
|
259
|
-
phase: injectionPhase,
|
|
260
|
-
injectionCount: this.injectionCount,
|
|
261
|
-
contextLength: this.pendingExternalContext.length,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
catch {
|
|
266
|
-
// memoryProvider failed — degrade gracefully, continue without context
|
|
267
|
-
this.pendingExternalContext = "";
|
|
268
|
-
logEvent("memory_provider_error", {
|
|
269
|
-
loopId: this.config.loopId,
|
|
270
|
-
round: this.currentRound,
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
// ── Compile ──────────────────────────────────────────────────
|
|
275
|
-
const lcr = this.buildCompileRequest(previousAgentOutput);
|
|
276
|
-
const compileResult = this.engine.invokeLoopCompile({
|
|
277
|
-
task: this.config.task,
|
|
278
|
-
mode: Mode.LOOP_COMPILE,
|
|
279
|
-
vault_config: makeVaultConfig(),
|
|
280
|
-
feedback: null,
|
|
281
|
-
skill_name: null,
|
|
282
|
-
task_id: null,
|
|
283
|
-
loop_id: lcr.loop_id,
|
|
284
|
-
round: lcr.round,
|
|
285
|
-
goal_id: lcr.goal_id,
|
|
286
|
-
domain: lcr.domain ?? "",
|
|
287
|
-
plan_source: lcr.plan_source ?? null,
|
|
288
|
-
constraints_from_plan: lcr.constraints_from_plan ?? [],
|
|
289
|
-
health_check_interval: lcr.health_check_interval,
|
|
290
|
-
last_round_result: (lcr.last_round_result ?? undefined),
|
|
291
|
-
verification_flags: this.pendingVerificationFlags,
|
|
292
|
-
external_context: this.pendingExternalContext || undefined,
|
|
293
|
-
max_rounds: this.config.maxRounds,
|
|
294
|
-
});
|
|
295
|
-
// v1.14: Write state file if the compiler produced one and policy allows it
|
|
296
|
-
const sfContent = compileResult.response?.state_file_content;
|
|
297
|
-
if (sfContent && getPolicy().state_file.enabled) {
|
|
298
|
-
const sfDir = join(process.cwd(), getPolicy().state_file.directory);
|
|
299
|
-
mkdirSync(sfDir, { recursive: true });
|
|
300
|
-
writeFileSync(join(sfDir, `${this.config.loopId}-state.md`), sfContent, "utf-8");
|
|
301
|
-
}
|
|
302
|
-
if (!compileResult.response?.prompt) {
|
|
303
|
-
stopReason = "stalled";
|
|
304
|
-
break;
|
|
305
|
-
}
|
|
306
|
-
let prompt = compileResult.response.prompt;
|
|
307
|
-
// If this is a redo of a rejected round, use ONLY the rejection prompt.
|
|
308
|
-
// The agent needs to fix a specific issue, not get a fresh compilation.
|
|
309
|
-
// Normal compilation resumes on the next round after enforcement accepts.
|
|
310
|
-
if (this.pendingRejectionNotice) {
|
|
311
|
-
prompt = this.pendingRejectionNotice;
|
|
312
|
-
}
|
|
313
|
-
const analysis = compileResult.response.analysis;
|
|
314
|
-
const level = analysis?.rationale?.includes("l0")
|
|
315
|
-
? "l0"
|
|
316
|
-
: analysis?.rationale?.includes("l1")
|
|
317
|
-
? "l1"
|
|
318
|
-
: "l2";
|
|
319
|
-
const technique = analysis?.technique ?? "unknown";
|
|
320
|
-
// ── Emit round:start ──────────────────────────────────────────
|
|
321
|
-
const startInfo = { round: this.currentRound, level, technique, prompt };
|
|
322
|
-
this.emit("round:start", startInfo);
|
|
323
|
-
this.config.onRoundStart?.(startInfo);
|
|
324
|
-
// ── Execute ───────────────────────────────────────────────────
|
|
325
|
-
this.roundStartTime = Date.now();
|
|
326
|
-
this.lastProgressTime = this.roundStartTime;
|
|
327
|
-
this.timedOut = false;
|
|
328
|
-
const ctx = {
|
|
329
|
-
round: this.currentRound,
|
|
330
|
-
signal: { aborted: false },
|
|
331
|
-
reportProgress: (message) => {
|
|
332
|
-
this.lastProgressTime = Date.now();
|
|
333
|
-
void message; // consumed by heartbeat via lastProgressTime
|
|
334
|
-
},
|
|
335
|
-
};
|
|
336
|
-
this.activeCtx = ctx;
|
|
337
|
-
let agentOutput;
|
|
338
|
-
try {
|
|
339
|
-
agentOutput = await this.config.execute(prompt, ctx);
|
|
340
|
-
consecutiveErrors = 0;
|
|
341
|
-
}
|
|
342
|
-
catch (err) {
|
|
343
|
-
consecutiveErrors++;
|
|
344
|
-
const dur = Date.now() - this.roundStartTime;
|
|
345
|
-
this.activeCtx = null;
|
|
346
|
-
// Emit round:complete with error info
|
|
347
|
-
const errInfo = {
|
|
348
|
-
round: this.currentRound,
|
|
349
|
-
roundSuccess: false,
|
|
350
|
-
selfEval: makeSelfEvaluation({
|
|
351
|
-
success: false,
|
|
352
|
-
output_summary: `execute threw: ${String(err).slice(0, 200)}`,
|
|
353
|
-
constraint_violations: ["execute() threw an exception"],
|
|
354
|
-
should_continue: true,
|
|
355
|
-
}),
|
|
356
|
-
durationMs: dur,
|
|
357
|
-
};
|
|
358
|
-
this.emit("round:complete", errInfo);
|
|
359
|
-
this.config.onRoundComplete?.(errInfo);
|
|
360
|
-
this.successTrajectory.push(false);
|
|
361
|
-
if (consecutiveErrors >= this.config.maxConsecutiveErrors) {
|
|
362
|
-
stopReason = "executor_failure";
|
|
363
|
-
break;
|
|
364
|
-
}
|
|
365
|
-
// Record as a failed round so the next round's compilation
|
|
366
|
-
// sees the failure context (forces L1/L2)
|
|
367
|
-
previousAgentOutput = null;
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
const durationMs = Date.now() - this.roundStartTime;
|
|
371
|
-
this.activeCtx = null;
|
|
372
|
-
// ── Extract self-eval ─────────────────────────────────────────
|
|
373
|
-
let selfEval = extractSelfEvaluation(agentOutput);
|
|
374
|
-
let extractionSucceeded = selfEval !== null;
|
|
375
|
-
if (selfEval === null) {
|
|
376
|
-
selfEval = heuristicSelfEvaluation(agentOutput);
|
|
377
|
-
extractionSucceeded = false;
|
|
378
|
-
}
|
|
379
|
-
// ── Verification gate (v1.6 unified) + Enforcement (v1.13) ────
|
|
380
|
-
// Run BEFORE autoFeedback so rejected rounds don't pollute vault.
|
|
381
|
-
this.pendingVerificationFlags = [];
|
|
382
|
-
let gateContradicted = false;
|
|
383
|
-
if (selfEval !== null) {
|
|
384
|
-
const vaultEntries = this.engine.getBackend().queryEntries({
|
|
385
|
-
prefix: `loop:${this.config.loopId}:r`,
|
|
386
|
-
});
|
|
387
|
-
const verifyResult = verifySelfEvaluation(selfEval, this.currentRound, vaultEntries, this.lastSelfEval);
|
|
388
|
-
this.pendingVerificationFlags = verifyResult.flags;
|
|
389
|
-
if (verifyResult.verdict === "contradicted") {
|
|
390
|
-
gateContradicted = true;
|
|
391
|
-
logEvent("gate_contradicted", {
|
|
392
|
-
loopId: this.config.loopId,
|
|
393
|
-
round: this.currentRound,
|
|
394
|
-
flags: verifyResult.flags.map((f) => f.check),
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
// ── Enforcement gate (v1.13) — before autoFeedback ──────────
|
|
398
|
-
// Only run for structured self-evals, not heuristic fallback.
|
|
399
|
-
if (extractionSucceeded) {
|
|
400
|
-
const enforceResult = enforceRound(selfEval, verifyResult, this.currentRound, vaultEntries, this.consecutiveRejections);
|
|
401
|
-
if (enforceResult.action === "reject") {
|
|
402
|
-
this.consecutiveRejections++;
|
|
403
|
-
this.pendingRejectionNotice = buildRejectionPrompt(this.currentRound, this.config.task, enforceResult);
|
|
404
|
-
logEvent("enforcement_reject", {
|
|
405
|
-
loopId: this.config.loopId,
|
|
406
|
-
round: this.currentRound,
|
|
407
|
-
reason: enforceResult.reason.slice(0, 120),
|
|
408
|
-
consecutiveRejections: this.consecutiveRejections,
|
|
409
|
-
});
|
|
410
|
-
// Don't update lastSelfEval or successTrajectory.
|
|
411
|
-
// Decrement currentRound so the for-loop increment returns
|
|
412
|
-
// to the same round for the retry.
|
|
413
|
-
this.currentRound--;
|
|
414
|
-
continue;
|
|
415
|
-
}
|
|
416
|
-
if (enforceResult.action === "terminate") {
|
|
417
|
-
stopReason = "enforcement_terminated";
|
|
418
|
-
logEvent("session_end", {
|
|
419
|
-
loopId: this.config.loopId,
|
|
420
|
-
stopReason: "enforcement_terminated",
|
|
421
|
-
round: this.currentRound,
|
|
422
|
-
});
|
|
423
|
-
break;
|
|
424
|
-
}
|
|
425
|
-
// Accept: reset rejection counter
|
|
426
|
-
this.consecutiveRejections = 0;
|
|
427
|
-
this.pendingRejectionNotice = "";
|
|
428
|
-
} // end if (extractionSucceeded)
|
|
429
|
-
this.lastSelfEval = selfEval;
|
|
430
|
-
}
|
|
431
|
-
// ── Auto-feedback (AFTER enforcement, only if accepted) ─────────
|
|
432
|
-
if (selfEval !== null) {
|
|
433
|
-
this.engine.autoFeedback(selfEval, this.config.loopId, this.currentRound, this.config.task);
|
|
434
|
-
}
|
|
435
|
-
// Compute round success
|
|
436
|
-
const roundSuccess = selfEval?.success ?? false;
|
|
437
|
-
// Contradicted gate verdict: skip success trend (match MCP path behavior)
|
|
438
|
-
if (!gateContradicted) {
|
|
439
|
-
this.successTrajectory.push(roundSuccess);
|
|
440
|
-
}
|
|
441
|
-
// ── Emit round:complete ───────────────────────────────────────
|
|
442
|
-
const completeInfo = {
|
|
443
|
-
round: this.currentRound,
|
|
444
|
-
roundSuccess,
|
|
445
|
-
selfEval,
|
|
446
|
-
durationMs,
|
|
447
|
-
};
|
|
448
|
-
this.emit("round:complete", completeInfo);
|
|
449
|
-
this.config.onRoundComplete?.(completeInfo);
|
|
450
|
-
logEvent("round_complete", {
|
|
451
|
-
loopId: this.config.loopId ?? "unknown",
|
|
452
|
-
round: this.currentRound,
|
|
453
|
-
success: roundSuccess,
|
|
454
|
-
durationMs,
|
|
455
|
-
technique: "loop_runtime",
|
|
456
|
-
});
|
|
457
|
-
// ── Check stop conditions ─────────────────────────────────────
|
|
458
|
-
// Extraction check FIRST — heuristic results are low-confidence
|
|
459
|
-
if (!extractionSucceeded) {
|
|
460
|
-
stopReason = "stalled"; // extraction_failure mapped to stalled
|
|
461
|
-
break;
|
|
462
|
-
}
|
|
463
|
-
if (selfEval && !selfEval.should_continue) {
|
|
464
|
-
stopReason = "task_complete";
|
|
465
|
-
break;
|
|
466
|
-
}
|
|
467
|
-
if (this.engine.shouldBreak()) {
|
|
468
|
-
stopReason = "circuit_breaker";
|
|
469
|
-
break;
|
|
470
|
-
}
|
|
471
|
-
if (this.timedOut) {
|
|
472
|
-
// Next round will be forced L2 via the compile request
|
|
473
|
-
this.timedOut = false;
|
|
474
|
-
}
|
|
475
|
-
previousAgentOutput = agentOutput;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
finally {
|
|
479
|
-
// ── Cleanup — always runs even if the loop body throws ──────────
|
|
480
|
-
this.stopHeartbeat();
|
|
481
|
-
this.unregisterSignalHandlers();
|
|
482
|
-
if (this._status === RuntimeStatus.RUNNING) {
|
|
483
|
-
this._status = RuntimeStatus.STOPPED;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
const result = {
|
|
487
|
-
success: stopReason === "task_complete",
|
|
488
|
-
stopReason,
|
|
489
|
-
roundsCompleted: this.currentRound > this.config.maxRounds
|
|
490
|
-
? this.config.maxRounds
|
|
491
|
-
: this.currentRound,
|
|
492
|
-
successTrajectory: [...this.successTrajectory],
|
|
493
|
-
};
|
|
494
|
-
// ── v1.7: Memory Writeback ───────────────────────────────────────
|
|
495
|
-
const wp = getPolicy().memory_writeback;
|
|
496
|
-
if (wp.enabled && this.config.memoryWriter) {
|
|
497
|
-
const shouldWrite = wp.write_on_outcomes.includes(stopReason) ||
|
|
498
|
-
wp.write_on_outcomes.length === 0;
|
|
499
|
-
if (shouldWrite && result.roundsCompleted > 0) {
|
|
500
|
-
try {
|
|
501
|
-
const lastEval = this.lastSelfEval;
|
|
502
|
-
const discoveries = lastEval
|
|
503
|
-
? [
|
|
504
|
-
...(lastEval.wrong_assumptions ?? []),
|
|
505
|
-
...(lastEval.emerged_subtasks ?? []),
|
|
506
|
-
...(lastEval.discovered_constraints ?? []),
|
|
507
|
-
]
|
|
508
|
-
: [];
|
|
509
|
-
const payload = buildWritebackPayload(this.config.loopId, this.config.task, stopReason, result, discoveries);
|
|
510
|
-
if (payload) {
|
|
511
|
-
await this.config.memoryWriter(payload);
|
|
512
|
-
logEvent("memory_writeback", {
|
|
513
|
-
loopId: this.config.loopId,
|
|
514
|
-
stopReason,
|
|
515
|
-
feedbackCount: payload.feedbackEntries.length,
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
catch {
|
|
520
|
-
// memoryWriter failed — degrade gracefully
|
|
521
|
-
logEvent("memory_writeback_error", {
|
|
522
|
-
loopId: this.config.loopId,
|
|
523
|
-
stopReason,
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
this.emit("done", result);
|
|
529
|
-
return result;
|
|
530
|
-
}
|
|
531
|
-
/** Stop the loop gracefully. Safe to call from any thread/timer. */
|
|
532
|
-
stop() {
|
|
533
|
-
if (this._status === RuntimeStatus.RUNNING) {
|
|
534
|
-
this._status = RuntimeStatus.STOPPED;
|
|
535
|
-
this.emit("stop");
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
// ── Internal: compile request builder ─────────────────────────────────
|
|
539
|
-
buildCompileRequest(previousAgentOutput) {
|
|
540
|
-
const lcr = makeLoopCompileRequest({
|
|
541
|
-
loop_id: this.config.loopId,
|
|
542
|
-
round: this.currentRound,
|
|
543
|
-
goal_id: this.config.goalId || "",
|
|
544
|
-
task: this.config.task,
|
|
545
|
-
domain: this.config.domain,
|
|
546
|
-
plan_source: this.config.planSource ?? null,
|
|
547
|
-
constraints_from_plan: this.config.constraintsFromPlan,
|
|
548
|
-
health_check_interval: this.config.healthCheckInterval,
|
|
549
|
-
vault_config: makeVaultConfig(),
|
|
550
|
-
});
|
|
551
|
-
if (this.timedOut && this.currentRound > 1) {
|
|
552
|
-
lcr.force_level = "l2";
|
|
553
|
-
}
|
|
554
|
-
// Attach last round result from previous iteration
|
|
555
|
-
if (previousAgentOutput !== null && this.currentRound > 1) {
|
|
556
|
-
const selfEval = extractSelfEvaluation(previousAgentOutput) ??
|
|
557
|
-
heuristicSelfEvaluation(previousAgentOutput);
|
|
558
|
-
if (selfEval) {
|
|
559
|
-
lcr.last_round_result = makeLoopRoundResult({
|
|
560
|
-
round: this.currentRound - 1,
|
|
561
|
-
success: selfEval.success,
|
|
562
|
-
output_summary: selfEval.output_summary,
|
|
563
|
-
constraint_violations: selfEval.constraint_violations,
|
|
564
|
-
manual_fixes_needed: "",
|
|
565
|
-
// P0–P2: Cognitive evolution
|
|
566
|
-
discovered_constraints: selfEval.discovered_constraints ?? [],
|
|
567
|
-
objective_refinement: selfEval.objective_refinement ?? "",
|
|
568
|
-
emerged_subtasks: selfEval.emerged_subtasks ?? [],
|
|
569
|
-
// P4: Execution evidence
|
|
570
|
-
execution_evidence: selfEval.execution_evidence,
|
|
571
|
-
// P5: Self-correction
|
|
572
|
-
retracted_constraints: selfEval.retracted_constraints ?? [],
|
|
573
|
-
revised_success_criteria: selfEval.revised_success_criteria ?? [],
|
|
574
|
-
wrong_assumptions: selfEval.wrong_assumptions ?? [],
|
|
575
|
-
// v1.10: Checkpoint
|
|
576
|
-
compression_checkpoint: selfEval.compression_checkpoint ?? false,
|
|
577
|
-
checkpoint_label: selfEval.checkpoint_label ?? "",
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
return lcr;
|
|
582
|
-
}
|
|
583
|
-
// ── Internal: heartbeat ──────────────────────────────────────────────
|
|
584
|
-
startHeartbeat() {
|
|
585
|
-
if (this.config.heartbeatIntervalMs <= 0)
|
|
586
|
-
return;
|
|
587
|
-
this.heartbeatTimer = setInterval(() => {
|
|
588
|
-
this.heartbeatTick();
|
|
589
|
-
}, this.config.heartbeatIntervalMs);
|
|
590
|
-
// Allow the process to exit even if this timer is still running
|
|
591
|
-
if (this.heartbeatTimer && typeof this.heartbeatTimer === "object") {
|
|
592
|
-
this.heartbeatTimer.unref?.();
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
heartbeatTick() {
|
|
596
|
-
if (this._status !== RuntimeStatus.RUNNING)
|
|
597
|
-
return;
|
|
598
|
-
if (this.roundStartTime === 0)
|
|
599
|
-
return;
|
|
600
|
-
try {
|
|
601
|
-
const now = Date.now();
|
|
602
|
-
const elapsed = now - this.roundStartTime;
|
|
603
|
-
const sinceProgress = now - this.lastProgressTime;
|
|
604
|
-
const hbInfo = {
|
|
605
|
-
round: this.currentRound,
|
|
606
|
-
elapsedMs: elapsed,
|
|
607
|
-
sinceProgressMs: sinceProgress,
|
|
608
|
-
};
|
|
609
|
-
this.emit("heartbeat", hbInfo);
|
|
610
|
-
this.config.onHeartbeat?.(hbInfo);
|
|
611
|
-
// Interactive mode: only emit heartbeat, never timeout/stall
|
|
612
|
-
if (this.config.interactive)
|
|
613
|
-
return;
|
|
614
|
-
// Approaching timeout warning (80%)
|
|
615
|
-
if (elapsed > this.config.roundTimeoutMs * 0.8 && !this.timedOut) {
|
|
616
|
-
const warning = {
|
|
617
|
-
type: "approaching_timeout",
|
|
618
|
-
message: `Round ${this.currentRound} approaching timeout ` +
|
|
619
|
-
`(${elapsed}ms / ${this.config.roundTimeoutMs}ms)`,
|
|
620
|
-
};
|
|
621
|
-
this.emit("health:warning", warning);
|
|
622
|
-
this.config.onHealthWarning?.(warning);
|
|
623
|
-
}
|
|
624
|
-
// Timeout
|
|
625
|
-
if (elapsed > this.config.roundTimeoutMs && !this.timedOut) {
|
|
626
|
-
this.timedOut = true;
|
|
627
|
-
if (this.activeCtx) {
|
|
628
|
-
this.activeCtx.signal.aborted = true;
|
|
629
|
-
}
|
|
630
|
-
const timeoutInfo = {
|
|
631
|
-
round: this.currentRound,
|
|
632
|
-
elapsedMs: elapsed,
|
|
633
|
-
};
|
|
634
|
-
this.emit("timeout", timeoutInfo);
|
|
635
|
-
this.config.onTimeout?.(timeoutInfo);
|
|
636
|
-
}
|
|
637
|
-
// Stall — timeout + grace period elapsed, execute still hasn't returned
|
|
638
|
-
if (this.activeCtx !== null &&
|
|
639
|
-
elapsed > this.config.roundTimeoutMs + this.config.stallGraceMs) {
|
|
640
|
-
this._status = RuntimeStatus.STALLED;
|
|
641
|
-
const stallMsg = `Round ${this.currentRound} stalled after ${elapsed}ms`;
|
|
642
|
-
this.emit("stalled", {
|
|
643
|
-
reason: "round_timeout",
|
|
644
|
-
lastRound: this.currentRound,
|
|
645
|
-
elapsedMs: elapsed,
|
|
646
|
-
message: stallMsg,
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
catch {
|
|
651
|
-
// heartbeatTick must never throw — silently ignore errors
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
stopHeartbeat() {
|
|
655
|
-
if (this.heartbeatTimer !== null) {
|
|
656
|
-
clearInterval(this.heartbeatTimer);
|
|
657
|
-
this.heartbeatTimer = null;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
// ── Internal: signal handlers ────────────────────────────────────────
|
|
661
|
-
registerSignalHandlers() {
|
|
662
|
-
this.sigintHandler = () => {
|
|
663
|
-
this.stop();
|
|
664
|
-
};
|
|
665
|
-
this.sigtermHandler = () => {
|
|
666
|
-
this.stop();
|
|
667
|
-
};
|
|
668
|
-
process.on("SIGINT", this.sigintHandler);
|
|
669
|
-
process.on("SIGTERM", this.sigtermHandler);
|
|
670
|
-
}
|
|
671
|
-
unregisterSignalHandlers() {
|
|
672
|
-
if (this.sigintHandler) {
|
|
673
|
-
process.removeListener("SIGINT", this.sigintHandler);
|
|
674
|
-
this.sigintHandler = null;
|
|
675
|
-
}
|
|
676
|
-
if (this.sigtermHandler) {
|
|
677
|
-
process.removeListener("SIGTERM", this.sigtermHandler);
|
|
678
|
-
this.sigtermHandler = null;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
683
|
-
// Convenience function — the 80% API
|
|
684
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
685
|
-
/** Run a fully autonomous loop. Only `task` and `execute` are required.
|
|
686
|
-
* All other configuration has sensible defaults from `loop_policy.json`.
|
|
687
|
-
*
|
|
688
|
-
* @example
|
|
689
|
-
* const result = await run({
|
|
690
|
-
* task: 'Audit ERC20 token for security vulnerabilities',
|
|
691
|
-
* execute: async (prompt) => await myAgent.call(prompt),
|
|
692
|
-
* });
|
|
693
|
-
* // result: { success, stopReason, roundsCompleted, successTrajectory }
|
|
694
|
-
*/
|
|
695
|
-
export async function run(rawConfig) {
|
|
696
|
-
const runtime = new LoopRuntime(rawConfig);
|
|
697
|
-
return runtime.start();
|
|
698
|
-
}
|
|
699
|
-
//# sourceMappingURL=runtime.js.map
|