infinity-harness 2.1.0 → 2.2.0
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/CHANGELOG.md +66 -0
- package/README.md +56 -3
- package/extensions/infinity-harness/index.ts +573 -1
- package/harness/docs/ARCHITECTURE.md +1 -1
- package/package.json +1 -1
- package/src/core/brief.ts +14 -0
- package/src/core/gates.ts +37 -3
- package/src/escalate.ts +370 -0
- package/src/goal.ts +411 -0
- package/src/loop.ts +158 -12
- package/src/ui/widget.ts +15 -0
- package/src/unstuck.ts +46 -19
package/src/unstuck.ts
CHANGED
|
@@ -34,6 +34,23 @@ export interface ChooseUnstuckOpts {
|
|
|
34
34
|
masterUsed?: boolean;
|
|
35
35
|
// allow explicit strategies override for testing (otherwise reads harness/config.json)
|
|
36
36
|
strategies?: UnstuckStrategy[];
|
|
37
|
+
/**
|
|
38
|
+
* Rungs already taken during this stall. Without it the ladder cannot climb:
|
|
39
|
+
* `reframe` has no budget of its own, so it is eligible forever and shadows
|
|
40
|
+
* every rung below it. Reframing twice in a row is also just reframing.
|
|
41
|
+
*/
|
|
42
|
+
tried?: UnstuckStrategy[];
|
|
43
|
+
/**
|
|
44
|
+
* Whether `rework` and `replan` require the tree to have moved.
|
|
45
|
+
*
|
|
46
|
+
* Defaults to the review policy `review.bounceRequiresDelta`, which is what
|
|
47
|
+
* it was written for: do not bounce REVIEW backwards again if nothing has
|
|
48
|
+
* changed since the last bounce. A stuck run is the opposite case — it is
|
|
49
|
+
* *defined* by nothing having changed — so the escalation path passes false
|
|
50
|
+
* explicitly. A ladder that refuses to climb in the only situation it exists
|
|
51
|
+
* for is not a ladder.
|
|
52
|
+
*/
|
|
53
|
+
requireDeltaForRework?: boolean;
|
|
37
54
|
}
|
|
38
55
|
|
|
39
56
|
export interface ChooseUnstuckResult {
|
|
@@ -138,6 +155,9 @@ export function chooseUnstuckStrategy(opts: ChooseUnstuckOpts = {}): ChooseUnstu
|
|
|
138
155
|
}
|
|
139
156
|
const consultedCount = typeof opts.consultedCount === "number" ? opts.consultedCount : 0;
|
|
140
157
|
const masterUsed = !!opts.masterUsed;
|
|
158
|
+
const tried = new Set<UnstuckStrategy>(opts.tried ?? []);
|
|
159
|
+
const requireDelta =
|
|
160
|
+
typeof opts.requireDeltaForRework === "boolean" ? opts.requireDeltaForRework : bounceRequiresDelta;
|
|
141
161
|
|
|
142
162
|
// hysteresis guard
|
|
143
163
|
if (hysteresisMs > 0) {
|
|
@@ -154,15 +174,21 @@ export function chooseUnstuckStrategy(opts: ChooseUnstuckOpts = {}): ChooseUnstu
|
|
|
154
174
|
const fileDelta = opts.fileDelta !== undefined ? !!opts.fileDelta : true;
|
|
155
175
|
|
|
156
176
|
for (const strategy of strategies) {
|
|
177
|
+
// One turn per rung per stall.
|
|
178
|
+
//
|
|
179
|
+
// The budgeted rungs count their *effects* — rework.json entries, replan
|
|
180
|
+
// history — which only exist if the agent acted on the advice. A stuck
|
|
181
|
+
// agent by definition does not, so the budget never moved and the ladder
|
|
182
|
+
// jammed: offering `replan` to an agent that ignores it, forever, is not
|
|
183
|
+
// an escalation ladder, it is the same stall with a different sentence.
|
|
184
|
+
// The budgets still bound the run across stalls; this bounds one stall.
|
|
185
|
+
if (tried.has(strategy)) continue;
|
|
186
|
+
|
|
157
187
|
if (strategy === "retry") {
|
|
158
188
|
if (dedup) continue; // same fingerprint loop, skip retry
|
|
159
|
-
// retry has no budget beyond hysteresis
|
|
160
189
|
return { strategy: "retry", reason: "retry eligible", fingerprintDedup: dedup };
|
|
161
190
|
}
|
|
162
191
|
if (strategy === "reframe") {
|
|
163
|
-
if (dedup) {
|
|
164
|
-
// allow reframe even with dedup, but if dedup and no fileDelta, still allow? For now allow reframe
|
|
165
|
-
}
|
|
166
192
|
return { strategy: "reframe", reason: "reframe eligible", fingerprintDedup: dedup };
|
|
167
193
|
}
|
|
168
194
|
if (strategy === "consult") {
|
|
@@ -181,29 +207,30 @@ export function chooseUnstuckStrategy(opts: ChooseUnstuckOpts = {}): ChooseUnstu
|
|
|
181
207
|
if (strategy === "rework") {
|
|
182
208
|
if ((reworkCount ?? 0) >= maxReworks) continue;
|
|
183
209
|
if ((bounceCount ?? 0) >= maxBounces) continue;
|
|
184
|
-
if (
|
|
185
|
-
// also fileDelta guard if configured as bounceRequiresDelta
|
|
210
|
+
if (requireDelta && !fileDelta) continue;
|
|
186
211
|
return { strategy: "rework", reason: "rework eligible", fingerprintDedup: dedup };
|
|
187
212
|
}
|
|
188
213
|
if (strategy === "replan") {
|
|
189
214
|
if ((replanCount ?? 0) >= maxReplans) continue;
|
|
190
|
-
if (
|
|
215
|
+
if (requireDelta && !fileDelta) continue;
|
|
191
216
|
return { strategy: "replan", reason: "replan eligible", fingerprintDedup: dedup };
|
|
192
217
|
}
|
|
193
218
|
if (strategy === "master") {
|
|
194
219
|
if (masterUsed) continue;
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
220
|
+
// MASTER is the last rung and fires once per run. It is deliberately
|
|
221
|
+
// NOT defaulted to a specific model: routing ships vendor-neutral, and
|
|
222
|
+
// an empty slot means "whatever pi is already configured with". A
|
|
223
|
+
// hardcoded default here would silently redirect the hardest work in
|
|
224
|
+
// the run to one vendor's model.
|
|
225
|
+
const masterModel = typeof routerCfg.master === "string" && routerCfg.master.trim()
|
|
226
|
+
? routerCfg.master.trim()
|
|
227
|
+
: null;
|
|
228
|
+
return {
|
|
229
|
+
strategy: "master",
|
|
230
|
+
reason: masterModel ? `master ${masterModel}` : "master (pi's current model)",
|
|
231
|
+
nextModel: masterModel,
|
|
232
|
+
fingerprintDedup: dedup,
|
|
233
|
+
};
|
|
207
234
|
}
|
|
208
235
|
}
|
|
209
236
|
|