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.
Files changed (45) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +68 -15
  3. package/extensions/infinity-harness/index.ts +600 -26
  4. package/harness/docs/ARCHITECTURE.md +13 -7
  5. package/harness/docs/CONSTRAINTS.md +13 -5
  6. package/harness/docs/DECISIONS.md +44 -0
  7. package/harness/docs/DOMAIN.md +44 -8
  8. package/package.json +1 -1
  9. package/src/core/config.ts +88 -1
  10. package/src/core/featureList.ts +85 -17
  11. package/src/core/gates.ts +8 -6
  12. package/src/core/init.ts +33 -3
  13. package/src/core/modelRouter.ts +149 -0
  14. package/src/core/paths.ts +29 -0
  15. package/src/core/plan.ts +39 -0
  16. package/src/core/runState.ts +151 -0
  17. package/src/core/settings.ts +138 -4
  18. package/src/core/types.ts +49 -0
  19. package/src/daemon/budget.ts +94 -0
  20. package/src/daemon/guard.ts +113 -0
  21. package/src/daemon/index.ts +421 -0
  22. package/src/daemon/isolation.ts +95 -0
  23. package/src/daemon/preflight.ts +132 -0
  24. package/src/daemon/server.ts +153 -0
  25. package/src/daemon/supervisorState.ts +83 -0
  26. package/src/daemon/worker.ts +239 -0
  27. package/src/daemon/worktree.ts +95 -0
  28. package/src/exec/piWorker.ts +706 -0
  29. package/src/goalState.ts +2 -22
  30. package/src/intake.ts +4 -1
  31. package/src/loop.ts +35 -34
  32. package/src/modelRouter.ts +0 -0
  33. package/src/remote.ts +28 -7
  34. package/src/replan.ts +7 -3
  35. package/src/rework.ts +9 -3
  36. package/src/runState.ts +15 -121
  37. package/src/scheduler.ts +115 -135
  38. package/src/supervisor.ts +955 -0
  39. package/src/taskList.ts +41 -3
  40. package/src/ui/dashboard.ts +127 -0
  41. package/src/ui/viewState.ts +77 -0
  42. package/src/ui/widget.ts +189 -0
  43. package/src/ui/wizard.ts +43 -7
  44. package/src/unstuck.ts +0 -0
  45. 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
- So the run hands itself to a fresh session at each boundary, and the replacement picks up
201
- exactly where the last one stopped:
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 | Fresh session when |
233
+ | Setting | A fresh session, and a fresh model choice, when |
204
234
  |---|---|
205
- | `phase` *(default)* | the pipeline advances a phase, or a goal pass finishes |
206
- | `task` | that, plus every time the run moves to a different task |
207
- | `off` | never one session for the whole run |
208
-
209
- Any of them also hands off early once the context passes `session.contextThreshold` (0.7 by
210
- default), because a handoff that arrives after compaction has arrived too late to be the thing
211
- that prevented it.
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/ 33 files, plain node:assert
588
+ ├── tests/ 36 files, plain node:assert
536
589
  └── scripts/
537
590
  ├── run-tests.mjs
538
- ├── e2e.mjs 16 scenarios, including one against a real pi process
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 # 33 test files
551
- npm run e2e # 16 end-to-end scenarios
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
  ```