infinity-harness 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +82 -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 +422 -0
- package/src/loop.ts +158 -12
- package/src/ui/widget.ts +15 -0
- package/src/unstuck.ts +46 -19
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,88 @@ All notable changes to this project are documented here.
|
|
|
4
4
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.2.1] — 2026-08-23
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Reviewing a goal before the pipeline finished threw an internal error.** `GoalLoopStateError:
|
|
12
|
+
Cannot update goal iteration 2 from status pending` — a phase name from inside the state machine,
|
|
13
|
+
thrown at whoever called the tool. Reviewing early is legitimate: you can see a pass will not meet
|
|
14
|
+
the goal well before the pipeline agrees, and waiting for a doomed pipeline to finish first is
|
|
15
|
+
theatre. The review now records the pass itself and answers.
|
|
16
|
+
|
|
17
|
+
Found by running the shipped package against a real project, in the first minute. Every test
|
|
18
|
+
recorded a pipeline pass before reviewing, so not one of them ever asked what happens when a
|
|
19
|
+
review arrives without one.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## [2.2.0] — 2026-08-23
|
|
24
|
+
|
|
25
|
+
Nine modules shipped in this package, typechecked, and passed their tests while no code path in the
|
|
26
|
+
running product could reach a single one — about 2,800 lines, advertised in the README. They are all
|
|
27
|
+
connected now, and connecting them found four reasons the most important of them had never worked.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **The escalation ladder actually escalates.** `unstuck.ts` could always *choose* what to do when a
|
|
32
|
+
run stalled — retry → reframe → consult → rework → replan → master, with budgets, fingerprint
|
|
33
|
+
dedup and a cooldown — and nothing ever executed one. It was a chooser with no actuator, so
|
|
34
|
+
`/infinity:run` did the only thing it could when the gate kept failing: count three strikes and
|
|
35
|
+
stop. `src/escalate.ts` is the actuator. On a stall it climbs a rung, does the part that is ours
|
|
36
|
+
(flipping tasks to `rework`, naming the model to escalate to) and hands the agent an instruction
|
|
37
|
+
for the part that is the agent's. Every rung says something different; a run that gives up now
|
|
38
|
+
names every rung it spent first.
|
|
39
|
+
- **The goal loop turns.** `goalSpec`, `goalLoop` and `goalState` are a complete outer loop that
|
|
40
|
+
nothing ever drove, which meant the harness could finish a pipeline and declare "complete" without
|
|
41
|
+
anyone asking whether the thing the human asked for was done. `src/goal.ts` drives it, and the
|
|
42
|
+
mapping is the design: **one goal iteration is one full pass of the pipeline.** `/infinity:goal
|
|
43
|
+
<what you want>` states it; when the pipeline finishes, the run asks whether the GOAL is met, not
|
|
44
|
+
whether the plan is. A verdict of anything but `complete` must name what is still missing, and the
|
|
45
|
+
pipeline rewinds to the first phase with that list carried into the brief — so the next pass plans
|
|
46
|
+
for the remainder instead of rebuilding what the last review already accepted.
|
|
47
|
+
- **Five tools and three commands** for what was previously unreachable: `infinity_unstuck`,
|
|
48
|
+
`infinity_rework`, `infinity_replan`, `infinity_spawn_worker`, `infinity_goal`, and
|
|
49
|
+
`/infinity:goal`, `/infinity:unstuck`, `/infinity:rework`.
|
|
50
|
+
- **A `skills-load` advisory gate check.** The skills audit was the ninth orphan. It now runs at
|
|
51
|
+
DEFINE and REVIEW over any skills a project ships, so a project finds out that pi will print a
|
|
52
|
+
`[Skill conflicts]` block before its users do. Advisory: a malformed skill does not make the code
|
|
53
|
+
wrong.
|
|
54
|
+
- **The widget shows which pass you are on and the last rung taken.** A second pass at a goal looks
|
|
55
|
+
identical to a first one in every other part of the display, which is exactly when someone glances
|
|
56
|
+
at a half-full progress bar and walks away thinking it is nearly done.
|
|
57
|
+
- **Two E2E scenarios** — `escalation` and `goal` — driving both through the real adapter over real
|
|
58
|
+
projects, and the reachability allowlist in the `package` scenario is now **empty**.
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
Wiring the ladder in exposed why it had never worked, none of which its own passing tests could see:
|
|
63
|
+
|
|
64
|
+
- **`reframe` had no budget**, so it was eligible forever and shadowed every rung below it. The
|
|
65
|
+
ladder could not climb past rung two — `consult`, `rework`, `replan` and `master` were unreachable
|
|
66
|
+
through the function whose job was to reach them.
|
|
67
|
+
- **`rework` and `replan` were vetoed unless the working tree had moved.** A stall is *defined* by
|
|
68
|
+
the tree not moving, so the two rungs that exist for exactly this situation could never fire in
|
|
69
|
+
it. That guard is a review-bounce policy — do not bounce REVIEW backwards again if nothing changed
|
|
70
|
+
— and it stays that for review bounces; the stuck ladder opts out explicitly.
|
|
71
|
+
- **The budgets counted effects on disk**, which only appear if the agent acts on the advice. A
|
|
72
|
+
stuck agent does not, so the budget never moved and the ladder jammed, offering `replan` forever.
|
|
73
|
+
Each rung now gets one turn per stall; the on-disk budgets still bound the run across stalls.
|
|
74
|
+
- **MASTER defaulted to a specific third-party model** — in a package whose 2.0.0 release promised
|
|
75
|
+
every routing slot ships empty. The last rung of the ladder silently redirected the hardest work
|
|
76
|
+
in the run to one vendor. It is `null` now, meaning "whatever pi is configured with", unless the
|
|
77
|
+
user chose one.
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
|
|
81
|
+
- A stalled iteration consults the ladder before the no-progress strike is spent, and a new rung
|
|
82
|
+
resets the streak — a different attempt is not another repetition of the same one. This cannot run
|
|
83
|
+
forever: every rung is bounded, so the ladder runs out, returns nothing, and the run stops with a
|
|
84
|
+
full account of what was tried.
|
|
85
|
+
- `loop-state.json` carries the ladder's position. State written before it existed loads fine.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
7
89
|
## [2.1.0] — 2026-08-23
|
|
8
90
|
|
|
9
91
|
You could not start, and if you had, you could not have got past the first gate. Both are fixed.
|
package/README.md
CHANGED
|
@@ -99,6 +99,9 @@ the craft skills that match the work, and what to do next. Do the work, then:
|
|
|
99
99
|
/infinity:config change any setting, including which model runs which tier
|
|
100
100
|
/infinity:models what models pi has, and how they are being routed
|
|
101
101
|
/infinity:dashboard open the live web view
|
|
102
|
+
/infinity:goal state a goal and pursue it across passes
|
|
103
|
+
/infinity:unstuck what the escalation ladder would try next
|
|
104
|
+
/infinity:rework send a task and its dependents backwards
|
|
102
105
|
/infinity:halt take the wheel back
|
|
103
106
|
```
|
|
104
107
|
|
|
@@ -164,6 +167,49 @@ define → plan → build → verify → [simplify] → review → ship
|
|
|
164
167
|
|
|
165
168
|
Enable or disable phases in `harness/config.json` under `phases.enabled`. SIMPLIFY is off by default.
|
|
166
169
|
|
|
170
|
+
## When it gets stuck
|
|
171
|
+
|
|
172
|
+
Stopping safely is the easy half. The hard half is trying something *else* first, and that is the
|
|
173
|
+
escalation ladder: when a run stalls — the gate fails and the working tree has not moved, meaning
|
|
174
|
+
the agent produced nothing — `/infinity:run` climbs it before spending a strike.
|
|
175
|
+
|
|
176
|
+
| Rung | What it does |
|
|
177
|
+
|---|---|
|
|
178
|
+
| **retry** | One more attempt. Sometimes a run is just slow. |
|
|
179
|
+
| **reframe** | State the assumption you have been working under, say why the evidence contradicts it, then try a different approach. |
|
|
180
|
+
| **consult** | Escalate to a stronger model, one step up the difficulty ladder. |
|
|
181
|
+
| **rework** | Flip the task and everything that depends on it back to `rework`. Work built on a broken thing is suspect until re-proved. |
|
|
182
|
+
| **replan** | The plan is wrong: something this needed was never planned. Amend it. |
|
|
183
|
+
| **master** | Last resort. State the problem from scratch, including what has been ruled out. |
|
|
184
|
+
|
|
185
|
+
Each rung gets one turn per stall, and each is bounded — reworks and replans have budgets, `consult`
|
|
186
|
+
has a per-task limit, `master` fires once per run. When the ladder runs out, the run stops and names
|
|
187
|
+
every rung it spent. Real progress resets it: a moving tree means a new problem, and a new problem
|
|
188
|
+
gets a fresh ladder.
|
|
189
|
+
|
|
190
|
+
`/infinity:unstuck` shows what it would try next without doing it.
|
|
191
|
+
|
|
192
|
+
## Goals, and knowing when you are actually done
|
|
193
|
+
|
|
194
|
+
A finished pipeline is not a met goal. The gate decides whether the **work** is done; it has no
|
|
195
|
+
opinion on whether the work was the *right* work, because it only ever sees the plan — and the plan
|
|
196
|
+
is just what you thought the goal needed when you wrote it.
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
/infinity:goal Ship the payments rewrite behind a flag
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
That states the goal and starts pass 1. One pass at the goal is one full trip through the pipeline.
|
|
203
|
+
When the pipeline completes, the run does not end: it asks whether the goal is met.
|
|
204
|
+
|
|
205
|
+
- **complete** ends the run.
|
|
206
|
+
- Anything else must name what is still missing — and the pipeline rewinds to the first phase with
|
|
207
|
+
that list carried into the brief, so the next pass plans for the remainder rather than rebuilding
|
|
208
|
+
what the last review already accepted.
|
|
209
|
+
|
|
210
|
+
Bounded by an iteration ceiling and a wall clock, both configurable. The widget shows which pass you
|
|
211
|
+
are on, because a second pass looks exactly like a first one otherwise.
|
|
212
|
+
|
|
167
213
|
## Knowing when to stop
|
|
168
214
|
|
|
169
215
|
This is the part that makes an unattended run safe. `/infinity:run` halts on any of:
|
|
@@ -284,6 +330,11 @@ that looks like a broken endpoint but is only a small cap.
|
|
|
284
330
|
| `infinity_validate` | Run the gate for this phase |
|
|
285
331
|
| `infinity_advance` | Move to the next phase (refuses on a failing gate) |
|
|
286
332
|
| `infinity_dashboard` | Start/stop/query the web view |
|
|
333
|
+
| `infinity_unstuck` | What should I try next? (recommends; does not act) |
|
|
334
|
+
| `infinity_rework` | Send a task and its dependents back to rework |
|
|
335
|
+
| `infinity_replan` | Add what the plan was missing, mid-run |
|
|
336
|
+
| `infinity_spawn_worker` | Attempt one task in a clean-room worker |
|
|
337
|
+
| `infinity_goal` | State a goal, review it, or check which pass it is on |
|
|
287
338
|
|
|
288
339
|
## Layout
|
|
289
340
|
|
|
@@ -296,19 +347,21 @@ infinity-harness/
|
|
|
296
347
|
│ │ · skills (match) · skillsAudit (guard)
|
|
297
348
|
│ ├── ui/ theme · widget (terminal) · dashboard (web)
|
|
298
349
|
│ ├── loop.ts the continuous-run driver and its stop conditions
|
|
350
|
+
│ ├── escalate.ts the ladder's actuator: chooses a rung and takes it
|
|
351
|
+
│ ├── goal.ts the outer loop: is the thing asked for actually done?
|
|
299
352
|
│ ├── taskList.ts atomic plan editor
|
|
300
353
|
│ ├── worker.ts isolated per-task workers
|
|
301
354
|
│ ├── modelRouter.ts difficulty ladder + consultation
|
|
302
355
|
│ ├── rework.ts · replan.ts backward rework with BFS impact · mid-build amendment
|
|
303
356
|
│ ├── unstuck.ts · review.ts escalation strategy matrix · review bounce guard
|
|
304
|
-
│ └── goalLoop.ts · goalState.ts · goalSpec.ts
|
|
357
|
+
│ └── goalLoop.ts · goalState.ts · goalSpec.ts goal state machine and its store
|
|
305
358
|
├── harness/
|
|
306
359
|
│ ├── features/feature-list.json the plan
|
|
307
360
|
│ ├── config.json pipeline state and settings
|
|
308
361
|
│ ├── model-router.json optional routing
|
|
309
362
|
│ ├── docs/ architecture · decisions · phase and role docs
|
|
310
363
|
│ └── skills/ 28 craft skills the brief points at
|
|
311
|
-
├── tests/
|
|
364
|
+
├── tests/ 28 files, plain node:assert
|
|
312
365
|
└── scripts/run-tests.mjs
|
|
313
366
|
```
|
|
314
367
|
|
|
@@ -320,7 +373,7 @@ there is one implementation, and the adapter calls it.
|
|
|
320
373
|
```bash
|
|
321
374
|
npm install
|
|
322
375
|
npm run check # tsc --noEmit
|
|
323
|
-
npm test #
|
|
376
|
+
npm test # 28 test files
|
|
324
377
|
npm run e2e # end-to-end against a live model
|
|
325
378
|
```
|
|
326
379
|
|