loopforge 3.9.4 → 4.0.7
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 +236 -112
- package/dist/audit.d.ts +73 -15
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +131 -61
- package/dist/audit.js.map +1 -1
- package/dist/canonical-state.d.ts +114 -40
- package/dist/canonical-state.d.ts.map +1 -1
- package/dist/canonical-state.js +276 -182
- package/dist/canonical-state.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +41 -12
- package/dist/cli.js.map +1 -1
- package/dist/cognitive-governance.d.ts +0 -2
- package/dist/cognitive-governance.d.ts.map +1 -1
- package/dist/cognitive-governance.js +0 -4
- package/dist/cognitive-governance.js.map +1 -1
- package/dist/committed-round.d.ts +97 -156
- package/dist/committed-round.d.ts.map +1 -1
- package/dist/committed-round.js +223 -419
- package/dist/committed-round.js.map +1 -1
- package/dist/constraint-lifecycle.d.ts +73 -0
- package/dist/constraint-lifecycle.d.ts.map +1 -0
- package/dist/constraint-lifecycle.js +174 -0
- package/dist/constraint-lifecycle.js.map +1 -0
- package/dist/context-boundary.d.ts +83 -0
- package/dist/context-boundary.d.ts.map +1 -0
- package/dist/context-boundary.js +101 -0
- package/dist/context-boundary.js.map +1 -0
- package/dist/contract-items.d.ts +12 -8
- package/dist/contract-items.d.ts.map +1 -1
- package/dist/contract-items.js +31 -26
- package/dist/contract-items.js.map +1 -1
- package/dist/contract-requirement.d.ts +47 -0
- package/dist/contract-requirement.d.ts.map +1 -0
- package/dist/contract-requirement.js +51 -0
- package/dist/contract-requirement.js.map +1 -0
- package/dist/criterion-status.d.ts +35 -0
- package/dist/criterion-status.d.ts.map +1 -0
- package/dist/criterion-status.js +89 -0
- package/dist/criterion-status.js.map +1 -0
- package/dist/enforcement-gate.d.ts +26 -38
- package/dist/enforcement-gate.d.ts.map +1 -1
- package/dist/enforcement-gate.js +196 -319
- package/dist/enforcement-gate.js.map +1 -1
- package/dist/engine.d.ts +9 -103
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +27 -897
- package/dist/engine.js.map +1 -1
- package/dist/errors.d.ts +41 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +69 -0
- package/dist/errors.js.map +1 -0
- package/dist/evidence-claims.d.ts +33 -31
- package/dist/evidence-claims.d.ts.map +1 -1
- package/dist/evidence-claims.js +58 -72
- package/dist/evidence-claims.js.map +1 -1
- package/dist/evidence-posture-view.d.ts +19 -0
- package/dist/evidence-posture-view.d.ts.map +1 -0
- package/dist/evidence-posture-view.js +116 -0
- package/dist/evidence-posture-view.js.map +1 -0
- package/dist/evidence-posture.d.ts +73 -0
- package/dist/evidence-posture.d.ts.map +1 -0
- package/dist/evidence-posture.js +281 -0
- package/dist/evidence-posture.js.map +1 -0
- package/dist/evidence-provider.d.ts +55 -37
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +182 -332
- package/dist/evidence-provider.js.map +1 -1
- package/dist/explain.d.ts +166 -27
- package/dist/explain.d.ts.map +1 -1
- package/dist/explain.js +372 -58
- package/dist/explain.js.map +1 -1
- package/dist/field-accounting.d.ts.map +1 -1
- package/dist/field-accounting.js +3 -22
- package/dist/field-accounting.js.map +1 -1
- package/dist/generate-schema.js +13 -8
- package/dist/generate-schema.js.map +1 -1
- package/dist/git-boundary.d.ts +128 -0
- package/dist/git-boundary.d.ts.map +1 -0
- package/dist/git-boundary.js +625 -0
- package/dist/git-boundary.js.map +1 -0
- package/dist/git-facts.d.ts +22 -0
- package/dist/git-facts.d.ts.map +1 -0
- package/dist/git-facts.js +29 -0
- package/dist/git-facts.js.map +1 -0
- package/dist/git-motion.d.ts +37 -0
- package/dist/git-motion.d.ts.map +1 -0
- package/dist/git-motion.js +59 -0
- package/dist/git-motion.js.map +1 -0
- package/dist/history.d.ts +44 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +77 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +16 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +80 -14
- package/dist/init.js.map +1 -1
- package/dist/loop-compiler.d.ts +39 -23
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +368 -565
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-store.d.ts +6 -5
- package/dist/loop-store.d.ts.map +1 -1
- package/dist/loop-store.js +153 -35
- package/dist/loop-store.js.map +1 -1
- package/dist/mcp/round-lifecycle.d.ts +255 -81
- package/dist/mcp/round-lifecycle.d.ts.map +1 -1
- package/dist/mcp/round-lifecycle.js +1552 -539
- package/dist/mcp/round-lifecycle.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +42 -18
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +81 -39
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +956 -282
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/submission-boundary.d.ts +23 -31
- package/dist/mcp/submission-boundary.d.ts.map +1 -1
- package/dist/mcp/submission-boundary.js +242 -92
- package/dist/mcp/submission-boundary.js.map +1 -1
- package/dist/mcp/submission-handle.d.ts +66 -0
- package/dist/mcp/submission-handle.d.ts.map +1 -0
- package/dist/mcp/submission-handle.js +173 -0
- package/dist/mcp/submission-handle.js.map +1 -0
- package/dist/mcp/tools.d.ts +17 -7
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +707 -244
- package/dist/mcp/tools.js.map +1 -1
- package/dist/open-question.d.ts +7 -0
- package/dist/open-question.d.ts.map +1 -0
- package/dist/open-question.js +14 -0
- package/dist/open-question.js.map +1 -0
- package/dist/policy-metrics.d.ts +31 -21
- package/dist/policy-metrics.d.ts.map +1 -1
- package/dist/policy-metrics.js +117 -64
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +11 -39
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +12 -12
- package/dist/policy.js.map +1 -1
- package/dist/progress.d.ts +45 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +136 -0
- package/dist/progress.js.map +1 -0
- package/dist/prompt-assembler.d.ts +20 -6
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +475 -479
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/prompt-coverage.d.ts +0 -6
- package/dist/prompt-coverage.d.ts.map +1 -1
- package/dist/prompt-coverage.js +4 -8
- package/dist/prompt-coverage.js.map +1 -1
- package/dist/prompt-freshness.d.ts +6 -17
- package/dist/prompt-freshness.d.ts.map +1 -1
- package/dist/prompt-freshness.js +27 -22
- package/dist/prompt-freshness.js.map +1 -1
- package/dist/prompt-policy.d.ts +14 -7
- package/dist/prompt-policy.d.ts.map +1 -1
- package/dist/prompt-policy.js +21 -5
- package/dist/prompt-policy.js.map +1 -1
- package/dist/protocol.d.ts +843 -424
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +73 -102
- package/dist/protocol.js.map +1 -1
- package/dist/recovery-guidance.d.ts +13 -0
- package/dist/recovery-guidance.d.ts.map +1 -0
- package/dist/recovery-guidance.js +15 -0
- package/dist/recovery-guidance.js.map +1 -0
- package/dist/recovery.d.ts +68 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +234 -0
- package/dist/recovery.js.map +1 -0
- package/dist/replay.d.ts +42 -8
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +311 -84
- package/dist/replay.js.map +1 -1
- package/dist/result-facts.d.ts +45 -0
- package/dist/result-facts.d.ts.map +1 -0
- package/dist/result-facts.js +86 -0
- package/dist/result-facts.js.map +1 -0
- package/dist/round-contract.d.ts +38 -8
- package/dist/round-contract.d.ts.map +1 -1
- package/dist/round-contract.js +71 -31
- package/dist/round-contract.js.map +1 -1
- package/dist/round-coordinator.d.ts +41 -89
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +250 -267
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-driver.d.ts +56 -16
- package/dist/round-driver.d.ts.map +1 -1
- package/dist/round-driver.js +225 -39
- package/dist/round-driver.js.map +1 -1
- package/dist/round-facts.d.ts +33 -52
- package/dist/round-facts.d.ts.map +1 -1
- package/dist/round-facts.js +51 -160
- package/dist/round-facts.js.map +1 -1
- package/dist/round-guidance.d.ts +56 -0
- package/dist/round-guidance.d.ts.map +1 -0
- package/dist/round-guidance.js +161 -0
- package/dist/round-guidance.js.map +1 -0
- package/dist/round-outcome.d.ts +21 -0
- package/dist/round-outcome.d.ts.map +1 -0
- package/dist/round-outcome.js +31 -0
- package/dist/round-outcome.js.map +1 -0
- package/dist/round-transaction.d.ts +168 -21
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +837 -38
- package/dist/round-transaction.js.map +1 -1
- package/dist/self-eval.d.ts +37 -61
- package/dist/self-eval.d.ts.map +1 -1
- package/dist/self-eval.js +413 -392
- package/dist/self-eval.js.map +1 -1
- package/dist/storage.d.ts +16 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +54 -4
- package/dist/storage.js.map +1 -1
- package/dist/subgoal-state.d.ts +2 -3
- package/dist/subgoal-state.d.ts.map +1 -1
- package/dist/subgoal-state.js +2 -3
- package/dist/subgoal-state.js.map +1 -1
- package/dist/submission-guidance.d.ts +29 -21
- package/dist/submission-guidance.d.ts.map +1 -1
- package/dist/submission-guidance.js +308 -260
- package/dist/submission-guidance.js.map +1 -1
- package/dist/submission-receipt.d.ts +16 -8
- package/dist/submission-receipt.d.ts.map +1 -1
- package/dist/submission-receipt.js +145 -51
- package/dist/submission-receipt.js.map +1 -1
- package/dist/token-utils.d.ts +28 -7
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +27 -11
- package/dist/token-utils.js.map +1 -1
- package/dist/transaction-identity.d.ts +9 -0
- package/dist/transaction-identity.d.ts.map +1 -0
- package/dist/transaction-identity.js +69 -0
- package/dist/transaction-identity.js.map +1 -0
- package/dist/verification-actions.d.ts.map +1 -1
- package/dist/verification-actions.js +10 -11
- package/dist/verification-actions.js.map +1 -1
- package/dist/verification-gate.d.ts +23 -47
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +202 -423
- package/dist/verification-gate.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/working-memory.d.ts +187 -0
- package/dist/working-memory.d.ts.map +1 -0
- package/dist/working-memory.js +767 -0
- package/dist/working-memory.js.map +1 -0
- package/dist/workspace-checkpoint.d.ts +88 -0
- package/dist/workspace-checkpoint.d.ts.map +1 -0
- package/dist/workspace-checkpoint.js +326 -0
- package/dist/workspace-checkpoint.js.map +1 -0
- package/dist/workspace-restore.d.ts +68 -0
- package/dist/workspace-restore.d.ts.map +1 -0
- package/dist/workspace-restore.js +604 -0
- package/dist/workspace-restore.js.map +1 -0
- package/dist/workspace.d.ts +0 -9
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +0 -17
- package/dist/workspace.js.map +1 -1
- package/loop_policy.json +4 -18
- package/package.json +68 -68
- package/skills/forge/SKILL.md +212 -0
- package/skills/forge/references/evidence-and-contracts.md +204 -0
- package/skills/forge/references/recovery-and-views.md +188 -0
- package/dist/cognitive-facts.d.ts +0 -30
- package/dist/cognitive-facts.d.ts.map +0 -1
- package/dist/cognitive-facts.js +0 -113
- package/dist/cognitive-facts.js.map +0 -1
- package/dist/loop-extras-parser.d.ts +0 -81
- package/dist/loop-extras-parser.d.ts.map +0 -1
- package/dist/loop-extras-parser.js +0 -213
- package/dist/loop-extras-parser.js.map +0 -1
- package/dist/loop-projection.d.ts +0 -6
- package/dist/loop-projection.d.ts.map +0 -1
- package/dist/loop-projection.js +0 -28
- package/dist/loop-projection.js.map +0 -1
- package/skills/perception/SKILL.md +0 -389
package/dist/mcp/tools.js
CHANGED
|
@@ -1,24 +1,76 @@
|
|
|
1
1
|
/** LoopForge MCP — Tool definitions and handlers.
|
|
2
2
|
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* status is the unified inspection tool (view=session|loop|all|audit).
|
|
3
|
+
* 10 tools: start, next, status, stop, pause, resume, replay,
|
|
4
|
+
* checkpoint, gate_check, gate_resolve.
|
|
5
|
+
* status is the unified inspection tool (view=session|loop|all|audit|explain).
|
|
6
6
|
* Each handler receives SessionManager + parsed input, returns the output object.
|
|
7
7
|
*/
|
|
8
|
-
import { StartCriteriaError } from "./round-lifecycle.js";
|
|
8
|
+
import { InvocationInputError, MANUAL_STOP_REASON, StartCriteriaError, } from "./round-lifecycle.js";
|
|
9
|
+
import { PROMPT_CONTEXT_REASONS } from "../protocol.js";
|
|
9
10
|
import { ProjectionCoverageError } from "../prompt-coverage.js";
|
|
11
|
+
import { LegacyHistoryError, LoopExistsError, OperatorAcceptanceConflictError, StartFailureError, } from "../errors.js";
|
|
10
12
|
import { validateCoreSelfEvaluation } from "../self-eval.js";
|
|
11
13
|
import { isRecord } from "../token-utils.js";
|
|
12
14
|
import { getPolicy, validateLoopId } from "../policy.js";
|
|
13
15
|
import { deriveEvidenceCapability } from "../evidence-provider.js";
|
|
14
|
-
import { fieldGuideDescription } from "../submission-guidance.js";
|
|
16
|
+
import { CORE_SELF_EVALUATION_FIELDS, fieldGuideDescription, } from "../submission-guidance.js";
|
|
17
|
+
import { LOST_HANDLE_ROUTING } from "../recovery-guidance.js";
|
|
18
|
+
import { CONTEXT_MODE_DESCRIPTION } from "../round-guidance.js";
|
|
19
|
+
import { SessionLeaseConflictError } from "../storage.js";
|
|
20
|
+
import { ReplayWindowError } from "../replay.js";
|
|
15
21
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
16
22
|
// Tool schemas (MCP JSON Schema format)
|
|
17
23
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
24
|
+
/** v4.0.0: the ONE held-round anchor. The object is closed so a client cannot
|
|
25
|
+
* smuggle separate anchor fields alongside it. */
|
|
26
|
+
const SUBMISSION_HANDLE_SCHEMA = {
|
|
27
|
+
type: "object",
|
|
28
|
+
description: "The submissionHandle object returned with the held prompt. Copy it " +
|
|
29
|
+
"verbatim; never edit, reconstruct, or derive it. A stale handle never " +
|
|
30
|
+
"submits work. " + LOST_HANDLE_ROUTING,
|
|
31
|
+
properties: {
|
|
32
|
+
sessionId: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "The owning session identity.",
|
|
35
|
+
},
|
|
36
|
+
roundId: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description: "The stable logical round identity.",
|
|
39
|
+
},
|
|
40
|
+
transactionId: {
|
|
41
|
+
type: "string",
|
|
42
|
+
description: "The exact held transaction attempt.",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
required: ["sessionId", "roundId", "transactionId"],
|
|
46
|
+
additionalProperties: false,
|
|
47
|
+
};
|
|
48
|
+
/** Output form: absent/null on terminal or non-held results, exact shape when
|
|
49
|
+
* present. */
|
|
50
|
+
const SUBMISSION_HANDLE_OUTPUT_SCHEMA = {
|
|
51
|
+
...SUBMISSION_HANDLE_SCHEMA,
|
|
52
|
+
type: ["object", "null"],
|
|
53
|
+
};
|
|
54
|
+
/** The closed argument set for `loopforge_start`.
|
|
55
|
+
*
|
|
56
|
+
* The handler's unknown-argument guard reads this list; `schema-sync.test`
|
|
57
|
+
* asserts that the advertised tool schema exposes exactly the same keys. A
|
|
58
|
+
* field removed from the protocol (for example `domain` in 4.0.1) must not
|
|
59
|
+
* survive in only one of them: the schema says what clients may send, and
|
|
60
|
+
* the guard makes a stale caller fail loudly instead of silently dropping
|
|
61
|
+
* the field. */
|
|
62
|
+
export const START_INPUT_KEYS = [
|
|
63
|
+
"task",
|
|
64
|
+
"loopId",
|
|
65
|
+
"maxRounds",
|
|
66
|
+
"planSource",
|
|
67
|
+
"constraints",
|
|
68
|
+
"criteria",
|
|
69
|
+
];
|
|
18
70
|
const TOOL_BASE_SCHEMAS = [
|
|
19
71
|
{
|
|
20
72
|
name: "loopforge_start",
|
|
21
|
-
description: "Start a new LoopForge loop
|
|
73
|
+
description: "Start a new LoopForge loop for a NEW objective. Compiles the first-round prompt from the task description and returns it. Use loopforge_status/loopforge_resume instead for an objective that already has a loopId.",
|
|
22
74
|
inputSchema: {
|
|
23
75
|
type: "object",
|
|
24
76
|
properties: {
|
|
@@ -28,17 +80,16 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
28
80
|
},
|
|
29
81
|
loopId: {
|
|
30
82
|
type: "string",
|
|
31
|
-
description: "Optional loop ID. Generated
|
|
83
|
+
description: "Optional durable loop ID. Generated when omitted and returned " +
|
|
84
|
+
"with the first prompt. Keep it: loopforge_resume and every " +
|
|
85
|
+
"loopforge_status view use loopId after a restart, and a live " +
|
|
86
|
+
"session can re-anchor a lost handle with loopId.",
|
|
32
87
|
},
|
|
33
88
|
maxRounds: {
|
|
34
89
|
type: "integer",
|
|
35
90
|
minimum: 1,
|
|
36
91
|
description: "Maximum rounds before auto-stop. Default: 200 from policy.",
|
|
37
92
|
},
|
|
38
|
-
domain: {
|
|
39
|
-
type: "string",
|
|
40
|
-
description: "Domain hint (e.g. 'solidity', 'react', 'rust') for state context.",
|
|
41
|
-
},
|
|
42
93
|
planSource: {
|
|
43
94
|
type: "string",
|
|
44
95
|
description: "Plan reference for constraint extraction (e.g. 'docs/plan.md').",
|
|
@@ -51,7 +102,9 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
51
102
|
criteria: {
|
|
52
103
|
type: "array",
|
|
53
104
|
items: { type: "string" },
|
|
54
|
-
description: "Initial success criteria
|
|
105
|
+
description: "Initial success criteria — the operator's acceptance surface. Phrase " +
|
|
106
|
+
"each as an observable outcome; implementation detail belongs in " +
|
|
107
|
+
"sub-goals or task text. Each becomes a stable cr-XXXXXXXX anchor " +
|
|
55
108
|
"rendered in every prompt and usable by contract items. Omit to keep " +
|
|
56
109
|
"the default criterion.",
|
|
57
110
|
},
|
|
@@ -61,30 +114,58 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
61
114
|
},
|
|
62
115
|
{
|
|
63
116
|
name: "loopforge_next",
|
|
64
|
-
description: "Submit the
|
|
117
|
+
description: "Submit one evaluation for the held attempt. Returns the next prompt (with its derived contextMode/contextReason and a strategy label), or null with stopReason/stopDetail when the loop ends. The submission may be accepted, rejected (same logical round, attempt+1, new handle), replayed, or answered with a rollback directive. A structured evaluation is required unless currentUserInstruction is the only argument: then the held prompt is recompiled for that instruction without submitting or advancing. If the held prompt is same_round_retry and the original invocation is gone, do NOT use next — call loopforge_resume(loopId, invocationLost: true) so LoopForge recompiles the same round/attempt as a cold-context prompt.",
|
|
65
118
|
inputSchema: {
|
|
66
119
|
type: "object",
|
|
67
120
|
properties: {
|
|
68
|
-
|
|
121
|
+
submissionHandle: SUBMISSION_HANDLE_SCHEMA,
|
|
122
|
+
output: {
|
|
69
123
|
type: "string",
|
|
70
|
-
description: "
|
|
124
|
+
description: "Optional. The agent's full output from executing the current round's prompt. The structured evaluation is the authoritative report.",
|
|
71
125
|
},
|
|
72
|
-
|
|
126
|
+
currentUserInstruction: {
|
|
73
127
|
type: "string",
|
|
74
|
-
description: "
|
|
128
|
+
description: "v4.0.5: the host's current user instruction for this invocation, " +
|
|
129
|
+
"trimmed and non-empty. It is invocation input, not a task update: " +
|
|
130
|
+
"it never rewrites session.task, the Objective, Success Criteria, " +
|
|
131
|
+
"hard constraints or the Round Contract. With evaluation/recovery " +
|
|
132
|
+
"it is carried into the retry/redo/next prompt compiled after the " +
|
|
133
|
+
"submission; with only the handle it recompiles the held prompt " +
|
|
134
|
+
"without submitting or advancing the round.",
|
|
75
135
|
},
|
|
76
|
-
|
|
77
|
-
type: "
|
|
78
|
-
description: "
|
|
136
|
+
recovery: {
|
|
137
|
+
type: "object",
|
|
138
|
+
description: "v3.9.7: Agent-initiated recovery. Abandon the current branch and " +
|
|
139
|
+
"restore a visible workspace checkpoint (the held before-image, a " +
|
|
140
|
+
"mid-round savepoint, or a committed round's before-image). " +
|
|
141
|
+
"Mutually exclusive with evaluation. LoopForge records the directive, " +
|
|
142
|
+
"executes the restore, verifies the full Git boundary, then issues " +
|
|
143
|
+
"the redo prompt. It never chooses a target or judges the reason.",
|
|
144
|
+
properties: {
|
|
145
|
+
target_checkpoint_id: {
|
|
146
|
+
type: "string",
|
|
147
|
+
pattern: "^ck-[a-f0-9]{24}$",
|
|
148
|
+
description: "Checkpoint id (ck-<24 hex>) from the held prompt, a loopforge_checkpoint result, or a committed round's before-image.",
|
|
149
|
+
},
|
|
150
|
+
reason: {
|
|
151
|
+
type: "string",
|
|
152
|
+
minLength: 1,
|
|
153
|
+
maxLength: 500,
|
|
154
|
+
description: "The Agent's reason for abandoning the branch. Recorded verbatim; never judged.",
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
required: ["target_checkpoint_id", "reason"],
|
|
158
|
+
additionalProperties: false,
|
|
79
159
|
},
|
|
80
160
|
evaluation: {
|
|
81
161
|
type: "object",
|
|
82
|
-
description: "
|
|
83
|
-
required: ["
|
|
162
|
+
description: "Structured self-evaluation for this round. Required when recovery is absent. Free-text or embedded evaluation blocks are not accepted.",
|
|
163
|
+
required: ["outcome", "output_summary", "should_continue", "constraint_violations"],
|
|
84
164
|
properties: {
|
|
85
|
-
|
|
86
|
-
type: "
|
|
87
|
-
|
|
165
|
+
outcome: {
|
|
166
|
+
type: "string",
|
|
167
|
+
enum: ["success", "partial", "failed", "blocked"],
|
|
168
|
+
description: fieldGuideDescription("outcome"),
|
|
88
169
|
},
|
|
89
170
|
output_summary: {
|
|
90
171
|
type: "string",
|
|
@@ -104,10 +185,6 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
104
185
|
items: { type: "string" },
|
|
105
186
|
description: fieldGuideDescription("discovered_constraints"),
|
|
106
187
|
},
|
|
107
|
-
objective_refinement: {
|
|
108
|
-
type: "string",
|
|
109
|
-
description: fieldGuideDescription("objective_refinement"),
|
|
110
|
-
},
|
|
111
188
|
emerged_subtasks: {
|
|
112
189
|
type: "array",
|
|
113
190
|
items: { type: "string" },
|
|
@@ -122,38 +199,21 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
122
199
|
items: { type: "string" },
|
|
123
200
|
description: fieldGuideDescription("execution_report.files_changed"),
|
|
124
201
|
},
|
|
125
|
-
|
|
126
|
-
type: "object",
|
|
127
|
-
description: fieldGuideDescription("execution_report.tests_reported"),
|
|
128
|
-
properties: {
|
|
129
|
-
passed: { type: "integer", minimum: 0, description: "Number of passing tests." },
|
|
130
|
-
failed: { type: "integer", minimum: 0, description: "Number of failing tests." },
|
|
131
|
-
skipped: { type: "integer", minimum: 0, description: "Number of skipped tests." },
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
criterion_claims: {
|
|
202
|
+
claims: {
|
|
135
203
|
type: "array",
|
|
136
|
-
|
|
204
|
+
maxItems: 100,
|
|
205
|
+
description: fieldGuideDescription("execution_report.claims"),
|
|
137
206
|
items: {
|
|
138
207
|
type: "object",
|
|
139
208
|
properties: {
|
|
140
|
-
|
|
209
|
+
id: {
|
|
210
|
+
type: "string",
|
|
211
|
+
description: "cr-XXXXXXXX objective criterion id or rci-XXXXXXXX " +
|
|
212
|
+
"id of an item of the ACTIVE contract.",
|
|
213
|
+
},
|
|
141
214
|
outcome: { type: "string", enum: ["met", "remaining"] },
|
|
142
215
|
},
|
|
143
|
-
required: ["
|
|
144
|
-
additionalProperties: false,
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
contract_item_claims: {
|
|
148
|
-
type: "array",
|
|
149
|
-
description: fieldGuideDescription("execution_report.contract_item_claims"),
|
|
150
|
-
items: {
|
|
151
|
-
type: "object",
|
|
152
|
-
properties: {
|
|
153
|
-
item_id: { type: "string", description: "rci-XXXXXXXX id of an item of the ACTIVE contract." },
|
|
154
|
-
outcome: { type: "string", enum: ["met", "remaining"], description: "Your claim for that item this round." },
|
|
155
|
-
},
|
|
156
|
-
required: ["item_id", "outcome"],
|
|
216
|
+
required: ["id", "outcome"],
|
|
157
217
|
additionalProperties: false,
|
|
158
218
|
},
|
|
159
219
|
},
|
|
@@ -164,22 +224,26 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
164
224
|
items: { type: "string" },
|
|
165
225
|
description: fieldGuideDescription("retracted_constraints"),
|
|
166
226
|
},
|
|
167
|
-
revised_success_criteria: {
|
|
168
|
-
type: "array",
|
|
169
|
-
description: fieldGuideDescription("revised_success_criteria"),
|
|
170
|
-
items: {
|
|
171
|
-
type: "object",
|
|
172
|
-
properties: {
|
|
173
|
-
old: { type: "string", description: "Original criterion." },
|
|
174
|
-
new: { type: "string", description: "Revised criterion." },
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
227
|
wrong_assumptions: {
|
|
179
228
|
type: "array",
|
|
180
229
|
items: { type: "string" },
|
|
181
230
|
description: fieldGuideDescription("wrong_assumptions"),
|
|
182
231
|
},
|
|
232
|
+
open_questions: {
|
|
233
|
+
type: "array",
|
|
234
|
+
maxItems: 3,
|
|
235
|
+
items: { type: "string", maxLength: 300 },
|
|
236
|
+
description: fieldGuideDescription("open_questions"),
|
|
237
|
+
},
|
|
238
|
+
resolved_open_question_ids: {
|
|
239
|
+
type: "array",
|
|
240
|
+
maxItems: 20,
|
|
241
|
+
items: {
|
|
242
|
+
type: "string",
|
|
243
|
+
pattern: "^oq-[a-f0-9]{12}$",
|
|
244
|
+
},
|
|
245
|
+
description: fieldGuideDescription("resolved_open_question_ids"),
|
|
246
|
+
},
|
|
183
247
|
worker_results: {
|
|
184
248
|
type: "array",
|
|
185
249
|
description: fieldGuideDescription("worker_results"),
|
|
@@ -187,28 +251,23 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
187
251
|
type: "object",
|
|
188
252
|
properties: {
|
|
189
253
|
agentId: { type: "string" },
|
|
190
|
-
subAgentType: { type: "string" },
|
|
191
254
|
subTask: { type: "string" },
|
|
192
255
|
resultSummary: { type: "string" },
|
|
193
256
|
outcome: {
|
|
194
257
|
type: "string",
|
|
195
258
|
enum: ["success", "partial", "failed"],
|
|
196
259
|
},
|
|
197
|
-
discoveredConstraints: {
|
|
198
|
-
type: "array",
|
|
199
|
-
items: { type: "string" },
|
|
200
|
-
},
|
|
201
260
|
},
|
|
202
261
|
required: ["agentId", "subTask", "resultSummary", "outcome"],
|
|
203
262
|
},
|
|
204
263
|
},
|
|
205
|
-
|
|
264
|
+
subtask_boundary: {
|
|
206
265
|
type: "boolean",
|
|
207
|
-
description: fieldGuideDescription("
|
|
266
|
+
description: fieldGuideDescription("subtask_boundary"),
|
|
208
267
|
},
|
|
209
|
-
|
|
268
|
+
subtask_boundary_label: {
|
|
210
269
|
type: "string",
|
|
211
|
-
description: fieldGuideDescription("
|
|
270
|
+
description: fieldGuideDescription("subtask_boundary_label"),
|
|
212
271
|
},
|
|
213
272
|
subgoal_updates: {
|
|
214
273
|
type: "array",
|
|
@@ -233,22 +292,18 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
233
292
|
required: ["id", "status"],
|
|
234
293
|
},
|
|
235
294
|
},
|
|
236
|
-
stop_reason: {
|
|
237
|
-
type: "string",
|
|
238
|
-
enum: ["gave_up", "blocked", "needs_human_input"],
|
|
239
|
-
description: fieldGuideDescription("stop_reason"),
|
|
240
|
-
},
|
|
241
295
|
no_change_reason: {
|
|
242
296
|
type: "string",
|
|
243
297
|
description: fieldGuideDescription("no_change_reason"),
|
|
244
298
|
},
|
|
245
|
-
|
|
299
|
+
objective_refinement: {
|
|
246
300
|
type: "string",
|
|
247
|
-
|
|
248
|
-
description: fieldGuideDescription("
|
|
301
|
+
maxLength: 1000,
|
|
302
|
+
description: fieldGuideDescription("objective_refinement"),
|
|
249
303
|
},
|
|
250
304
|
blocker: {
|
|
251
305
|
type: "string",
|
|
306
|
+
maxLength: 500,
|
|
252
307
|
description: fieldGuideDescription("blocker"),
|
|
253
308
|
},
|
|
254
309
|
gate_ids: {
|
|
@@ -270,54 +325,53 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
270
325
|
},
|
|
271
326
|
description: fieldGuideDescription("retroactiveClaims"),
|
|
272
327
|
},
|
|
273
|
-
prompt_requests: {
|
|
274
|
-
type: "object",
|
|
275
|
-
description: fieldGuideDescription("prompt_requests"),
|
|
276
|
-
properties: {
|
|
277
|
-
emphasize: {
|
|
278
|
-
type: "array",
|
|
279
|
-
items: { type: "string" },
|
|
280
|
-
description: "Constraints, discoveries, or decisions that need emphasis in the next prompt. Each entry is matched against active state items by STABLE ID (c-/cr-/sg-XXXXXXXX) or by exact text after normalization — a paraphrase matches nothing and renders nothing. Matched items appear in a 'Critical Context' section. Max 5 entries. Pure reordering — no token overhead.",
|
|
281
|
-
},
|
|
282
|
-
confusion_points: {
|
|
283
|
-
type: "array",
|
|
284
|
-
items: { type: "string" },
|
|
285
|
-
description: "Things you're confused about. Rendered at the TOP of the next prompt before the objective. Max 3 entries, each ≤ 200 chars recommended. Be specific — 'I don't understand why milestone 3 is marked complete when phase 2 appears unfinished.'",
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
328
|
round_contract: {
|
|
290
329
|
type: "object",
|
|
291
330
|
description: fieldGuideDescription("round_contract"),
|
|
292
331
|
properties: {
|
|
293
332
|
work_item: {
|
|
294
333
|
type: "string",
|
|
295
|
-
|
|
334
|
+
minLength: 1,
|
|
335
|
+
maxLength: 200,
|
|
336
|
+
description: "Required. The bounded deliverable this contract slice " +
|
|
337
|
+
"produces — an outcome, not an activity. Rendered as the " +
|
|
338
|
+
"Current Task heading. 1–200 chars.",
|
|
296
339
|
},
|
|
297
340
|
items: {
|
|
298
341
|
type: "array",
|
|
299
|
-
|
|
342
|
+
minItems: 1,
|
|
343
|
+
maxItems: 20,
|
|
344
|
+
description: "Required, at least one (max 20). Each item is an " +
|
|
345
|
+
"acceptance condition for the deliverable, bound to its own " +
|
|
346
|
+
"evidence commands. The runtime derives a stable " +
|
|
347
|
+
"rci-XXXXXXXX id per item and renders it in the prompt — " +
|
|
348
|
+
"cite that id in execution_report.claims.",
|
|
300
349
|
items: {
|
|
301
350
|
type: "object",
|
|
302
351
|
properties: {
|
|
303
352
|
description: {
|
|
304
353
|
type: "string",
|
|
305
|
-
|
|
354
|
+
minLength: 1,
|
|
355
|
+
maxLength: 200,
|
|
356
|
+
description: "What must be true for this acceptance condition. " +
|
|
357
|
+
"1–200 chars.",
|
|
306
358
|
},
|
|
307
359
|
criterion_refs: {
|
|
308
360
|
type: "array",
|
|
361
|
+
maxItems: 20,
|
|
309
362
|
items: { type: "string" },
|
|
310
363
|
description: "Optional. Real cr-XXXXXXXX ids of ACTIVE criteria rendered in this prompt. Free text and unknown ids are rejected. Max 20.",
|
|
311
364
|
},
|
|
312
|
-
subgoal_refs: {
|
|
313
|
-
type: "array",
|
|
314
|
-
items: { type: "string" },
|
|
315
|
-
description: "Optional. sg-XXXXXXXX ids of ACTIVE sub-goals displayed in this prompt. A sub-goal created by this same submission's emerged_subtasks becomes referenceable next round. Max 20 ids. A machine-verified item produces the derived verified-subgoal fact for exactly the ids it names here.",
|
|
316
|
-
},
|
|
317
365
|
verify_with: {
|
|
318
366
|
type: "array",
|
|
367
|
+
minItems: 1,
|
|
368
|
+
maxItems: 20,
|
|
319
369
|
items: { type: "string" },
|
|
320
|
-
description: "Required, at least one. Names of configured,
|
|
370
|
+
description: "Required, at least one. Names of configured, " +
|
|
371
|
+
"enabled, after-capable evidence.commands " +
|
|
372
|
+
"(loop_policy.json) that check this acceptance " +
|
|
373
|
+
"condition. Unknown or disabled names are rejected. A " +
|
|
374
|
+
"command may be shared by multiple conditions.",
|
|
321
375
|
},
|
|
322
376
|
},
|
|
323
377
|
required: ["description", "verify_with"],
|
|
@@ -326,31 +380,87 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
326
380
|
},
|
|
327
381
|
scope: {
|
|
328
382
|
type: "array",
|
|
383
|
+
maxItems: 50,
|
|
329
384
|
items: { type: "string" },
|
|
330
|
-
description: "
|
|
385
|
+
description: "Optional, report-only. The files/directories you intend " +
|
|
386
|
+
"this slice to touch. Omission and [] both mean no declared " +
|
|
387
|
+
"intent. It is NOT enforced and is not part of the rc- id: " +
|
|
388
|
+
"out-of-scope changes are reported as an info fact and " +
|
|
389
|
+
"never reject the round. Max 50 items.",
|
|
390
|
+
},
|
|
391
|
+
supersedes: {
|
|
392
|
+
type: "object",
|
|
393
|
+
description: "Optional redirect modifier. Replaces the named ACTIVE " +
|
|
394
|
+
"contract with this proposal's slice; its acceptance items " +
|
|
395
|
+
"must differ. The old contract closes as superseded. Not " +
|
|
396
|
+
"allowed with should_continue=false or outcome=blocked.",
|
|
397
|
+
properties: {
|
|
398
|
+
contract_id: {
|
|
399
|
+
type: "string",
|
|
400
|
+
pattern: "^rc-[a-f0-9]{8}$",
|
|
401
|
+
description: "The rc-XXXXXXXX id of the ACTIVE contract rendered in " +
|
|
402
|
+
"this prompt. A stale/other id is rejected.",
|
|
403
|
+
},
|
|
404
|
+
reason: {
|
|
405
|
+
type: "string",
|
|
406
|
+
maxLength: 500,
|
|
407
|
+
description: "Optional Agent report, recorded verbatim for audit only.",
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
required: ["contract_id"],
|
|
411
|
+
additionalProperties: false,
|
|
331
412
|
},
|
|
332
413
|
},
|
|
333
|
-
required: ["
|
|
414
|
+
required: ["work_item", "items"],
|
|
415
|
+
additionalProperties: false,
|
|
334
416
|
},
|
|
335
417
|
},
|
|
336
418
|
},
|
|
337
419
|
},
|
|
338
|
-
required: ["
|
|
420
|
+
required: ["submissionHandle"],
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: "loopforge_checkpoint",
|
|
425
|
+
description: "v3.9.7: Create an explicit mid-round workspace checkpoint (savepoint). " +
|
|
426
|
+
"The call itself is the Agent's decision that this state is worth " +
|
|
427
|
+
"restoring to. It creates no round, no commit and no progress; the " +
|
|
428
|
+
"checkpoint becomes a visible recovery target only through the held " +
|
|
429
|
+
"transaction's ref list. Requires the same submissionHandle as " +
|
|
430
|
+
"loopforge_next; the handle is unchanged after a successful checkpoint.",
|
|
431
|
+
inputSchema: {
|
|
432
|
+
type: "object",
|
|
433
|
+
properties: {
|
|
434
|
+
submissionHandle: SUBMISSION_HANDLE_SCHEMA,
|
|
435
|
+
},
|
|
436
|
+
required: ["submissionHandle"],
|
|
437
|
+
additionalProperties: false,
|
|
339
438
|
},
|
|
340
439
|
},
|
|
341
440
|
{
|
|
342
441
|
name: "loopforge_status",
|
|
343
|
-
description: "
|
|
442
|
+
description: "Read-only inspection. view=session (default): live session status, " +
|
|
443
|
+
"round identity, the current held submissionHandle when one exists, " +
|
|
444
|
+
"success trajectory, active contract, current guidance and the latest " +
|
|
445
|
+
"committed receipt. It never returns the held prompt; a paused or " +
|
|
446
|
+
"stalled session must resume before submitting. view=loop: committed " +
|
|
447
|
+
"round counts, unverified-item rounds and policy metrics. view=all: " +
|
|
448
|
+
"summary of every persisted loop. view=audit: end-of-loop verification " +
|
|
449
|
+
"audit. view=explain: per-round \"why\" view over committed facts.",
|
|
344
450
|
inputSchema: {
|
|
345
451
|
type: "object",
|
|
346
452
|
properties: {
|
|
347
453
|
sessionId: {
|
|
348
454
|
type: "string",
|
|
349
|
-
description: "
|
|
455
|
+
description: "Live session ID returned by loopforge_start or loopforge_resume. " +
|
|
456
|
+
"Resolvable only in the current process; after a restart use loopId. " +
|
|
457
|
+
"view=session returns the current held submissionHandle.",
|
|
350
458
|
},
|
|
351
459
|
loopId: {
|
|
352
460
|
type: "string",
|
|
353
|
-
description: "
|
|
461
|
+
description: "Durable loop ID. Required for views loop/audit/explain; also " +
|
|
462
|
+
"accepted for view=session, which resolves it to the live session " +
|
|
463
|
+
"and returns its current held submissionHandle.",
|
|
354
464
|
},
|
|
355
465
|
view: {
|
|
356
466
|
type: "string",
|
|
@@ -367,7 +477,8 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
367
477
|
},
|
|
368
478
|
{
|
|
369
479
|
name: "loopforge_stop",
|
|
370
|
-
description: "Manually stop a loop session and return its final trajectory."
|
|
480
|
+
description: "Manually stop a loop session and return its final trajectory. The " +
|
|
481
|
+
"returned `success` is tool-call success, not a task-success verdict.",
|
|
371
482
|
inputSchema: {
|
|
372
483
|
type: "object",
|
|
373
484
|
properties: {
|
|
@@ -381,7 +492,7 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
381
492
|
},
|
|
382
493
|
{
|
|
383
494
|
name: "loopforge_pause",
|
|
384
|
-
description: "Pause a running loop session
|
|
495
|
+
description: "Pause a running loop session: it immediately stops accepting submissions (an in-flight advance may still finish) and its state is persisted to vault. Paused loops can be resumed with loopforge_resume. Use this to interrupt a long-running loop without losing progress.",
|
|
385
496
|
inputSchema: {
|
|
386
497
|
type: "object",
|
|
387
498
|
properties: {
|
|
@@ -395,10 +506,12 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
395
506
|
},
|
|
396
507
|
{
|
|
397
508
|
name: "loopforge_replay",
|
|
398
|
-
description: "Replay
|
|
399
|
-
"
|
|
400
|
-
"
|
|
401
|
-
"
|
|
509
|
+
description: "Replay committed rounds as an auditable timeline. Pass sessionId for a " +
|
|
510
|
+
"live session, or loopId to replay straight from the vault (works after " +
|
|
511
|
+
"a process restart). Returns the last 10 committed rounds by default; " +
|
|
512
|
+
"pass start/end for an explicit window (at most 50 rounds). Each round " +
|
|
513
|
+
"carries its context boundary, committed report, assumptions, open " +
|
|
514
|
+
"questions, blocker and contract redirect. Read-only projection.",
|
|
402
515
|
inputSchema: {
|
|
403
516
|
type: "object",
|
|
404
517
|
properties: {
|
|
@@ -411,6 +524,16 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
411
524
|
description: "Loop ID to replay from the vault — works without a " +
|
|
412
525
|
"live session, e.g. after a restart.",
|
|
413
526
|
},
|
|
527
|
+
start: {
|
|
528
|
+
type: "integer",
|
|
529
|
+
minimum: 1,
|
|
530
|
+
description: "First round of the explicit window (inclusive).",
|
|
531
|
+
},
|
|
532
|
+
end: {
|
|
533
|
+
type: "integer",
|
|
534
|
+
minimum: 1,
|
|
535
|
+
description: "Last round of the explicit window (inclusive).",
|
|
536
|
+
},
|
|
414
537
|
},
|
|
415
538
|
// v3.3.1: either identifier resolves the timeline; sessionId wins when
|
|
416
539
|
// both are given (it also disambiguates re-used loopIds).
|
|
@@ -419,7 +542,20 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
419
542
|
},
|
|
420
543
|
{
|
|
421
544
|
name: "loopforge_resume",
|
|
422
|
-
description: "Resume a loop from vault state.
|
|
545
|
+
description: "Resume a loop from vault state. Requires the durable loopId returned by " +
|
|
546
|
+
"loopforge_start (not sessionId). Use this when the held prompt was lost, " +
|
|
547
|
+
"no live session exists, or the session is paused/stalled; if only the " +
|
|
548
|
+
"submissionHandle was lost while the prompt is still in context, prefer " +
|
|
549
|
+
"loopforge_status. Returns the held prompt and its current " +
|
|
550
|
+
"submissionHandle, or a newly compiled prompt when none is held. A " +
|
|
551
|
+
"terminal loop returns no prompt, the committed stopReason when one was " +
|
|
552
|
+
"committed, and/or sessionStatus (paused/stopped/stalled); a stopped " +
|
|
553
|
+
"loop without a committed stop round reports sessionStatus only. " +
|
|
554
|
+
"v4.0.5: pass invocationLost=true when the original in-context retry " +
|
|
555
|
+
"invocation no longer exists; LoopForge recompiles the SAME logical " +
|
|
556
|
+
"round and attempt as a cold-context invocation_recovery prompt. " +
|
|
557
|
+
"currentUserInstruction carries the host's current instruction into " +
|
|
558
|
+
"that recompile or redelivery.",
|
|
423
559
|
inputSchema: {
|
|
424
560
|
type: "object",
|
|
425
561
|
properties: {
|
|
@@ -427,6 +563,24 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
427
563
|
type: "string",
|
|
428
564
|
description: "Loop ID to resume. Must have a saved session_state entry from a previous start/run.",
|
|
429
565
|
},
|
|
566
|
+
invocationLost: {
|
|
567
|
+
type: "boolean",
|
|
568
|
+
description: "v4.0.5: set true when the invocation that received the held " +
|
|
569
|
+
"same_round_retry prompt is gone. A held retry is then recompiled " +
|
|
570
|
+
"as fresh_invocation / invocation_recovery for the SAME round and " +
|
|
571
|
+
"attempt (L2, with Machine Evidence, Working Memory and rejection " +
|
|
572
|
+
"lessons). On an already clean-context prompt this is an " +
|
|
573
|
+
"idempotent no-op.",
|
|
574
|
+
},
|
|
575
|
+
currentUserInstruction: {
|
|
576
|
+
type: "string",
|
|
577
|
+
description: "v4.0.5: the host's current user instruction, trimmed and " +
|
|
578
|
+
"non-empty. It is invocation input only; it never rewrites " +
|
|
579
|
+
"session.task, the Objective, criteria, constraints or the Round " +
|
|
580
|
+
"Contract. An instruction identical (trimmed, case-sensitive) to " +
|
|
581
|
+
"the held prompt's is idempotent; a different one recompiles the " +
|
|
582
|
+
"same round/attempt with a new handle.",
|
|
583
|
+
},
|
|
430
584
|
},
|
|
431
585
|
required: ["loopId"],
|
|
432
586
|
},
|
|
@@ -442,8 +596,7 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
442
596
|
inputSchema: {
|
|
443
597
|
type: "object",
|
|
444
598
|
properties: {
|
|
445
|
-
|
|
446
|
-
roundId: { type: "string", description: "RoundId from the latest response." },
|
|
599
|
+
submissionHandle: SUBMISSION_HANDLE_SCHEMA,
|
|
447
600
|
action: {
|
|
448
601
|
type: "object",
|
|
449
602
|
description: "The action the agent plans to perform.",
|
|
@@ -480,7 +633,7 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
480
633
|
required: ["description", "scope", "effects", "reversibility", "authorization"],
|
|
481
634
|
},
|
|
482
635
|
},
|
|
483
|
-
required: ["
|
|
636
|
+
required: ["submissionHandle", "action"],
|
|
484
637
|
},
|
|
485
638
|
},
|
|
486
639
|
{
|
|
@@ -519,19 +672,79 @@ const ADVANCE_OUTPUT_SCHEMA = {
|
|
|
519
672
|
type: "object",
|
|
520
673
|
properties: {
|
|
521
674
|
ok: { type: "boolean", description: "v3.8: uniform result envelope — false means the error object describes a rejected call." },
|
|
522
|
-
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?,
|
|
675
|
+
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?, submissionHandle?, details? }" },
|
|
523
676
|
details: { type: "object", additionalProperties: true },
|
|
524
677
|
sessionId: { type: "string" },
|
|
678
|
+
loopId: {
|
|
679
|
+
type: "string",
|
|
680
|
+
description: "Durable loop identity. Returned with every held prompt so a restarted " +
|
|
681
|
+
"agent can call loopforge_resume without reconstructing IDs.",
|
|
682
|
+
},
|
|
525
683
|
round: { type: "number" },
|
|
526
|
-
|
|
684
|
+
submissionHandle: SUBMISSION_HANDLE_OUTPUT_SCHEMA,
|
|
527
685
|
prompt: { type: ["string", "null"] },
|
|
528
|
-
stopReason: {
|
|
529
|
-
|
|
686
|
+
stopReason: {
|
|
687
|
+
type: ["string", "null"],
|
|
688
|
+
enum: [
|
|
689
|
+
"completed", "failed", "blocked", "cancelled", "max_rounds",
|
|
690
|
+
"stalled", "enforcement_terminated", "paused", "incomplete", null,
|
|
691
|
+
],
|
|
692
|
+
description: "v4.0.4: LoopForge's terminal reason. Never a session status, a tool " +
|
|
693
|
+
"error or the Agent's round outcome.",
|
|
694
|
+
},
|
|
695
|
+
sessionStatus: {
|
|
696
|
+
enum: ["running", "stopped", "paused", "stalled"],
|
|
697
|
+
description: "v4.0.4: the live session status when the result is not a committed " +
|
|
698
|
+
"loop terminal reason.",
|
|
699
|
+
},
|
|
700
|
+
stopDetail: { type: ["string", "null"], description: "Human-readable context for why the loop stopped. Three fact blocks: Agent report, LoopForge decision, Machine evidence. Provides facts without prescribing a specific agent action." },
|
|
530
701
|
level: { type: ["string", "null"] },
|
|
531
|
-
|
|
532
|
-
|
|
702
|
+
contextMode: {
|
|
703
|
+
enum: ["fresh_invocation", "same_round_retry"],
|
|
704
|
+
description: CONTEXT_MODE_DESCRIPTION,
|
|
705
|
+
},
|
|
706
|
+
contextReason: {
|
|
707
|
+
// v4.0.5: the ONE runtime whitelist; a new reason cannot drift between
|
|
708
|
+
// the protocol, the decoder and this output schema.
|
|
709
|
+
enum: [...PROMPT_CONTEXT_REASONS],
|
|
710
|
+
description: "v4.0.2: why the context boundary was selected.",
|
|
711
|
+
},
|
|
712
|
+
agentSuccessClaim: { type: ["boolean", "null"] },
|
|
713
|
+
agentOutcome: {
|
|
714
|
+
type: ["string", "null"],
|
|
715
|
+
enum: ["success", "partial", "failed", "blocked", null],
|
|
716
|
+
description: "v4.0.6: the Agent-declared round outcome.",
|
|
717
|
+
},
|
|
718
|
+
agentShouldContinue: {
|
|
719
|
+
type: ["boolean", "null"],
|
|
720
|
+
description: "v4.0.4: the Agent's declared should_continue for that same round.",
|
|
721
|
+
},
|
|
722
|
+
checkpoints: {
|
|
723
|
+
type: ["array", "null"],
|
|
724
|
+
items: { type: "object", additionalProperties: true },
|
|
725
|
+
description: "v3.9.7: the held transaction's checkpoint refs (before-image + explicit mid savepoints).",
|
|
726
|
+
},
|
|
727
|
+
beforeCheckpointError: {
|
|
728
|
+
type: ["object", "null"],
|
|
729
|
+
additionalProperties: true,
|
|
730
|
+
description: "v3.9.7: explicit machine fact when the automatic before-image could not be captured.",
|
|
731
|
+
},
|
|
732
|
+
enforcementAction: { enum: ["accept", "reject", "terminate", "backtrack", "recovery"] },
|
|
533
733
|
enforcementReason: { type: ["string", "null"] },
|
|
534
|
-
|
|
734
|
+
recovery: {
|
|
735
|
+
type: ["object", "null"],
|
|
736
|
+
additionalProperties: true,
|
|
737
|
+
description: "v3.9.7: committed rollback directive facts and the verified restore that produced the redo prompt.",
|
|
738
|
+
},
|
|
739
|
+
evidencePosture: {
|
|
740
|
+
type: ["object", "null"],
|
|
741
|
+
additionalProperties: true,
|
|
742
|
+
description: "v4.0.3: the ONE evidence posture of the round this result is about " +
|
|
743
|
+
"(status, machineBackedSuccess, verificationConfigured, workspace, " +
|
|
744
|
+
"files reconciliation, providers, commands, contract snapshot, " +
|
|
745
|
+
"findings and omitted counts). Accepted rounds carry the persisted " +
|
|
746
|
+
"posture; a rejection carries its transient copy.",
|
|
747
|
+
},
|
|
535
748
|
submission_guidance: {
|
|
536
749
|
type: ["object", "null"],
|
|
537
750
|
additionalProperties: true,
|
|
@@ -547,11 +760,6 @@ const ADVANCE_OUTPUT_SCHEMA = {
|
|
|
547
760
|
"Returned by loopforge_next only.",
|
|
548
761
|
},
|
|
549
762
|
warnings: { type: "array", items: { type: "string" } },
|
|
550
|
-
projection: {
|
|
551
|
-
type: "object",
|
|
552
|
-
additionalProperties: true,
|
|
553
|
-
description: "v2.12: Typed cognitive state projection (focus/todo/phase/delegation/handoff).",
|
|
554
|
-
},
|
|
555
763
|
},
|
|
556
764
|
additionalProperties: true,
|
|
557
765
|
};
|
|
@@ -559,22 +767,59 @@ const ADVANCE_OUTPUT_SCHEMA = {
|
|
|
559
767
|
* validates each handler's output against these before returning it —
|
|
560
768
|
* a mismatch is a contract bug, surfaced as a JSON-RPC error instead of
|
|
561
769
|
* silently shipping an output that violates the declared schema. */
|
|
770
|
+
const CHECKPOINT_OUTPUT_SCHEMA = {
|
|
771
|
+
type: "object",
|
|
772
|
+
properties: {
|
|
773
|
+
ok: { type: "boolean" },
|
|
774
|
+
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?, submissionHandle?, details? }" },
|
|
775
|
+
sessionId: { type: "string" },
|
|
776
|
+
submissionHandle: SUBMISSION_HANDLE_SCHEMA,
|
|
777
|
+
checkpointId: { type: "string" },
|
|
778
|
+
round: { type: "number" },
|
|
779
|
+
role: { const: "mid" },
|
|
780
|
+
redoRound: { type: "number" },
|
|
781
|
+
createdAt: { type: "number" },
|
|
782
|
+
head: { type: ["string", "null"] },
|
|
783
|
+
fileCount: { type: "number" },
|
|
784
|
+
contentCount: { type: "number" },
|
|
785
|
+
contentHash: { type: "string" },
|
|
786
|
+
},
|
|
787
|
+
additionalProperties: true,
|
|
788
|
+
};
|
|
562
789
|
export const TOOL_OUTPUT_SCHEMAS = {
|
|
563
790
|
loopforge_start: ADVANCE_OUTPUT_SCHEMA,
|
|
564
791
|
loopforge_next: ADVANCE_OUTPUT_SCHEMA,
|
|
565
792
|
loopforge_resume: ADVANCE_OUTPUT_SCHEMA,
|
|
793
|
+
loopforge_checkpoint: CHECKPOINT_OUTPUT_SCHEMA,
|
|
566
794
|
loopforge_status: {
|
|
567
795
|
type: "object",
|
|
568
796
|
properties: {
|
|
569
797
|
ok: { type: "boolean", description: "v3.8: uniform result envelope — false means the error object describes a rejected call." },
|
|
570
|
-
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?,
|
|
798
|
+
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?, submissionHandle?, details? }" },
|
|
571
799
|
sessionId: { type: "string" },
|
|
572
800
|
loopId: { type: "string" },
|
|
573
801
|
round: { type: "number" },
|
|
574
|
-
|
|
802
|
+
submissionHandle: SUBMISSION_HANDLE_OUTPUT_SCHEMA,
|
|
803
|
+
checkpoints: { type: ["array", "null"], items: { type: "object", additionalProperties: true } },
|
|
804
|
+
beforeCheckpointError: { type: ["object", "null"], additionalProperties: true },
|
|
575
805
|
maxRounds: { type: "number" },
|
|
576
806
|
status: { enum: ["running", "stopped", "stalled", "paused"] },
|
|
577
|
-
|
|
807
|
+
// v4.0.4: a stopped loop's committed terminal facts are readable from
|
|
808
|
+
// view=session without calling resume.
|
|
809
|
+
loopStopReason: {
|
|
810
|
+
type: ["string", "null"],
|
|
811
|
+
enum: [
|
|
812
|
+
"completed", "failed", "blocked", "cancelled", "max_rounds",
|
|
813
|
+
"stalled", "enforcement_terminated", "paused", "incomplete", null,
|
|
814
|
+
],
|
|
815
|
+
},
|
|
816
|
+
agentShouldContinue: { type: ["boolean", "null"] },
|
|
817
|
+
stopDetail: { type: ["string", "null"] },
|
|
818
|
+
machineBackedSuccessTrajectory: { type: "array", items: { type: "boolean" } },
|
|
819
|
+
evidencePosture: { type: ["object", "null"], additionalProperties: true },
|
|
820
|
+
machineBackedSuccess: { type: ["boolean", "null"] },
|
|
821
|
+
agentSuccessClaim: { type: ["boolean", "null"] },
|
|
822
|
+
agentOutcome: { type: ["string", "null"] },
|
|
578
823
|
lease: { type: ["object", "null"], additionalProperties: true },
|
|
579
824
|
metrics: { type: "object", additionalProperties: true },
|
|
580
825
|
// v3.5: the ACTIVE Round Contract governing the next round (derived
|
|
@@ -592,10 +837,16 @@ export const TOOL_OUTPUT_SCHEMAS = {
|
|
|
592
837
|
type: "object",
|
|
593
838
|
properties: {
|
|
594
839
|
ok: { type: "boolean", description: "v3.8: uniform result envelope — false means the error object describes a rejected call." },
|
|
595
|
-
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?,
|
|
596
|
-
success: { type: "boolean" },
|
|
597
|
-
|
|
598
|
-
|
|
840
|
+
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?, submissionHandle?, details? }" },
|
|
841
|
+
success: { type: "boolean", description: "Tool-call success, not a task-success verdict." },
|
|
842
|
+
sessionId: { type: "string" },
|
|
843
|
+
loopId: { type: "string" },
|
|
844
|
+
roundsCompleted: { type: "number", description: "Deprecated alias of roundCursor (the session round cursor, not a committed count)." },
|
|
845
|
+
roundCursor: { type: "number", description: "The session's current round cursor." },
|
|
846
|
+
sessionStatus: { const: "stopped" },
|
|
847
|
+
loopStopReason: { const: MANUAL_STOP_REASON, description: "The manual-cancel terminal reason sent to the terminal sink; not a committed round fact." },
|
|
848
|
+
machineBackedSuccessRounds: { type: "number", description: "Machine-backed successful rounds (same committed-view source as the trajectory)." },
|
|
849
|
+
machineBackedSuccessTrajectory: { type: "array", items: { type: "boolean" } },
|
|
599
850
|
},
|
|
600
851
|
additionalProperties: true,
|
|
601
852
|
},
|
|
@@ -603,7 +854,7 @@ export const TOOL_OUTPUT_SCHEMAS = {
|
|
|
603
854
|
type: "object",
|
|
604
855
|
properties: {
|
|
605
856
|
ok: { type: "boolean", description: "v3.8: uniform result envelope — false means the error object describes a rejected call." },
|
|
606
|
-
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?,
|
|
857
|
+
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?, submissionHandle?, details? }" },
|
|
607
858
|
sessionId: { type: "string" },
|
|
608
859
|
round: { type: "number" },
|
|
609
860
|
status: { type: "string" },
|
|
@@ -614,10 +865,16 @@ export const TOOL_OUTPUT_SCHEMAS = {
|
|
|
614
865
|
type: "object",
|
|
615
866
|
properties: {
|
|
616
867
|
ok: { type: "boolean", description: "v3.8: uniform result envelope — false means the error object describes a rejected call." },
|
|
617
|
-
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?,
|
|
868
|
+
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?, submissionHandle?, details? }" },
|
|
618
869
|
sessionId: { type: "string" },
|
|
619
870
|
loopId: { type: "string" },
|
|
620
871
|
timeline: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
872
|
+
window: {
|
|
873
|
+
type: "object",
|
|
874
|
+
additionalProperties: true,
|
|
875
|
+
description: "v4.0.2: the returned round window: { start, end, totalRounds, " +
|
|
876
|
+
"hasBefore, hasAfter }.",
|
|
877
|
+
},
|
|
621
878
|
legacyRounds: {
|
|
622
879
|
type: "array",
|
|
623
880
|
items: { type: "object", additionalProperties: true },
|
|
@@ -631,8 +888,8 @@ export const TOOL_OUTPUT_SCHEMAS = {
|
|
|
631
888
|
type: "object",
|
|
632
889
|
properties: {
|
|
633
890
|
ok: { type: "boolean", description: "v3.8: uniform result envelope — false means the error object describes a rejected call." },
|
|
634
|
-
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?,
|
|
635
|
-
|
|
891
|
+
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?, submissionHandle?, details? }" },
|
|
892
|
+
submissionHandle: SUBMISSION_HANDLE_SCHEMA,
|
|
636
893
|
gateId: { type: "string" },
|
|
637
894
|
risk: { type: "string", enum: ["low", "high", "unknown"] },
|
|
638
895
|
decision: { type: "string", enum: ["agent_allowed", "user_required"] },
|
|
@@ -648,7 +905,7 @@ export const TOOL_OUTPUT_SCHEMAS = {
|
|
|
648
905
|
type: "object",
|
|
649
906
|
properties: {
|
|
650
907
|
ok: { type: "boolean", description: "v3.8: uniform result envelope — false means the error object describes a rejected call." },
|
|
651
|
-
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?,
|
|
908
|
+
error: { type: "object", additionalProperties: true, description: "v3.8 ToolError: { code, message, retryable, sessionId?, submissionHandle?, details? }" },
|
|
652
909
|
sessionId: { type: "string" },
|
|
653
910
|
loopId: { type: "string" },
|
|
654
911
|
gateId: { type: "string" },
|
|
@@ -673,12 +930,6 @@ function closeObjectSchemas(schema) {
|
|
|
673
930
|
}
|
|
674
931
|
return result;
|
|
675
932
|
}
|
|
676
|
-
const CORE_EVALUATION_FIELDS = new Set([
|
|
677
|
-
"success",
|
|
678
|
-
"output_summary",
|
|
679
|
-
"constraint_violations",
|
|
680
|
-
"should_continue",
|
|
681
|
-
]);
|
|
682
933
|
/** Keep the four authoritative fields schema-strict while leaving optional
|
|
683
934
|
* reporting fields available for buildSelfEvaluation's lossy normalization.
|
|
684
935
|
* Their descriptions remain in tools/list, but malformed optional values do
|
|
@@ -691,7 +942,7 @@ function prepareInputSchema(name, schema) {
|
|
|
691
942
|
if (!isRecord(evaluation) || !isRecord(evaluation.properties))
|
|
692
943
|
return result;
|
|
693
944
|
evaluation.properties = Object.fromEntries(Object.entries(evaluation.properties).map(([field, value]) => {
|
|
694
|
-
if (
|
|
945
|
+
if (CORE_SELF_EVALUATION_FIELDS.has(field) || !isRecord(value))
|
|
695
946
|
return [field, value];
|
|
696
947
|
return [field, typeof value.description === "string"
|
|
697
948
|
? { description: value.description }
|
|
@@ -814,7 +1065,7 @@ export function validateToolDispatchInput(name, input) {
|
|
|
814
1065
|
: {};
|
|
815
1066
|
validateSchema(input, {
|
|
816
1067
|
...contract.inputSchema,
|
|
817
|
-
required: ["
|
|
1068
|
+
required: ["submissionHandle"],
|
|
818
1069
|
properties: { ...properties, evaluation: {} },
|
|
819
1070
|
}, "arguments");
|
|
820
1071
|
if (!isRecord(input.evaluation))
|
|
@@ -855,13 +1106,51 @@ function projectionErrorResult(error) {
|
|
|
855
1106
|
}
|
|
856
1107
|
return null;
|
|
857
1108
|
}
|
|
1109
|
+
/** Stable state-integrity errors. They are never retryable with the same
|
|
1110
|
+
* payload: the loop data or the operator surface must be repaired first. */
|
|
1111
|
+
function stateErrorResult(error) {
|
|
1112
|
+
if (error instanceof LegacyHistoryError) {
|
|
1113
|
+
return {
|
|
1114
|
+
error: error.code,
|
|
1115
|
+
errorMessage: error.message,
|
|
1116
|
+
details: { loopId: error.loopId, rounds: error.rounds },
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
if (error instanceof OperatorAcceptanceConflictError) {
|
|
1120
|
+
return {
|
|
1121
|
+
error: error.code,
|
|
1122
|
+
errorMessage: error.message,
|
|
1123
|
+
details: { loopId: error.loopId },
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
return null;
|
|
1127
|
+
}
|
|
858
1128
|
export const TOOL_HANDLERS = {
|
|
859
1129
|
async loopforge_start(mgr, input) {
|
|
1130
|
+
// A removed or misspelled start argument must not disappear silently: the
|
|
1131
|
+
// schema is permissive about extra top-level keys, and the handler used to
|
|
1132
|
+
// rebuild StartInput from a whitelist. Without this guard a stale manual
|
|
1133
|
+
// (for example one still naming `domain`) produces a successful start with
|
|
1134
|
+
// part of the caller's intent ignored.
|
|
1135
|
+
const unknownStartArgs = Object.keys(input).filter((key) => !START_INPUT_KEYS.includes(key));
|
|
1136
|
+
if (unknownStartArgs.length > 0) {
|
|
1137
|
+
const retired = unknownStartArgs.includes("domain")
|
|
1138
|
+
? " `domain` was removed in 4.0.1."
|
|
1139
|
+
: "";
|
|
1140
|
+
return {
|
|
1141
|
+
error: "invalid_argument",
|
|
1142
|
+
errorMessage: `unknown loopforge_start argument(s): ${unknownStartArgs.join(", ")}.` +
|
|
1143
|
+
`${retired} Accepted: ${START_INPUT_KEYS.join(", ")}.`,
|
|
1144
|
+
details: {
|
|
1145
|
+
unknown: unknownStartArgs,
|
|
1146
|
+
accepted: [...START_INPUT_KEYS],
|
|
1147
|
+
},
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
860
1150
|
const startInput = {
|
|
861
1151
|
task: String(input.task ?? ""),
|
|
862
1152
|
loopId: input.loopId,
|
|
863
1153
|
maxRounds: typeof input.maxRounds === "number" ? input.maxRounds : undefined,
|
|
864
|
-
domain: input.domain,
|
|
865
1154
|
planSource: input.planSource,
|
|
866
1155
|
constraints: Array.isArray(input.constraints)
|
|
867
1156
|
? input.constraints
|
|
@@ -920,20 +1209,34 @@ export const TOOL_HANDLERS = {
|
|
|
920
1209
|
const projectionFailure = projectionErrorResult(error);
|
|
921
1210
|
if (projectionFailure)
|
|
922
1211
|
return projectionFailure;
|
|
1212
|
+
const stateFailure = stateErrorResult(error);
|
|
1213
|
+
if (stateFailure)
|
|
1214
|
+
return stateFailure;
|
|
1215
|
+
if (error instanceof LoopExistsError) {
|
|
1216
|
+
return {
|
|
1217
|
+
error: error.code,
|
|
1218
|
+
errorMessage: error.message,
|
|
1219
|
+
details: { loopId: error.loopId, reason: error.reason },
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
if (error instanceof StartFailureError) {
|
|
1223
|
+
return {
|
|
1224
|
+
error: error.code,
|
|
1225
|
+
errorMessage: error.message,
|
|
1226
|
+
details: { retry: "call loopforge_start again" },
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
if (error instanceof SessionLeaseConflictError) {
|
|
1230
|
+
return {
|
|
1231
|
+
error: "lease_conflict",
|
|
1232
|
+
errorMessage: `loop "${error.loopId}" is owned by another running LoopForge process`,
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
923
1235
|
return {
|
|
924
1236
|
error: "policy_invalid",
|
|
925
1237
|
errorMessage: `the runtime policy could not be loaded: ${error.message}`,
|
|
926
1238
|
};
|
|
927
1239
|
}
|
|
928
|
-
// v2.14: a same-process duplicate loopId surfaces as a clean argument
|
|
929
|
-
// error instead of a prompt-less "success" the client cannot act on.
|
|
930
|
-
if (typeof result.stopReason === "string" &&
|
|
931
|
-
result.stopReason.startsWith("loop_already_running:")) {
|
|
932
|
-
return {
|
|
933
|
-
error: "loop_already_running",
|
|
934
|
-
errorMessage: result.stopDetail ?? "loop already running",
|
|
935
|
-
};
|
|
936
|
-
}
|
|
937
1240
|
// v3.8: the static capability warning — a loop with no provider or no
|
|
938
1241
|
// enabled command can still run; its success claims are recorded as
|
|
939
1242
|
// insufficient instead of verified, and the agent is told so up front.
|
|
@@ -943,57 +1246,86 @@ export const TOOL_HANDLERS = {
|
|
|
943
1246
|
: { ...result };
|
|
944
1247
|
},
|
|
945
1248
|
async loopforge_next(mgr, input) {
|
|
946
|
-
const sessionId = String(input.sessionId ?? "");
|
|
947
1249
|
const output = String(input.output ?? "");
|
|
948
1250
|
const rawEval = input.evaluation;
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
// v3.0.1: every submission is anchored to the round it reports on. A
|
|
954
|
-
// missing roundId would silently re-process a stale submission against a
|
|
955
|
-
// later round — reject it here so the contract is enforced even when the
|
|
956
|
-
// handler is invoked directly (bypassing schema validation).
|
|
957
|
-
if (!roundId || !roundId.trim()) {
|
|
1251
|
+
const rawRecovery = input.recovery;
|
|
1252
|
+
const handle = input.submissionHandle;
|
|
1253
|
+
const rawInstruction = input.currentUserInstruction;
|
|
1254
|
+
if (rawRecovery !== undefined && rawEval !== undefined) {
|
|
958
1255
|
return {
|
|
959
|
-
error: "
|
|
960
|
-
errorMessage: "
|
|
961
|
-
"loopforge_start / loopforge_next / loopforge_resume / loopforge_status response",
|
|
1256
|
+
error: "recovery_invalid",
|
|
1257
|
+
errorMessage: "evaluation and recovery are mutually exclusive",
|
|
962
1258
|
};
|
|
963
1259
|
}
|
|
964
|
-
//
|
|
965
|
-
//
|
|
966
|
-
//
|
|
967
|
-
//
|
|
968
|
-
//
|
|
969
|
-
//
|
|
1260
|
+
// v4.0.0: the handle is the only identity. The runtime validates its
|
|
1261
|
+
// shape, resolves the session, checks the exact held attempt, then runs
|
|
1262
|
+
// the payload boundary — all before its queue/lease heartbeat. This
|
|
1263
|
+
// handler only maps the refusal onto the tool envelope.
|
|
1264
|
+
// v4.0.5: handle + instruction alone is the instruction-recompile mode:
|
|
1265
|
+
// no submission and no round/attempt advance.
|
|
970
1266
|
let result;
|
|
971
1267
|
try {
|
|
972
|
-
|
|
1268
|
+
if (rawRecovery !== undefined) {
|
|
1269
|
+
result = await mgr.recover(handle, rawRecovery, rawInstruction !== undefined
|
|
1270
|
+
? { currentUserInstruction: String(rawInstruction) }
|
|
1271
|
+
: undefined);
|
|
1272
|
+
}
|
|
1273
|
+
else if (rawEval !== undefined) {
|
|
1274
|
+
result = await mgr.advance(handle, output, rawEval, rawInstruction !== undefined
|
|
1275
|
+
? { currentUserInstruction: String(rawInstruction) }
|
|
1276
|
+
: undefined);
|
|
1277
|
+
}
|
|
1278
|
+
else if (rawInstruction !== undefined) {
|
|
1279
|
+
result = await mgr.recompileHeld(handle, String(rawInstruction));
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
result = await mgr.advance(handle, output, rawEval);
|
|
1283
|
+
}
|
|
973
1284
|
}
|
|
974
1285
|
catch (error) {
|
|
1286
|
+
if (error instanceof InvocationInputError) {
|
|
1287
|
+
return { error: "invalid_argument", errorMessage: error.message };
|
|
1288
|
+
}
|
|
975
1289
|
const projectionFailure = projectionErrorResult(error);
|
|
976
1290
|
if (projectionFailure)
|
|
977
1291
|
return projectionFailure;
|
|
1292
|
+
const stateFailure = stateErrorResult(error);
|
|
1293
|
+
if (stateFailure)
|
|
1294
|
+
return stateFailure;
|
|
978
1295
|
throw error;
|
|
979
1296
|
}
|
|
1297
|
+
const loopId = result.sessionId
|
|
1298
|
+
? mgr.get(result.sessionId)?.loopId
|
|
1299
|
+
: undefined;
|
|
980
1300
|
if (result.submissionError) {
|
|
981
1301
|
return {
|
|
982
1302
|
error: result.submissionError.code,
|
|
983
|
-
errorMessage: result.stopDetail ??
|
|
1303
|
+
errorMessage: result.stopDetail ??
|
|
1304
|
+
(rawRecovery !== undefined
|
|
1305
|
+
? "the recovery request was rejected"
|
|
1306
|
+
: "the submission was rejected"),
|
|
984
1307
|
details: result.submissionError.details,
|
|
985
|
-
sessionId,
|
|
986
|
-
|
|
1308
|
+
...(result.sessionId ? { sessionId: result.sessionId } : {}),
|
|
1309
|
+
...(loopId ? { loopId } : {}),
|
|
1310
|
+
...(result.submissionHandle ? { submissionHandle: result.submissionHandle } : {}),
|
|
987
1311
|
};
|
|
988
1312
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1313
|
+
return { ...result, ...(loopId ? { loopId } : {}) };
|
|
1314
|
+
},
|
|
1315
|
+
async loopforge_checkpoint(mgr, input) {
|
|
1316
|
+
const result = await mgr.checkpoint(input.submissionHandle);
|
|
1317
|
+
if (result.error) {
|
|
1318
|
+
return {
|
|
1319
|
+
error: String(result.error),
|
|
1320
|
+
errorMessage: String(result.errorMessage ?? "the checkpoint was rejected"),
|
|
1321
|
+
details: result.details,
|
|
1322
|
+
...(typeof result.sessionId === "string" ? { sessionId: result.sessionId } : {}),
|
|
1323
|
+
...(isRecord(result.submissionHandle)
|
|
1324
|
+
? { submissionHandle: result.submissionHandle }
|
|
1325
|
+
: {}),
|
|
1326
|
+
};
|
|
995
1327
|
}
|
|
996
|
-
return
|
|
1328
|
+
return { ok: true, ...result };
|
|
997
1329
|
},
|
|
998
1330
|
async loopforge_status(mgr, input) {
|
|
999
1331
|
const view = input.view === "loop" || input.view === "all" ||
|
|
@@ -1043,52 +1375,125 @@ export const TOOL_HANDLERS = {
|
|
|
1043
1375
|
errorMessage: `no health data for loop "${loopId}"`,
|
|
1044
1376
|
};
|
|
1045
1377
|
}
|
|
1046
|
-
if (!sessionId) {
|
|
1378
|
+
if (!sessionId && !loopId) {
|
|
1047
1379
|
return {
|
|
1048
1380
|
error: "invalid_argument",
|
|
1049
|
-
errorMessage: "sessionId is required for view=session",
|
|
1381
|
+
errorMessage: "sessionId or loopId is required for view=session",
|
|
1050
1382
|
};
|
|
1051
1383
|
}
|
|
1052
|
-
|
|
1384
|
+
// sessionId is a live-process key; loopId is the durable identity. Prefer
|
|
1385
|
+
// a live sessionId, then fall back to loopId so a model that kept only the
|
|
1386
|
+
// durable key can still read session state in the same process. A stale
|
|
1387
|
+
// sessionId after a restart resolves through loopId when both are present.
|
|
1388
|
+
const session = (sessionId ? mgr.get(sessionId) : undefined) ??
|
|
1389
|
+
(loopId ? mgr.getByLoopId(loopId) : undefined);
|
|
1053
1390
|
if (!session) {
|
|
1391
|
+
// v4.0.4: a persisted STOPPED loop has no live session, but its
|
|
1392
|
+
// committed terminal facts are still readable. This is a projection of
|
|
1393
|
+
// committed history — no lease, no session save, no resume side effect.
|
|
1394
|
+
// A running/paused/stalled loop with no live session still goes through
|
|
1395
|
+
// resume; "no live session" is never treated as "stopped".
|
|
1396
|
+
if (loopId) {
|
|
1397
|
+
let stopped = null;
|
|
1398
|
+
try {
|
|
1399
|
+
stopped = mgr.getStoppedLoopSummary(loopId);
|
|
1400
|
+
}
|
|
1401
|
+
catch (error) {
|
|
1402
|
+
const stateFailure = stateErrorResult(error);
|
|
1403
|
+
if (stateFailure)
|
|
1404
|
+
return stateFailure;
|
|
1405
|
+
throw error;
|
|
1406
|
+
}
|
|
1407
|
+
if (stopped) {
|
|
1408
|
+
return {
|
|
1409
|
+
ok: true,
|
|
1410
|
+
loopId,
|
|
1411
|
+
round: stopped.round,
|
|
1412
|
+
status: stopped.sessionStatus ?? "stopped",
|
|
1413
|
+
loopStopReason: stopped.stopReason ?? null,
|
|
1414
|
+
agentSuccessClaim: stopped.agentSuccessClaim ?? null,
|
|
1415
|
+
agentOutcome: stopped.agentOutcome ?? null,
|
|
1416
|
+
agentShouldContinue: stopped.agentShouldContinue ?? null,
|
|
1417
|
+
stopDetail: stopped.stopDetail ?? null,
|
|
1418
|
+
last_submission: stopped.submission_receipt ?? null,
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1054
1422
|
return {
|
|
1055
1423
|
error: "session_not_found",
|
|
1056
|
-
errorMessage:
|
|
1057
|
-
|
|
1424
|
+
errorMessage: sessionId
|
|
1425
|
+
? `session not found: ${sessionId} (process restarted?) — call ` +
|
|
1426
|
+
`loopforge_status with view="all" to find the loopId; if the ` +
|
|
1427
|
+
`held prompt is still in context, call loopforge_status with ` +
|
|
1428
|
+
`that loopId to fetch the current handle; otherwise call ` +
|
|
1429
|
+
`loopforge_resume with it`
|
|
1430
|
+
: `no live session for loop "${loopId}" — call loopforge_resume with this loopId`,
|
|
1431
|
+
...(sessionId ? { sessionId } : {}),
|
|
1432
|
+
...(loopId ? { loopId } : {}),
|
|
1433
|
+
details: {
|
|
1434
|
+
next: "loopforge_resume",
|
|
1435
|
+
...(loopId ? { loopId } : {}),
|
|
1436
|
+
},
|
|
1058
1437
|
};
|
|
1059
1438
|
}
|
|
1060
|
-
|
|
1439
|
+
// Every live-session-derived projection must use the resolved session's
|
|
1440
|
+
// identity. The caller's raw sessionId is empty on a loopId-only query and
|
|
1441
|
+
// may be stale when the loopId fallback resolved the session; using it here
|
|
1442
|
+
// silently nulled the handle/contract/guidance and forced a full resume.
|
|
1443
|
+
const liveSessionId = session.sessionId;
|
|
1444
|
+
const policyMetricsView = mgr.getPolicyMetrics(session.loopId);
|
|
1445
|
+
// v4.0.3: a live held prompt has no result yet; the last committed
|
|
1446
|
+
// receipt is the correct current machine posture then.
|
|
1447
|
+
const lastReceipt = mgr.getLastSubmissionReceiptByLoopId(session.loopId);
|
|
1448
|
+
const posture = session.roundSnapshot?.result?.evidencePosture ??
|
|
1449
|
+
lastReceipt?.verification.evidence_posture ??
|
|
1450
|
+
null;
|
|
1061
1451
|
return {
|
|
1062
|
-
sessionId:
|
|
1452
|
+
sessionId: liveSessionId,
|
|
1063
1453
|
loopId: session.loopId,
|
|
1064
1454
|
round: session.currentRound,
|
|
1065
|
-
|
|
1455
|
+
submissionHandle: mgr.getSubmissionHandle(liveSessionId),
|
|
1456
|
+
checkpoints: session.roundSnapshot
|
|
1457
|
+
? [
|
|
1458
|
+
...(session.roundSnapshot.beforeCheckpoint
|
|
1459
|
+
? [session.roundSnapshot.beforeCheckpoint]
|
|
1460
|
+
: []),
|
|
1461
|
+
...(session.roundSnapshot.checkpoints ?? []),
|
|
1462
|
+
]
|
|
1463
|
+
: [],
|
|
1464
|
+
beforeCheckpointError: session.roundSnapshot?.beforeCheckpointError ?? null,
|
|
1066
1465
|
maxRounds: session.maxRounds,
|
|
1067
1466
|
status: session.status,
|
|
1068
|
-
|
|
1467
|
+
evidencePosture: posture,
|
|
1468
|
+
// v4.0.4: no committed/held posture is null (unknown), not false.
|
|
1469
|
+
machineBackedSuccess: posture?.machineBackedSuccess ?? null,
|
|
1470
|
+
agentSuccessClaim: session.roundSnapshot?.result?.agentSuccessClaim ??
|
|
1471
|
+
lastReceipt?.agent_success_claim ?? null,
|
|
1472
|
+
agentOutcome: session.roundSnapshot?.result?.agentOutcome ??
|
|
1473
|
+
lastReceipt?.agent_outcome ?? null,
|
|
1474
|
+
machineBackedSuccessTrajectory: mgr.getMachineBackedSuccessTrajectory(session.loopId),
|
|
1069
1475
|
lease: mgr.getLeaseStatus(session.loopId),
|
|
1070
1476
|
// v3.5: derived ACTIVE Round Contract (display-only — the compile and
|
|
1071
1477
|
// verification gates derive the same value from the same committed
|
|
1072
1478
|
// evals via the shared round-contract walker).
|
|
1073
|
-
activeContract: mgr.getActiveContract(
|
|
1479
|
+
activeContract: mgr.getActiveContract(liveSessionId),
|
|
1074
1480
|
// v3.9.1: current-fact submission guidance for the held round. Same
|
|
1075
1481
|
// derivation as start/next/resume; read-only and never persisted.
|
|
1076
|
-
submission_guidance: mgr.getSubmissionGuidance(
|
|
1077
|
-
last_submission:
|
|
1482
|
+
submission_guidance: mgr.getSubmissionGuidance(liveSessionId),
|
|
1483
|
+
last_submission: lastReceipt,
|
|
1484
|
+
last_rollback: mgr.replayRollbacks(session.loopId).at(-1) ?? null,
|
|
1078
1485
|
// v3.8: the ONE capability fact — the static half the compile hashes
|
|
1079
1486
|
// (what verification is possible) plus the live half rendered from the
|
|
1080
1487
|
// last baseline. `available` is provider-registry state: reported here
|
|
1081
1488
|
// and by `doctor`, never hashed.
|
|
1082
|
-
capability: deriveEvidenceCapability(getPolicy(), session.
|
|
1083
|
-
projection: mgr.getProjection(sessionId),
|
|
1489
|
+
capability: deriveEvidenceCapability(getPolicy(), session.roundSnapshot?.beforeEvidence ?? []),
|
|
1084
1490
|
metrics: {
|
|
1085
|
-
|
|
1086
|
-
//
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
policy: mgr.getPolicyMetrics(session.loopId),
|
|
1491
|
+
// The vault write counters live in the shared policy metrics now;
|
|
1492
|
+
// the engine no longer owns a second copy.
|
|
1493
|
+
vaultWriteErrors: policyMetricsView.vaultWriteErrors,
|
|
1494
|
+
startedAt: session.createdAt,
|
|
1495
|
+
sessionAgeSeconds: Math.max(0, Math.floor((Date.now() - session.createdAt) / 1000)),
|
|
1496
|
+
policy: policyMetricsView,
|
|
1092
1497
|
},
|
|
1093
1498
|
};
|
|
1094
1499
|
},
|
|
@@ -1105,10 +1510,27 @@ export const TOOL_HANDLERS = {
|
|
|
1105
1510
|
sessionId,
|
|
1106
1511
|
};
|
|
1107
1512
|
}
|
|
1108
|
-
const
|
|
1109
|
-
const
|
|
1513
|
+
const loopId = session.loopId;
|
|
1514
|
+
const roundCursor = session.currentRound;
|
|
1515
|
+
const roundsCompleted = roundCursor;
|
|
1516
|
+
const machineBackedSuccessTrajectory = mgr.getMachineBackedSuccessTrajectory(loopId);
|
|
1110
1517
|
mgr.delete(sessionId);
|
|
1111
|
-
|
|
1518
|
+
// v4.0.4: make the stop response self-describing. `cancelled` is the
|
|
1519
|
+
// terminal reason the terminal sink just received; it is NOT persisted as
|
|
1520
|
+
// a committed round fact, so resume/status still report "no committed
|
|
1521
|
+
// terminal reason" after a restart. `success` remains tool-call success.
|
|
1522
|
+
return {
|
|
1523
|
+
ok: true,
|
|
1524
|
+
success: true,
|
|
1525
|
+
sessionId,
|
|
1526
|
+
loopId,
|
|
1527
|
+
roundsCompleted,
|
|
1528
|
+
roundCursor,
|
|
1529
|
+
sessionStatus: "stopped",
|
|
1530
|
+
loopStopReason: MANUAL_STOP_REASON,
|
|
1531
|
+
machineBackedSuccessRounds: machineBackedSuccessTrajectory.length,
|
|
1532
|
+
machineBackedSuccessTrajectory,
|
|
1533
|
+
};
|
|
1112
1534
|
},
|
|
1113
1535
|
async loopforge_pause(mgr, input) {
|
|
1114
1536
|
const sessionId = String(input.sessionId ?? "");
|
|
@@ -1128,16 +1550,46 @@ export const TOOL_HANDLERS = {
|
|
|
1128
1550
|
"after a restart (no live session)",
|
|
1129
1551
|
};
|
|
1130
1552
|
}
|
|
1553
|
+
// v4.0.2: an explicit window is validated at the tool boundary. The
|
|
1554
|
+
// backend owns the rule; the boundary only converts the type.
|
|
1555
|
+
const windowOptions = {};
|
|
1556
|
+
for (const name of ["start", "end"]) {
|
|
1557
|
+
const value = input[name];
|
|
1558
|
+
if (value === undefined)
|
|
1559
|
+
continue;
|
|
1560
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
1561
|
+
return {
|
|
1562
|
+
error: "invalid_argument",
|
|
1563
|
+
errorMessage: `${name} must be a positive integer`,
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
windowOptions[name] = value;
|
|
1567
|
+
}
|
|
1568
|
+
const pageFor = (resolvePage) => {
|
|
1569
|
+
try {
|
|
1570
|
+
return resolvePage();
|
|
1571
|
+
}
|
|
1572
|
+
catch (error) {
|
|
1573
|
+
if (error instanceof ReplayWindowError) {
|
|
1574
|
+
return { error: "invalid_argument", errorMessage: error.message };
|
|
1575
|
+
}
|
|
1576
|
+
throw error;
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1131
1579
|
// Live session first: it disambiguates re-used loopIds and the session
|
|
1132
1580
|
// registry is the cheapest lookup.
|
|
1133
1581
|
if (sessionId) {
|
|
1134
1582
|
const session = mgr.get(sessionId);
|
|
1135
1583
|
if (session) {
|
|
1136
|
-
const
|
|
1584
|
+
const page = pageFor(() => mgr.replayPage(sessionId, windowOptions));
|
|
1585
|
+
if ("error" in page)
|
|
1586
|
+
return page;
|
|
1137
1587
|
return {
|
|
1138
1588
|
sessionId,
|
|
1139
1589
|
loopId: session.loopId,
|
|
1140
|
-
timeline,
|
|
1590
|
+
timeline: page.timeline,
|
|
1591
|
+
window: page.window,
|
|
1592
|
+
rollbacks: mgr.replayRollbacks(session.loopId),
|
|
1141
1593
|
legacyRounds: mgr.replayLegacyRounds(session.loopId),
|
|
1142
1594
|
};
|
|
1143
1595
|
}
|
|
@@ -1154,8 +1606,10 @@ export const TOOL_HANDLERS = {
|
|
|
1154
1606
|
}
|
|
1155
1607
|
// Vault-direct replay — the timeline lives in the committed round
|
|
1156
1608
|
// documents, not in the session registry.
|
|
1157
|
-
const
|
|
1158
|
-
if (
|
|
1609
|
+
const page = pageFor(() => mgr.replayPageByLoop(loopId, windowOptions));
|
|
1610
|
+
if ("error" in page)
|
|
1611
|
+
return page;
|
|
1612
|
+
if (page.window.totalRounds === 0 && page.timeline.length === 0) {
|
|
1159
1613
|
return {
|
|
1160
1614
|
error: "state_unavailable",
|
|
1161
1615
|
errorMessage: `no committed rounds found for loop "${loopId}"`,
|
|
@@ -1165,7 +1619,9 @@ export const TOOL_HANDLERS = {
|
|
|
1165
1619
|
return {
|
|
1166
1620
|
...(sessionId ? { sessionId } : {}),
|
|
1167
1621
|
loopId,
|
|
1168
|
-
timeline,
|
|
1622
|
+
timeline: page.timeline,
|
|
1623
|
+
window: page.window,
|
|
1624
|
+
rollbacks: mgr.replayRollbacks(loopId),
|
|
1169
1625
|
legacyRounds: mgr.replayLegacyRounds(loopId),
|
|
1170
1626
|
};
|
|
1171
1627
|
},
|
|
@@ -1183,48 +1639,57 @@ export const TOOL_HANDLERS = {
|
|
|
1183
1639
|
errorMessage: `invalid loopId: ${error.message}`,
|
|
1184
1640
|
};
|
|
1185
1641
|
}
|
|
1642
|
+
const inputs = {
|
|
1643
|
+
...(input.invocationLost === true ? { invocationLost: true } : {}),
|
|
1644
|
+
...(input.currentUserInstruction !== undefined
|
|
1645
|
+
? { currentUserInstruction: String(input.currentUserInstruction) }
|
|
1646
|
+
: {}),
|
|
1647
|
+
};
|
|
1186
1648
|
// Paused recovery must run first so the persisted status is atomically
|
|
1187
1649
|
// changed back to running before a prompt is returned.
|
|
1188
1650
|
let result;
|
|
1189
1651
|
try {
|
|
1190
|
-
result = await mgr.unpause(loopId);
|
|
1652
|
+
result = await mgr.unpause(loopId, inputs);
|
|
1191
1653
|
if (!result) {
|
|
1192
|
-
result = await mgr.resume(loopId);
|
|
1654
|
+
result = await mgr.resume(loopId, inputs);
|
|
1193
1655
|
}
|
|
1194
1656
|
}
|
|
1195
1657
|
catch (error) {
|
|
1658
|
+
if (error instanceof InvocationInputError) {
|
|
1659
|
+
return { error: "invalid_argument", errorMessage: error.message };
|
|
1660
|
+
}
|
|
1661
|
+
if (error instanceof SessionLeaseConflictError) {
|
|
1662
|
+
return {
|
|
1663
|
+
error: "lease_conflict",
|
|
1664
|
+
errorMessage: `loop "${error.loopId}" is owned by another running LoopForge process`,
|
|
1665
|
+
details: { reason_code: "lease_conflict" },
|
|
1666
|
+
loopId,
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1196
1669
|
const projectionFailure = projectionErrorResult(error);
|
|
1197
1670
|
if (projectionFailure)
|
|
1198
1671
|
return projectionFailure;
|
|
1672
|
+
const stateFailure = stateErrorResult(error);
|
|
1673
|
+
if (stateFailure)
|
|
1674
|
+
return stateFailure;
|
|
1199
1675
|
throw error;
|
|
1200
1676
|
}
|
|
1201
1677
|
if (!result) {
|
|
1202
1678
|
return {
|
|
1203
1679
|
error: "state_unavailable",
|
|
1204
1680
|
errorMessage: `no saved session found for loop "${loopId}"`,
|
|
1681
|
+
loopId,
|
|
1205
1682
|
};
|
|
1206
1683
|
}
|
|
1207
|
-
return { ...result };
|
|
1684
|
+
return { ...result, loopId };
|
|
1208
1685
|
},
|
|
1209
1686
|
async loopforge_gate_check(mgr, input) {
|
|
1210
1687
|
// v3.7.1: hidden when disabled — direct calls get a stable error.
|
|
1211
1688
|
if (!getPolicy().gate.enabled)
|
|
1212
1689
|
return { error: "gate_disabled" };
|
|
1213
|
-
const sessionId = String(input.sessionId ?? "");
|
|
1214
|
-
const roundId = String(input.roundId ?? "");
|
|
1215
1690
|
const action = input.action;
|
|
1216
|
-
if (!sessionId) {
|
|
1217
|
-
return { error: "invalid_argument", errorMessage: "sessionId is required" };
|
|
1218
|
-
}
|
|
1219
|
-
if (!roundId) {
|
|
1220
|
-
return {
|
|
1221
|
-
error: "round_id_required",
|
|
1222
|
-
errorMessage: "roundId is required — pass the roundId from the latest response",
|
|
1223
|
-
sessionId,
|
|
1224
|
-
};
|
|
1225
|
-
}
|
|
1226
1691
|
if (!action || !isRecord(action)) {
|
|
1227
|
-
return { error: "invalid_argument", errorMessage: "action is required"
|
|
1692
|
+
return { error: "invalid_argument", errorMessage: "action is required" };
|
|
1228
1693
|
}
|
|
1229
1694
|
const descriptor = {
|
|
1230
1695
|
description: String(action.description ?? ""),
|
|
@@ -1248,11 +1713,9 @@ export const TOOL_HANDLERS = {
|
|
|
1248
1713
|
return {
|
|
1249
1714
|
error: "invalid_argument",
|
|
1250
1715
|
errorMessage: "action.description is required",
|
|
1251
|
-
sessionId,
|
|
1252
|
-
roundId,
|
|
1253
1716
|
};
|
|
1254
1717
|
}
|
|
1255
|
-
return mgr.checkGate(
|
|
1718
|
+
return mgr.checkGate(input.submissionHandle, descriptor);
|
|
1256
1719
|
},
|
|
1257
1720
|
async loopforge_gate_resolve(mgr, input) {
|
|
1258
1721
|
// v3.7.1: hidden when disabled — direct calls get a stable error.
|