infinity-harness 2.6.6 → 2.8.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 +80 -0
- package/README.md +68 -15
- package/extensions/infinity-harness/index.ts +600 -26
- package/harness/docs/ARCHITECTURE.md +13 -7
- package/harness/docs/CONSTRAINTS.md +13 -5
- package/harness/docs/DECISIONS.md +44 -0
- package/harness/docs/DOMAIN.md +44 -8
- package/package.json +1 -1
- package/src/core/config.ts +88 -1
- package/src/core/featureList.ts +85 -17
- package/src/core/gates.ts +8 -6
- package/src/core/init.ts +33 -3
- package/src/core/modelRouter.ts +149 -0
- package/src/core/paths.ts +29 -0
- package/src/core/plan.ts +39 -0
- package/src/core/runState.ts +151 -0
- package/src/core/settings.ts +138 -4
- package/src/core/types.ts +49 -0
- package/src/daemon/budget.ts +94 -0
- package/src/daemon/guard.ts +113 -0
- package/src/daemon/index.ts +421 -0
- package/src/daemon/isolation.ts +95 -0
- package/src/daemon/preflight.ts +132 -0
- package/src/daemon/server.ts +153 -0
- package/src/daemon/supervisorState.ts +83 -0
- package/src/daemon/worker.ts +239 -0
- package/src/daemon/worktree.ts +95 -0
- package/src/exec/piWorker.ts +706 -0
- package/src/goalState.ts +2 -22
- package/src/intake.ts +4 -1
- package/src/loop.ts +35 -34
- package/src/modelRouter.ts +0 -0
- package/src/remote.ts +28 -7
- package/src/replan.ts +7 -3
- package/src/rework.ts +9 -3
- package/src/runState.ts +15 -121
- package/src/scheduler.ts +115 -135
- package/src/supervisor.ts +955 -0
- package/src/taskList.ts +41 -3
- package/src/ui/dashboard.ts +127 -0
- package/src/ui/viewState.ts +77 -0
- package/src/ui/widget.ts +189 -0
- package/src/ui/wizard.ts +43 -7
- package/src/unstuck.ts +0 -0
- package/src/worker.ts +12 -8
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,86 @@ 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.8.0] — 2026-08-30
|
|
8
|
+
|
|
9
|
+
The harness stops shipping its own run. The repo is the driver, not the project.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **`harness/` is now driver-only.** Runtime state (`harness/config.json`, `harness/plan.json`, `harness/features/feature-list.json`, `harness/progress.md`, `harness/session-handoff.md`, `harness/sprint-contract.md`, `harness/lessons-decisions.md`, `harness/capability/index.json`, `harness/run.json`, `harness/daemon.json`, `harness/supervisor.json`, `harness/activity.json`, `harness/rework.json`, `harness/replan.json`, `harness/loop-state.json`, `harness/next-session.json`, `harness/daemon.log`, `harness/.run-prompt.md`, `harness/.preflight`, `harness/run-journal.jsonl`, `harness/**/*.bak`, `harness/**/*.lock`, `harness/**/*.ilock`, `harness/worktrees/`) is git-ignored and deleted. Fresh clone shows no `SHIP 37/37` widget until `/infinity:init` creates a local (ignored) harness. Templates (`harness/docs`, `harness/skills`, `harness/features/feature-list.schema.json`, `harness/model-router.json`, `harness/ci`, `harness/scripts`) stay tracked. Fixes the dogfooding leak where every clone rendered its builder's SHIP bar.
|
|
14
|
+
|
|
15
|
+
- **Refactored and simplified without changing behaviour.** Removed dead helpers (`migrateModelRouterTiers`, `tiersFromLegacyModelRouter`, `writeCanonicalWithLockSync`), replaced 11 inline `require()` calls with static imports, extracted `scheduler.ts` helpers (`isBudgetFull`, `hasSerializeTask`, `pickSerializeTask`, `isSerializeBlocked`, `eligibleTasks`, `groupKeyFor`, `roundRobin`), removed unused `WorkerSnapshot`/`tailWorkerOutput`/`listWorkers`/`nextModelForTask` exports, cleaned `goalState.ts`/`core/config.ts`/`modelRouter.ts` dead imports and empty blocks. Net −115 lines. `tsc --noEmit` clean and 35/35 tests pass.
|
|
16
|
+
|
|
17
|
+
### Verified
|
|
18
|
+
|
|
19
|
+
- `tsc --noEmit` clean · 35 unit tests pass · `harness/` on disk equals `harness/` tracked · fresh clone renders no legacy `SHIP 37/37`
|
|
20
|
+
|
|
21
|
+
## [2.7.0] — 2026-08-29
|
|
22
|
+
|
|
23
|
+
The work leaves your session. A run is driven by background pi processes on the models you
|
|
24
|
+
routed them to, and the session you started it from becomes a control panel.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **The run used your own session, your own model and your own tokens.** The loop pushed the brief
|
|
29
|
+
back into the session the human was typing in (`pi.sendUserMessage`), so that session's model did
|
|
30
|
+
every task whatever the router said, its context window carried the whole run, and a "session
|
|
31
|
+
handoff" replaced the human's terminal. Difficulty tiers had nowhere to be applied, because a
|
|
32
|
+
session has one model and it was yours. Work now runs in **separate `pi --mode rpc` child
|
|
33
|
+
processes**, one per unit, each started with `--model` from its tier. `src/supervisor.ts` drives
|
|
34
|
+
them from the extension's own process — plain JavaScript, no LLM, zero tokens in your session.
|
|
35
|
+
|
|
36
|
+
- **Model routing did nothing.** `applyRouting` called `ctx.setModel` on the *live* session, which
|
|
37
|
+
is only ever right when that session is the worker. It now applies only under the legacy engine;
|
|
38
|
+
under the default engine the model is chosen when the worker is spawned, and the model that
|
|
39
|
+
actually answered is recorded (`servedModel`) and shown, so a reference that silently fell back
|
|
40
|
+
to pi's default is visible instead of invisible.
|
|
41
|
+
|
|
42
|
+
- **Session handoff was not a handoff.** It replaced the human's session via `ctx.newSession`. The
|
|
43
|
+
handoff level now names the *unit* — goal, phase, sprint, feature, task, subtask — and one worker
|
|
44
|
+
owns one unit from start to finish. Crossing a unit boundary closes that worker and starts a new
|
|
45
|
+
one; because the model is chosen at spawn, **the session boundary and the model boundary are the
|
|
46
|
+
same boundary by construction.** A feature-level handoff really is one session for the whole
|
|
47
|
+
feature, and its tasks share that feature's hardest tier — which the wizard now says out loud in
|
|
48
|
+
the question that sets it.
|
|
49
|
+
|
|
50
|
+
- **A fire-and-forget `spawnWorkers` in `decideNext` created empty attempt directories on every
|
|
51
|
+
failing gate** — litter in the project tree, an unawaited promise outliving its caller, and no
|
|
52
|
+
work done. Removed; background execution belongs to the supervisor.
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- **`execution.engine`** — `background` (default) or `main-session` (the pre-2.7 behaviour, for a
|
|
57
|
+
machine that cannot spawn a second pi). Configurable from `/infinity:config`.
|
|
58
|
+
- **`/infinity:workers`** — which background sessions exist, which unit and model each has, what it
|
|
59
|
+
is doing, and the recent log. Prints; never costs a turn.
|
|
60
|
+
- **A background section in the widget and a Background panel on the dashboard** — a live worker
|
|
61
|
+
rail with unit, tier, model and context pressure, over a reverse-chronological log.
|
|
62
|
+
- **`harness/supervisor.json` and `harness/activity.json`** — what is running and what has happened,
|
|
63
|
+
on disk, so a run that spans days outlives every terminal that watched part of it.
|
|
64
|
+
- **Worker sessions are real sessions.** They are stored under `tmp/infinity-harness/sessions/`, so
|
|
65
|
+
`/resume` opens any of them, and each worker's prompt, event stream and output are kept beside it.
|
|
66
|
+
|
|
67
|
+
### Reliability
|
|
68
|
+
|
|
69
|
+
- **One driver per project.** A second pi window on the same project is refused the wheel and stays
|
|
70
|
+
a viewer — two supervisors would put two workers in one working tree.
|
|
71
|
+
- **Orphaned workers are reaped.** A `SIGKILL`ed pi cannot close its child; the next session finds
|
|
72
|
+
the recorded pid and ends it.
|
|
73
|
+
- **`stop()` is bounded.** Halting races every long await against a stop signal, so a wedged worker
|
|
74
|
+
cannot make the run unstoppable.
|
|
75
|
+
- **A worker that did not discover the harness is restarted with it loaded**, so it can never edit
|
|
76
|
+
code it has no way to record.
|
|
77
|
+
- **Windows**: the `pi.cmd` shim is resolved to the script it wraps and run with node directly;
|
|
78
|
+
a shell is used only when there is no alternative.
|
|
79
|
+
|
|
80
|
+
### Verified
|
|
81
|
+
|
|
82
|
+
`tsc --noEmit` clean · 36 unit test files · 16/17 e2e scenarios (live skipped), including a new
|
|
83
|
+
**`background`** scenario that drives real `pi` and proves a child process is spawned on the routed
|
|
84
|
+
model, that a unit boundary swaps both, that a coarser level does not, and that the main session's
|
|
85
|
+
own model is asked for nothing the whole time.
|
|
86
|
+
|
|
7
87
|
## [2.6.6] — 2026-08-28
|
|
8
88
|
|
|
9
89
|
Every phase shows its tracked work, tabs isolate it, and autopilot actually drives.
|
package/README.md
CHANGED
|
@@ -186,6 +186,35 @@ A rejection is pinned to the state of the project when you made it, so the run w
|
|
|
186
186
|
same question again until the agent has actually changed something in response. If it never does,
|
|
187
187
|
the run stops and says so instead of nagging forever.
|
|
188
188
|
|
|
189
|
+
## Your session is a control panel
|
|
190
|
+
|
|
191
|
+
The work does not happen in the pi session you start the harness from. That session shows you the
|
|
192
|
+
plan, the background log and the commands; the run itself happens in **separate `pi` processes**,
|
|
193
|
+
each with its own model, its own context window and its own session file.
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
your pi session ──── widget · background log · /infinity:* (no tokens spent on the run)
|
|
197
|
+
│
|
|
198
|
+
└── supervisor plain JavaScript in the extension, no LLM
|
|
199
|
+
│
|
|
200
|
+
└── worker pi --mode rpc --model <the tier's model> ← the work happens here
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
That is what makes the difficulty tiers mean anything. A session has exactly one model, so if the
|
|
204
|
+
run happens in *your* session, your model does every task however carefully you routed them — which
|
|
205
|
+
is precisely what used to happen. Now the model is chosen when a worker is started.
|
|
206
|
+
|
|
207
|
+
Set it with `/infinity:config` → **Execution** → *Where work runs*:
|
|
208
|
+
|
|
209
|
+
| `execution.engine` | |
|
|
210
|
+
|---|---|
|
|
211
|
+
| `background` *(default)* | one background pi session per unit of work, on that unit's model |
|
|
212
|
+
| `main-session` | the pre-2.7 behaviour — everything runs in your session, on your model |
|
|
213
|
+
|
|
214
|
+
`/infinity:workers` prints which sessions exist, which unit and model each has, what it is doing and
|
|
215
|
+
the recent log. It reads files; it never costs a turn. Workers keep their session files under
|
|
216
|
+
`tmp/infinity-harness/sessions/`, so you can `/resume` any of them and read the whole transcript.
|
|
217
|
+
|
|
189
218
|
## One run, many sessions
|
|
190
219
|
|
|
191
220
|
A harness that never starts a new pi session is a harness whose context window only ever grows.
|
|
@@ -197,18 +226,27 @@ Nothing the harness knows lives in the conversation. The plan, the phase, the ga
|
|
|
197
226
|
retry budgets and the escalation ladder are all files under `harness/`, so a session boundary
|
|
198
227
|
costs one thing: the brief — which is what the agent should have been working from anyway.
|
|
199
228
|
|
|
200
|
-
|
|
201
|
-
|
|
229
|
+
`session.handoff` names one level of the plan, and that level is the **unit**: one worker owns one
|
|
230
|
+
unit from start to finish, in one session, on one model. Crossing a unit boundary closes that
|
|
231
|
+
worker and starts a new one — that is the handoff.
|
|
202
232
|
|
|
203
|
-
| Setting |
|
|
233
|
+
| Setting | A fresh session, and a fresh model choice, when |
|
|
204
234
|
|---|---|
|
|
205
|
-
| `
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
235
|
+
| `off` / `goal` | never — one session for the whole run |
|
|
236
|
+
| `phase` | the pipeline advances a phase |
|
|
237
|
+
| `sprint` | the active sprint changes (or coarser) |
|
|
238
|
+
| `feature` | the active feature changes (or coarser) |
|
|
239
|
+
| `task` *(default)* | the active task changes (or coarser) |
|
|
240
|
+
| `subtask` | the active subtask changes (or coarser) |
|
|
241
|
+
|
|
242
|
+
**The model boundary is the session boundary.** Because a worker's model is chosen when it starts,
|
|
243
|
+
whatever you pick here is also how often the model can change — so the difficulty that matters is
|
|
244
|
+
the difficulty of the *unit*, not of the task. At `feature`, a feature runs on its hardest task's
|
|
245
|
+
model and the easy tasks inside it get that model too. At `task`, each task gets its own tier. The
|
|
246
|
+
wizard says which of those you are choosing in the question that sets the models.
|
|
247
|
+
|
|
248
|
+
Any level also replaces the worker early once its context passes `session.contextThreshold`, because
|
|
249
|
+
a handoff that arrives after compaction has arrived too late to be the thing that prevented it.
|
|
212
250
|
|
|
213
251
|
The run itself — its id, its wall-clock budget, its iteration ceiling, its no-progress strikes
|
|
214
252
|
and its position on the escalation ladder — lives in `harness/run.json` and is the same run
|
|
@@ -474,7 +512,14 @@ task.modelHint → byTask → byDifficulty → byFeature → bySprint → byPhas
|
|
|
474
512
|
```
|
|
475
513
|
|
|
476
514
|
`master` is never assigned directly; it's reachable only through one-step consultation after the
|
|
477
|
-
normal ladder is exhausted.
|
|
515
|
+
normal ladder is exhausted. An escalation that names a stronger model is also a session boundary —
|
|
516
|
+
the worker is replaced rather than switched, because putting a stronger model in front of the weaker
|
|
517
|
+
one's failed reasoning is the context the escalation exists to get away from.
|
|
518
|
+
|
|
519
|
+
An empty slot means "whatever model pi is on", and a background worker cannot inherit that by magic:
|
|
520
|
+
the harness reads the model your session is on and passes it to the child explicitly. The widget and
|
|
521
|
+
`/infinity:workers` show both what was asked for and what actually answered, so a reference that
|
|
522
|
+
quietly fell back to a default is visible rather than invisible.
|
|
478
523
|
|
|
479
524
|
**Reasoning models need headroom.** A reasoning model emits nothing on the content channel until it
|
|
480
525
|
has finished thinking — measured at ~370 reasoning tokens to answer "reply with one word". If you
|
|
@@ -497,6 +542,10 @@ that looks like a broken endpoint but is only a small cap.
|
|
|
497
542
|
| `infinity_spawn_worker` | Attempt one task in a clean-room worker |
|
|
498
543
|
| `infinity_goal` | State a goal, review it, or check which pass it is on |
|
|
499
544
|
|
|
545
|
+
Commands worth knowing: `/infinity:run` starts the run, `/infinity:workers` shows the background
|
|
546
|
+
sessions and their log, `/infinity:halt` stops everything and closes them, `/infinity:approve` signs
|
|
547
|
+
a phase that is waiting for you, and `/infinity:dashboard` opens the web view.
|
|
548
|
+
|
|
500
549
|
## Layout
|
|
501
550
|
|
|
502
551
|
```
|
|
@@ -509,6 +558,8 @@ infinity-harness/
|
|
|
509
558
|
│ ├── ui/ theme · planTree (the five levels, once)
|
|
510
559
|
│ │ · display (what to draw, and the templates)
|
|
511
560
|
│ │ · widget (terminal) · dashboard (web) · wizard · config
|
|
561
|
+
│ ├── supervisor.ts the orchestrator: which unit, which model, which session
|
|
562
|
+
│ ├── exec/piWorker.ts one background pi process, driven over RPC
|
|
512
563
|
│ ├── loop.ts the continuous-run driver and its stop conditions
|
|
513
564
|
│ ├── runState.ts is a run armed, and which run is it — on disk, across sessions
|
|
514
565
|
│ ├── handoff.ts when to continue in a fresh session, and what to tell it
|
|
@@ -526,16 +577,18 @@ infinity-harness/
|
|
|
526
577
|
│ ├── features/feature-list.json the plan
|
|
527
578
|
│ ├── config.json pipeline state and settings
|
|
528
579
|
│ ├── run.json the armed run — survives every session it spans
|
|
580
|
+
│ ├── supervisor.json which unit and which worker, right now
|
|
581
|
+
│ ├── activity.json the background log the widget and dashboard read
|
|
529
582
|
│ ├── model-router.json optional routing
|
|
530
583
|
│ ├── docs/ architecture · decisions · phase and role docs
|
|
531
584
|
│ └── skills/ 28 craft skills the brief points at
|
|
532
585
|
│
|
|
533
586
|
│ ~/.pi/agent/infinity-harness/ the workflows and display templates you saved —
|
|
534
587
|
│ they belong to you, not to a project
|
|
535
|
-
├── tests/
|
|
588
|
+
├── tests/ 36 files, plain node:assert
|
|
536
589
|
└── scripts/
|
|
537
590
|
├── run-tests.mjs
|
|
538
|
-
├── e2e.mjs
|
|
591
|
+
├── e2e.mjs 17 scenarios, two of them against real pi processes
|
|
539
592
|
└── rig/ the real-pi driver: a scripted model + the RPC protocol
|
|
540
593
|
```
|
|
541
594
|
|
|
@@ -547,8 +600,8 @@ there is one implementation, and the adapter calls it.
|
|
|
547
600
|
```bash
|
|
548
601
|
npm install
|
|
549
602
|
npm run check # tsc --noEmit, strict
|
|
550
|
-
npm test #
|
|
551
|
-
npm run e2e #
|
|
603
|
+
npm test # 36 test files
|
|
604
|
+
npm run e2e # 17 end-to-end scenarios
|
|
552
605
|
npm run e2e -- --only realpi # just the ones that drive a real pi process
|
|
553
606
|
npm run e2e -- --list # what the scenarios are
|
|
554
607
|
```
|