loopforge 3.7.1 → 3.8.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 +171 -169
- package/dist/audit.d.ts +53 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +106 -22
- package/dist/audit.js.map +1 -1
- package/dist/canonical-state.d.ts +52 -67
- package/dist/canonical-state.d.ts.map +1 -1
- package/dist/canonical-state.js +134 -147
- package/dist/canonical-state.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +103 -4
- package/dist/cli.js.map +1 -1
- package/dist/cognitive-facts.d.ts +13 -0
- package/dist/cognitive-facts.d.ts.map +1 -1
- package/dist/cognitive-facts.js +28 -13
- package/dist/cognitive-facts.js.map +1 -1
- package/dist/committed-round.d.ts +103 -21
- package/dist/committed-round.d.ts.map +1 -1
- package/dist/committed-round.js +188 -45
- package/dist/committed-round.js.map +1 -1
- package/dist/contract-items.d.ts +55 -0
- package/dist/contract-items.d.ts.map +1 -0
- package/dist/contract-items.js +180 -0
- package/dist/contract-items.js.map +1 -0
- package/dist/enforcement-gate.d.ts +20 -2
- package/dist/enforcement-gate.d.ts.map +1 -1
- package/dist/enforcement-gate.js +197 -386
- package/dist/enforcement-gate.js.map +1 -1
- package/dist/engine.d.ts +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +53 -38
- package/dist/engine.js.map +1 -1
- package/dist/evidence-claims.d.ts +4 -4
- package/dist/evidence-claims.d.ts.map +1 -1
- package/dist/evidence-claims.js +16 -14
- package/dist/evidence-claims.js.map +1 -1
- package/dist/evidence-provider.d.ts +85 -89
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +328 -184
- package/dist/evidence-provider.js.map +1 -1
- package/dist/explain.d.ts +76 -0
- package/dist/explain.d.ts.map +1 -0
- package/dist/explain.js +127 -0
- package/dist/explain.js.map +1 -0
- package/dist/generate-schema.js +152 -16
- package/dist/generate-schema.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +40 -53
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +229 -553
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-extras-parser.d.ts +0 -1
- package/dist/loop-extras-parser.d.ts.map +1 -1
- package/dist/loop-extras-parser.js +0 -4
- package/dist/loop-extras-parser.js.map +1 -1
- package/dist/loop-projection.d.ts.map +1 -1
- package/dist/loop-projection.js +3 -1
- package/dist/loop-projection.js.map +1 -1
- package/dist/mcp/round-lifecycle.d.ts +30 -21
- package/dist/mcp/round-lifecycle.d.ts.map +1 -1
- package/dist/mcp/round-lifecycle.js +47 -103
- package/dist/mcp/round-lifecycle.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +51 -7
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +39 -23
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +148 -163
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/submission-boundary.d.ts +89 -0
- package/dist/mcp/submission-boundary.d.ts.map +1 -0
- package/dist/mcp/submission-boundary.js +203 -0
- package/dist/mcp/submission-boundary.js.map +1 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +249 -128
- package/dist/mcp/tools.js.map +1 -1
- package/dist/policy-metrics.js +2 -2
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +55 -93
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +149 -38
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts +1 -32
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +282 -435
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/prompt-policy.d.ts +1 -4
- package/dist/prompt-policy.d.ts.map +1 -1
- package/dist/prompt-policy.js +6 -7
- package/dist/prompt-policy.js.map +1 -1
- package/dist/protocol.d.ts +365 -142
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +21 -66
- package/dist/protocol.js.map +1 -1
- package/dist/replay.js +2 -2
- package/dist/replay.js.map +1 -1
- package/dist/round-contract.d.ts +69 -57
- package/dist/round-contract.d.ts.map +1 -1
- package/dist/round-contract.js +104 -64
- package/dist/round-contract.js.map +1 -1
- package/dist/round-coordinator.d.ts +36 -16
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +166 -102
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-driver.d.ts +3 -5
- package/dist/round-driver.d.ts.map +1 -1
- package/dist/round-driver.js +0 -1
- package/dist/round-driver.js.map +1 -1
- package/dist/round-facts.d.ts +96 -0
- package/dist/round-facts.d.ts.map +1 -0
- package/dist/round-facts.js +216 -0
- package/dist/round-facts.js.map +1 -0
- package/dist/round-transaction.d.ts +19 -9
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +90 -22
- package/dist/round-transaction.js.map +1 -1
- package/dist/self-eval.d.ts +87 -9
- package/dist/self-eval.d.ts.map +1 -1
- package/dist/self-eval.js +396 -27
- package/dist/self-eval.js.map +1 -1
- package/dist/subgoal-state.d.ts +77 -0
- package/dist/subgoal-state.d.ts.map +1 -0
- package/dist/subgoal-state.js +198 -0
- package/dist/subgoal-state.js.map +1 -0
- package/dist/token-utils.d.ts +52 -19
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +83 -72
- package/dist/token-utils.js.map +1 -1
- package/dist/verification-gate.d.ts +14 -31
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +178 -496
- package/dist/verification-gate.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workspace.d.ts +9 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +17 -0
- package/dist/workspace.js.map +1 -1
- package/loop_policy.json +6 -30
- package/package.json +1 -1
- package/skills/perception/SKILL.md +243 -79
package/dist/mcp/session.d.ts
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* SessionManager → RoundDriver → RoundCoordinator path.
|
|
14
14
|
*/
|
|
15
15
|
import type { GateActionDescriptor } from "../protocol.js";
|
|
16
|
-
import type { ExternalContextProvider, LoopTerminalSink
|
|
16
|
+
import type { ExternalContextProvider, LoopTerminalSink } from "../protocol.js";
|
|
17
|
+
import type { ActiveContractView } from "../round-contract.js";
|
|
17
18
|
import type { LoopStore } from "../loop-store.js";
|
|
18
19
|
import type { PolicyMetricsSnapshot } from "../policy-metrics.js";
|
|
19
20
|
import type { SessionStateStore } from "../storage.js";
|
|
@@ -94,41 +95,40 @@ export declare class SessionManager implements SessionRegistry {
|
|
|
94
95
|
* canonicalized action hash — if the action changed, the match fails and
|
|
95
96
|
* the old approval expires automatically. */
|
|
96
97
|
resolveGate(sessionId: string, gateId: string, approved: boolean, note?: string): Record<string, unknown>;
|
|
97
|
-
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
98
|
+
/** The compile context for this round.
|
|
99
|
+
*
|
|
100
|
+
* v3.8.1: delegates to the SHARED implementation in round-lifecycle.ts —
|
|
101
|
+
* the runtime's submission boundary reads the same context (the sub-goal
|
|
102
|
+
* reference space is exactly the compiled set the prompt carried), and two
|
|
103
|
+
* copies would let the projection and the boundary disagree about it. */
|
|
103
104
|
private compileContext;
|
|
104
|
-
/** v3.7.1: pre-advance referential check for subgoal_updates. The
|
|
105
|
-
* reference space is the SAME derivation the agent saw in its prompt
|
|
106
|
-
* (the compiled sub_goals of the current round) plus this payload's own
|
|
107
|
-
* emerged items (a sub-goal may be created and transitioned in one
|
|
108
|
-
* round). Unknown IDs, terminal references, and illegal migrations
|
|
109
|
-
* return evaluation_invalid before anything mutates. Compile failure
|
|
110
|
-
* fails open (the shape checks above stay strict). */
|
|
111
|
-
preflightSubGoalUpdates(sessionId: string, roundId: string, updates: import("../protocol.js").SubGoalUpdate[], emerged: string[]): Array<{
|
|
112
|
-
field: string;
|
|
113
|
-
reason: string;
|
|
114
|
-
detail: string;
|
|
115
|
-
}>;
|
|
116
105
|
/** Typed cognitive state projection for an active session. Derived on
|
|
117
106
|
* demand — zero persistence. Null when nothing meaningful exists yet. */
|
|
118
107
|
getProjection(sessionId: string): Record<string, unknown> | null;
|
|
108
|
+
/** v3.8: Read-only per-round "why" view over committed facts. Never
|
|
109
|
+
* rebuilds history and never writes. */
|
|
110
|
+
getExplain(loopId: string, round?: number): Record<string, unknown>;
|
|
119
111
|
/** Read-only end-of-loop audit (verification view). Never writes. */
|
|
120
112
|
getAudit(loopId: string): Record<string, unknown> | null;
|
|
121
113
|
/** v3.5: The ACTIVE Round Contract governing the session's next round —
|
|
122
114
|
* derived from the committed :feedback evals (the SAME adapter + walker
|
|
123
115
|
* the verification gate uses; display-only, zero persistence). Null when
|
|
124
116
|
* nothing is active (whole-task round). */
|
|
125
|
-
getActiveContract(sessionId: string):
|
|
117
|
+
getActiveContract(sessionId: string): ActiveContractView | null;
|
|
126
118
|
/** v2.12: Policy metrics that survive restarts — vault-derived round
|
|
127
119
|
* statistics (A4 port) folded with this process's live observations.
|
|
128
120
|
* Non-durable fields (evidence, vault errors) come from live only. */
|
|
129
121
|
getPolicyMetrics(loopId: string): PolicyMetricsSnapshot;
|
|
130
|
-
/** Get
|
|
131
|
-
*
|
|
122
|
+
/** Get machine facts about a loop (in-memory or vault).
|
|
123
|
+
*
|
|
124
|
+
* v3.8.1: this view used to report `goal_alignment`, `drift_detected`,
|
|
125
|
+
* `strategy_stability` and `task_continuity`. Every one was a
|
|
126
|
+
* text-similarity verdict rather than a fact, and two were degenerate in
|
|
127
|
+
* THIS method specifically: `task_continuity` was pinned to 1.0 because the
|
|
128
|
+
* request was built with `round: 1`, so `getPreviousRound(loopId, 0)`
|
|
129
|
+
* returned null and the code took its hardcoded `?? 1` branch; and
|
|
130
|
+
* `strategy_stability` was a literal `true`. What remains is counted
|
|
131
|
+
* directly from committed round flags. */
|
|
132
132
|
getHealth(loopId: string): Record<string, unknown> | null;
|
|
133
133
|
/** Core cycle: extract self-eval → record feedback → check stop → compile next.
|
|
134
134
|
* The lease + per-session queue wrap the RoundLifecycle state machine.
|
|
@@ -138,7 +138,23 @@ export declare class SessionManager implements SessionRegistry {
|
|
|
138
138
|
* (from the last start/next/resume response). Anchors the submission so a
|
|
139
139
|
* stale or duplicate submission is not processed against a later round.
|
|
140
140
|
* Optional for library callers — when absent the anchor check is skipped. */
|
|
141
|
-
|
|
141
|
+
/** \`submission\` is the RAW evaluation payload, not a built one.
|
|
142
|
+
*
|
|
143
|
+
* v3.8.1: this method used to carry a partial copy of the submission
|
|
144
|
+
* boundary (an object check plus core-field validation) while the MCP tool
|
|
145
|
+
* handler carried the rest — one rule, three copies, across two layers.
|
|
146
|
+
* The boundary now lives entirely in the runtime
|
|
147
|
+
* (`RoundLifecycle.submissionBoundary`), so every caller — this manager, a
|
|
148
|
+
* library user, a future CLI command — gets the same strictness. */
|
|
149
|
+
/** \`submission\` is the RAW evaluation payload, not a built one.
|
|
150
|
+
*
|
|
151
|
+
* v3.8.1: this method used to carry a partial copy of the submission
|
|
152
|
+
* boundary (an object check plus core-field validation) while the MCP tool
|
|
153
|
+
* handler carried the rest — one rule, three copies, across two layers.
|
|
154
|
+
* The whole boundary now runs HERE, before the queue and before the lease
|
|
155
|
+
* heartbeat, so every caller of the public API gets it and a rejected
|
|
156
|
+
* payload leaves nothing durable behind. */
|
|
157
|
+
advance(sessionId: string, output: string, submission?: unknown, roundId?: string): Promise<AdvanceResult>;
|
|
142
158
|
/** Replay timeline for a session — creates ReplayBackend from the stored backend. */
|
|
143
159
|
replayTimeline(sessionId: string): Record<string, unknown>[] | null;
|
|
144
160
|
/** v3.3.1: Replay a loop straight from the vault — no in-memory session
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/mcp/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAWH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EACV,uBAAuB,
|
|
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
|
@@ -15,18 +15,17 @@
|
|
|
15
15
|
import { randomUUID } from "node:crypto";
|
|
16
16
|
import { LoopForgeEngine } from "../engine.js";
|
|
17
17
|
import { canonicalizeGateAction, deriveGate, preflightStructuredGate, } from "../cognitive-governance.js";
|
|
18
|
-
import { makeGateDecision
|
|
18
|
+
import { makeGateDecision } from "../protocol.js";
|
|
19
19
|
import { buildLoopProjection } from "../loop-projection.js";
|
|
20
20
|
import { deriveCognitiveFacts } from "../cognitive-facts.js";
|
|
21
|
+
import { NO_IN_FLIGHT_ROUND, deriveRoundFacts } from "../round-facts.js";
|
|
21
22
|
import { listVerifiedClaims } from "../evidence-claims.js";
|
|
22
23
|
import { buildAudit } from "../audit.js";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
24
|
+
import { buildExplain } from "../explain.js";
|
|
25
|
+
import { CHECK_CONTRACT_ITEMS_UNVERIFIED } from "../verification-gate.js";
|
|
26
|
+
import { readOnlyRounds } from "../committed-round.js";
|
|
26
27
|
import { getPolicy, validateLoopId } from "../policy.js";
|
|
27
28
|
import { isRecord } from "../token-utils.js";
|
|
28
|
-
import { buildSelfEvaluation, validateCoreSelfEvaluation, } from "../self-eval.js";
|
|
29
|
-
import { makeLoopCompileRequest } from "../protocol.js";
|
|
30
29
|
import { ReplayBackend } from "../replay.js";
|
|
31
30
|
import { FileLoopStore, queryLoopEntries } from "../loop-store.js";
|
|
32
31
|
import { makeRoundId, prepareRoundTransaction } from "../round-transaction.js";
|
|
@@ -36,6 +35,7 @@ import { policyMetrics, derivePolicyMetrics, mergePolicyMetrics, } from "../poli
|
|
|
36
35
|
import { SessionLeaseConflictError, VaultSessionStateStore, } from "../storage.js";
|
|
37
36
|
import { StorageCorruptionError } from "../loop-store.js";
|
|
38
37
|
import { RoundLifecycle, buildLoopRequest } from "./round-lifecycle.js";
|
|
38
|
+
import { activeContractOf, compileSessionContext, validateSubmission, } from "./submission-boundary.js";
|
|
39
39
|
// ── SessionManager ─────────────────────────────────────────────────────────
|
|
40
40
|
export class SessionManager {
|
|
41
41
|
sessions = new Map();
|
|
@@ -193,7 +193,6 @@ export class SessionManager {
|
|
|
193
193
|
maxRounds, successTrajectory: [], status: "running", createdAt: Date.now(),
|
|
194
194
|
consecutiveRejections: 0,
|
|
195
195
|
lastRejectionCheck: "",
|
|
196
|
-
driftClarificationStreak: 0,
|
|
197
196
|
backtrackSkippedFiles: [],
|
|
198
197
|
backtrackSkippedFingerprints: {},
|
|
199
198
|
evidenceBaseline: [],
|
|
@@ -235,7 +234,6 @@ export class SessionManager {
|
|
|
235
234
|
// v1.13: Enforcement gate state
|
|
236
235
|
consecutiveRejections: 0,
|
|
237
236
|
lastRejectionCheck: "",
|
|
238
|
-
driftClarificationStreak: 0,
|
|
239
237
|
backtrackSkippedFiles: [],
|
|
240
238
|
backtrackSkippedFingerprints: {},
|
|
241
239
|
evidenceBaseline,
|
|
@@ -509,12 +507,22 @@ export class SessionManager {
|
|
|
509
507
|
checkGate(sessionId, roundId, action) {
|
|
510
508
|
const session = this.sessions.get(sessionId);
|
|
511
509
|
if (!session) {
|
|
512
|
-
return {
|
|
510
|
+
return {
|
|
511
|
+
error: "session_not_found",
|
|
512
|
+
errorMessage: `session not found: ${sessionId}`,
|
|
513
|
+
sessionId,
|
|
514
|
+
};
|
|
513
515
|
}
|
|
514
516
|
const expectedRoundId = session.roundSnapshot?.roundId
|
|
515
517
|
?? makeRoundId(session.loopId, session.currentRound);
|
|
516
518
|
if (expectedRoundId !== roundId) {
|
|
517
|
-
return {
|
|
519
|
+
return {
|
|
520
|
+
error: "round_id_mismatch",
|
|
521
|
+
errorMessage: "roundId does not match the current round — re-run loopforge_gate_check " +
|
|
522
|
+
"with the roundId from the latest response",
|
|
523
|
+
sessionId,
|
|
524
|
+
roundId,
|
|
525
|
+
};
|
|
518
526
|
}
|
|
519
527
|
const verdict = preflightStructuredGate(action);
|
|
520
528
|
if (verdict.kind === "user") {
|
|
@@ -561,8 +569,13 @@ export class SessionManager {
|
|
|
561
569
|
return lineage.session_id === sessionId;
|
|
562
570
|
});
|
|
563
571
|
loopId = persistedEntry?.loop_id;
|
|
564
|
-
if (!loopId)
|
|
565
|
-
return {
|
|
572
|
+
if (!loopId) {
|
|
573
|
+
return {
|
|
574
|
+
error: "session_not_found",
|
|
575
|
+
errorMessage: `session not found: ${sessionId}`,
|
|
576
|
+
sessionId,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
566
579
|
const claimed = this.claimSessionEntry(loopId);
|
|
567
580
|
if (!claimed) {
|
|
568
581
|
return { ...this.leaseConflictResult(loopId, persistedEntry) };
|
|
@@ -573,14 +586,23 @@ export class SessionManager {
|
|
|
573
586
|
? lineage.current_round
|
|
574
587
|
: 0;
|
|
575
588
|
}
|
|
576
|
-
if (!loopId)
|
|
577
|
-
return {
|
|
589
|
+
if (!loopId) {
|
|
590
|
+
return {
|
|
591
|
+
error: "session_not_found",
|
|
592
|
+
errorMessage: `session not found: ${sessionId}`,
|
|
593
|
+
sessionId,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
578
596
|
try {
|
|
579
597
|
const prefix = `loop:${loopId}:gate:`;
|
|
580
598
|
const opened = queryLoopEntries(this.loopStore, loopId, { prefix })
|
|
581
599
|
.find((entry) => entry.task_type === "gate_opened" && entry.gate_id === gateId);
|
|
582
600
|
if (!opened) {
|
|
583
|
-
return {
|
|
601
|
+
return {
|
|
602
|
+
error: "state_unavailable",
|
|
603
|
+
errorMessage: `no gate_opened record for gate ${gateId} — the gate may have ` +
|
|
604
|
+
"expired or was never recorded",
|
|
605
|
+
};
|
|
584
606
|
}
|
|
585
607
|
const actionText = typeof opened.gate_action === "string" ? opened.gate_action : "";
|
|
586
608
|
const openedLineage = isRecord(opened.loop_lineage) ? opened.loop_lineage : {};
|
|
@@ -604,7 +626,11 @@ export class SessionManager {
|
|
|
604
626
|
descriptor = JSON.parse(actionText);
|
|
605
627
|
}
|
|
606
628
|
catch {
|
|
607
|
-
return {
|
|
629
|
+
return {
|
|
630
|
+
error: "state_unavailable",
|
|
631
|
+
errorMessage: `gate ${gateId} has a corrupted structured action — ` +
|
|
632
|
+
"re-run loopforge_gate_check",
|
|
633
|
+
};
|
|
608
634
|
}
|
|
609
635
|
const verdict = preflightStructuredGate(descriptor);
|
|
610
636
|
matchedId = verdict.gateId;
|
|
@@ -619,10 +645,18 @@ export class SessionManager {
|
|
|
619
645
|
userScope = derived.gate.blockedScope ?? [];
|
|
620
646
|
}
|
|
621
647
|
if (matchedId !== gateId) {
|
|
622
|
-
return {
|
|
648
|
+
return {
|
|
649
|
+
error: "invalid_argument",
|
|
650
|
+
errorMessage: "gateId does not match the recorded action — the action changed, " +
|
|
651
|
+
"old approvals expire",
|
|
652
|
+
};
|
|
623
653
|
}
|
|
624
654
|
if (!userKind) {
|
|
625
|
-
return {
|
|
655
|
+
return {
|
|
656
|
+
error: "invalid_argument",
|
|
657
|
+
errorMessage: "agent gates are resolved by submitting the required evidence, " +
|
|
658
|
+
"not by user approval",
|
|
659
|
+
};
|
|
626
660
|
}
|
|
627
661
|
const decision = makeGateDecision({
|
|
628
662
|
gateId,
|
|
@@ -658,77 +692,14 @@ export class SessionManager {
|
|
|
658
692
|
}
|
|
659
693
|
}
|
|
660
694
|
// ── v2.12: Typed projection + audit ────────────────────────────────────
|
|
661
|
-
/**
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
695
|
+
/** The compile context for this round.
|
|
696
|
+
*
|
|
697
|
+
* v3.8.1: delegates to the SHARED implementation in round-lifecycle.ts —
|
|
698
|
+
* the runtime's submission boundary reads the same context (the sub-goal
|
|
699
|
+
* reference space is exactly the compiled set the prompt carried), and two
|
|
700
|
+
* copies would let the projection and the boundary disagree about it. */
|
|
667
701
|
compileContext(session) {
|
|
668
|
-
|
|
669
|
-
const expectedRoundId = session.roundSnapshot?.roundId
|
|
670
|
-
?? makeRoundId(session.loopId, session.currentRound);
|
|
671
|
-
if (cached?.prompt_artifact && cached.prompt_artifact.roundId === expectedRoundId) {
|
|
672
|
-
return cached;
|
|
673
|
-
}
|
|
674
|
-
try {
|
|
675
|
-
const request = {
|
|
676
|
-
task: session.task,
|
|
677
|
-
mode: Mode.LOOP_COMPILE,
|
|
678
|
-
feedback: null,
|
|
679
|
-
skill_name: null,
|
|
680
|
-
task_id: null,
|
|
681
|
-
loop_id: session.loopId,
|
|
682
|
-
round: session.currentRound,
|
|
683
|
-
max_rounds: session.maxRounds,
|
|
684
|
-
verification_flags: [],
|
|
685
|
-
};
|
|
686
|
-
const compiled = session.engine.invokeLoopCompile(request, undefined, { persistLineage: false });
|
|
687
|
-
return compiled.response ?? null;
|
|
688
|
-
}
|
|
689
|
-
catch {
|
|
690
|
-
return null; // projection/preflight degrades gracefully
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
/** v3.7.1: pre-advance referential check for subgoal_updates. The
|
|
694
|
-
* reference space is the SAME derivation the agent saw in its prompt
|
|
695
|
-
* (the compiled sub_goals of the current round) plus this payload's own
|
|
696
|
-
* emerged items (a sub-goal may be created and transitioned in one
|
|
697
|
-
* round). Unknown IDs, terminal references, and illegal migrations
|
|
698
|
-
* return evaluation_invalid before anything mutates. Compile failure
|
|
699
|
-
* fails open (the shape checks above stay strict). */
|
|
700
|
-
preflightSubGoalUpdates(sessionId, roundId, updates, emerged) {
|
|
701
|
-
if (updates.length === 0)
|
|
702
|
-
return [];
|
|
703
|
-
const session = this.sessions.get(sessionId);
|
|
704
|
-
if (!session)
|
|
705
|
-
return [];
|
|
706
|
-
const expectedRoundId = session.roundSnapshot?.roundId
|
|
707
|
-
?? makeRoundId(session.loopId, session.currentRound);
|
|
708
|
-
if (expectedRoundId !== roundId)
|
|
709
|
-
return []; // stale/foreign submission — advance handles it
|
|
710
|
-
const response = this.compileContext(session);
|
|
711
|
-
if (!response?.sub_goals)
|
|
712
|
-
return []; // fail open: cannot observe
|
|
713
|
-
const known = [...response.sub_goals];
|
|
714
|
-
for (const desc of emerged) {
|
|
715
|
-
const id = deriveSubGoalId(desc);
|
|
716
|
-
if (!known.some((sg) => sg.id === id)) {
|
|
717
|
-
known.push({
|
|
718
|
-
id,
|
|
719
|
-
description: desc.trim(),
|
|
720
|
-
status: "pending",
|
|
721
|
-
declared_at_round: session.currentRound,
|
|
722
|
-
status_changed_at_round: session.currentRound,
|
|
723
|
-
priority: known.length,
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
return validateSubGoalUpdates(known, updates).map((error) => ({
|
|
728
|
-
field: "subgoal_updates",
|
|
729
|
-
reason: error.reason,
|
|
730
|
-
detail: `${error.reason.replace(/_/g, " ")}: ${error.id}`,
|
|
731
|
-
}));
|
|
702
|
+
return compileSessionContext(session);
|
|
732
703
|
}
|
|
733
704
|
/** Typed cognitive state projection for an active session. Derived on
|
|
734
705
|
* demand — zero persistence. Null when nothing meaningful exists yet. */
|
|
@@ -743,14 +714,43 @@ export class SessionManager {
|
|
|
743
714
|
];
|
|
744
715
|
const compileResponse = this.compileContext(session);
|
|
745
716
|
const openGates = this.lifecycle.listOpenGateDescriptions(session.loopId);
|
|
717
|
+
// v3.8: the projection reads the WHOLE committed history. The old
|
|
718
|
+
// `beforeRound: session.currentRound` bound is only correct while the loop
|
|
719
|
+
// is running, where currentRound is always one past the last committed
|
|
720
|
+
// round; a stop / terminate / max_rounds leaves currentRound ON the round
|
|
721
|
+
// it just committed, so the bound silently dropped the loop's final round
|
|
722
|
+
// and the projection disagreed with audit and explain about it.
|
|
723
|
+
// v3.8.1: one window, one decode, one derivation. The contract facts come
|
|
724
|
+
// from the same `deriveRoundFacts` the compile path calls, so the prompt
|
|
725
|
+
// and the projection cannot tell different stories about what the machine
|
|
726
|
+
// verified.
|
|
727
|
+
const rounds = readOnlyRounds(entries);
|
|
746
728
|
const projection = buildLoopProjection(deriveCognitiveFacts({
|
|
747
729
|
compileResponse,
|
|
748
|
-
rounds
|
|
730
|
+
rounds,
|
|
731
|
+
facts: deriveRoundFacts({
|
|
732
|
+
rounds,
|
|
733
|
+
currentRound: (rounds[rounds.length - 1]?.round ?? 0) + 1,
|
|
734
|
+
inFlight: NO_IN_FLIGHT_ROUND,
|
|
735
|
+
subGoals: compileResponse?.sub_goals ?? [],
|
|
736
|
+
commands: getPolicy().evidence.commands ?? [],
|
|
737
|
+
}),
|
|
749
738
|
verifiedClaims: listVerifiedClaims(entries, session.loopId),
|
|
750
739
|
openGates,
|
|
751
740
|
}));
|
|
752
741
|
return projection ? { ...projection } : null;
|
|
753
742
|
}
|
|
743
|
+
/** v3.8: Read-only per-round "why" view over committed facts. Never
|
|
744
|
+
* rebuilds history and never writes. */
|
|
745
|
+
getExplain(loopId, round) {
|
|
746
|
+
validateLoopId(loopId);
|
|
747
|
+
const prefix = `loop:${loopId}:`;
|
|
748
|
+
const entries = [
|
|
749
|
+
...queryLoopEntries(this.loopStore, loopId, { prefix }),
|
|
750
|
+
...queryLoopEntries(this.loopStore, loopId, { prefix, feedbackOnly: true }),
|
|
751
|
+
];
|
|
752
|
+
return buildExplain(loopId, entries, round);
|
|
753
|
+
}
|
|
754
754
|
/** Read-only end-of-loop audit (verification view). Never writes. */
|
|
755
755
|
getAudit(loopId) {
|
|
756
756
|
validateLoopId(loopId);
|
|
@@ -767,7 +767,7 @@ export class SessionManager {
|
|
|
767
767
|
// Zero committed decisions → nothing to audit. Returning null lets the
|
|
768
768
|
// tools layer report "no audit data" instead of the external auditor
|
|
769
769
|
// solemnly passing a loop that never ran (or a mistyped loopId).
|
|
770
|
-
const hasCommittedDecision =
|
|
770
|
+
const hasCommittedDecision = readOnlyRounds(entries).length > 0;
|
|
771
771
|
if (!hasCommittedDecision)
|
|
772
772
|
return null;
|
|
773
773
|
const audit = buildAudit(loopId, entries, this.loopStore);
|
|
@@ -781,12 +781,8 @@ export class SessionManager {
|
|
|
781
781
|
const session = this.sessions.get(sessionId);
|
|
782
782
|
if (!session)
|
|
783
783
|
return null;
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
...queryLoopEntries(this.loopStore, session.loopId, { prefix }),
|
|
787
|
-
...queryLoopEntries(this.loopStore, session.loopId, { prefix, feedbackOnly: true }),
|
|
788
|
-
];
|
|
789
|
-
return deriveActiveRoundContract(contractRoundEvaluations(committedRoundsFromEntries(entries, session.currentRound)));
|
|
784
|
+
// One derivation, shared with the submission boundary.
|
|
785
|
+
return activeContractOf(this.loopStore, session);
|
|
790
786
|
}
|
|
791
787
|
/** v2.12: Policy metrics that survive restarts — vault-derived round
|
|
792
788
|
* statistics (A4 port) folded with this process's live observations.
|
|
@@ -808,48 +804,35 @@ export class SessionManager {
|
|
|
808
804
|
return live;
|
|
809
805
|
return mergePolicyMetrics(derived, live);
|
|
810
806
|
}
|
|
811
|
-
/** Get
|
|
812
|
-
*
|
|
807
|
+
/** Get machine facts about a loop (in-memory or vault).
|
|
808
|
+
*
|
|
809
|
+
* v3.8.1: this view used to report `goal_alignment`, `drift_detected`,
|
|
810
|
+
* `strategy_stability` and `task_continuity`. Every one was a
|
|
811
|
+
* text-similarity verdict rather than a fact, and two were degenerate in
|
|
812
|
+
* THIS method specifically: `task_continuity` was pinned to 1.0 because the
|
|
813
|
+
* request was built with `round: 1`, so `getPreviousRound(loopId, 0)`
|
|
814
|
+
* returned null and the code took its hardcoded `?? 1` branch; and
|
|
815
|
+
* `strategy_stability` was a literal `true`. What remains is counted
|
|
816
|
+
* directly from committed round flags. */
|
|
813
817
|
getHealth(loopId) {
|
|
814
|
-
// Find the task — check in-memory sessions first, then vault
|
|
815
|
-
let task = "";
|
|
816
|
-
let goalId = loopId;
|
|
817
|
-
for (const s of this.sessions.values()) {
|
|
818
|
-
if (s.loopId === loopId) {
|
|
819
|
-
task = s.task;
|
|
820
|
-
if (s.engine.state?.task_id) {
|
|
821
|
-
goalId = s.engine.state.task_id;
|
|
822
|
-
}
|
|
823
|
-
break;
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
// Fall back to vault for task
|
|
827
|
-
if (!task) {
|
|
828
|
-
const sessionEntry = this.findSessionEntry(loopId);
|
|
829
|
-
if (sessionEntry) {
|
|
830
|
-
task = sessionEntry.task ?? "";
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
if (!task)
|
|
834
|
-
return null;
|
|
835
|
-
// Hydrate vault context
|
|
836
818
|
const engine = new LoopForgeEngine(this.loopStore);
|
|
837
|
-
const
|
|
838
|
-
//
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
const
|
|
819
|
+
const context = engine.hydrateLoopContext(loopId);
|
|
820
|
+
// No hydratable context at all → unknown loop. A started-but-uncommitted
|
|
821
|
+
// loop DOES have a context (the compile persists a lineage entry), so it
|
|
822
|
+
// still gets a view — with zeros — exactly as it did before.
|
|
823
|
+
if (!context)
|
|
824
|
+
return null;
|
|
825
|
+
const entries = Array.isArray(context.results) ? context.results : [];
|
|
826
|
+
const views = readOnlyRounds(entries);
|
|
827
|
+
const roundsWithUnverifiedItems = views.filter((view) => view.verificationFlags.some((flag) => flag.check === CHECK_CONTRACT_ITEMS_UNVERIFIED)).length;
|
|
846
828
|
return {
|
|
847
829
|
loopId,
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
830
|
+
committed_rounds: views.length,
|
|
831
|
+
rounds_with_unverified_items: roundsWithUnverifiedItems,
|
|
832
|
+
// The policy value the enforcement gate escalates on. The streak itself
|
|
833
|
+
// is derived in the gate — re-deriving it here would be a second
|
|
834
|
+
// implementation of the same rule, which is what this release removes.
|
|
835
|
+
unverified_streak_limit: getPolicy().engine.unverified_claim_streak_limit,
|
|
853
836
|
policy_metrics: this.getPolicyMetrics(loopId),
|
|
854
837
|
};
|
|
855
838
|
}
|
|
@@ -861,34 +844,36 @@ export class SessionManager {
|
|
|
861
844
|
* (from the last start/next/resume response). Anchors the submission so a
|
|
862
845
|
* stale or duplicate submission is not processed against a later round.
|
|
863
846
|
* Optional for library callers — when absent the anchor check is skipped. */
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
847
|
+
/** \`submission\` is the RAW evaluation payload, not a built one.
|
|
848
|
+
*
|
|
849
|
+
* v3.8.1: this method used to carry a partial copy of the submission
|
|
850
|
+
* boundary (an object check plus core-field validation) while the MCP tool
|
|
851
|
+
* handler carried the rest — one rule, three copies, across two layers.
|
|
852
|
+
* The boundary now lives entirely in the runtime
|
|
853
|
+
* (`RoundLifecycle.submissionBoundary`), so every caller — this manager, a
|
|
854
|
+
* library user, a future CLI command — gets the same strictness. */
|
|
855
|
+
/** \`submission\` is the RAW evaluation payload, not a built one.
|
|
856
|
+
*
|
|
857
|
+
* v3.8.1: this method used to carry a partial copy of the submission
|
|
858
|
+
* boundary (an object check plus core-field validation) while the MCP tool
|
|
859
|
+
* handler carried the rest — one rule, three copies, across two layers.
|
|
860
|
+
* The whole boundary now runs HERE, before the queue and before the lease
|
|
861
|
+
* heartbeat, so every caller of the public API gets it and a rejected
|
|
862
|
+
* payload leaves nothing durable behind. */
|
|
863
|
+
async advance(sessionId, output, submission, roundId) {
|
|
864
|
+
// Judged before the session is even resolved: a malformed evaluation is a
|
|
865
|
+
// payload defect whatever the session's state. See submission-boundary.ts.
|
|
866
|
+
const boundary = validateSubmission(this.loopStore, this.sessions.get(sessionId), submission);
|
|
867
|
+
if (!boundary.ok) {
|
|
868
|
+
return {
|
|
869
|
+
sessionId,
|
|
870
|
+
round: this.sessions.get(sessionId)?.currentRound ?? 0,
|
|
871
|
+
...(roundId !== undefined ? { roundId } : {}),
|
|
872
|
+
prompt: null,
|
|
873
|
+
stopReason: boundary.submissionError.code,
|
|
874
|
+
stopDetail: boundary.stopDetail,
|
|
875
|
+
submissionError: boundary.submissionError,
|
|
876
|
+
};
|
|
892
877
|
}
|
|
893
878
|
return this.withSessionQueue(sessionId, async () => {
|
|
894
879
|
const session = this.sessions.get(sessionId);
|
|
@@ -897,7 +882,7 @@ export class SessionManager {
|
|
|
897
882
|
return this.leaseConflictResult(session.loopId, this.findSessionEntry(session.loopId));
|
|
898
883
|
}
|
|
899
884
|
try {
|
|
900
|
-
return await this.lifecycle.advance(sessionId, output,
|
|
885
|
+
return await this.lifecycle.advance(sessionId, output, boundary.evaluation, roundId);
|
|
901
886
|
}
|
|
902
887
|
catch (error) {
|
|
903
888
|
if (error instanceof SessionLeaseConflictError) {
|