loopforge 2.0.0 → 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/dist/backends/interface.d.ts +4 -3
- package/dist/backends/interface.d.ts.map +1 -1
- package/dist/backends/interface.js +4 -1
- package/dist/backends/interface.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/engine.d.ts +2 -24
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +72 -238
- package/dist/engine.js.map +1 -1
- package/dist/evidence-provider.d.ts +38 -5
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +131 -38
- package/dist/evidence-provider.js.map +1 -1
- package/dist/index.d.ts +3 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/interop.d.ts +5 -0
- package/dist/interop.d.ts.map +1 -1
- package/dist/interop.js +5 -0
- package/dist/interop.js.map +1 -1
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +4 -2
- 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 +26 -4
- package/dist/loop-store.d.ts.map +1 -1
- package/dist/loop-store.js +96 -7
- package/dist/loop-store.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/session.d.ts +27 -2
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +166 -117
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +1 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/policy.d.ts +0 -8
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +1 -9
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +25 -4
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/protocol.d.ts +2 -74
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +0 -26
- package/dist/protocol.js.map +1 -1
- package/dist/round-coordinator.d.ts +4 -4
- package/dist/round-coordinator.js +4 -4
- 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 +3 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +57 -65
- package/dist/storage.js.map +1 -1
- package/dist/verification-gate.d.ts +16 -18
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +149 -94
- package/dist/verification-gate.js.map +1 -1
- package/loop_policy.json +1 -9
- package/package.json +1 -1
- package/dist/runtime.d.ts +0 -94
- package/dist/runtime.d.ts.map +0 -1
- package/dist/runtime.js +0 -718
- package/dist/runtime.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/mcp/session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,eAAe,EAAkD,MAAM,cAAc,CAAC;AAK/F,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,0BAA0B,CAAC;AAEzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EACQ,cAAc,EAChC,uBAAuB,EAAqB,gBAAgB,EAC7D,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAKhE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/mcp/session.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp/session.js
CHANGED
|
@@ -10,7 +10,7 @@ import { checkLoopHealth } from "../loop-compiler.js";
|
|
|
10
10
|
import { getPolicy } from "../policy.js";
|
|
11
11
|
import { Mode, makeLoopCompileRequest } from "../protocol.js";
|
|
12
12
|
import { ReplayBackend } from "../replay.js";
|
|
13
|
-
import { FileLoopStore, LoopStoreBackend } from "../loop-store.js";
|
|
13
|
+
import { FileLoopStore, LoopStoreBackend, VaultBackendLoopStore } from "../loop-store.js";
|
|
14
14
|
import { EvidenceCollector } from "../evidence-provider.js";
|
|
15
15
|
import { parseRoundTransactionSnapshot, prepareRoundTransaction, } from "../round-transaction.js";
|
|
16
16
|
import { RoundDriver } from "../round-driver.js";
|
|
@@ -19,8 +19,7 @@ import { policyMetrics } from "../policy-metrics.js";
|
|
|
19
19
|
import { SessionLeaseConflictError, VaultSessionStateStore, } from "../storage.js";
|
|
20
20
|
import { createCognitiveCheckpoint } from "../interop.js";
|
|
21
21
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
22
|
-
function buildLoopRequest(session, lastEval,
|
|
23
|
-
verificationFlags) {
|
|
22
|
+
function buildLoopRequest(session, lastEval, verificationFlags) {
|
|
24
23
|
const req = {
|
|
25
24
|
task: session.task,
|
|
26
25
|
mode: Mode.LOOP_COMPILE,
|
|
@@ -96,13 +95,16 @@ export class SessionManager {
|
|
|
96
95
|
contextProvider;
|
|
97
96
|
terminalSinks = new Set();
|
|
98
97
|
constructor(storeOrBackend, sessionStore) {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.
|
|
98
|
+
const isLoopStore = (v) => "readSession" in v && typeof v.readSession === "function";
|
|
99
|
+
const store = storeOrBackend
|
|
100
|
+
? isLoopStore(storeOrBackend)
|
|
101
|
+
? storeOrBackend
|
|
102
|
+
: new VaultBackendLoopStore(storeOrBackend)
|
|
103
|
+
: new FileLoopStore(getPolicy().backend.root_dir);
|
|
104
|
+
this.backend = storeOrBackend && !isLoopStore(storeOrBackend)
|
|
105
|
+
? storeOrBackend
|
|
106
|
+
: new LoopStoreBackend(store);
|
|
107
|
+
this.sessionStore = sessionStore ?? new VaultSessionStateStore(store);
|
|
106
108
|
const mcpPolicy = getPolicy().mcp;
|
|
107
109
|
this.leaseMs = Math.max(1, mcpPolicy.session_lease_ms);
|
|
108
110
|
this.leaseRenewIntervalMs = Math.max(1, Math.min(mcpPolicy.session_lease_renew_interval_ms, this.leaseMs));
|
|
@@ -165,6 +167,7 @@ export class SessionManager {
|
|
|
165
167
|
round: typeof lineage.current_round === "number" ? lineage.current_round : 0,
|
|
166
168
|
prompt: null,
|
|
167
169
|
stopReason: `session_owned_elsewhere:${loopId}`,
|
|
170
|
+
stopDetail: `Another process (PID ${entry?.loop_lineage ? entry.loop_lineage.lease_owner ?? "unknown" : "unknown"}) holds the lease for loop "${loopId}". Wait for the lease to expire or stop the other process.`,
|
|
168
171
|
};
|
|
169
172
|
}
|
|
170
173
|
async withSessionQueue(sessionId, work) {
|
|
@@ -190,7 +193,7 @@ export class SessionManager {
|
|
|
190
193
|
const sessionId = randomUUID();
|
|
191
194
|
const loopId = input.loopId ?? randomUUID();
|
|
192
195
|
const engine = new LoopForgeEngine(this.backend);
|
|
193
|
-
const maxRounds = input.maxRounds ?? getPolicy().
|
|
196
|
+
const maxRounds = input.maxRounds ?? getPolicy().engine.max_rounds;
|
|
194
197
|
// Populate extra fields for the first round
|
|
195
198
|
const request = buildLoopRequest({
|
|
196
199
|
sessionId, loopId, task: input.task, engine, currentRound: 1,
|
|
@@ -258,7 +261,6 @@ export class SessionManager {
|
|
|
258
261
|
prompt: initialPrompt,
|
|
259
262
|
level: initialLevel,
|
|
260
263
|
roundSuccess: false,
|
|
261
|
-
quality: 0,
|
|
262
264
|
warnings: parseWarnings(initialPrompt),
|
|
263
265
|
};
|
|
264
266
|
}
|
|
@@ -290,7 +292,7 @@ export class SessionManager {
|
|
|
290
292
|
if (!session)
|
|
291
293
|
return false;
|
|
292
294
|
session.status = "stopped";
|
|
293
|
-
this.save(session);
|
|
295
|
+
this.save(session);
|
|
294
296
|
void this.notifyTerminal(session, "cancelled");
|
|
295
297
|
this.sessions.delete(sessionId);
|
|
296
298
|
logEvent("session_end", {
|
|
@@ -329,7 +331,6 @@ export class SessionManager {
|
|
|
329
331
|
roundId: session.roundSnapshot?.roundId,
|
|
330
332
|
prompt: session.currentPrompt,
|
|
331
333
|
level: session.currentLevel ?? "l2",
|
|
332
|
-
quality: 0,
|
|
333
334
|
roundSuccess: undefined,
|
|
334
335
|
warnings: parseWarnings(session.currentPrompt),
|
|
335
336
|
};
|
|
@@ -377,14 +378,18 @@ export class SessionManager {
|
|
|
377
378
|
roundId: recovered.snapshot.roundId,
|
|
378
379
|
prompt: null,
|
|
379
380
|
stopReason: reason,
|
|
381
|
+
stopDetail: reason === "completed"
|
|
382
|
+
? "Task completed successfully — the agent declared should_continue=false with success=true."
|
|
383
|
+
: reason === "failed"
|
|
384
|
+
? "Agent declared should_continue=false with success=false — the task was not achieved."
|
|
385
|
+
: `Loop stopped: ${reason}.`,
|
|
380
386
|
roundSuccess: pr.roundSuccess,
|
|
381
|
-
quality: pr.roundSuccess ? 5 : 1,
|
|
382
387
|
};
|
|
383
388
|
}
|
|
384
389
|
if (pr.action !== "continue")
|
|
385
390
|
return null;
|
|
386
391
|
session.currentRound++;
|
|
387
|
-
const request = buildLoopRequest(session, pr.newLastSelfEval, pr.
|
|
392
|
+
const request = buildLoopRequest(session, pr.newLastSelfEval, pr.verificationFlags);
|
|
388
393
|
const prepared = new RoundDriver(session.engine, this.backend).prepareSync(request, session.loopId, session.currentRound);
|
|
389
394
|
if (!prepared) {
|
|
390
395
|
session.status = "stalled";
|
|
@@ -394,6 +399,7 @@ export class SessionManager {
|
|
|
394
399
|
round: session.currentRound,
|
|
395
400
|
prompt: null,
|
|
396
401
|
stopReason: "stalled",
|
|
402
|
+
stopDetail: "RoundDriver.prepareSync returned null — prompt compilation failed during crash recovery. The session state may be inconsistent.",
|
|
397
403
|
};
|
|
398
404
|
}
|
|
399
405
|
const prompt = prepared.prompt;
|
|
@@ -410,7 +416,6 @@ export class SessionManager {
|
|
|
410
416
|
prompt,
|
|
411
417
|
level: session.currentLevel,
|
|
412
418
|
roundSuccess: pr.roundSuccess,
|
|
413
|
-
quality: pr.roundSuccess ? 5 : 1,
|
|
414
419
|
warnings: parseWarnings(prompt),
|
|
415
420
|
};
|
|
416
421
|
}
|
|
@@ -490,7 +495,7 @@ export class SessionManager {
|
|
|
490
495
|
session.status = "stopped";
|
|
491
496
|
this.save(session);
|
|
492
497
|
void this.notifyTerminal(session, "stalled");
|
|
493
|
-
return { sessionId: session.sessionId, round: session.currentRound, prompt: null, stopReason: "stalled" };
|
|
498
|
+
return { sessionId: session.sessionId, round: session.currentRound, prompt: null, stopReason: "stalled", stopDetail: "RoundDriver.prepare returned null during unpause — the compiler could not produce a prompt." };
|
|
494
499
|
}
|
|
495
500
|
const prompt = prepared.prompt;
|
|
496
501
|
const level = prepared.level;
|
|
@@ -506,7 +511,6 @@ export class SessionManager {
|
|
|
506
511
|
roundId: session.roundSnapshot.roundId,
|
|
507
512
|
prompt,
|
|
508
513
|
level,
|
|
509
|
-
quality: 0,
|
|
510
514
|
roundSuccess: undefined,
|
|
511
515
|
warnings: [],
|
|
512
516
|
};
|
|
@@ -575,7 +579,7 @@ export class SessionManager {
|
|
|
575
579
|
const currentRound = lineage.current_round ?? 1;
|
|
576
580
|
const successTrajectory = lineage.success_trajectory ?? lineage.quality_trajectory ?? [];
|
|
577
581
|
const task = entry.task ?? "";
|
|
578
|
-
const maxRounds = lineage.max_rounds ?? getPolicy().
|
|
582
|
+
const maxRounds = lineage.max_rounds ?? getPolicy().engine.max_rounds;
|
|
579
583
|
const roundSnapshot = parseRoundTransactionSnapshot(lineage.round_snapshot);
|
|
580
584
|
const fallbackEvidence = EvidenceCollector.fromProviderNames(getPolicy().evidence.providers).collect();
|
|
581
585
|
const persistedEval = lineage.last_self_eval;
|
|
@@ -635,6 +639,7 @@ export class SessionManager {
|
|
|
635
639
|
round: currentRound,
|
|
636
640
|
prompt: null,
|
|
637
641
|
stopReason: status,
|
|
642
|
+
stopDetail: `Loop is not running (status: ${status}). It may have already completed or been stopped.`,
|
|
638
643
|
};
|
|
639
644
|
}
|
|
640
645
|
this.sessions.set(session.sessionId, session);
|
|
@@ -661,7 +666,6 @@ export class SessionManager {
|
|
|
661
666
|
prompt,
|
|
662
667
|
level: session.currentLevel,
|
|
663
668
|
roundSuccess: false,
|
|
664
|
-
quality: 0,
|
|
665
669
|
warnings: parseWarnings(prompt),
|
|
666
670
|
};
|
|
667
671
|
}
|
|
@@ -802,34 +806,30 @@ export class SessionManager {
|
|
|
802
806
|
}
|
|
803
807
|
});
|
|
804
808
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
809
|
+
// ── advanceUnlocked pipeline ───────────────────────────────────────────
|
|
810
|
+
//
|
|
811
|
+
// The method is split into focused private helpers so each phase of the
|
|
812
|
+
// round-boundary decision loop is independently readable and testable.
|
|
813
|
+
//
|
|
814
|
+
// Pipeline: validate → extract → execute transaction → route disposition
|
|
815
|
+
// → advance to next round (continue) or return terminal result.
|
|
816
|
+
/** Extract a SelfEvaluation from agent output.
|
|
817
|
+
* Structured param preferred → regex extraction → heuristic fallback.
|
|
818
|
+
* Returns null only when both regex and heuristic returned null. */
|
|
819
|
+
extractEvaluation(output, preExtractedEval) {
|
|
815
820
|
if (preExtractedEval) {
|
|
816
|
-
selfEval
|
|
817
|
-
extractionFailed = false;
|
|
818
|
-
}
|
|
819
|
-
else {
|
|
820
|
-
const structured = extractSelfEvaluation(output);
|
|
821
|
-
extractionFailed = structured === null;
|
|
822
|
-
selfEval = structured ?? heuristicSelfEvaluation(output);
|
|
821
|
+
return { selfEval: preExtractedEval, extractionFailed: false };
|
|
823
822
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
823
|
+
const structured = extractSelfEvaluation(output);
|
|
824
|
+
const extractionFailed = structured === null;
|
|
825
|
+
const selfEval = structured ?? heuristicSelfEvaluation(output);
|
|
826
|
+
return selfEval ? { selfEval, extractionFailed } : null;
|
|
827
|
+
}
|
|
828
|
+
/** Execute the round transaction and apply per-rule rejection tracking.
|
|
829
|
+
* MUTATES: session.roundSnapshot, session.consecutiveRejections,
|
|
830
|
+
* session.lastRejectionCheck, session.lastSelfEval,
|
|
831
|
+
* session.successTrajectory */
|
|
832
|
+
async executeRoundTransaction(session, selfEval, extractionFailed) {
|
|
833
833
|
const snapshot = session.roundSnapshot ?? prepareRoundTransaction(session.loopId, session.currentRound, session.evidenceBaseline ?? []);
|
|
834
834
|
const completed = await new RoundDriver(session.engine, this.backend).complete({
|
|
835
835
|
snapshot,
|
|
@@ -843,13 +843,12 @@ export class SessionManager {
|
|
|
843
843
|
successTrajectory: session.successTrajectory,
|
|
844
844
|
});
|
|
845
845
|
const outcome = completed.outcome;
|
|
846
|
-
const
|
|
846
|
+
const actualEvidence = completed.actualEvidence;
|
|
847
847
|
session.roundSnapshot = outcome.snapshot;
|
|
848
848
|
const pr = outcome.result;
|
|
849
849
|
policyMetrics.recordStrategyOutcome(session.loopId, session.currentLevel, pr, outcome.replayed);
|
|
850
|
-
|
|
851
|
-
//
|
|
852
|
-
// accumulate. A different rejection reason resets the counter.
|
|
850
|
+
// Per-rule rejection tracking: same-check rejections accumulate;
|
|
851
|
+
// a different rejection reason resets the counter.
|
|
853
852
|
if (pr.action === "reject" && pr.rejectionCheck) {
|
|
854
853
|
session.consecutiveRejections =
|
|
855
854
|
pr.rejectionCheck === session.lastRejectionCheck
|
|
@@ -867,75 +866,92 @@ export class SessionManager {
|
|
|
867
866
|
if (pr.shouldPushSuccessTrajectory) {
|
|
868
867
|
session.successTrajectory.push(pr.roundSuccess);
|
|
869
868
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
sessionId,
|
|
880
|
-
round: session.currentRound,
|
|
881
|
-
roundId: session.roundSnapshot.roundId,
|
|
882
|
-
prompt: null,
|
|
883
|
-
stopReason: "stalled",
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
session.roundSnapshot = preparedRetry.snapshot;
|
|
887
|
-
session.currentPrompt = preparedRetry.prompt;
|
|
888
|
-
session.currentLevel = preparedRetry.level;
|
|
889
|
-
this.save(session);
|
|
890
|
-
return {
|
|
891
|
-
sessionId,
|
|
892
|
-
round: session.currentRound,
|
|
893
|
-
roundId: session.roundSnapshot.roundId,
|
|
894
|
-
prompt: preparedRetry.prompt,
|
|
895
|
-
level: preparedRetry.level,
|
|
896
|
-
enforcementAction: "reject",
|
|
897
|
-
enforcementReason: pr.enforcementReason,
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
// Accepted rounds advance the before-snapshot. Rejected rounds retain the
|
|
901
|
-
// original baseline so their retry is still a zero-commit transaction.
|
|
902
|
-
session.evidenceBaseline = actualSnapshots;
|
|
903
|
-
if (pr.action === "terminate") {
|
|
904
|
-
session.status = "stopped";
|
|
869
|
+
return { pr, verificationFlags: pr.verificationFlags, actualEvidence };
|
|
870
|
+
}
|
|
871
|
+
/** Build a rejection result: compile a retry prompt, persist, return.
|
|
872
|
+
* MUTATES: session.roundSnapshot, session.currentPrompt, session.currentLevel */
|
|
873
|
+
async buildRejectionResult(sessionId, session, pr, verificationFlags) {
|
|
874
|
+
const retryRequest = buildLoopRequest(session, undefined, verificationFlags);
|
|
875
|
+
const preparedRetry = await new RoundDriver(session.engine, this.backend).prepareRetry(retryRequest, session.roundSnapshot, pr.rejectionPrompt ?? "", session.consecutiveRejections);
|
|
876
|
+
if (!preparedRetry) {
|
|
877
|
+
session.status = "stalled";
|
|
905
878
|
session.currentPrompt = null;
|
|
906
879
|
this.save(session);
|
|
907
|
-
void this.notifyTerminal(session, "enforcement_terminated");
|
|
908
|
-
logEvent("session_end", {
|
|
909
|
-
sessionId, loopId: session.loopId,
|
|
910
|
-
stopReason: "enforcement_terminated", round: session.currentRound,
|
|
911
|
-
});
|
|
912
880
|
return {
|
|
913
881
|
sessionId,
|
|
914
882
|
round: session.currentRound,
|
|
915
|
-
roundId: session.roundSnapshot
|
|
883
|
+
roundId: session.roundSnapshot?.roundId,
|
|
916
884
|
prompt: null,
|
|
917
|
-
stopReason: "
|
|
918
|
-
|
|
919
|
-
enforcementReason: pr.enforcementReason,
|
|
885
|
+
stopReason: "stalled",
|
|
886
|
+
stopDetail: "RoundDriver.prepareRetry returned null — the retry prompt could not be compiled.",
|
|
920
887
|
};
|
|
921
888
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
889
|
+
session.roundSnapshot = preparedRetry.snapshot;
|
|
890
|
+
session.currentPrompt = preparedRetry.prompt;
|
|
891
|
+
session.currentLevel = preparedRetry.level;
|
|
892
|
+
this.save(session);
|
|
893
|
+
return {
|
|
894
|
+
sessionId,
|
|
895
|
+
round: session.currentRound,
|
|
896
|
+
roundId: session.roundSnapshot.roundId,
|
|
897
|
+
prompt: preparedRetry.prompt,
|
|
898
|
+
level: preparedRetry.level,
|
|
899
|
+
enforcementAction: "reject",
|
|
900
|
+
enforcementReason: pr.enforcementReason,
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
/** Build a termination result: persist stopped status, notify sinks.
|
|
904
|
+
* MUTATES: session.status, session.currentPrompt */
|
|
905
|
+
buildTerminationResult(sessionId, session, pr) {
|
|
906
|
+
session.status = "stopped";
|
|
907
|
+
session.currentPrompt = null;
|
|
908
|
+
this.save(session);
|
|
909
|
+
void this.notifyTerminal(session, "enforcement_terminated");
|
|
910
|
+
logEvent("session_end", {
|
|
911
|
+
sessionId, loopId: session.loopId,
|
|
912
|
+
stopReason: "enforcement_terminated", round: session.currentRound,
|
|
913
|
+
});
|
|
914
|
+
return {
|
|
915
|
+
sessionId,
|
|
916
|
+
round: session.currentRound,
|
|
917
|
+
roundId: session.roundSnapshot?.roundId,
|
|
918
|
+
prompt: null,
|
|
919
|
+
stopReason: "enforcement_terminated",
|
|
920
|
+
stopDetail: pr.enforcementReason ?? "The enforcement gate terminated the loop.",
|
|
921
|
+
enforcementAction: "terminate",
|
|
922
|
+
enforcementReason: pr.enforcementReason,
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
/** Build a stop result: persist stopped/stalled status, notify sinks.
|
|
926
|
+
* MUTATES: session.status, session.currentPrompt */
|
|
927
|
+
buildStopResult(sessionId, session, pr) {
|
|
928
|
+
const reason = pr.stopReason ?? "stalled";
|
|
929
|
+
session.status = reason === "stalled" ? "stalled" : "stopped";
|
|
930
|
+
session.currentPrompt = null;
|
|
931
|
+
this.save(session);
|
|
932
|
+
void this.notifyTerminal(session, reason);
|
|
933
|
+
logEvent("session_end", { sessionId, loopId: session.loopId, stopReason: reason, round: session.currentRound });
|
|
934
|
+
return {
|
|
935
|
+
sessionId, round: session.currentRound,
|
|
936
|
+
roundId: session.roundSnapshot?.roundId,
|
|
937
|
+
prompt: null, stopReason: reason,
|
|
938
|
+
stopDetail: pr.stopReason === "circuit_breaker"
|
|
939
|
+
? `${pr.roundSuccess ? "No" : "All"} recent rounds failed, triggering the circuit breaker.`
|
|
940
|
+
: pr.stopReason === "max_rounds"
|
|
941
|
+
? "The configured maximum number of rounds has been reached."
|
|
942
|
+
: `Loop stopped: ${reason}.`,
|
|
943
|
+
roundSuccess: pr.roundSuccess,
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
/** Compile the next round's prompt and advance the session.
|
|
947
|
+
* Includes the commit fence (pause/delete race guard) and context provider.
|
|
948
|
+
* MUTATES: session.currentRound, session.currentPrompt, session.currentLevel,
|
|
949
|
+
* session.evidenceBaseline, session.roundSnapshot */
|
|
950
|
+
async advanceToNextRound(sessionId, session, selfEval, pr, verificationFlags, actualEvidence) {
|
|
931
951
|
const roundSuccess = pr.roundSuccess;
|
|
932
|
-
// Feedback + decision metadata were committed by the transaction layer.
|
|
933
|
-
// Build deprecated quality alias for backward compat
|
|
934
|
-
const deprecatedQuality = roundSuccess ? 5 : 1;
|
|
935
|
-
// 4. Compile next round after the accepted commit.
|
|
936
952
|
session.currentRound++;
|
|
937
953
|
session.currentPrompt = null;
|
|
938
|
-
// v1.8: Memory injection
|
|
954
|
+
// v1.8: Memory injection — tier-aware external context.
|
|
939
955
|
let externalCtx = "";
|
|
940
956
|
if (this.contextProvider) {
|
|
941
957
|
try {
|
|
@@ -954,28 +970,26 @@ export class SessionManager {
|
|
|
954
970
|
});
|
|
955
971
|
}
|
|
956
972
|
}
|
|
957
|
-
// pause()/delete()
|
|
958
|
-
//
|
|
959
|
-
// session map + status as a commit fence so an in-flight advance cannot
|
|
960
|
-
// compile/save another round after a terminal lifecycle transition.
|
|
973
|
+
// Commit fence: pause()/delete() may have run while the context provider
|
|
974
|
+
// was awaited. Do not compile if the session is no longer running.
|
|
961
975
|
if (this.sessions.get(sessionId) !== session || session.status !== "running") {
|
|
962
976
|
return {
|
|
963
977
|
sessionId,
|
|
964
978
|
round: session.currentRound,
|
|
965
979
|
prompt: null,
|
|
966
980
|
stopReason: session.status,
|
|
981
|
+
stopDetail: `Session is no longer running (status: ${session.status}). It was paused or deleted while the context provider was running.`,
|
|
967
982
|
roundSuccess,
|
|
968
|
-
quality: deprecatedQuality,
|
|
969
983
|
};
|
|
970
984
|
}
|
|
971
|
-
const request = buildLoopRequest(session, selfEval,
|
|
985
|
+
const request = buildLoopRequest(session, selfEval, verificationFlags);
|
|
972
986
|
if (externalCtx) {
|
|
973
987
|
request.external_context = externalCtx;
|
|
974
988
|
}
|
|
975
989
|
const prepared = await new RoundDriver(session.engine, this.backend).prepare(request, session.loopId, session.currentRound);
|
|
976
990
|
const nextPrompt = prepared?.prompt ?? null;
|
|
977
991
|
const nextLevel = prepared?.level ?? "l2";
|
|
978
|
-
const nextBaseline = prepared?.evidenceBaseline ??
|
|
992
|
+
const nextBaseline = prepared?.evidenceBaseline ?? actualEvidence;
|
|
979
993
|
session.evidenceBaseline = nextBaseline;
|
|
980
994
|
session.roundSnapshot = prepared?.snapshot ?? prepareRoundTransaction(session.loopId, session.currentRound, nextBaseline);
|
|
981
995
|
session.currentPrompt = nextPrompt;
|
|
@@ -989,10 +1003,45 @@ export class SessionManager {
|
|
|
989
1003
|
prompt: nextPrompt,
|
|
990
1004
|
level: nextLevel,
|
|
991
1005
|
roundSuccess,
|
|
992
|
-
quality: deprecatedQuality,
|
|
993
1006
|
warnings: parseWarnings(nextPrompt),
|
|
994
1007
|
};
|
|
995
1008
|
}
|
|
1009
|
+
async advanceUnlocked(sessionId, output, preExtractedEval) {
|
|
1010
|
+
// ── 1. Validate session ──────────────────────────────────────────────
|
|
1011
|
+
const session = this.sessions.get(sessionId);
|
|
1012
|
+
if (!session)
|
|
1013
|
+
return { sessionId, round: 0, prompt: null, stopReason: "session_not_found", stopDetail: "No session exists with this ID. It may have expired, been deleted, or never existed." };
|
|
1014
|
+
if (session.status !== "running") {
|
|
1015
|
+
return { sessionId, round: session.currentRound, prompt: null, stopReason: session.status, stopDetail: `Session is ${session.status}. Use loopforge_resume to restart a paused session, or loopforge_start for a new task.` };
|
|
1016
|
+
}
|
|
1017
|
+
// ── 2. Extract self-evaluation ───────────────────────────────────────
|
|
1018
|
+
const extracted = this.extractEvaluation(output, preExtractedEval);
|
|
1019
|
+
if (!extracted) {
|
|
1020
|
+
session.status = "stalled";
|
|
1021
|
+
this.save(session);
|
|
1022
|
+
void this.notifyTerminal(session, "stalled");
|
|
1023
|
+
logEvent("session_end", { sessionId, loopId: session.loopId, stopReason: "stalled", round: session.currentRound });
|
|
1024
|
+
return { sessionId, round: session.currentRound, prompt: null, stopReason: "stalled", stopDetail: "Self-evaluation could not be extracted from agent output. Both structured regex and heuristic fallback returned null.", roundSuccess: false };
|
|
1025
|
+
}
|
|
1026
|
+
const { selfEval, extractionFailed } = extracted;
|
|
1027
|
+
// ── 3. Execute round transaction ─────────────────────────────────────
|
|
1028
|
+
const tx = await this.executeRoundTransaction(session, selfEval, extractionFailed);
|
|
1029
|
+
// ── 4. Route disposition ─────────────────────────────────────────────
|
|
1030
|
+
if (tx.pr.action === "reject") {
|
|
1031
|
+
return this.buildRejectionResult(sessionId, session, tx.pr, tx.verificationFlags);
|
|
1032
|
+
}
|
|
1033
|
+
// Accepted rounds advance the before-snapshot baseline. Rejected rounds
|
|
1034
|
+
// retain the original baseline so their retry remains zero-commit.
|
|
1035
|
+
session.evidenceBaseline = tx.actualEvidence;
|
|
1036
|
+
if (tx.pr.action === "terminate") {
|
|
1037
|
+
return this.buildTerminationResult(sessionId, session, tx.pr);
|
|
1038
|
+
}
|
|
1039
|
+
if (tx.pr.action === "stop") {
|
|
1040
|
+
return this.buildStopResult(sessionId, session, tx.pr);
|
|
1041
|
+
}
|
|
1042
|
+
// ── 5. Continue — compile next round ─────────────────────────────────
|
|
1043
|
+
return this.advanceToNextRound(sessionId, session, selfEval, tx.pr, tx.verificationFlags, tx.actualEvidence);
|
|
1044
|
+
}
|
|
996
1045
|
/** Write back loop knowledge to long-term memory.
|
|
997
1046
|
* Uses shared base builder from policy.ts. Called when a loop terminates. */
|
|
998
1047
|
async notifyTerminal(session, stopReason) {
|