@osolmaz/pi-workflows 0.11.2 → 0.12.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.
Files changed (177) hide show
  1. package/README.md +15 -10
  2. package/dist/builtins/autoimplement-command-batches.d.ts +52 -0
  3. package/dist/builtins/autoimplement-command-batches.js +258 -0
  4. package/dist/builtins/autoimplement-command-batches.js.map +1 -0
  5. package/dist/builtins/autoimplement.workflow.d.ts +509 -124
  6. package/dist/builtins/autoimplement.workflow.js +884 -327
  7. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  8. package/dist/builtins/catalog.js +5 -3
  9. package/dist/builtins/catalog.js.map +1 -1
  10. package/dist/builtins/index.d.ts +4 -1
  11. package/dist/builtins/index.js +3 -1
  12. package/dist/builtins/index.js.map +1 -1
  13. package/dist/builtins/monitor.workflow.d.ts +2 -4
  14. package/dist/builtins/monitor.workflow.js +26 -128
  15. package/dist/builtins/monitor.workflow.js.map +1 -1
  16. package/dist/builtins/pi-agent-group.d.ts +72 -0
  17. package/dist/builtins/pi-agent-group.js +1087 -0
  18. package/dist/builtins/pi-agent-group.js.map +1 -0
  19. package/dist/builtins/plan-approval.workflow.d.ts +39 -5
  20. package/dist/builtins/plan-approval.workflow.js +92 -14
  21. package/dist/builtins/plan-approval.workflow.js.map +1 -1
  22. package/dist/builtins/plan-change.workflow.d.ts +301 -0
  23. package/dist/builtins/plan-change.workflow.js +256 -0
  24. package/dist/builtins/plan-change.workflow.js.map +1 -0
  25. package/dist/builtins/plan-presentation.js +2 -2
  26. package/dist/builtins/plan-presentation.js.map +1 -1
  27. package/dist/builtins/sanity-check.workflow.d.ts +85 -0
  28. package/dist/builtins/sanity-check.workflow.js +482 -0
  29. package/dist/builtins/sanity-check.workflow.js.map +1 -0
  30. package/dist/controllers/sqlite.d.ts +90 -2
  31. package/dist/controllers/sqlite.js +380 -5
  32. package/dist/controllers/sqlite.js.map +1 -1
  33. package/dist/extension/decision-channels.d.ts +2 -2
  34. package/dist/extension/decision-channels.js +29 -35
  35. package/dist/extension/decision-channels.js.map +1 -1
  36. package/dist/extension/deferred-turn-coordinator.d.ts +32 -0
  37. package/dist/extension/deferred-turn-coordinator.js +143 -0
  38. package/dist/extension/deferred-turn-coordinator.js.map +1 -0
  39. package/dist/extension/deferred-turn.d.ts +44 -0
  40. package/dist/extension/deferred-turn.js +110 -0
  41. package/dist/extension/deferred-turn.js.map +1 -0
  42. package/dist/extension/index.js +348 -80
  43. package/dist/extension/index.js.map +1 -1
  44. package/dist/extension/session-events.d.ts +2 -2
  45. package/dist/extension/step-message.d.ts +1 -0
  46. package/dist/extension/step-message.js.map +1 -1
  47. package/dist/extension/widget.js +23 -3
  48. package/dist/extension/widget.js.map +1 -1
  49. package/dist/herdr/setup.js +4 -4
  50. package/dist/render/graph-render.js +1 -2
  51. package/dist/render/graph-render.js.map +1 -1
  52. package/dist/viewer/render.js +7 -6
  53. package/dist/viewer/render.js.map +1 -1
  54. package/dist/workflows/catalog.js +7 -2
  55. package/dist/workflows/catalog.js.map +1 -1
  56. package/dist/workflows/command-batch.d.ts +38 -0
  57. package/dist/workflows/command-batch.js +176 -0
  58. package/dist/workflows/command-batch.js.map +1 -0
  59. package/dist/workflows/composition.js +8 -0
  60. package/dist/workflows/composition.js.map +1 -1
  61. package/dist/workflows/decision-presentation.d.ts +1 -1
  62. package/dist/workflows/decision-presentation.js +51 -38
  63. package/dist/workflows/decision-presentation.js.map +1 -1
  64. package/dist/workflows/engine.d.ts +2 -2
  65. package/dist/workflows/engine.js +22 -17
  66. package/dist/workflows/engine.js.map +1 -1
  67. package/dist/workflows/errors.d.ts +13 -0
  68. package/dist/workflows/errors.js +15 -0
  69. package/dist/workflows/errors.js.map +1 -1
  70. package/dist/workflows/human-decision.d.ts +16 -4
  71. package/dist/workflows/human-decision.js +175 -72
  72. package/dist/workflows/human-decision.js.map +1 -1
  73. package/dist/workflows/index.d.ts +3 -2
  74. package/dist/workflows/index.js +2 -1
  75. package/dist/workflows/index.js.map +1 -1
  76. package/dist/workflows/progress.d.ts +1 -0
  77. package/dist/workflows/progress.js +15 -3
  78. package/dist/workflows/progress.js.map +1 -1
  79. package/dist/workflows/schema.js +12 -1
  80. package/dist/workflows/schema.js.map +1 -1
  81. package/dist/workflows/shell.d.ts +4 -0
  82. package/dist/workflows/shell.js +6 -0
  83. package/dist/workflows/shell.js.map +1 -1
  84. package/dist/workflows/store.js +8 -1
  85. package/dist/workflows/store.js.map +1 -1
  86. package/dist/workflows/types.d.ts +37 -48
  87. package/docs/2026-08-18-herdr-piw-plan.md +1 -1
  88. package/docs/2026-08-20-durable-workflow-launch-plan.md +19 -23
  89. package/docs/CONTROLLERS.md +2 -2
  90. package/docs/DEFERRED_TURNS.md +298 -0
  91. package/docs/DESIGN_PHILOSOPHY.md +1 -1
  92. package/docs/HUMAN_DECISIONS.md +30 -40
  93. package/docs/HUMAN_DECISION_PRESENTATIONS.md +17 -27
  94. package/docs/MONITOR.md +7 -13
  95. package/docs/WORKFLOW_COMPOSITION.md +10 -9
  96. package/docs/WORKFLOW_STEP_MESSAGES.md +5 -5
  97. package/docs/WORKFLOW_UPDATES.md +28 -5
  98. package/docs/plans/2026-08-04-controller-runtime-plan.md +1 -1
  99. package/docs/plans/2026-08-05-always-on-workflows-plan.md +1 -1
  100. package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +9 -9
  101. package/docs/plans/2026-08-13-built-in-workflow-catalog-plan.md +1 -1
  102. package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +1 -1
  103. package/docs/plans/2026-08-16-workflow-updates-plan.md +3 -3
  104. package/docs/plans/2026-08-17-bundled-skills-plan.md +6 -6
  105. package/docs/plans/2026-08-19-human-decision-gates-plan.md +5 -5
  106. package/docs/plans/2026-08-19-human-decision-presentations-plan.md +1 -1
  107. package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +1 -1
  108. package/docs/plans/2026-08-19-workflow-composition-plan.md +6 -6
  109. package/docs/plans/2026-08-20-autoimplement-blocker-challenge-plan.md +3 -3
  110. package/docs/plans/2026-08-20-bounded-command-batches-plan.md +206 -0
  111. package/docs/plans/2026-08-20-herdr-plugin-sync-plan.md +9 -9
  112. package/docs/plans/2026-08-21-autoimplement-timeout-fallback-plan.md +242 -0
  113. package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +324 -0
  114. package/docs/plans/2026-08-21-plan-change-approval-policy-plan.md +322 -0
  115. package/docs/plans/2026-08-21-sanity-check-plan.md +283 -0
  116. package/docs/run-bundles.md +8 -6
  117. package/docs/workflows.md +85 -22
  118. package/examples/workflows/approved-plan.workflow.ts +19 -46
  119. package/examples/workflows/command-batch.workflow.ts +50 -0
  120. package/examples/workflows/sanity-check.workflow.ts +1 -0
  121. package/herdr-plugin.toml +3 -3
  122. package/package.json +7 -7
  123. package/schemas/decision-presentation-v1.schema.json +1 -1
  124. package/schemas/human-decision-accepted-v1.schema.json +16 -4
  125. package/schemas/human-decision-answer-attempt-v1.schema.json +1 -1
  126. package/schemas/human-decision-cancellation-v1.schema.json +1 -1
  127. package/schemas/human-decision-continuation-v1.schema.json +11 -2
  128. package/schemas/human-decision-delivery-v1.schema.json +9 -1
  129. package/schemas/human-decision-receipt-v1.schema.json +9 -1
  130. package/schemas/human-decision-request-v1.schema.json +25 -5
  131. package/schemas/human-decision-resolution-v1.schema.json +1 -1
  132. package/schemas/human-decision-settlement-v1.schema.json +1 -1
  133. package/skills/autodoc/SKILL.md +33 -2
  134. package/skills/autoimplement/SKILL.md +93 -12
  135. package/skills/autoplan/SKILL.md +26 -2
  136. package/skills/monitor/SKILL.md +66 -24
  137. package/skills/pi-workflows/SKILL.md +21 -6
  138. package/skills/sanity-check/SKILL.md +44 -0
  139. package/src/builtins/autoimplement-command-batches.ts +364 -0
  140. package/src/builtins/autoimplement.workflow.ts +1075 -370
  141. package/src/builtins/catalog.ts +5 -3
  142. package/src/builtins/index.ts +24 -0
  143. package/src/builtins/monitor.workflow.ts +27 -150
  144. package/src/builtins/pi-agent-group.ts +1407 -0
  145. package/src/builtins/plan-approval.workflow.ts +157 -24
  146. package/src/builtins/plan-change.workflow.ts +321 -0
  147. package/src/builtins/plan-presentation.ts +2 -2
  148. package/src/builtins/sanity-check.workflow.ts +769 -0
  149. package/src/controllers/sqlite.ts +580 -7
  150. package/src/extension/decision-channels.ts +36 -66
  151. package/src/extension/deferred-turn-coordinator.ts +171 -0
  152. package/src/extension/deferred-turn.ts +166 -0
  153. package/src/extension/index.ts +438 -90
  154. package/src/extension/session-events.ts +2 -2
  155. package/src/extension/step-message.ts +1 -0
  156. package/src/extension/widget.ts +24 -5
  157. package/src/herdr/setup.ts +4 -4
  158. package/src/render/graph-render.ts +1 -2
  159. package/src/viewer/render.ts +7 -6
  160. package/src/workflows/catalog.ts +7 -2
  161. package/src/workflows/command-batch.ts +254 -0
  162. package/src/workflows/composition.ts +9 -0
  163. package/src/workflows/decision-presentation.ts +56 -43
  164. package/src/workflows/engine.ts +25 -20
  165. package/src/workflows/errors.ts +24 -0
  166. package/src/workflows/human-decision.ts +218 -101
  167. package/src/workflows/index.ts +20 -11
  168. package/src/workflows/progress.ts +18 -3
  169. package/src/workflows/schema.ts +19 -1
  170. package/src/workflows/shell.ts +10 -0
  171. package/src/workflows/store.ts +8 -1
  172. package/src/workflows/types.ts +43 -59
  173. package/schemas/human-decision-accepted-v2.schema.json +0 -50
  174. package/schemas/human-decision-delivery-v2.schema.json +0 -36
  175. package/schemas/human-decision-receipt-v2.schema.json +0 -39
  176. package/schemas/human-decision-request-v2.schema.json +0 -69
  177. package/schemas/human-decision-resolution-v2.schema.json +0 -27
package/docs/workflows.md CHANGED
@@ -16,7 +16,7 @@ Files are discovered by suffix (`.workflow.ts`, `.workflow.js`, `.workflow.mts`,
16
16
  3. Workflows built into Pi Workflows
17
17
 
18
18
  Pi Workflows includes built-in `autoplan`, `autodoc`, `autoimplement`,
19
- `plan-approval`, and `monitor` workflows. `autoplan` is the current name for the
19
+ `plan-approval`, `sanity-check`, and `monitor` workflows. `autoplan` is the current name for the
20
20
  planning workflow that was first released as `autodevise`; the old command and
21
21
  export are not retained. A project or global file named `monitor.workflow.ts`
22
22
  replaces the built-in monitor. The package registers each built-in in
@@ -161,11 +161,13 @@ then fails the step. If an agent node times out or the workflow is cancelled,
161
161
  the extension also aborts its active Pi turn. The model cannot continue to use
162
162
  tools after the engine has closed that attempt.
163
163
 
164
- `timeoutMs` can be a finite positive number or a function of the normal node
165
- context. A timeout function can use prepared outputs to select a deadline for
166
- this run. It has 30 seconds to return a value. Computed timeout functions are
167
- runtime code, so definition snapshots omit them; fixed numeric timeouts remain
168
- in the snapshot.
164
+ `timeoutMs` can be a finite positive number, `null`, or a function of the normal
165
+ node context that returns either value. Omit it to use the 15-minute engine
166
+ default. Set it to `null` to disable only the wall-clock deadline; cancellation,
167
+ parking, claim loss, shutdown, and the node's abort signal still work. A timeout
168
+ function can use prepared outputs to select a policy for this run. It has 30
169
+ seconds to return. Computed timeout functions are runtime code, so definition
170
+ snapshots omit them. Snapshots keep fixed numbers and fixed `null` values.
169
171
 
170
172
  ### compute
171
173
 
@@ -289,6 +291,10 @@ const choices = defineHumanChoices({
289
291
  humanDecision({
290
292
  audience: "operator",
291
293
  choices,
294
+ onTimeout: {
295
+ afterMs: 10 * 60_000,
296
+ response: { choice: "continue" },
297
+ },
292
298
  request: ({ outputs }) => ({
293
299
  title: "Approve plan",
294
300
  subject: outputs.plan,
@@ -301,7 +307,7 @@ humanDecision({
301
307
  });
302
308
  ```
303
309
 
304
- The waiting run stores a versioned request and asks every channel configured for the logical audience. The structured `subject` remains machine data. Channels receive only the normalized `presentation`, title, choices, and input prompts. The first valid verified human answer wins. A continuation preserves the original workflow input and exposes the accepted answer as the checkpoint output. `humanDecisionEdge()` provides exhaustive routing for the choices. Existing `body` requests remain a legacy compatibility form and use deterministic readable formatting.
310
+ The waiting run stores a versioned request and asks every channel configured for the logical audience. The structured `subject` remains machine data. Channels receive only the normalized `presentation`, title, choices, input prompts, and any deadline policy. The first valid verified human answer wins. When `onTimeout` is present and no human answer wins before the saved deadline, recovery applies the validated response with `timeout` provenance. This policy can continue without a configured channel. A continuation preserves the original workflow input and exposes the resolved response as the checkpoint output. `humanDecisionEdge()` provides exhaustive routing for the choices. Existing `body` requests remain a legacy compatibility form and use deterministic readable formatting.
305
311
 
306
312
  The model-facing workflow tool cannot answer a protected human decision. Pi interactive UI and configured external channels use a host-owned answer path. Ordinary checkpoints keep the existing `/workflow answer` behavior.
307
313
 
@@ -415,14 +421,64 @@ runs.
415
421
 
416
422
  The built-in `autoplan` workflow selects a practical in-scope solution and writes a detailed plan. The standalone `autodoc` workflow finds an already selected plan, records it in canonical documentation, verifies those documents, and never devises or implements. The built-in `autoimplement` workflow finds a clear existing plan from explicit input, conversation context, or referenced canonical documents. It blocks when no clear plan exists. An explicit plan bypasses autodoc only when a current-document receipt carries its matching plan digest; otherwise autodoc inspects and adopts or updates the canonical documents. Later invalidating evidence returns to `autoplan` followed by `autodoc`.
417
423
 
418
- The built-in `plan-approval` workflow offers verified human `continue`, `stop`, and exact-text `replan` exits. It is optional. A replan exit returns the unchanged text to autoplan, documents the revised plan, and asks again through a new plan digest.
424
+ The built-in `plan-approval` workflow offers `continue`, `stop`, and exact-text `replan` exits. Its shared policy uses `auto`, `required`, or `skip` mode. Omitted policy defaults to `auto`: ask audience `operator`, then continue with the exact plan after 10 minutes without an answer. Required mode waits for a human. Skip mode creates no decision. Stop and replan always require a human answer.
425
+
426
+ The internal plan-change workflow composes Autoplan, Autodoc, plan approval, and bounded replanning. Autoimplement and Monitor use it whenever they create or change a plan. Existing supplied or discovered plans bypass the gate. A plan selected by Monitor enters Autoimplement without another decision for the same digest.
427
+
428
+ Autoimplement runs independent commands through bounded command batches. A batch is an ordinary function action that calls the public `runCommandBatch` helper. Each command has a stable ID, executable, arguments, absolute working directory, timeout, and output limit. Results stay separate and return in input order. One command uses the same path with concurrency one.
429
+
430
+ Autoimplement gives `implement` an eight-hour deadline. When a supported
431
+ long-running agent node times out, one shared read-only fallback inspects the
432
+ current repository, accepted workflow outputs, and relevant pull-request state.
433
+ It then retries the timed-out stage or routes to verification, review, CI,
434
+ delivery, the existing redesign workflow, or blocked. The fallback can run at
435
+ most three times in one Autoimplement run. Its own failure or timeout is
436
+ terminal. Cancellation remains immediate and never enters fallback. A repeated
437
+ effect step first checks what already exists and performs only missing work.
438
+ This graph fallback starts after the timed-out turn ends and is separate from
439
+ successor-turn delivery.
440
+
441
+ Autoimplement uses batches for pi-reviewer, pending CI watches, and local verification commands from independent repositories. It keeps model turns, fixes, pushes, comment changes, merges, and releases in their existing order. Reviewer commands are tied to the repository, base branch, pushed head, and relevant dependency fingerprint. A later review round includes only repositories whose head or dependency fingerprint changed. P0 or P1 work still requires another review. P2-only work can be addressed and verified without another reviewer run only because of that P2 work.
442
+
443
+ Autoimplement inspects every pull request before it waits for CI. It accepts only supported pending `gh pr checks --watch` or `gh run watch` descriptors and binds each one to the validated pull request as `gh pr checks <PR URL> --watch`. Repository and pull-request overrides are rejected. One watch lasts at most five minutes. A failed or timed-out watch affects only its pull request. When checks remain pending, the model runs more useful local tests before checking CI again. Autoimplement does not invent an ETA.
419
444
 
420
- Autoimplement writes and runs the exact Pi Reviewer command. It records P0 through P2 by review round. P0 or P1 work requires another review. P2-only work can be addressed and verified without another reviewer run. CI tracking commands are also explicit. One CI watch lasts at most five minutes, after which the model runs more useful local tests before checking CI again.
445
+ The action abort signal stops active command process groups and prevents queued commands from starting. Accepted outputs use the existing trace and artifacts. An interrupted unaccepted batch runs again because batch commands are read-only or isolated local checks. Progress updates contain metadata only and never control routing. Truncated reviewer or CI output cannot count as clean. See [Run independent commands in bounded batches](plans/2026-08-20-bounded-command-batches-plan.md) for the complete contract and implementation plan.
421
446
 
422
447
  A model-generated blocker from implementation or a safe later stage does not end autoimplement by itself. A separate blocker-challenge agent checks the task, approved plan, current result, evidence, scope, authority, earlier attempts, and practical alternatives. It confirms a blocker only when the blocker exists now, is outside the granted authority, has no safe path forward, has an empty next action, and includes concrete evidence and checked alternatives. A rejected blocker must name the next practical action and routes through the existing redesign workflow before implementation and verification continue.
423
448
 
424
449
  Autoimplement can run the blocker challenge at most three times in one run. Each later challenge receives the earlier challenge results. Reaching the limit stops with the normal workflow safety-limit reason. Explicit human stops, cancellation, exhausted workflow or replan limits, protected authorization gaps, and an independent blocked result from redesign remain direct stops. These hard boundaries do not enter the blocker challenge.
425
450
 
451
+ ### Built-in sanity check
452
+
453
+ The built-in `sanity-check` workflow reviews a pull request or local contribution before implementation, approval, or merge. It checks whether the change is needed, duplicates existing code, should use a simpler design, adds unnecessary data models or public plugin and SDK APIs, or has scope and test problems.
454
+
455
+ ```json
456
+ {
457
+ "mode": "serial",
458
+ "baseRef": "origin/main"
459
+ }
460
+ ```
461
+
462
+ Serial mode is the default. It runs one review session for all four review areas, then one verification session. Parallel mode runs four focused review sessions at the same time, then one verification session. Serial mode uses two model sessions. Parallel mode uses five. When `baseRef` is omitted, the workflow tries the remote default branch, the current branch upstream, and the first parent, then uses `HEAD` for a working-tree-only review.
463
+
464
+ The workflow collects pull request intent and repository diff evidence before model review. It bounds evidence and review results before prompt construction and marks truncated input. Every review must cite evidence and give the strongest case for accepting the current design. The verification session removes unsupported claims, requires exact file and symbol references, resolves supported conflicts, and returns `keep`, `simplify`, `refactor`, `drop`, or `needs_evidence`.
465
+
466
+ Sanity Check revision 3 creates child sessions directly through the documented Pi SDK. A private built-in runner uses `createAgentSession` with `SessionManager.inMemory`, one independent context per child, and only the verified built-in `read`, `grep`, `find`, and `ls` tools. Child sessions load no skills, prompt templates, themes, or context files. They create no Pi session file.
467
+
468
+ The parent Pi process keeps its normal configured extensions enabled. The child runner resolves enabled user extension paths, excludes Pi Workflows and project extensions by default, and preflights the remaining paths without creating a session. It admits only the extension that registers the exact configured provider, plus any behavior extension on an explicit private allowlist. It rejects competing provider owners, workflow tools or commands, and extensions that replace a built-in read-only tool. The admitted paths are frozen for the group, and each child loads only those explicit paths without a second discovery pass.
469
+
470
+ Each child owns a separate `ModelRuntime`, provider instance, extension runtime, resource loader, and in-memory session. The runner loads validated model and ordinary credential snapshots into per-child in-memory stores without writing them back. A provider extension uses its existing provider-owned credential store in place; Pi Workflows does not copy, print, migrate, or persist those credentials.
471
+
472
+ A complete explicit provider, model, and thinking override wins. Otherwise, the runner uses the configured process defaults. It passes the exact cached model and thinking level to the child, then verifies the actual provider, model, thinking level, authentication, extension state, active tools, and tool sources before prompting. A missing or different dispatch is terminal. The runner never silently falls back to OpenRouter, Kimi, a local model, or another provider or model. It does not promise to inherit a model selected temporarily in the origin Pi TUI.
473
+
474
+ Children do not receive the `workflow` tool, workflow commands, parent run identifiers, update channels, or workflow callbacks. Prompts that invoke extension slash commands are rejected. The parent Sanity Check action remains the only workflow owner. These controls block normal child model and extension bindings from inspecting or changing workflow state. Extensions still run as trusted code in the parent Node process, so this is not an OS sandbox against direct filesystem or network access.
475
+
476
+ Only bounded final assistant text and safe operational facts leave a live child session. Pi Workflows does not persist child prompts, reasoning, intermediate messages, tool arguments, tool results, message history, credentials, or extension-private state. Timeout and cancellation abort and settle active work before the runner disposes the session, shuts down extensions, and releases provider resources.
477
+
478
+ The workflow publishes aggregate and per-agent `pi-workflows.progress.v1` tracks under `agents/review/*` and `agents/verification/*`. Progress contains role, the verified actual model when known, state, elapsed facts, and safe phases such as `thinking` or `tool: read`. The Pi widget shows the aggregate plus failed and active children within its ten-line limit. `piw` shows every durable child track and its samples. Both views use existing progress records, so no child workflow run or new persisted schema is needed.
479
+
480
+ Serial mode still uses two sessions, and parallel mode still uses five. Prompts, review areas, strict result validation, verdicts, and final notification stay unchanged. The workflow sends the final report through a final notification with `triggerTurn: false`, so the origin model does not produce another response. The CLI, JSON or RPC stream, temporary prompt file, standard-output cap, subprocess fallback, shared child runtime, and blanket child-extension ban are not retained. See [the Sanity Check plan](plans/2026-08-21-sanity-check-plan.md) for the selected implementation and test boundaries.
481
+
426
482
  ### Built-in monitor
427
483
 
428
484
  The built-in `monitor` workflow turns a plain request for repeated checks into
@@ -439,7 +495,7 @@ one looping workflow run. Its input is:
439
495
  }
440
496
  ```
441
497
 
442
- The first check runs immediately. Omit `repair` for observation-only monitoring. An authorized repair routes through outer `autoplan`, `autodoc`, optional `plan-approval`, `autoimplement`, and internal redesign before the monitor checks the target again. A repeated issue with unchanged target evidence stops as blocked. Add `repair.approval` with a named audience and bounded replan limit only when the operator wants a human decision before implementation.
498
+ The first check runs immediately. Omit `repair` for observation-only monitoring. An authorized repair routes through the shared plan-change workflow, Autoimplement, and a fresh check. A repeated issue with unchanged target evidence stops as blocked. Omit `repair.approval` for the 10-minute autonomous default. Use `approval.mode: "required"` to wait for an explicit answer or `approval.mode: "skip"` to continue without asking.
443
499
 
444
500
  `everyMinutes` defaults to 30. Each accepted check must provide one concise report and choose `continue`, `repair` when authorized, or `stop`. The
445
501
  runtime queues that report as a workflow notification with `triggerTurn:
@@ -496,9 +552,10 @@ use the same card and keep the active attempt id.
496
552
 
497
553
  Headless RPC execution receives the same complete prompt without TUI metadata.
498
554
  Workflow notifications use a separate message type with `triggerTurn: false`,
499
- so a notification does not start an assistant response. See
500
- [WORKFLOW_STEP_MESSAGES.md](WORKFLOW_STEP_MESSAGES.md) for the message contract
501
- and renderer rules.
555
+ so a notification does not start an assistant response. Deferred successor turns
556
+ use an internal turn-intent contract instead of the notification outbox. See
557
+ [WORKFLOW_STEP_MESSAGES.md](WORKFLOW_STEP_MESSAGES.md) for the step-message contract
558
+ and [Deferred workflow turns](DEFERRED_TURNS.md) for the successor-turn contract.
502
559
 
503
560
  ## Result presentation
504
561
 
@@ -521,9 +578,11 @@ After the final run state has been persisted, the Pi extension sends the
521
578
  presentation instructions and bounded final result to the model as a hidden
522
579
  follow-up message. The next visible message is a normal assistant response.
523
580
  Returning `undefined`, returning an empty string, or omitting
524
- `presentationPrompt` produces no follow-up. Failed, timed-out, and cancelled
525
- runs are never presented; the extension reports their persisted status and
526
- error directly. Async prompt builders have 30 seconds to finish and receive an
581
+ `presentationPrompt` produces no presentation. Failed, timed-out, and cancelled
582
+ runs are never presented. When one of those outcomes would otherwise strand an
583
+ agent after a workflow-caused turn abort or asynchronous crash, the extension
584
+ uses the deferred-turn contract to send one factual fallback after settlement.
585
+ Async prompt builders have 30 seconds to finish and receive an
527
586
  `AbortSignal` that fires on timeout, session shutdown, or when a new workflow
528
587
  or normal user turn starts; stale presentations are discarded. Once a presentation message has
529
588
  been queued, another workflow cannot start until that assistant response
@@ -561,13 +620,17 @@ possible. Defaults worth knowing:
561
620
  and `running`. `workflow status` and `workflow cancel` accept the run ID before a run bundle
562
621
  exists.
563
622
  - If deferred activation fails, the queue stores a bounded safe error, releases the session
564
- reservation, and sends one follow-up turn to the initiating model. The model can correct the
565
- cause and make a new explicit start call. Pi Workflows does not retry blindly.
566
- - `/workflow cancel` aborts the current node and marks the run `cancelled`.
567
- When no run is live but the widget still shows a parked or finished run,
568
- the same command clears the widget.
623
+ reservation, and creates one deferred-turn intent for the initiating session. A workflow that
624
+ reports `started` and then crashes before its first prompt follows the same path. The model gets
625
+ one factual follow-up after settlement and can make a new explicit start call. Pi Workflows does
626
+ not retry blindly.
627
+ - An agent-issued `workflow cancel` aborts the current node and the current Pi turn, then creates
628
+ one deferred-turn intent. The next natural workflow message resolves it when possible; otherwise
629
+ one factual fallback starts after settlement. Direct `/workflow cancel` remains quiet because it
630
+ is explicit user control. When no run is live but the widget still shows a parked or finished run,
631
+ the command clears the widget.
569
632
  - One workflow runs per session at a time.
570
- - After the workflow tool accepts an agent-step submission, the extension removes any assistant tail text from the rest of that agent run. The next workflow message or notification is the visible continuation. Explicit final presentation remains a separate opt-in response.
633
+ - After the workflow tool accepts an agent-step submission, the extension removes any assistant tail text from the rest of that agent run. The next workflow message is the visible continuation. A deferred intent makes a workflow prompt, presentation, and factual fallback compete to provide one successor turn, so an abort cannot produce two continuation turns.
571
634
  - Agent nudges: if the model ends its turn without submitting the pending
572
635
  step, it gets a reminder, twice by default, then the step fails.
573
636
 
@@ -1,58 +1,31 @@
1
- import { compute, defineWorkflow, includeWorkflow, includedResult } from "@osolmaz/pi-workflows";
2
- import autodoc from "../../src/builtins/autodoc.workflow.js";
3
- import autoplan from "../../src/builtins/autoplan.workflow.js";
4
- import planApproval from "../../src/builtins/plan-approval.workflow.js";
1
+ import { compute, defineWorkflow, includeWorkflow } from "@osolmaz/pi-workflows";
2
+ import planChange from "../../src/builtins/plan-change.workflow.js";
5
3
 
6
4
  export default defineWorkflow({
7
5
  name: "approved-plan-example",
8
- startAt: "design",
9
- maxSteps: 80,
6
+ startAt: "start",
7
+ maxSteps: 100,
10
8
  includes: {
11
- design: includeWorkflow(autoplan, {
12
- input: ({ input, outputs }) => {
13
- const prior = outputs.design as { exit?: string; output?: { plan?: unknown } } | undefined;
14
- const answer = outputs.approval as
15
- | { exit?: string; output?: { instructions?: string } }
16
- | undefined;
17
- return {
18
- problem: (input as { task: string }).task,
19
- ...(prior?.exit === "ready" ? { previousPlan: prior.output?.plan } : {}),
20
- ...(answer?.exit === "replan" ? { newEvidence: answer.output?.instructions } : {}),
21
- };
22
- },
23
- }),
24
- documentation: includeWorkflow(autodoc, {
25
- input: ({ input, outputs }) => {
26
- const result = includedResult(autoplan, outputs.design);
27
- if (result.exit !== "ready") throw new Error("design is not ready");
28
- return { task: (input as { task: string }).task, plan: result.output.plan };
29
- },
30
- }),
31
- approval: includeWorkflow(planApproval, {
32
- input: ({ input, outputs }) => {
33
- const result = includedResult(autodoc, outputs.documentation);
34
- if (result.exit !== "ready") throw new Error("documentation is not ready");
35
- return {
36
- task: (input as { task: string }).task,
37
- plan: result.output.plan,
38
- planDigest: result.output.planDigest,
9
+ planChange: includeWorkflow(planChange, {
10
+ input: ({ input }) => ({
11
+ task: (input as { task: string }).task,
12
+ approval: {
13
+ mode: "auto" as const,
39
14
  audience: "operator",
40
- };
41
- },
15
+ timeoutMinutes: 10,
16
+ maxReplans: 3,
17
+ },
18
+ }),
42
19
  }),
43
20
  },
44
21
  nodes: {
45
- done: compute({ run: ({ outputs }) => ({ status: "approved", approval: outputs.approval }) }),
46
- stopped: compute({ run: ({ outputs }) => ({ status: "stopped", approval: outputs.approval }) }),
47
- blocked: compute({ run: ({ outputs }) => ({ status: "blocked", outputs }) }),
22
+ start: compute({ run: () => ({ route: "plan" }) }),
23
+ done: compute({ run: ({ outputs }) => ({ status: "ready", plan: outputs.planChange }) }),
24
+ blocked: compute({ run: ({ outputs }) => ({ status: "blocked", plan: outputs.planChange }) }),
48
25
  },
49
26
  edges: [
50
- { from: "design.ready", to: "documentation" },
51
- { from: "design.blocked", to: "blocked" },
52
- { from: "documentation.ready", to: "approval" },
53
- { from: "documentation.blocked", to: "blocked" },
54
- { from: "approval.continue", to: "done" },
55
- { from: "approval.stop", to: "stopped" },
56
- { from: "approval.replan", to: "design" },
27
+ { from: "start", to: "planChange" },
28
+ { from: "planChange.ready", to: "done" },
29
+ { from: "planChange.blocked", to: "blocked" },
57
30
  ],
58
31
  });
@@ -0,0 +1,50 @@
1
+ import {
2
+ action,
3
+ compute,
4
+ defineWorkflow,
5
+ runCommandBatch,
6
+ type CommandBatchRequest,
7
+ } from "@osolmaz/pi-workflows";
8
+
9
+ type Input = {
10
+ cwd: string;
11
+ };
12
+
13
+ export default defineWorkflow({
14
+ name: "command-batch",
15
+ input: (value): Input => value as Input,
16
+ startAt: "prepare",
17
+ nodes: {
18
+ prepare: compute({
19
+ run: ({ input }) =>
20
+ ({
21
+ maxConcurrency: 2,
22
+ items: [
23
+ {
24
+ id: "first",
25
+ command: "printf",
26
+ args: ["%s", "first"],
27
+ cwd: (input as Input).cwd,
28
+ timeoutMs: 10_000,
29
+ maxOutputChars: 10_000,
30
+ },
31
+ {
32
+ id: "second",
33
+ command: "printf",
34
+ args: ["%s", "second"],
35
+ cwd: (input as Input).cwd,
36
+ timeoutMs: 10_000,
37
+ maxOutputChars: 10_000,
38
+ },
39
+ ],
40
+ }) satisfies CommandBatchRequest,
41
+ }),
42
+ run: action({
43
+ run: async (context) =>
44
+ await runCommandBatch(context.outputs.prepare as CommandBatchRequest, {
45
+ signal: context.signal,
46
+ }),
47
+ }),
48
+ },
49
+ edges: [{ from: "prepare", to: "run" }],
50
+ });
@@ -0,0 +1 @@
1
+ export { sanityCheckWorkflow as default } from "@osolmaz/pi-workflows/builtins";
package/herdr-plugin.toml CHANGED
@@ -1,8 +1,8 @@
1
1
  id = "osolmaz.pi-workflows"
2
- name = "Pi Workflows"
3
- version = "0.11.2"
2
+ name = "pi-workflows"
3
+ version = "0.12.1"
4
4
  min_herdr_version = "0.7.0"
5
- description = "Open the active Pi Workflows run in piw from a managed Herdr pane."
5
+ description = "Open the active pi-workflows run in piw from a managed Herdr pane."
6
6
  platforms = ["linux", "macos"]
7
7
 
8
8
  [[panes]]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osolmaz/pi-workflows",
3
- "version": "0.11.2",
3
+ "version": "0.12.1",
4
4
  "description": "Workflow and controller runtime with a live terminal viewer for the pi coding agent",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -69,9 +69,9 @@
69
69
  "jiti": "^2.7.0"
70
70
  },
71
71
  "devDependencies": {
72
- "@earendil-works/pi-ai": "^0.80.10",
73
- "@earendil-works/pi-coding-agent": "^0.80.10",
74
- "@earendil-works/pi-tui": "^0.80.10",
72
+ "@earendil-works/pi-ai": "0.84.2",
73
+ "@earendil-works/pi-coding-agent": "0.84.2",
74
+ "@earendil-works/pi-tui": "0.84.2",
75
75
  "@types/better-sqlite3": "^7.6.13",
76
76
  "@types/node": "^26.1.1",
77
77
  "@vitest/coverage-istanbul": "^4.1.10",
@@ -83,9 +83,9 @@
83
83
  "vitest": "^4.1.10"
84
84
  },
85
85
  "peerDependencies": {
86
- "@earendil-works/pi-ai": "*",
87
- "@earendil-works/pi-coding-agent": "*",
88
- "@earendil-works/pi-tui": "*",
86
+ "@earendil-works/pi-ai": ">=0.84.2 <0.85.0",
87
+ "@earendil-works/pi-coding-agent": ">=0.84.2 <0.85.0",
88
+ "@earendil-works/pi-tui": ">=0.84.2 <0.85.0",
89
89
  "typebox": "*"
90
90
  },
91
91
  "engines": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/osolmaz/pi-workflows/schemas/decision-presentation-v1.schema.json",
4
- "title": "Pi Workflows decision presentation v1",
4
+ "title": "pi-workflows decision presentation v1",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
7
  "required": ["schema", "summary", "blocks"],
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/osolmaz/pi-workflows/schemas/human-decision-accepted-v1.schema.json",
4
- "title": "Pi Workflows accepted human decision v1",
4
+ "title": "pi-workflows accepted human decision v1",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
7
  "required": [
8
8
  "schema",
9
9
  "decisionId",
10
10
  "requestDigest",
11
+ "subjectDigest",
12
+ "presentationDigest",
13
+ "revision",
11
14
  "response",
12
- "source",
13
- "idempotencyKey",
15
+ "provenance",
14
16
  "acceptedAt",
15
17
  "answerDigest"
16
18
  ],
@@ -18,6 +20,9 @@
18
20
  "schema": { "const": "pi-workflows.human-decision-accepted.v1" },
19
21
  "decisionId": { "type": "string" },
20
22
  "requestDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
23
+ "subjectDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
24
+ "presentationDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
25
+ "revision": { "type": "integer", "minimum": 1 },
21
26
  "response": {
22
27
  "type": "object",
23
28
  "additionalProperties": false,
@@ -27,6 +32,7 @@
27
32
  "input": { "type": "object", "additionalProperties": { "type": "string" } }
28
33
  }
29
34
  },
35
+ "provenance": { "enum": ["human", "timeout"] },
30
36
  "source": {
31
37
  "type": "object",
32
38
  "additionalProperties": false,
@@ -40,5 +46,11 @@
40
46
  "idempotencyKey": { "type": "string" },
41
47
  "acceptedAt": { "type": "string", "format": "date-time" },
42
48
  "answerDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }
43
- }
49
+ },
50
+ "allOf": [
51
+ {
52
+ "if": { "properties": { "provenance": { "const": "human" } } },
53
+ "then": { "required": ["source", "idempotencyKey"] }
54
+ }
55
+ ]
44
56
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/osolmaz/pi-workflows/schemas/human-decision-answer-attempt-v1.schema.json",
4
- "title": "Pi Workflows human decision answer attempt v1",
4
+ "title": "pi-workflows human decision answer attempt v1",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
7
  "required": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/osolmaz/pi-workflows/schemas/human-decision-cancellation-v1.schema.json",
4
- "title": "Pi Workflows human decision cancellation v1",
4
+ "title": "pi-workflows human decision cancellation v1",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
7
  "required": ["schema", "decisionId", "requestDigest", "cancelledAt", "reason"],
@@ -1,14 +1,23 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/osolmaz/pi-workflows/schemas/human-decision-continuation-v1.schema.json",
4
- "title": "Pi Workflows human decision continuation v1",
4
+ "title": "pi-workflows human decision continuation v1",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
- "required": ["schema", "decisionId", "requestDigest", "parentRunId", "runId", "createdAt"],
7
+ "required": [
8
+ "schema",
9
+ "decisionId",
10
+ "requestDigest",
11
+ "provenance",
12
+ "parentRunId",
13
+ "runId",
14
+ "createdAt"
15
+ ],
8
16
  "properties": {
9
17
  "schema": { "const": "pi-workflows.human-decision-continuation.v1" },
10
18
  "decisionId": { "type": "string" },
11
19
  "requestDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
20
+ "provenance": { "enum": ["human", "timeout"] },
12
21
  "parentRunId": { "type": "string" },
13
22
  "runId": { "type": "string" },
14
23
  "createdAt": { "type": "string", "format": "date-time" }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/osolmaz/pi-workflows/schemas/human-decision-delivery-v1.schema.json",
4
- "title": "Pi Workflows human decision delivery v1",
4
+ "title": "pi-workflows human decision delivery v1",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
7
  "required": [
@@ -9,7 +9,9 @@
9
9
  "attemptId",
10
10
  "decisionId",
11
11
  "requestDigest",
12
+ "presentationDigest",
12
13
  "channel",
14
+ "phase",
13
15
  "state",
14
16
  "createdAt"
15
17
  ],
@@ -18,10 +20,16 @@
18
20
  "attemptId": { "type": "string" },
19
21
  "decisionId": { "type": "string" },
20
22
  "requestDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
23
+ "presentationDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
21
24
  "channel": { "type": "string" },
25
+ "phase": { "enum": ["intent", "part", "complete"] },
22
26
  "state": { "enum": ["intent", "confirmed", "failed", "unknown"] },
23
27
  "createdAt": { "type": "string", "format": "date-time" },
24
28
  "finishedAt": { "type": "string", "format": "date-time" },
29
+ "recipientIndex": { "type": "integer", "minimum": 1 },
30
+ "partIndex": { "type": "integer", "minimum": 1 },
31
+ "partCount": { "type": "integer", "minimum": 1, "maximum": 20 },
32
+ "contentDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
25
33
  "messageCount": { "type": "integer", "minimum": 0 },
26
34
  "errorCode": { "type": "string" }
27
35
  }
@@ -1,15 +1,19 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/osolmaz/pi-workflows/schemas/human-decision-receipt-v1.schema.json",
4
- "title": "Pi Workflows redacted human decision receipt v1",
4
+ "title": "pi-workflows redacted human decision receipt v1",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
7
  "required": [
8
8
  "schema",
9
9
  "decisionId",
10
10
  "requestDigest",
11
+ "subjectDigest",
12
+ "presentationDigest",
13
+ "revision",
11
14
  "nodeId",
12
15
  "response",
16
+ "provenance",
13
17
  "acceptedAt",
14
18
  "answerDigest"
15
19
  ],
@@ -17,6 +21,9 @@
17
21
  "schema": { "const": "pi-workflows.human-decision-receipt.v1" },
18
22
  "decisionId": { "type": "string" },
19
23
  "requestDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
24
+ "subjectDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
25
+ "presentationDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
26
+ "revision": { "type": "integer", "minimum": 1 },
20
27
  "nodeId": { "type": "string" },
21
28
  "response": {
22
29
  "type": "object",
@@ -27,6 +34,7 @@
27
34
  "input": { "type": "object", "additionalProperties": { "type": "string" } }
28
35
  }
29
36
  },
37
+ "provenance": { "enum": ["human", "timeout"] },
30
38
  "acceptedAt": { "type": "string", "format": "date-time" },
31
39
  "answerDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }
32
40
  }
@@ -1,20 +1,24 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/osolmaz/pi-workflows/schemas/human-decision-request-v1.schema.json",
4
- "title": "Pi Workflows human decision request v1",
4
+ "title": "pi-workflows human decision request v1",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
7
  "required": [
8
8
  "schema",
9
9
  "decisionId",
10
10
  "requestDigest",
11
+ "subjectDigest",
12
+ "presentationDigest",
11
13
  "runId",
12
14
  "workflowName",
13
15
  "nodeId",
14
16
  "attemptId",
15
17
  "audience",
16
18
  "title",
17
- "body",
19
+ "subject",
20
+ "presentation",
21
+ "revision",
18
22
  "choices",
19
23
  "createdAt"
20
24
  ],
@@ -22,13 +26,19 @@
22
26
  "schema": { "const": "pi-workflows.human-decision-request.v1" },
23
27
  "decisionId": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$" },
24
28
  "requestDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
29
+ "subjectDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
30
+ "presentationDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
25
31
  "runId": { "type": "string" },
26
32
  "workflowName": { "type": "string" },
27
33
  "nodeId": { "type": "string" },
28
34
  "attemptId": { "type": "string" },
29
35
  "audience": { "type": "string" },
30
36
  "title": { "type": "string", "minLength": 1 },
31
- "body": true,
37
+ "subject": true,
38
+ "presentation": {
39
+ "$ref": "decision-presentation-v1.schema.json"
40
+ },
41
+ "revision": { "type": "integer", "minimum": 1 },
32
42
  "choices": {
33
43
  "type": "object",
34
44
  "minProperties": 1,
@@ -54,6 +64,16 @@
54
64
  }
55
65
  },
56
66
  "createdAt": { "type": "string", "format": "date-time" },
57
- "expiresAt": { "type": "string", "format": "date-time" }
58
- }
67
+ "expiresAt": { "type": "string", "format": "date-time" },
68
+ "defaultResponse": {
69
+ "type": "object",
70
+ "additionalProperties": false,
71
+ "required": ["choice"],
72
+ "properties": {
73
+ "choice": { "type": "string" },
74
+ "input": { "type": "object", "additionalProperties": { "type": "string" } }
75
+ }
76
+ }
77
+ },
78
+ "dependentRequired": { "defaultResponse": ["expiresAt"] }
59
79
  }