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.
Files changed (144) hide show
  1. package/README.md +171 -169
  2. package/dist/audit.d.ts +53 -1
  3. package/dist/audit.d.ts.map +1 -1
  4. package/dist/audit.js +106 -22
  5. package/dist/audit.js.map +1 -1
  6. package/dist/canonical-state.d.ts +52 -67
  7. package/dist/canonical-state.d.ts.map +1 -1
  8. package/dist/canonical-state.js +134 -147
  9. package/dist/canonical-state.js.map +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +103 -4
  12. package/dist/cli.js.map +1 -1
  13. package/dist/cognitive-facts.d.ts +13 -0
  14. package/dist/cognitive-facts.d.ts.map +1 -1
  15. package/dist/cognitive-facts.js +28 -13
  16. package/dist/cognitive-facts.js.map +1 -1
  17. package/dist/committed-round.d.ts +103 -21
  18. package/dist/committed-round.d.ts.map +1 -1
  19. package/dist/committed-round.js +188 -45
  20. package/dist/committed-round.js.map +1 -1
  21. package/dist/contract-items.d.ts +55 -0
  22. package/dist/contract-items.d.ts.map +1 -0
  23. package/dist/contract-items.js +180 -0
  24. package/dist/contract-items.js.map +1 -0
  25. package/dist/enforcement-gate.d.ts +20 -2
  26. package/dist/enforcement-gate.d.ts.map +1 -1
  27. package/dist/enforcement-gate.js +197 -386
  28. package/dist/enforcement-gate.js.map +1 -1
  29. package/dist/engine.d.ts +1 -1
  30. package/dist/engine.d.ts.map +1 -1
  31. package/dist/engine.js +53 -38
  32. package/dist/engine.js.map +1 -1
  33. package/dist/evidence-claims.d.ts +4 -4
  34. package/dist/evidence-claims.d.ts.map +1 -1
  35. package/dist/evidence-claims.js +16 -14
  36. package/dist/evidence-claims.js.map +1 -1
  37. package/dist/evidence-provider.d.ts +85 -89
  38. package/dist/evidence-provider.d.ts.map +1 -1
  39. package/dist/evidence-provider.js +328 -184
  40. package/dist/evidence-provider.js.map +1 -1
  41. package/dist/explain.d.ts +76 -0
  42. package/dist/explain.d.ts.map +1 -0
  43. package/dist/explain.js +127 -0
  44. package/dist/explain.js.map +1 -0
  45. package/dist/generate-schema.js +152 -16
  46. package/dist/generate-schema.js.map +1 -1
  47. package/dist/index.d.ts +6 -6
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +5 -5
  50. package/dist/index.js.map +1 -1
  51. package/dist/loop-compiler.d.ts +40 -53
  52. package/dist/loop-compiler.d.ts.map +1 -1
  53. package/dist/loop-compiler.js +229 -553
  54. package/dist/loop-compiler.js.map +1 -1
  55. package/dist/loop-extras-parser.d.ts +0 -1
  56. package/dist/loop-extras-parser.d.ts.map +1 -1
  57. package/dist/loop-extras-parser.js +0 -4
  58. package/dist/loop-extras-parser.js.map +1 -1
  59. package/dist/loop-projection.d.ts.map +1 -1
  60. package/dist/loop-projection.js +3 -1
  61. package/dist/loop-projection.js.map +1 -1
  62. package/dist/mcp/round-lifecycle.d.ts +30 -21
  63. package/dist/mcp/round-lifecycle.d.ts.map +1 -1
  64. package/dist/mcp/round-lifecycle.js +47 -103
  65. package/dist/mcp/round-lifecycle.js.map +1 -1
  66. package/dist/mcp/server.d.ts.map +1 -1
  67. package/dist/mcp/server.js +51 -7
  68. package/dist/mcp/server.js.map +1 -1
  69. package/dist/mcp/session.d.ts +39 -23
  70. package/dist/mcp/session.d.ts.map +1 -1
  71. package/dist/mcp/session.js +148 -163
  72. package/dist/mcp/session.js.map +1 -1
  73. package/dist/mcp/submission-boundary.d.ts +89 -0
  74. package/dist/mcp/submission-boundary.d.ts.map +1 -0
  75. package/dist/mcp/submission-boundary.js +203 -0
  76. package/dist/mcp/submission-boundary.js.map +1 -0
  77. package/dist/mcp/tools.d.ts.map +1 -1
  78. package/dist/mcp/tools.js +249 -128
  79. package/dist/mcp/tools.js.map +1 -1
  80. package/dist/policy-metrics.js +2 -2
  81. package/dist/policy-metrics.js.map +1 -1
  82. package/dist/policy.d.ts +55 -93
  83. package/dist/policy.d.ts.map +1 -1
  84. package/dist/policy.js +149 -38
  85. package/dist/policy.js.map +1 -1
  86. package/dist/prompt-assembler.d.ts +1 -32
  87. package/dist/prompt-assembler.d.ts.map +1 -1
  88. package/dist/prompt-assembler.js +282 -435
  89. package/dist/prompt-assembler.js.map +1 -1
  90. package/dist/prompt-policy.d.ts +1 -4
  91. package/dist/prompt-policy.d.ts.map +1 -1
  92. package/dist/prompt-policy.js +6 -7
  93. package/dist/prompt-policy.js.map +1 -1
  94. package/dist/protocol.d.ts +365 -142
  95. package/dist/protocol.d.ts.map +1 -1
  96. package/dist/protocol.js +21 -66
  97. package/dist/protocol.js.map +1 -1
  98. package/dist/replay.js +2 -2
  99. package/dist/replay.js.map +1 -1
  100. package/dist/round-contract.d.ts +69 -57
  101. package/dist/round-contract.d.ts.map +1 -1
  102. package/dist/round-contract.js +104 -64
  103. package/dist/round-contract.js.map +1 -1
  104. package/dist/round-coordinator.d.ts +36 -16
  105. package/dist/round-coordinator.d.ts.map +1 -1
  106. package/dist/round-coordinator.js +166 -102
  107. package/dist/round-coordinator.js.map +1 -1
  108. package/dist/round-driver.d.ts +3 -5
  109. package/dist/round-driver.d.ts.map +1 -1
  110. package/dist/round-driver.js +0 -1
  111. package/dist/round-driver.js.map +1 -1
  112. package/dist/round-facts.d.ts +96 -0
  113. package/dist/round-facts.d.ts.map +1 -0
  114. package/dist/round-facts.js +216 -0
  115. package/dist/round-facts.js.map +1 -0
  116. package/dist/round-transaction.d.ts +19 -9
  117. package/dist/round-transaction.d.ts.map +1 -1
  118. package/dist/round-transaction.js +90 -22
  119. package/dist/round-transaction.js.map +1 -1
  120. package/dist/self-eval.d.ts +87 -9
  121. package/dist/self-eval.d.ts.map +1 -1
  122. package/dist/self-eval.js +396 -27
  123. package/dist/self-eval.js.map +1 -1
  124. package/dist/subgoal-state.d.ts +77 -0
  125. package/dist/subgoal-state.d.ts.map +1 -0
  126. package/dist/subgoal-state.js +198 -0
  127. package/dist/subgoal-state.js.map +1 -0
  128. package/dist/token-utils.d.ts +52 -19
  129. package/dist/token-utils.d.ts.map +1 -1
  130. package/dist/token-utils.js +83 -72
  131. package/dist/token-utils.js.map +1 -1
  132. package/dist/verification-gate.d.ts +14 -31
  133. package/dist/verification-gate.d.ts.map +1 -1
  134. package/dist/verification-gate.js +178 -496
  135. package/dist/verification-gate.js.map +1 -1
  136. package/dist/version.d.ts +1 -1
  137. package/dist/version.js +1 -1
  138. package/dist/workspace.d.ts +9 -0
  139. package/dist/workspace.d.ts.map +1 -1
  140. package/dist/workspace.js +17 -0
  141. package/dist/workspace.js.map +1 -1
  142. package/loop_policy.json +6 -30
  143. package/package.json +1 -1
  144. package/skills/perception/SKILL.md +243 -79
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: perception
3
- description: Drive a recoverable multi-round coding task through LoopForge MCP while the external Agent remains the execution owner.
3
+ description: Drive a multi-round coding task through LoopForge MCP so goals, constraints, evidence, and decisions survive context loss and restarts. Use when a task needs several rounds, must survive context compression or a process restart, or needs an auditable record of what was verified by the machine rather than claimed by the model. The external Agent stays the execution owner.
4
4
  ---
5
5
 
6
6
  # Perception
@@ -20,8 +20,15 @@ Call `loopforge_start` with:
20
20
 
21
21
  - `task`: the complete user objective.
22
22
  - `constraints`: hard boundaries that must survive every round.
23
- - `maxRounds`: optional positive safety limit. The default is 20.
23
+ - `maxRounds`: optional positive safety limit. The default is 200 from policy.
24
24
  - `domain`: optional context label.
25
+ - `loopId`: optional stable loop id. Generated for you when omitted — pass one
26
+ when the caller already knows it, so later calls can address the loop.
27
+ - `planSource`: optional path to a plan document to extract constraints from.
28
+
29
+ Before starting, `loopforge_status` with `view: "all"` lists the loops the
30
+ runtime already holds — reuse an existing session instead of opening a second
31
+ one for the same objective.
25
32
 
26
33
  Keep the returned `sessionId`, `roundId`, prompt, and level. Always pass the
27
34
  most recent `roundId` to `loopforge_next`. It anchors the submission to the
@@ -43,16 +50,16 @@ Choose a round around a coherent, verifiable change:
43
50
  machine-checkable next boundary. A round may be small when uncertainty or
44
51
  risk is high, and larger when the changes are tightly coupled.
45
52
  4. Before submitting, check the actual diff, relevant tests, hard constraints,
46
- and the next action. Do not mark the whole task complete because one slice
47
- is complete.
53
+ and what is still open. Do not mark the whole task complete because one
54
+ slice is complete.
48
55
 
49
56
  For work that spans several rounds, declare a `round_contract` as a proposal for
50
- the next slice. Keep its `work_item` narrow, list observable `done_when` items,
51
- name only configured evidence commands in `verification_plan`, and constrain
52
- `scope` to the files or directories that slice may touch. The proposal becomes
53
- active only after the current round commits. If the slice is blocked, report
54
- `outcome: "blocked"` with a concrete `blocker`; do not silently replace the
55
- active contract.
57
+ the next slice. Keep its `work_item` narrow, declare `items` that each bind a
58
+ configured evidence command in `verify_with`, and constrain `scope` to the files
59
+ or directories that slice may touch. The proposal becomes active only after the
60
+ current round commits, and the prompt then renders its `rc-` / `rci-` ids. If
61
+ the slice is blocked, report `outcome: "blocked"` with a concrete `blocker`; do
62
+ not silently replace the active contract.
56
63
 
57
64
  This is a granularity preflight, not another gate: ask whether the slice has a
58
65
  clear boundary, evidence, and recovery path. LoopForge cannot see an implicit
@@ -78,20 +85,41 @@ The four core evaluation fields are strict:
78
85
  | `should_continue` | Boolean. Use `false` only when the entire task is complete. |
79
86
 
80
87
  All other evaluation fields are optional and normalized by LoopForge. Useful
81
- fields include `execution_evidence`, `discovered_constraints`,
82
- `emerged_subtasks` (the only sub-goal creation channel — never an `sg-` ID),
88
+ fields include `execution_report` (`files_changed`, `tests_reported`,
89
+ `criterion_claims`, `contract_item_claims`, `progress_estimate`),
90
+ `discovered_constraints`, `emerged_subtasks` (the only sub-goal creation
91
+ channel — never an `sg-` ID; at most 50 entries, 500 characters each),
83
92
  `subgoal_updates` (explicit status transitions: active `sg-` IDs with a target
84
- status; done/canceled are terminal and never reopen), `next_action`,
85
- `drift_clarification`, `prompt_requests`, `outcome`, `blocker`,
93
+ status; done/canceled are terminal and never reopen), `prompt_requests`,
86
94
  `round_contract`, `retroactiveClaims`, `gate_ids` (only when the gate layer is
87
- enabled), and `worker_results` (each entry declares an `outcome`; entries
88
- without one are dropped).
89
-
90
- Optional malformed values are ignored or defaulted. They do not turn a valid
91
- submission into a transport error. Missing or mistyped core fields return
92
- `evaluation_invalid` with `details.missing` and `details.invalid`. Correct the
93
- payload and resubmit the same `roundId`. This does not save session state,
94
- write a round, enter either gate, increment rejection state, or record metrics.
95
+ enabled), and `worker_results` (entries need `agentId`, `subTask`,
96
+ `resultSummary` and an `outcome` of `success` / `partial` / `failed`; entries
97
+ missing any of these are dropped).
98
+
99
+ The objective and the constraint set evolve only through your own reports:
100
+ `objective_refinement` deepens the objective, `revised_success_criteria` (each
101
+ entry `{old, new}`) replaces a criterion, `retracted_constraints` retires a
102
+ constraint you now consider wrong, `wrong_assumptions` records what you
103
+ believed and had to correct, and `compression_checkpoint` with a
104
+ `checkpoint_label` marks a subtask boundary that forces a full-state prompt.
105
+ `outcome` is the declared outcome (`success` / `partial` / `failed` /
106
+ `blocked`) and `blocker` describes a `blocked` outcome; `stop_reason`
107
+ (`"blocked"` or `"needs_human_input"`) is how you stop without claiming
108
+ success. `no_change_reason` is the honest escape hatch for a round that
109
+ genuinely produced no machine-verifiable work: it downgrades the "success with
110
+ no machine evidence" finding to info, but only while NO enabled verification
111
+ command is configured — a configured command closes that escape. It never
112
+ excuses a success claim with no `execution_report` at all, and it never
113
+ excuses a contract check: every successful round still needs `execution_report`
114
+ with `files_changed` and `tests_reported`.
115
+
116
+ Optional malformed values are ignored or defaulted — the Round Contract
117
+ declaration, `contract_item_claims`, and `subgoal_updates` are the strict
118
+ exceptions. They do not turn a valid submission into a transport error. Missing
119
+ or mistyped core fields return `evaluation_invalid` with `details.missing` and
120
+ `details.invalid`. Correct the payload and resubmit the same `roundId`. This
121
+ does not save session state, write a round, enter either gate, increment
122
+ rejection state, or record metrics.
95
123
 
96
124
  Minimal evaluation:
97
125
 
@@ -101,18 +129,24 @@ Minimal evaluation:
101
129
  "output_summary": "Implemented the parser and added 8 passing tests.",
102
130
  "constraint_violations": [],
103
131
  "should_continue": true,
104
- "execution_evidence": {
132
+ "execution_report": {
105
133
  "files_changed": ["src/parser.ts", "tests/parser.test.ts"],
106
- "test_results": { "passed": 8, "failed": 0, "skipped": 0 },
107
- "success_criteria_met": [],
108
- "success_criteria_remaining": ["All parser edge cases covered"],
134
+ "tests_reported": { "passed": 8, "failed": 0, "skipped": 0 },
135
+ "criterion_claims": [
136
+ { "criterion_id": "cr-b5a6c7d8", "outcome": "remaining" }
137
+ ],
138
+ "contract_item_claims": [
139
+ { "item_id": "rci-9c8b7a65", "outcome": "met" }
140
+ ],
109
141
  "progress_estimate": 0.4
110
142
  }
111
143
  }
112
144
  ```
113
145
 
114
- Do not put a guessed evaluation in free text. The structured object is the
115
- authoritative submission; `output` is optional supporting context.
146
+ Everything in `execution_report` is your CLAIM, quoted back for audit — it never
147
+ becomes a verified fact. Only machine observations create facts. Do not put a
148
+ guessed evaluation in free text. The structured object is the authoritative
149
+ submission; `output` is optional supporting context.
116
150
 
117
151
  If a submission warns that its `roundId` no longer matches, the reported round
118
152
  already committed. Use the returned held prompt and its `roundId` rather than
@@ -120,35 +154,116 @@ submitting against a later round.
120
154
 
121
155
  ## Stable IDs and contracts
122
156
 
123
- Prompts render stable IDs beside constraints, criteria, and sub-goals:
157
+ Prompts render stable IDs beside constraints, criteria, sub-goals, and contract
158
+ items:
124
159
 
125
160
  - Constraints: `[c-a3f2b1c0] No plaintext passwords`
126
161
  - Criteria: `[cr-b5a6c7d8] Unit test coverage >= 90%`
127
162
  - Sub-goals: `[sg-f1e2d3c4] Add error handling to login`
163
+ - Contract items: `[rci-9c8b7a65] All parser edge cases covered`
128
164
 
129
- Prefer IDs for `constraint_violations`, criteria lists, and drift
130
- clarifications (which may also anchor on real file paths). Sub-goal status
165
+ Prefer IDs for `constraint_violations` and criterion claims. A sub-goal ID is
166
+ scoped to its declaration event: re-declaring the same text in a later round is
167
+ a NEW sub-goal, so never reuse an old `sg-` ID for a fresh item. Sub-goal status
131
168
  changes REQUIRE exact ACTIVE `sg-` IDs via `subgoal_updates` — description
132
- matching is not a fallback there. Unknown, terminal (done/canceled), or
133
- illegal transitions return `evaluation_invalid` before the round advances:
134
- fix the payload and resubmit the same `roundId` (nothing is committed, no
135
- rejection counter moves).
169
+ matching is not a fallback there. Unknown, terminal (done/canceled), or illegal
170
+ transitions return `evaluation_invalid` before the round advances: fix the
171
+ payload and resubmit the same `roundId` (nothing is committed, no rejection
172
+ counter moves).
136
173
 
137
174
  An optional `round_contract` is a proposal for the next round. It becomes
138
- active only after the declaring round commits. While active, its `done_when`,
139
- `verification_plan`, and `scope` govern the current task until a committed eval
140
- completes it or reports `outcome: "blocked"`. A verification plan must be
141
- observed passing in the closing round.
175
+ active only after the declaring round commits, and it is then derived from
176
+ committed rounds on every later compile. A contract is items, not prose:
177
+
178
+ ```json
179
+ {
180
+ "work_item": "Harden the parser",
181
+ "scope": ["src/parser.ts", "tests/parser.test.ts"],
182
+ "items": [
183
+ {
184
+ "description": "All parser edge cases covered",
185
+ "criterion_refs": ["cr-b5a6c7d8"],
186
+ "subgoal_refs": ["sg-f1e2d3c4"],
187
+ "verify_with": ["unit-tests"]
188
+ }
189
+ ]
190
+ }
191
+ ```
192
+
193
+ Every item must bind at least one configured, enabled, after-capable evidence
194
+ command in `verify_with`, and `scope` entries must stay inside the workspace.
195
+ The runtime derives one `rci-XXXXXXXX` ID per item and an `rc-XXXXXXXX`
196
+ contract ID from the proposal's content only — restating an unchanged contract
197
+ keeps both, so keep citing the IDs the prompt rendered.
198
+
199
+ `subgoal_refs` sits on the ITEM: an item the machine verifies backs exactly the
200
+ sub-goals it names here. That is what produces a derived verified-sub-goal fact
201
+ — and it never changes `SubGoal.status`, which stays your declaration.
202
+
203
+ Item status is machine-derived: `pending` (never claimed met), `insufficient`
204
+ (claimed `met`, not backed), `contradicted` (a bound command failed, or a
205
+ passing command's entrypoint changed that round), `verified` (you claimed `met`
206
+ AND every bound command was observed passing — after phase, entrypoint
207
+ untampered, same command configuration as at declaration). Claim an item by
208
+ citing its `rci-` ID in `execution_report.contract_item_claims`.
209
+
210
+ A claim never creates a verified fact, and machine evidence can only go stale
211
+ in one direction: the LATEST observation at or after your claim decides, so a
212
+ command that passed early and FAILED in the closing round makes the item
213
+ `contradicted` — a pass cannot outlive a later failure. Claimed-but-unbacked is
214
+ `insufficient`: the round still commits and the debt is surfaced, but once the
215
+ debt persists for `engine.unverified_claim_streak_limit` consecutive rounds
216
+ (default 3) the round is rejected; a repeat of the SAME check rejects again with
217
+ an escalation notice, and the third consecutive same-check rejection terminates
218
+ the loop as `incomplete`.
219
+
220
+ The contract closes when every item is `verified`, or when a round reports
221
+ `outcome: "blocked"` with a `blocker`. While it is open, a DIFFERENT proposal is
222
+ ignored (`contract_premature`) — restate the active contract unchanged to
223
+ continue it. Git changes outside the declared `scope` are scope drift, a machine
224
+ fact no explanation waives: revert the out-of-scope files, or close the contract
225
+ with `outcome: "blocked"` and declare the extended scope in the same submission.
226
+ Repeated drift terminates the loop.
227
+
228
+ Declaration and item claims are a strict structural boundary — `contract_invalid`
229
+ with a same-`roundId` retry and zero state change: a contract with no items, more
230
+ items/scope/refs than the declared limits allow (20 items, 20 `criterion_refs`,
231
+ 20 `subgoal_refs` and 20 commands per item, 50 `scope` entries), an item with no
232
+ `verify_with`, an item naming an unknown, disabled, or non-after-capable
233
+ command, a `scope` entry that is not a string or that leaves the workspace, a
234
+ malformed `subgoal_refs` entry, a `subgoal_refs` ID that names no sub-goal of
235
+ this loop (including a sub-goal beyond the 50-entry `emerged_subtasks` intake),
236
+ and a malformed, duplicate, or unknown `contract_item_claims` entry.
237
+ `criterion_claims` are advisory and lenient: an entry with an unknown or
238
+ malformed criterion ID, or an outcome other than `met` / `remaining`, is
239
+ dropped with a warning and never rejects the round.
240
+
241
+ The commands `verify_with` may name are the ones your `loop_policy.json`
242
+ declares under `evidence.commands` and enables there; the runtime runs them
243
+ itself, without a shell, inside the workspace. An item whose bound command
244
+ changed configuration since the contract was declared is `insufficient`, not
245
+ `verified` — the declaration stamps the command configuration it was made
246
+ under.
142
247
 
143
248
  ## Backtrack
144
249
 
145
- A progress stall (stalled or exactly-flat window over the lookback rounds)
146
- can trigger backtrack to the last clean committed round — the most recent
147
- committed round with no error-level verification flags. The backtrack commits
148
- a rollback directive that stays out of final history: your next submission is
149
- the REDO of round `restorePoint + 1` and must reuse the round ID the prompt
150
- carries; when it commits, the rollback record is replaced and disappears from
151
- history views.
250
+ A progress stall can trigger backtrack to the last clean committed round — the
251
+ most recent committed round with no error-level verification flags. The stall
252
+ window is 3 committed rounds (the breaker tier that follows it uses
253
+ `engine.stall_lookback_rounds`, default 3), and the verdict is MACHINE-first:
254
+ when git observations exist for the window, machine motion alone decides it,
255
+ and your `progress_estimate` does not enter — a rising estimate cannot cancel a
256
+ stall, and git motion can only excuse one (it never creates one). Your reported
257
+ estimates are consulted only when the loop has no machine history to read (no
258
+ git provider, or fewer rounds than the window). One machine fact is exempt by
259
+ construction: a round whose success the machine verified is finishing, not
260
+ churning — the closing round of a loop often changes no files at all. So the way
261
+ out of a stall is real, observable work.
262
+
263
+ The backtrack commits a rollback directive that stays out of final history:
264
+ your next submission is the REDO of round `restorePoint + 1` and must reuse the
265
+ round ID the prompt carries; when it commits, the rollback record is replaced
266
+ and disappears from history views.
152
267
 
153
268
  The backtrack prompt opens with a derived **Recovery Brief**:
154
269
 
@@ -157,8 +272,9 @@ The backtrack prompt opens with a derived **Recovery Brief**:
157
272
  - the failed rounds and their approaches — what must NOT be repeated;
158
273
  - falsified assumptions — do not rebuild on these;
159
274
  - discoveries from the skipped rounds that remain valid;
160
- - the files to revert and the git commands to do it (the prompt may offer
161
- `git stash` / `git checkout -- .` / `git reset --hard <restore-commit>`).
275
+ - the files to revert and the HEAD the workspace must return to. The prompt
276
+ states these as FACTS and never prescribes a command — the restore is yours
277
+ to perform, by whatever means you judge best.
162
278
 
163
279
  The same brief renders in `.loopforge/state/<loopId>-state.md` under Recent
164
280
  while the recovery window is open; it is derived from committed rounds plus
@@ -174,17 +290,15 @@ When backtrack occurs:
174
290
  4. Keep valid discoveries from skipped rounds in later evaluations.
175
291
 
176
292
  If the workspace is not restored, the next submission is rejected with
177
- `backtrack_workspace_not_restored`. A stalled active contract should be closed
178
- with `outcome: "blocked"` and a blocker while proposing the revised contract
179
- in the same submission.
180
-
181
- ## Drift
293
+ `backtrack_workspace_not_restored`.
182
294
 
183
- The verification gate compares the previous `next_action` with the current
184
- work. When intentionally changing direction, provide a substantive
185
- `drift_clarification` with a concrete constraint, criterion, sub-goal ID, or
186
- file path that actually exists in your report or the vault. Three consecutive
187
- weak clarifications terminate the loop.
295
+ When the rolled-back rounds executed under an ACTIVE contract, the redo follows
296
+ the contract path instead of a plain retry: close the stalled contract in the
297
+ redo submission with `outcome: "blocked"` plus a `blocker` explaining why it
298
+ stalled, and declare the REVISED contract in the same submission. Restating the
299
+ stalled contract unchanged keeps it active and commits more of the same, and a
300
+ different proposal while an old contract is still open is ignored as
301
+ `contract_premature`.
188
302
 
189
303
  ## Interpret results
190
304
 
@@ -195,27 +309,66 @@ weak clarifications terminate the loop.
195
309
  the restored prompt and round.
196
310
  - `prompt` is null: inspect `stopReason`.
197
311
 
198
- `completed` means the task ended with success. `failed` means the Agent gave
199
- up. `blocked` means current constraints prevent progress. `max_rounds` is the
200
- safety limit. `stalled` means no usable next state was produced.
201
- `enforcement_terminated` means repeated integrity failures reached a terminal
202
- ladder. `paused` means the durable session remains available for resume.
312
+ Each response also carries the posture of the round that just committed:
313
+ `verificationStatus` (`trusted` when everything you claimed is machine-backed,
314
+ `insufficient` when claims are unbacked but nothing is denied, `contradicted`
315
+ when a machine fact denies a claim — it is never a judgement about the quality
316
+ of your work), `roundSuccess`, `level`, and `warnings`. `enforcementReason`
317
+ explains a rejection or termination. Read these before planning the next round:
318
+ an `insufficient` posture is a statement about evidence, not about effort.
319
+
320
+ Errors arrive as `{ok: false, error: {code, message, retryable, ...}}`. `code` is
321
+ stable — branch on it, not on the prose in `message`. `evaluation_invalid`,
322
+ `contract_invalid`, `policy_invalid`, `round_id_required`, `round_id_mismatch`,
323
+ and `invalid_argument` are `retryable`: fix the payload and resubmit the same
324
+ `roundId`. `state_unavailable` means the record you asked for is not there (no
325
+ audit data for that loop, no such gate record), `session_not_found` that the
326
+ session is gone: state conditions, not payload defects — do not resubmit the
327
+ same payload hoping for a different answer. `gate_disabled` means the gate layer
328
+ is off in this policy, and `loop_already_running` that a session for this loop
329
+ already exists in this process — inspect or resume it instead of starting a
330
+ duplicate.
331
+
332
+ `completed` means the machine verified the completion: success plus every
333
+ contract item `verified`, or no active contract. An explicit `outcome:
334
+ "blocked"` (or `stop_reason: "blocked"` / `"needs_human_input"`) always stops as
335
+ `blocked`, never as `completed`. `incomplete` means you stopped while the
336
+ machine could not verify completion — claims the bound commands did not back;
337
+ contract verification debt that persisted past
338
+ `engine.unverified_claim_streak_limit` also ends here rather than accepting
339
+ another unverified round. `failed` means the Agent gave up. `max_rounds` is the
340
+ safety limit. `stalled` means the runtime could not produce the next round's
341
+ prompt. `enforcement_terminated` means repeated integrity failures reached a
342
+ terminal ladder. `paused` means the durable session remains available for
343
+ resume, and `cancelled` that the session was deleted.
203
344
 
204
345
  ## Prompt levels and views
205
346
 
206
347
  L0 is a lean same-round retry, L1 is normal continuation, and L2 is full
207
348
  rehydration. They control state density only and never prescribe a reasoning
208
- technique. Mandatory prompt sections and the token budget remain enforced.
349
+ technique. L2 is chosen from FACTS about the round — the first round, a plan or
350
+ checkpoint boundary, a committed recovery, a machine contradiction, a repeated
351
+ rejection, a changed objective, a previous state the runtime can no longer read
352
+ — never from a round counter, so nothing changes in the prompt just because the
353
+ loop has been running for a while. Protected prompt sections and
354
+ the token budget remain enforced; when the protected content alone exceeds the
355
+ ceiling the prompt records `protectedOverflow` rather than dropping it, and
356
+ which sections were rendered is recorded in the prompt artifact.
209
357
 
210
358
  Use `loopforge_status` with `view: "session"` for the live round and typed
211
- projection, `view: "loop"` for alignment and drift, `view: "all"` after a
212
- restart, and `view: "audit"` for the read-only completeness and evidence audit.
213
- Use `loopforge_replay` for the committed timeline and round diffs. Replay
214
- answers what happened; Audit checks whether facts and claims are supported.
359
+ projection, `view: "loop"` for machine counts over committed rounds, `view: "all"`
360
+ after a restart, `view: "audit"` for the read-only completeness and evidence
361
+ audit, and `view: "explain"` (loopId, optional `round`) for the per-round "why"
362
+ view — the active contract, item statuses, observations, and flags. The same
363
+ view is available from the CLI as `loopforge explain LOOP_ID [--round N]
364
+ [--json]`. Use `loopforge_replay` for the committed timeline and round diffs.
365
+ Replay answers what happened; Audit checks whether facts and claims are
366
+ supported; Explain shows why a round was decided the way it was.
215
367
 
216
368
  The optional `.loopforge/state/<loopId>-state.md` file is derived and can be
217
- regenerated. Read it in L2 pointer mode when the prompt asks for full context,
218
- but do not treat it as an independent source of truth.
369
+ regenerated. In L2 the prompt may omit the full markdown state and point at
370
+ that file instead (pointer mode), so read it when the prompt says so — but it
371
+ is a derived view, never an independent source of truth.
219
372
 
220
373
  ## Control and delegation
221
374
 
@@ -224,15 +377,26 @@ Use `loopforge_pause` before an intentional interruption,
224
377
  for an intentional terminal stop.
225
378
 
226
379
  The two gate tools are opt-in (`policy.gate.enabled`, default false) and are
227
- hidden from `tools/list` when disabled. When enabled, preflight a high-risk
228
- action with `loopforge_gate_check` (a structured `GateActionDescriptor`): a
229
- `user_required` verdict returns a gate id and an approval question, and only
230
- an approved gate may be cited via `evaluation.gate_ids` in the round that
231
- performs the action. Present the approval question to the human and record
232
- their decision with `loopforge_gate_resolve`. Safe preparation work
233
- (investigation, dry-runs, rollback evidence) never needs a gate. Remember:
234
- approval is recorded through you, the Agent — LoopForge cannot machine-verify
235
- a human is present.
380
+ hidden from `tools/list` when disabled (a direct call then returns
381
+ `gate_disabled`). When enabled, preflight a high-risk action with
382
+ `loopforge_gate_check`, whose `action` is a structured descriptor:
383
+
384
+ - `description`: what the action does ("Deploy to production");
385
+ - `scope`: files, systems or services it touches;
386
+ - `effects`: any of `workspace_write`, `production`, `credentials`,
387
+ `data_migration`, `public_api`, `publish`, `payment`,
388
+ `external_communication`, `network`;
389
+ - `reversibility`: `reversible` / `recoverable` / `irreversible` / `unknown`;
390
+ - `authorization`: `agent_allowed` / `user_required` / `unknown`.
391
+
392
+ Classification is conservative: anything not provably safe is `user_required`,
393
+ which returns a gate id and an approval question. Present that question to the
394
+ human, record their decision with `loopforge_gate_resolve`, and cite the
395
+ approved gate id via `evaluation.gate_ids` in the round that performs the
396
+ action — an unapproved or unknown cited gate rejects the round. Safe
397
+ preparation work (investigation, dry-runs, rollback evidence) never needs a
398
+ gate. Remember: approval is recorded through you, the Agent — LoopForge cannot
399
+ machine-verify a human is present.
236
400
 
237
401
  Delegation does not create a separate LoopForge mode. Give each worker a
238
402
  self-contained subtask and relevant hard constraints, then place its result in