pi-harness-runtime 1.1.451 → 1.1.453

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 (118) hide show
  1. package/AGENTS.md +85 -3
  2. package/CHANGELOG.md +52 -0
  3. package/index.ts +248 -21
  4. package/package.json +1 -1
  5. package/packages/agent-policy/package.json +1 -1
  6. package/packages/auth/package.json +1 -1
  7. package/packages/autonomous-runtime/package.json +1 -1
  8. package/packages/checkpoint/package.json +1 -1
  9. package/packages/clipboard/package.json +1 -1
  10. package/packages/codex-watcher/package.json +1 -1
  11. package/packages/config-capture/package.json +1 -1
  12. package/packages/contracts/package.json +1 -1
  13. package/packages/cookie-sanitizer/package.json +1 -1
  14. package/packages/evaluation-engine/package.json +1 -1
  15. package/packages/event-bus/package.json +1 -1
  16. package/packages/event-store/package.json +1 -1
  17. package/packages/experience-replay/package.json +1 -1
  18. package/packages/file-copy-helper/package.json +1 -1
  19. package/packages/intent-analyzer/package.json +1 -1
  20. package/packages/jev-judge/dist/batch-plan.d.ts +150 -0
  21. package/packages/jev-judge/dist/batch-plan.d.ts.map +1 -0
  22. package/packages/jev-judge/dist/batch-plan.js +192 -0
  23. package/packages/jev-judge/dist/batch-plan.js.map +1 -0
  24. package/packages/jev-judge/dist/continuation-status.d.ts +31 -0
  25. package/packages/jev-judge/dist/continuation-status.d.ts.map +1 -0
  26. package/packages/jev-judge/dist/continuation-status.js +40 -0
  27. package/packages/jev-judge/dist/continuation-status.js.map +1 -0
  28. package/packages/jev-judge/dist/delegation-harness.d.ts +94 -1
  29. package/packages/jev-judge/dist/delegation-harness.d.ts.map +1 -1
  30. package/packages/jev-judge/dist/delegation-harness.js +247 -20
  31. package/packages/jev-judge/dist/delegation-harness.js.map +1 -1
  32. package/packages/jev-judge/dist/delegation-observer.d.ts +98 -0
  33. package/packages/jev-judge/dist/delegation-observer.d.ts.map +1 -0
  34. package/packages/jev-judge/dist/delegation-observer.js +152 -0
  35. package/packages/jev-judge/dist/delegation-observer.js.map +1 -0
  36. package/packages/jev-judge/dist/delegation-scope.d.ts +88 -0
  37. package/packages/jev-judge/dist/delegation-scope.d.ts.map +1 -0
  38. package/packages/jev-judge/dist/delegation-scope.js +218 -0
  39. package/packages/jev-judge/dist/delegation-scope.js.map +1 -0
  40. package/packages/jev-judge/dist/index.d.ts +4 -0
  41. package/packages/jev-judge/dist/index.d.ts.map +1 -1
  42. package/packages/jev-judge/dist/index.js +4 -0
  43. package/packages/jev-judge/dist/index.js.map +1 -1
  44. package/packages/jev-judge/dist/pilot.d.ts +127 -0
  45. package/packages/jev-judge/dist/pilot.d.ts.map +1 -0
  46. package/packages/jev-judge/dist/pilot.js +178 -0
  47. package/packages/jev-judge/dist/pilot.js.map +1 -0
  48. package/packages/jev-judge/dist/validation-plan.d.ts +18 -0
  49. package/packages/jev-judge/dist/validation-plan.d.ts.map +1 -1
  50. package/packages/jev-judge/dist/validation-plan.js +93 -4
  51. package/packages/jev-judge/dist/validation-plan.js.map +1 -1
  52. package/packages/jev-judge/package.json +1 -1
  53. package/packages/jev-judge/src/batch-plan.ts +273 -0
  54. package/packages/jev-judge/src/continuation-status.ts +41 -0
  55. package/packages/jev-judge/src/delegation-harness.ts +337 -47
  56. package/packages/jev-judge/src/delegation-observer.ts +181 -0
  57. package/packages/jev-judge/src/delegation-scope.ts +264 -0
  58. package/packages/jev-judge/src/index.ts +36 -0
  59. package/packages/jev-judge/src/pilot.ts +257 -0
  60. package/packages/jev-judge/src/validation-plan.ts +110 -4
  61. package/packages/knowledge-retrieval/package.json +1 -1
  62. package/packages/learning-engine/package.json +1 -1
  63. package/packages/logging/package.json +1 -1
  64. package/packages/memory-engine/package.json +1 -1
  65. package/packages/notification/package.json +1 -1
  66. package/packages/paperclip-integration/package.json +1 -1
  67. package/packages/performance-optimizer/package.json +1 -1
  68. package/packages/privilege-broker/package.json +1 -1
  69. package/packages/prompt-compiler/package.json +1 -1
  70. package/packages/providers/package.json +1 -1
  71. package/packages/qdrant-skills/package.json +1 -1
  72. package/packages/quota-manager/package.json +1 -1
  73. package/packages/relay/package.json +1 -1
  74. package/packages/skill-mcp-client/package.json +1 -1
  75. package/packages/skills/package.json +1 -1
  76. package/packages/todo-bd-sync/package.json +1 -1
  77. package/packages/token-estimation/package.json +1 -1
  78. package/packages/tooling-preference/package.json +1 -1
  79. package/packages/trajectory/package.json +1 -1
  80. package/packages/tui/package.json +1 -1
  81. package/packages/two-phase-review/dist/classify.d.ts +26 -1
  82. package/packages/two-phase-review/dist/classify.d.ts.map +1 -1
  83. package/packages/two-phase-review/dist/classify.js +42 -1
  84. package/packages/two-phase-review/dist/classify.js.map +1 -1
  85. package/packages/two-phase-review/dist/index.d.ts +4 -2
  86. package/packages/two-phase-review/dist/index.d.ts.map +1 -1
  87. package/packages/two-phase-review/dist/index.js +3 -2
  88. package/packages/two-phase-review/dist/index.js.map +1 -1
  89. package/packages/two-phase-review/dist/rules.d.ts +38 -3
  90. package/packages/two-phase-review/dist/rules.d.ts.map +1 -1
  91. package/packages/two-phase-review/dist/rules.js +83 -10
  92. package/packages/two-phase-review/dist/rules.js.map +1 -1
  93. package/packages/two-phase-review/dist/runtime.d.ts +17 -0
  94. package/packages/two-phase-review/dist/runtime.d.ts.map +1 -1
  95. package/packages/two-phase-review/dist/runtime.js +122 -22
  96. package/packages/two-phase-review/dist/runtime.js.map +1 -1
  97. package/packages/two-phase-review/dist/state.d.ts +15 -0
  98. package/packages/two-phase-review/dist/state.d.ts.map +1 -1
  99. package/packages/two-phase-review/dist/state.js +37 -2
  100. package/packages/two-phase-review/dist/state.js.map +1 -1
  101. package/packages/two-phase-review/dist/types.d.ts +2 -2
  102. package/packages/two-phase-review/dist/types.d.ts.map +1 -1
  103. package/packages/two-phase-review/dist/validation.d.ts +50 -0
  104. package/packages/two-phase-review/dist/validation.d.ts.map +1 -0
  105. package/packages/two-phase-review/dist/validation.js +54 -0
  106. package/packages/two-phase-review/dist/validation.js.map +1 -0
  107. package/packages/two-phase-review/package.json +1 -1
  108. package/packages/two-phase-review/src/classify.ts +47 -1
  109. package/packages/two-phase-review/src/index.ts +16 -0
  110. package/packages/two-phase-review/src/rules.ts +117 -9
  111. package/packages/two-phase-review/src/runtime.ts +151 -21
  112. package/packages/two-phase-review/src/state.ts +38 -2
  113. package/packages/two-phase-review/src/types.ts +2 -1
  114. package/packages/two-phase-review/src/validation.ts +86 -0
  115. package/packages/types/package.json +1 -1
  116. package/packages/webmcp-tester/package.json +1 -1
  117. package/packages/wiki/package.json +1 -1
  118. package/packages/write-review/package.json +1 -1
package/AGENTS.md CHANGED
@@ -46,8 +46,21 @@ A task with a cheap tier and an expensive tier MUST use the cheap tier while the
46
46
  {
47
47
  "metadata": {
48
48
  "validationPlan": {
49
- "fast": { "command": "bun test tests/fast", "expectedDurationMs": 18000, "datasetSize": 100 },
50
- "full": { "command": "bun test tests/full", "expectedDurationMs": 2400000, "datasetSize": 10000 },
49
+ "fast": {
50
+ "command": "bun test tests/fast",
51
+ "expectedDurationMs": 18000,
52
+ "datasetSize": 100,
53
+ "owner": "the agent that runs the validation",
54
+ "passCriteria": ["every record is written once", "the counts of source and target match"],
55
+ "failureAction": "stop the job and report the failure of the pilot"
56
+ },
57
+ "full": {
58
+ "command": "bun test tests/full",
59
+ "expectedDurationMs": 2400000,
60
+ "datasetSize": 10000,
61
+ "owner": "the agent that runs the validation",
62
+ "scaleUpFrom": "fast"
63
+ },
51
64
  "budgetMinutes": 90,
52
65
  "finalTierRequired": true
53
66
  }
@@ -57,6 +70,8 @@ A task with a cheap tier and an expensive tier MUST use the cheap tier while the
57
70
 
58
71
  The harness never guesses a tier from a command string. Without a declared plan the current behaviour is unchanged.
59
72
 
73
+ A tier that declares `scaleUpFrom: "fast"` is the scale-up of a large job: it only counts when the fast tier passed. The fast tier of such a plan is the representative pilot and declares `"representative": true`.
74
+
60
75
  2. **Iterate on the fast tier.** After every code edit, rerun the fast tier plus the focused unit/type checks the edit touches.
61
76
 
62
77
  3. **Run the full tier once.** Run the full tier as the final acceptance check, when the implementation and the fast tier are stable.
@@ -71,7 +86,9 @@ A task with a cheap tier and an expensive tier MUST use the cheap tier while the
71
86
 
72
87
  8. **The fast tier must be representative.** A small fixture keeps the shape of the data (optional fields, relations, uniqueness, boundary values, aggregates under test), not just the row count. Use deterministic data with cleanup so repeated runs do not accumulate rows.
73
88
 
74
- 9. **Record every tier run in the ledger.** The harness never runs the tests, so the run is recorded by whoever knows the result:
89
+ 9. **A large job starts with a measured pilot.** The pilot is the fast tier: it runs over a representative subset (100 records when that is safe) and it is measured, not assumed. The scale-up only starts when the pilot passed, the pilot ran on the declared number of records, and the run stayed inside the budget. A pilot that fails stops the job: report the finding instead of scaling up. The batches of a scale-up are bounded and explicit (`packages/jev-judge/src/batch-plan.ts`), a failed batch is retried instead of restarting the job, and the concurrency limit is always a declared number.
90
+
91
+ 10. **Record every tier run in the ledger.** The harness never runs the tests, so the run is recorded by whoever knows the result:
75
92
 
76
93
  ```bash
77
94
  bun scripts/validation-run.ts --item 12 --tier fast --status passed --duration 18s --dataset 100
@@ -83,6 +100,8 @@ A task with a cheap tier and an expensive tier MUST use the cheap tier while the
83
100
  **What the harness does with the plan:**
84
101
 
85
102
  - The continuation message carries the plan, the recorded tier state and the remaining budget, so the agent never re-runs a tier that already passed.
103
+ - A scale-up that ran before its pilot passed does not count: the final tier stays pending and the task stays open.
104
+ - The status line names the phase, the progress, the validation state and the children that really ran.
86
105
  - When `budgetMinutes` is spent, the item pauses at a safe boundary: nothing is dispatched, the work stays open, and the reason is reported.
87
106
  - When a todo with `finalTierRequired` is marked complete while that tier is pending or failed, the completion report is suppressed and the item is named instead. A todo may not be reported as done on the strength of an unreported run.
88
107
 
@@ -273,6 +292,18 @@ ls packages/notification/dist/
273
292
  bun add -d typescript
274
293
  ```
275
294
 
295
+ **The canonical build**: the compiled output that is committed comes from
296
+ `bun scripts/build-packages.ts`, which uses the TypeScript of the root
297
+ (`node_modules/.bin/tsc`). A package-level `bun run build` uses the
298
+ TypeScript that the package itself installed, and the two write different
299
+ source maps for the same source. A package-level build therefore leaves
300
+ the tree with a `dist/` that the next canonical build changes again.
301
+
302
+ ```bash
303
+ # The build whose output is committed
304
+ bun scripts/build-packages.ts
305
+ ```
306
+
276
307
  ## Icon Usage
277
308
 
278
309
  **This is a Node.js CLI daemon — do NOT add React as a dependency for icons.**
@@ -341,6 +372,57 @@ todo create "Replace Film icon with themed icon"
341
372
 
342
373
  **IMPORTANT**: The `todo` tool and `bd` commands are DIFFERENT systems. Use `todo` for local task tracking.
343
374
 
375
+ **Who owns the todo list.** The **agent** creates the todos, the harness
376
+ only reads them. Two consequences:
377
+
378
+ - The kind of a todo is read from `metadata.kind` first and from the title
379
+ prefix second. An explicit kind is more reliable than a prefix, and the
380
+ agent can always set it.
381
+ - When the todo provider is not available, the harness says so: the status
382
+ line reads `[?] TODOS unavailable` and the agent is told that the phase
383
+ gate is not active. The harness never claims a gate it cannot enforce.
384
+
385
+ ---
386
+
387
+ ## Phase Model: Implementation, Review, Validation (CRITICAL)
388
+
389
+ A task with several steps runs in three phases, and the harness enforces
390
+ the order. The todo prefixes are the contract:
391
+
392
+ | Prefix | Meaning |
393
+ |--------|---------|
394
+ | `[IMPLEMENT]` | implementation work of the current request |
395
+ | `[REVIEW]` | the review of the task diff (one todo per chunk) |
396
+ | `[REVIEW-FIX]` | a confirmed finding that has to be fixed |
397
+ | `[TEST]` | the validation of the task (the tiers are declared in its metadata) |
398
+
399
+ **Phase 1 - implementation.** Every open `[IMPLEMENT]` todo (and every
400
+ unclassified todo) is implementation work. The phase ends when the list
401
+ shows finished implementation work.
402
+
403
+ **Phase 2 - review.** The review starts **on its own**: as soon as the
404
+ implementation is done and the task changed a file, the harness derives the
405
+ scope from the task diff, splits it into chunks and asks for one `[REVIEW]`
406
+ todo per chunk. You never have to queue a review item to get one. A task
407
+ that changed no file completes without a review - there is nothing to
408
+ review.
409
+
410
+ **Phase 3 - validation.** A `[TEST]` todo declares the validation plan of
411
+ the task in `metadata.validationPlan` (see the section above) and the
412
+ recorded runs in the validation ledger decide whether the task is done:
413
+
414
+ - The task is **not** complete while the required final tier is pending.
415
+ - The task is **not** complete when the final tier failed, timed out or
416
+ errored.
417
+ - A plan that requires a final tier but declares none is an error to
418
+ report, not a plan to complete.
419
+ - A validation state that cannot be read is never a validation that
420
+ passed.
421
+
422
+ So the order of a real task is: implement, review the diff, fix the
423
+ findings, run the tiers, record them, and only then report the task as
424
+ done.
425
+
344
426
  ---
345
427
 
346
428
  ### Habit: Detect New Tasks from User Prompts (CRITICAL)
package/CHANGELOG.md CHANGED
@@ -2,6 +2,58 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [1.1.453](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v1.1.448...v1.1.453) (2026-10-27)
6
+
7
+
8
+ ### Features
9
+
10
+ * **delegation:** a child is only a child when it really ran ([ac4e678](https://github.com/ManotLuijiu/pi-harness-runtime/commit/ac4e6782d7d4bc4941e458425c8a9f38a51fc9d4))
11
+ * **delegation:** the scope of a request is measured before it is judged ([684b6a5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/684b6a55184f63d3be24c48dcecfb0b13410dc15))
12
+ * **jev:** the pending question carries the ask and the answer ([a792133](https://github.com/ManotLuijiu/pi-harness-runtime/commit/a792133e517fb8c12d3363e98942f3af30270f27))
13
+ * **merge:** a manual run can leave the merge to the pull request event ([b3d1950](https://github.com/ManotLuijiu/pi-harness-runtime/commit/b3d1950b5941c78ccd56951acdafd1d4afbe1bff))
14
+ * **phase:** the agent owns the todo list and the harness says so ([c489370](https://github.com/ManotLuijiu/pi-harness-runtime/commit/c489370bd7926aece8cb1a77aa20ca2d2672f1fb))
15
+ * **review:** the review starts on its own when the implementation is done ([04f51f5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/04f51f599cd3222b6c74dd8fadb1373bd72d9bd7))
16
+ * **status:** the status line names the phase, the progress and the children ([0167bf3](https://github.com/ManotLuijiu/pi-harness-runtime/commit/0167bf396a0b987c1d44f3b869351976a951dd65))
17
+ * **test:** the task waits for the final tier of the validation plan ([fa147db](https://github.com/ManotLuijiu/pi-harness-runtime/commit/fa147dbf7d50d56e6ea48c6d8fbb4fcb190caf8a))
18
+ * **validation:** a large job starts with a measured pilot ([425964b](https://github.com/ManotLuijiu/pi-harness-runtime/commit/425964b04a3d4fa02adfabed47ff62c5ec2b9c28))
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * **hooks:** a tag is checked through the commit behind it ([c823e8e](https://github.com/ManotLuijiu/pi-harness-runtime/commit/c823e8e1f6973c62b5449a97c101759a4d5cbba5))
24
+ * **jev:** the alternatives of a question are parsed without a quadratic pattern ([63016a3](https://github.com/ManotLuijiu/pi-harness-runtime/commit/63016a3f0488acb801990dd46770c00d382b5858))
25
+ * **keys:** the key directory is audited as a tree ([081cc75](https://github.com/ManotLuijiu/pi-harness-runtime/commit/081cc75ac8ceff36b0d619c3f76371dc08459d3c))
26
+ * **merge:** the boolean input is read as a boolean ([971b716](https://github.com/ManotLuijiu/pi-harness-runtime/commit/971b7169a43db382dbdeb6bb9bab02ab56e0351c))
27
+ * **merge:** the manual job needs the right of the merge ([7ba685e](https://github.com/ManotLuijiu/pi-harness-runtime/commit/7ba685ec43d815f90aa35b742579e87e76c69209))
28
+ * **merge:** the merge of the pull request is the GraphQL mutation ([972bab4](https://github.com/ManotLuijiu/pi-harness-runtime/commit/972bab4b0f7faf1b0047f3bb767ef6db3de4b2fa))
29
+ * **merge:** the workflow merges the pull request the gates cleared ([d7639ec](https://github.com/ManotLuijiu/pi-harness-runtime/commit/d7639ec9b83723510acb4a5c4e23d5d6167c3af6))
30
+ * **release:** a manual release keeps the version of its manifest ([122e661](https://github.com/ManotLuijiu/pi-harness-runtime/commit/122e661ea48cb8aff14c71dbd0221a4cf0ba4c8e))
31
+ * **release:** the release scans the tree of the commit it publishes ([7d47a0b](https://github.com/ManotLuijiu/pi-harness-runtime/commit/7d47a0b5406480a1e1d3a52623a76aabd1e2d0ba))
32
+
33
+ ### [1.1.452](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v1.1.448...v1.1.452) (2026-10-27)
34
+
35
+
36
+ ### Features
37
+
38
+ * **delegation:** the scope of a request is measured before it is judged ([684b6a5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/684b6a55184f63d3be24c48dcecfb0b13410dc15))
39
+ * **jev:** the pending question carries the ask and the answer ([a792133](https://github.com/ManotLuijiu/pi-harness-runtime/commit/a792133e517fb8c12d3363e98942f3af30270f27))
40
+ * **merge:** a manual run can leave the merge to the pull request event ([b3d1950](https://github.com/ManotLuijiu/pi-harness-runtime/commit/b3d1950b5941c78ccd56951acdafd1d4afbe1bff))
41
+ * **review:** the review starts on its own when the implementation is done ([04f51f5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/04f51f599cd3222b6c74dd8fadb1373bd72d9bd7))
42
+ * **test:** the task waits for the final tier of the validation plan ([fa147db](https://github.com/ManotLuijiu/pi-harness-runtime/commit/fa147dbf7d50d56e6ea48c6d8fbb4fcb190caf8a))
43
+
44
+
45
+ ### Bug Fixes
46
+
47
+ * **hooks:** a tag is checked through the commit behind it ([c823e8e](https://github.com/ManotLuijiu/pi-harness-runtime/commit/c823e8e1f6973c62b5449a97c101759a4d5cbba5))
48
+ * **jev:** the alternatives of a question are parsed without a quadratic pattern ([63016a3](https://github.com/ManotLuijiu/pi-harness-runtime/commit/63016a3f0488acb801990dd46770c00d382b5858))
49
+ * **keys:** the key directory is audited as a tree ([081cc75](https://github.com/ManotLuijiu/pi-harness-runtime/commit/081cc75ac8ceff36b0d619c3f76371dc08459d3c))
50
+ * **merge:** the boolean input is read as a boolean ([971b716](https://github.com/ManotLuijiu/pi-harness-runtime/commit/971b7169a43db382dbdeb6bb9bab02ab56e0351c))
51
+ * **merge:** the manual job needs the right of the merge ([7ba685e](https://github.com/ManotLuijiu/pi-harness-runtime/commit/7ba685ec43d815f90aa35b742579e87e76c69209))
52
+ * **merge:** the merge of the pull request is the GraphQL mutation ([972bab4](https://github.com/ManotLuijiu/pi-harness-runtime/commit/972bab4b0f7faf1b0047f3bb767ef6db3de4b2fa))
53
+ * **merge:** the workflow merges the pull request the gates cleared ([d7639ec](https://github.com/ManotLuijiu/pi-harness-runtime/commit/d7639ec9b83723510acb4a5c4e23d5d6167c3af6))
54
+ * **release:** a manual release keeps the version of its manifest ([122e661](https://github.com/ManotLuijiu/pi-harness-runtime/commit/122e661ea48cb8aff14c71dbd0221a4cf0ba4c8e))
55
+ * **release:** the release scans the tree of the commit it publishes ([7d47a0b](https://github.com/ManotLuijiu/pi-harness-runtime/commit/7d47a0b5406480a1e1d3a52623a76aabd1e2d0ba))
56
+
5
57
  ### [1.1.451](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v1.1.448...v1.1.451) (2026-10-27)
6
58
 
7
59
 
package/index.ts CHANGED
@@ -253,6 +253,9 @@ function keyFileFixesOrFallback(feature: string, labels: string[]): string[] {
253
253
  const sharedState = {
254
254
  waitingForUserSince: null as Date | null,
255
255
  todoContinuationController: null as unknown,
256
+ // The ledger of the validation runs. The two-phase workflow reads it to
257
+ // know whether the final tier of a test item passed.
258
+ validationLedger: null as unknown,
256
259
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
257
260
  updateAutoContinueStatus: null as any,
258
261
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -439,6 +442,43 @@ async function loadTwoPhaseModule(): Promise<TwoPhaseModule | null> {
439
442
  }
440
443
  }
441
444
 
445
+ /**
446
+ * Measure the scope of a request in the workspace.
447
+ *
448
+ * The measurement is the only source of the numbers the delegation
449
+ * assessment uses: the request names the files, or the session changed
450
+ * them. When nothing can be measured the result is null, and the caller
451
+ * says that instead of inventing a scope.
452
+ */
453
+ async function measureDelegationScope(
454
+ userMessage: string,
455
+ ): Promise<import("./packages/jev-judge/dist/delegation-harness.js").ScopeFacts | null> {
456
+ try {
457
+ const scope = await import("./packages/jev-judge/dist/delegation-scope.js");
458
+ const harness = await import("./packages/jev-judge/dist/delegation-harness.js");
459
+ const measurement = scope.measureScope({
460
+ workspacePath: _piSessionCwd || process.cwd(),
461
+ hints: scope.scopeHintsFromText(userMessage),
462
+ });
463
+ if (measurement.files.length === 0) return null;
464
+ return harness.scopeFactsFromMeasurement(measurement);
465
+ } catch (error) {
466
+ logToFile("[Delegation]", "Scope measurement failed:", oneLineError(error));
467
+ return null;
468
+ }
469
+ }
470
+
471
+ /**
472
+ * The decision of the delegation assessment, as the harness returns it.
473
+ */
474
+ type DelegationDecisionLike = {
475
+ strategy: string;
476
+ reason: string;
477
+ workGroups?: { id: string; files: string[] }[];
478
+ dispatched?: boolean;
479
+ dispatchTool?: string;
480
+ };
481
+
442
482
  /**
443
483
  * Register the two-phase workflow in the session.
444
484
  *
@@ -473,10 +513,79 @@ async function initTwoPhaseReview(pi: ExtensionAPI): Promise<void> {
473
513
  }
474
514
  };
475
515
 
516
+ /**
517
+ * Read the validation state of the test items of the session.
518
+ *
519
+ * The ledger is the only mechanical source for "did the final tier
520
+ * pass?". The workflow never runs a test itself, it only reads what the
521
+ * ledger recorded.
522
+ */
523
+ const readValidation = async (todos: TwoPhaseTodoLike[]) => {
524
+ const ledger = sharedState.validationLedger as
525
+ | import("./packages/jev-judge/dist/validation-ledger.js").ValidationLedger
526
+ | null
527
+ | undefined;
528
+ if (!ledger) {
529
+ return {
530
+ available: false,
531
+ pendingFinalTier: 0,
532
+ failedFinalTier: 0,
533
+ incompletePlan: 0,
534
+ reason: "the validation ledger is not initialized",
535
+ };
536
+ }
537
+ let pendingFinalTier = 0;
538
+ let failedFinalTier = 0;
539
+ let incompletePlan = 0;
540
+ for (const todo of todos) {
541
+ if (todo.id === undefined) continue;
542
+ if (mod.classifyTodo(todo) !== "test") continue;
543
+ const state = ledger.stateFor(todo as never);
544
+ if (state.planIncomplete) {
545
+ incompletePlan += 1;
546
+ continue;
547
+ }
548
+ if (!state.finalTierRequired) continue;
549
+ const status = state.full.status;
550
+ if (status === "passed") continue;
551
+ if (status === "failed" || status === "timeout" || status === "error") {
552
+ failedFinalTier += 1;
553
+ } else {
554
+ pendingFinalTier += 1;
555
+ }
556
+ }
557
+ return { available: true, pendingFinalTier, failedFinalTier, incompletePlan };
558
+ };
559
+
560
+ /**
561
+ * The delegation state of the session.
562
+ *
563
+ * The numbers are the observed ones: the children that really ran and
564
+ * the groups the harness measured. A plan that was never dispatched
565
+ * shows zero dispatched children.
566
+ */
567
+ const readDelegation = () => {
568
+ const harness = sharedState.delegationHarness as
569
+ | import("./packages/jev-judge/dist/delegation-harness.js").DelegationHarness
570
+ | null
571
+ | undefined;
572
+ if (!harness) return undefined;
573
+ try {
574
+ const receipt = harness.getReceipt();
575
+ const groups = receipt?.decision.workGroups?.length ?? 0;
576
+ const dispatched = harness.getChildren().length;
577
+ return { groups, dispatched };
578
+ } catch {
579
+ return undefined;
580
+ }
581
+ };
582
+
476
583
  try {
477
584
  await mod.initTwoPhaseReview({
478
585
  pi,
479
586
  readTodos,
587
+ readValidation,
588
+ readDelegation,
480
589
  workspaceRoot: () => {
481
590
  try {
482
591
  return realpathSync(process.cwd());
@@ -748,6 +857,12 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
748
857
  });
749
858
  logStartup("[pi-harness] Delegation harness initialized");
750
859
 
860
+ // The lifecycle of a continuation the harness started itself: the
861
+ // status stays "RUN" until the turn settles.
862
+ const { ContinuationGate } = await import(
863
+ "./packages/jev-judge/dist/continuation-status.js"
864
+ );
865
+
751
866
  // The Jev key is missing: the fix commands must survive every later
752
867
  // message with the same label - `HarnessStartupStatus.update` replaces
753
868
  // a message by label, so a later "verifying API..." line would drop
@@ -774,6 +889,96 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
774
889
  let lastUserMessage: Date | null = null;
775
890
  let autoContinueTimer: NodeJS.Timeout | null = null;
776
891
  let _inferenceInProgress = false; // Track for overlap prevention
892
+ // A continuation was dispatched and its turn has not settled yet.
893
+ // The gate owns that state: the status stays "RUN" until the turn
894
+ // settles, and no timer may clear it in the meantime.
895
+ const _continuation = new ContinuationGate();
896
+ // The measured scope that was already handed to the agent. The
897
+ // plan is sent once per scope, not once per message.
898
+ let _lastDelegationPlanFingerprint: string | null = null;
899
+
900
+ /**
901
+ * Hand the measured delegation plan to the agent.
902
+ *
903
+ * The harness measured the scope; the agent is the one that sends
904
+ * the groups to the sub-agent tool. The message says so, and the
905
+ * plan is not repeated for a scope the agent already knows.
906
+ */
907
+ async function handDelegationPlanToAgent(
908
+ decision: DelegationDecisionLike | null,
909
+ fingerprint: string,
910
+ ): Promise<void> {
911
+ if (!decision) return;
912
+ if (_lastDelegationPlanFingerprint === fingerprint) return;
913
+ let message: string | null = null;
914
+ try {
915
+ const harness = await import("./packages/jev-judge/dist/delegation-harness.js");
916
+ message = harness.buildDelegationSteer(decision);
917
+ } catch (error) {
918
+ logToFile("[Delegation]", "The delegation plan could not be built:", oneLineError(error));
919
+ return;
920
+ }
921
+ if (!message) return;
922
+ _lastDelegationPlanFingerprint = fingerprint;
923
+ try {
924
+ await pi.sendUserMessage(message, { deliverAs: "steer" });
925
+ logToFile("[Delegation]", "The measured delegation plan was handed to the agent");
926
+ } catch (error) {
927
+ logToFile("[Delegation]", "The delegation steer failed:", oneLineError(error));
928
+ }
929
+ }
930
+
931
+ // The children of a delegation are observed, not assumed. The
932
+ // harness does not dispatch anything: the agent sends the measured
933
+ // groups to the sub-agent tool, and the tool events are the proof
934
+ // that a child really ran.
935
+ pi.on("tool_execution_start", (event) => {
936
+ try {
937
+ const toolEvent = event as {
938
+ toolCallId?: string;
939
+ toolName?: string;
940
+ args?: unknown;
941
+ };
942
+ const child = sharedState.delegationHarness?.observeToolStart?.({
943
+ toolCallId: String(toolEvent.toolCallId ?? ""),
944
+ toolName: String(toolEvent.toolName ?? ""),
945
+ args: toolEvent.args,
946
+ });
947
+ if (child) {
948
+ logToFile(
949
+ "[Delegation]",
950
+ `A child was dispatched: ${child.toolName} (${child.groupId ?? "no group"})`,
951
+ );
952
+ }
953
+ } catch (error) {
954
+ logToFile("[Delegation]", "The child could not be recorded:", oneLineError(error));
955
+ }
956
+ });
957
+
958
+ pi.on("tool_execution_end", (event) => {
959
+ try {
960
+ const toolEvent = event as {
961
+ toolCallId?: string;
962
+ toolName?: string;
963
+ result?: unknown;
964
+ isError?: boolean;
965
+ };
966
+ const child = sharedState.delegationHarness?.observeToolEnd?.({
967
+ toolCallId: String(toolEvent.toolCallId ?? ""),
968
+ toolName: String(toolEvent.toolName ?? ""),
969
+ result: toolEvent.result,
970
+ isError: toolEvent.isError === true,
971
+ });
972
+ if (child) {
973
+ logToFile(
974
+ "[Delegation]",
975
+ `The child ended: ${child.toolName} (${child.status})${child.model ? `, model ${child.model}` : ""}`,
976
+ );
977
+ }
978
+ } catch (error) {
979
+ logToFile("[Delegation]", "The child could not be closed:", oneLineError(error));
980
+ }
981
+ });
777
982
  let _sessionId = Date.now().toString(36); // Track session for cancellation
778
983
  let _lastNotifiedQuestion = ""; // Track last question to prevent repeat notifications
779
984
  let _completedNotifiedFor: string | null = null; // Track completion notification deduplication
@@ -1478,8 +1683,11 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
1478
1683
  sharedState.waitingForUserSince = null;
1479
1684
  _lastNotifiedQuestion = "";
1480
1685
 
1481
- // Clear status after short delay
1482
- setTimeout(() => updateAutoContinueStatus("IDLE"), 2000);
1686
+ // The status stays "RUN" until the turn that the steer
1687
+ // started has settled. A timer would clear it while the
1688
+ // agent is still working, and the status would claim
1689
+ // that nothing runs any more.
1690
+ _continuation.begin();
1483
1691
  }
1484
1692
  _inferenceInProgress = false;
1485
1693
  } catch {
@@ -1523,6 +1731,9 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
1523
1731
  );
1524
1732
  },
1525
1733
  });
1734
+ // The two-phase workflow reads the same ledger: the phase gate
1735
+ // and the completion report must never disagree about a tier.
1736
+ sharedState.validationLedger = validationLedger;
1526
1737
  // SAFETY: ctx is ExtensionContext with sessionManager, isIdle, hasPendingMessages
1527
1738
  // dispatch uses pi.sendUserMessage for actual message delivery
1528
1739
  todoContinuationController = new TodoContinuationController(
@@ -1546,6 +1757,7 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
1546
1757
  // keep a timer alive nor block a later continuation.
1547
1758
  todoContinuationController?.dispose?.();
1548
1759
  todoContinuationController = null;
1760
+ sharedState.validationLedger = null;
1549
1761
  });
1550
1762
 
1551
1763
  // --- task-intake: Automatic todo capture from user requests -----------
@@ -1637,25 +1849,33 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
1637
1849
  ];
1638
1850
  const isLargeRefactor = largeRefactorPatterns.some((p) => p.test(lowerMsg));
1639
1851
  if (isLargeRefactor) {
1640
- logToFile("[Delegation]", "Large refactor request detected, triggering assessment");
1641
- // Trigger delegation assessment asynchronously
1642
- sharedState.delegationHarness.assess({
1643
- estimatedFiles: 10,
1644
- observedFiles: 10,
1645
- estimatedCallSites: 50,
1646
- observedCallSites: 50,
1647
- independentComponents: 2,
1648
- tightlyCoupled: false,
1649
- isMechanical: false,
1650
- isComplex: true,
1651
- fileTypes: {},
1652
- fingerprint: `user-msg-${Date.now()}`,
1653
- }, "user-request").then((decision: { strategy: string; reason: string } | null) => {
1654
- if (decision) {
1655
- logToFile("[Delegation]", `Assessment result: ${decision.strategy} - ${decision.reason}`);
1656
- }
1657
- }).catch((e: unknown) => logToFile("[Delegation]", "Assessment error:", String(e)));
1658
- }
1852
+ // The scope is measured in the workspace: the request
1853
+ // names the files, or the session changed them. A
1854
+ // number that nobody counted is not a scope, so the
1855
+ // assessment waits for the measurement.
1856
+ logToFile("[Delegation]", "Large refactor request detected, measuring the scope");
1857
+ measureDelegationScope(userMessage)
1858
+ .then((facts) => {
1859
+ if (!facts) {
1860
+ logToFile("[Delegation]", "No measurable scope in the request - no delegation assessment");
1861
+ return null;
1862
+ }
1863
+ return sharedState.delegationHarness
1864
+ .assess(facts, `user-request-${facts.fingerprint}`)
1865
+ .then(async (decision: DelegationDecisionLike | null) => {
1866
+ if (decision) {
1867
+ logToFile(
1868
+ "[Delegation]",
1869
+ `Assessment: ${decision.strategy} - ${decision.reason} (${decision.workGroups?.length ?? 0} measured group(s), dispatched=${decision.dispatched === true})`,
1870
+ );
1871
+ }
1872
+ await handDelegationPlanToAgent(decision, facts.fingerprint);
1873
+ return decision;
1874
+ })
1875
+ .catch((e: unknown) => logToFile("[Delegation]", "Assessment error:", String(e)));
1876
+ })
1877
+ .catch((e: unknown) => logToFile("[Delegation]", "Scope measurement failed:", String(e)));
1878
+ }
1659
1879
  }
1660
1880
 
1661
1881
  // Temporary pause for user input processing (auto-clears on next decision)
@@ -1721,6 +1941,13 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
1721
1941
  // consuming the retry budget.
1722
1942
  todoContinuationController?.releaseContinuationGuard?.("agent_settled");
1723
1943
 
1944
+ // The turn that a continuation started has settled: the status
1945
+ // returns to idle, unless the handler below dispatches the next
1946
+ // continuation.
1947
+ if (_continuation.settle()) {
1948
+ updateAutoContinueStatus("IDLE");
1949
+ }
1950
+
1724
1951
  // Don't continue if user is waiting for response
1725
1952
  if (waitingForUserSince) {
1726
1953
  todoContinuationController?.clearQueue();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-harness-runtime",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "description": "[BETA] Codex-style /usage status + autonomous coding harness for pi. Not production ready — expect breaking changes.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/agent-policy",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "description": "Agent policy runtime for pi-harness - enforces mandatory rule delivery and mutation gating",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/auth",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "description": "pi-harness-runtime — auth",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/autonomous-runtime",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "description": "Autonomous Operations Runtime — task inbox, lease management, and worker lifecycle (RFC-0101)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/checkpoint",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "description": "Enhanced checkpoint system with incremental snapshots, diff-based storage, and recovery strategies",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/clipboard",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/codex-watcher",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "description": "Watches Codex CLI sessions and emits visible messages for PING-PONG monitoring",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/config-capture",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "description": "Auto-capture API-fetched configuration values to AGENTS.md",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/contracts",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "type": "module",
5
5
  "description": "RPC schema definitions for pi-harness runtime",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/cookie-sanitizer",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "description": "pi-harness-runtime — cookie sanitizer (provider-quota cookie file normaliser)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/evaluation-engine",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "description": "Evaluation engine for pi-harness runtime (RFC-0057)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/event-bus",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/event-store",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/experience-replay",
3
- "version": "1.1.451",
3
+ "version": "1.1.453",
4
4
  "description": "Experience replay for debugging and reproducibility (RFC-0059)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",