@osolmaz/pi-workflows 0.9.1 → 0.11.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 (147) hide show
  1. package/README.md +64 -19
  2. package/dist/builtins/autodoc.workflow.d.ts +58 -0
  3. package/dist/builtins/autodoc.workflow.js +266 -0
  4. package/dist/builtins/autodoc.workflow.js.map +1 -0
  5. package/dist/builtins/autoimplement.workflow.d.ts +353 -0
  6. package/dist/builtins/autoimplement.workflow.js +961 -0
  7. package/dist/builtins/autoimplement.workflow.js.map +1 -0
  8. package/dist/builtins/autoplan.workflow.d.ts +58 -0
  9. package/dist/builtins/autoplan.workflow.js +190 -0
  10. package/dist/builtins/autoplan.workflow.js.map +1 -0
  11. package/dist/builtins/catalog.js +9 -1
  12. package/dist/builtins/catalog.js.map +1 -1
  13. package/dist/builtins/index.d.ts +6 -0
  14. package/dist/builtins/index.js +7 -0
  15. package/dist/builtins/index.js.map +1 -0
  16. package/dist/builtins/monitor.workflow.d.ts +29 -3
  17. package/dist/builtins/monitor.workflow.js +325 -13
  18. package/dist/builtins/monitor.workflow.js.map +1 -1
  19. package/dist/builtins/plan-approval.workflow.d.ts +83 -0
  20. package/dist/builtins/plan-approval.workflow.js +148 -0
  21. package/dist/builtins/plan-approval.workflow.js.map +1 -0
  22. package/dist/builtins/plan-presentation.d.ts +7 -0
  23. package/dist/builtins/plan-presentation.js +44 -0
  24. package/dist/builtins/plan-presentation.js.map +1 -0
  25. package/dist/extension/decision-channels.d.ts +132 -0
  26. package/dist/extension/decision-channels.js +1082 -0
  27. package/dist/extension/decision-channels.js.map +1 -0
  28. package/dist/extension/index.d.ts +1 -0
  29. package/dist/extension/index.js +426 -11
  30. package/dist/extension/index.js.map +1 -1
  31. package/dist/extension/widget.js +26 -2
  32. package/dist/extension/widget.js.map +1 -1
  33. package/dist/extension/workflow-tool.d.ts +1 -37
  34. package/dist/extension/workflow-tool.js +1 -42
  35. package/dist/extension/workflow-tool.js.map +1 -1
  36. package/dist/host/rpc-bridge.js +4 -21
  37. package/dist/host/rpc-bridge.js.map +1 -1
  38. package/dist/render/graph-render.js +46 -10
  39. package/dist/render/graph-render.js.map +1 -1
  40. package/dist/viewer/render.js +36 -2
  41. package/dist/viewer/render.js.map +1 -1
  42. package/dist/workflows/catalog.d.ts +1 -0
  43. package/dist/workflows/catalog.js +6 -0
  44. package/dist/workflows/catalog.js.map +1 -1
  45. package/dist/workflows/composition.d.ts +45 -0
  46. package/dist/workflows/composition.js +485 -0
  47. package/dist/workflows/composition.js.map +1 -0
  48. package/dist/workflows/decision-presentation.d.ts +18 -0
  49. package/dist/workflows/decision-presentation.js +417 -0
  50. package/dist/workflows/decision-presentation.js.map +1 -0
  51. package/dist/workflows/decision.d.ts +11 -5
  52. package/dist/workflows/decision.js.map +1 -1
  53. package/dist/workflows/definition.d.ts +22 -3
  54. package/dist/workflows/definition.js +46 -3
  55. package/dist/workflows/definition.js.map +1 -1
  56. package/dist/workflows/engine.d.ts +2 -1
  57. package/dist/workflows/engine.js +199 -18
  58. package/dist/workflows/engine.js.map +1 -1
  59. package/dist/workflows/graph.js +8 -6
  60. package/dist/workflows/graph.js.map +1 -1
  61. package/dist/workflows/human-decision.d.ts +87 -0
  62. package/dist/workflows/human-decision.js +583 -0
  63. package/dist/workflows/human-decision.js.map +1 -0
  64. package/dist/workflows/index.d.ts +5 -2
  65. package/dist/workflows/index.js +4 -1
  66. package/dist/workflows/index.js.map +1 -1
  67. package/dist/workflows/loader.d.ts +5 -4
  68. package/dist/workflows/loader.js +118 -18
  69. package/dist/workflows/loader.js.map +1 -1
  70. package/dist/workflows/schema.d.ts +3 -1
  71. package/dist/workflows/schema.js +64 -2
  72. package/dist/workflows/schema.js.map +1 -1
  73. package/dist/workflows/store.js +39 -2
  74. package/dist/workflows/store.js.map +1 -1
  75. package/dist/workflows/tool-input.d.ts +72 -0
  76. package/dist/workflows/tool-input.js +141 -0
  77. package/dist/workflows/tool-input.js.map +1 -0
  78. package/dist/workflows/types.d.ts +300 -2
  79. package/docs/CONTROLLERS.md +1 -1
  80. package/docs/DESIGN_PHILOSOPHY.md +1 -1
  81. package/docs/HUMAN_DECISIONS.md +371 -0
  82. package/docs/HUMAN_DECISION_PRESENTATIONS.md +323 -0
  83. package/docs/MONITOR.md +46 -18
  84. package/docs/WORKFLOW_COMPOSITION.md +333 -0
  85. package/docs/plans/2026-08-19-human-decision-gates-plan.md +296 -0
  86. package/docs/plans/2026-08-19-human-decision-presentations-plan.md +172 -0
  87. package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +45 -0
  88. package/docs/plans/2026-08-19-workflow-composition-plan.md +308 -0
  89. package/docs/run-bundles.md +45 -10
  90. package/docs/workflows.md +106 -15
  91. package/examples/workflows/approved-plan.workflow.ts +58 -0
  92. package/examples/workflows/autoimplement.workflow.ts +1 -92
  93. package/examples/workflows/autoplan.workflow.ts +1 -0
  94. package/examples/workflows/human-decision.workflow.ts +62 -0
  95. package/herdr-plugin.toml +1 -1
  96. package/package.json +6 -1
  97. package/schemas/decision-presentation-v1.schema.json +83 -0
  98. package/schemas/human-decision-accepted-v1.schema.json +44 -0
  99. package/schemas/human-decision-accepted-v2.schema.json +50 -0
  100. package/schemas/human-decision-answer-attempt-v1.schema.json +37 -0
  101. package/schemas/human-decision-cancellation-v1.schema.json +15 -0
  102. package/schemas/human-decision-continuation-v1.schema.json +16 -0
  103. package/schemas/human-decision-delivery-v1.schema.json +28 -0
  104. package/schemas/human-decision-delivery-v2.schema.json +36 -0
  105. package/schemas/human-decision-receipt-v1.schema.json +33 -0
  106. package/schemas/human-decision-receipt-v2.schema.json +39 -0
  107. package/schemas/human-decision-request-v1.schema.json +59 -0
  108. package/schemas/human-decision-request-v2.schema.json +69 -0
  109. package/schemas/human-decision-resolution-v1.schema.json +27 -0
  110. package/schemas/human-decision-resolution-v2.schema.json +27 -0
  111. package/schemas/human-decision-settlement-v1.schema.json +28 -0
  112. package/skills/autodoc/SKILL.md +43 -0
  113. package/skills/autoimplement/SKILL.md +58 -0
  114. package/skills/autoimplement/agents/openai.yaml +4 -0
  115. package/skills/autoplan/SKILL.md +25 -0
  116. package/skills/monitor/SKILL.md +8 -1
  117. package/skills/pi-workflows/SKILL.md +5 -3
  118. package/src/builtins/autodoc.workflow.ts +325 -0
  119. package/src/builtins/autoimplement.workflow.ts +1123 -0
  120. package/src/builtins/autoplan.workflow.ts +231 -0
  121. package/src/builtins/catalog.ts +9 -1
  122. package/src/builtins/index.ts +32 -0
  123. package/src/builtins/monitor.workflow.ts +396 -15
  124. package/src/builtins/plan-approval.workflow.ts +190 -0
  125. package/src/builtins/plan-presentation.ts +57 -0
  126. package/src/extension/decision-channels.ts +1533 -0
  127. package/src/extension/index.ts +499 -9
  128. package/src/extension/widget.ts +39 -2
  129. package/src/extension/workflow-tool.ts +5 -81
  130. package/src/host/rpc-bridge.ts +7 -30
  131. package/src/render/graph-render.ts +54 -5
  132. package/src/viewer/render.ts +56 -3
  133. package/src/workflows/catalog.ts +7 -0
  134. package/src/workflows/composition.ts +642 -0
  135. package/src/workflows/decision-presentation.ts +501 -0
  136. package/src/workflows/decision.ts +12 -5
  137. package/src/workflows/definition.ts +118 -8
  138. package/src/workflows/engine.ts +257 -20
  139. package/src/workflows/graph.ts +8 -6
  140. package/src/workflows/human-decision.ts +819 -0
  141. package/src/workflows/index.ts +89 -0
  142. package/src/workflows/loader.ts +186 -18
  143. package/src/workflows/schema.ts +79 -2
  144. package/src/workflows/store.ts +45 -2
  145. package/src/workflows/tool-input.ts +202 -0
  146. package/src/workflows/types.ts +374 -2
  147. package/examples/workflows/elegant-solution.workflow.ts +0 -95
@@ -0,0 +1,308 @@
1
+ ---
2
+ title: Add typed workflow composition and automatic repair
3
+ author: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
4
+ date: 2026-08-19
5
+ ---
6
+
7
+ # Add typed workflow composition and automatic repair
8
+
9
+ Pi Workflows needs reusable nested workflows with normal TypeScript imports. Monitor must be able to devise and implement a repair when mutation is authorized. Autoimplement must move back to solution design when new evidence invalidates its plan, correct failed reviewer commands, track P0, P1, and P2 findings, and use long CI waits for additional local testing.
10
+
11
+ The canonical behavior is in [Workflow composition](../WORKFLOW_COMPOSITION.md).
12
+
13
+ ## Outcome
14
+
15
+ A workflow can run alone or as a typed child in one durable parent run. Authors can import a child definition, map its input, and route from named exits. Dynamic project, global, built-in, and path references remain available.
16
+
17
+ The package will ship `autoplan`, `autoimplement`, and `monitor` as compatible built-ins. Monitor will remain observation-only by default. An explicitly authorized monitor can run outer solution design, autoimplementation, internal redesign, and a fresh target check without copying workflow nodes.
18
+
19
+ ## Scope
20
+
21
+ ### Public workflow API
22
+
23
+ - Add generic workflow input and named-exit contracts.
24
+ - Add runtime input and exit validation.
25
+ - Add direct typed `includeWorkflow(child, options)` composition.
26
+ - Keep dynamic `includeWorkflow({ workflow, input })` references.
27
+ - Add `defineWorkflowRegistry()` for shipped typed definitions.
28
+ - Type-check child input mappers and parent exit names where the child is imported directly.
29
+
30
+ ### Resolution and execution
31
+
32
+ - Resolve all direct and dynamic includes before `run_started`.
33
+ - Reject direct and indirect source cycles.
34
+ - Compile one qualified executable graph.
35
+ - Give child callbacks a local invocation view.
36
+ - Support repeated and nested mounts.
37
+ - Apply root and per-invocation step limits.
38
+ - Record named entry and exit transitions.
39
+ - Keep one pause, cancellation, checkpoint, update, notification, and presentation lifecycle.
40
+
41
+ ### Persistence and display
42
+
43
+ - Record all child sources and their mount paths.
44
+ - Record a digest of the resolved definition.
45
+ - Refuse normal resume when any source or digest changes.
46
+ - Add mount data to the definition snapshot.
47
+ - Add include entry and exit trace events.
48
+ - Group qualified child nodes in viewers while preserving flat replay.
49
+
50
+ ### Workflow library
51
+
52
+ - Make `autoplan` accept an existing plan and new evidence.
53
+ - Add `ready` and `blocked` exits to `autoplan`.
54
+ - Rebuild `autoimplement` around explicit issue routes.
55
+ - Include `autoplan` inside `autoimplement` for redesign.
56
+ - Track P0, P1, and P2 review findings by round.
57
+ - Rerun review only after P0 or P1 work.
58
+ - Permit P2 work without another reviewer round.
59
+ - Generate, validate, execute, and correct exact Pi Reviewer commands.
60
+ - Track PR comments, CI, merge, and final PR reporting.
61
+ - Bound one CI watch to five minutes.
62
+ - Route a long CI wait to useful local testing before checking CI again.
63
+ - Add authorized repair to monitor through outer `autoplan` and `autoimplement` mounts.
64
+ - Detect repeated repair with no changed issue, plan, implementation, evidence, or target state.
65
+
66
+ ## Non-goals
67
+
68
+ - Do not change Pi core or use private Pi APIs.
69
+ - Do not create another run store, service, or child Pi session.
70
+ - Do not let a child jump into a parent or sibling node.
71
+ - Do not permit recursive include graphs.
72
+ - Do not add unrestricted model-selected node names.
73
+ - Do not let monitor mutate a target without explicit authorization.
74
+ - Do not replace Pi Reviewer with another reviewer after an invocation failure.
75
+ - Do not merge before required gates pass or an allowed unrelated failure is recorded.
76
+ - Do not rewrite existing terminal run bundles.
77
+
78
+ ## Data contracts
79
+
80
+ ### Child result
81
+
82
+ A completed child mount exposes a discriminated result:
83
+
84
+ ```typescript
85
+ type IncludedResult<TExits> = {
86
+ [K in keyof TExits]: { exit: K; output: TExits[K] };
87
+ }[keyof TExits];
88
+ ```
89
+
90
+ ### Source record
91
+
92
+ ```typescript
93
+ type WorkflowMountedSource = {
94
+ mountPath: string[];
95
+ workflowName: string;
96
+ source: WorkflowSource;
97
+ };
98
+ ```
99
+
100
+ ### Review round
101
+
102
+ ```typescript
103
+ type ReviewRound = {
104
+ command: StructuredCommand;
105
+ p0: ReviewFinding[];
106
+ p1: ReviewFinding[];
107
+ p2: ReviewFinding[];
108
+ lower: ReviewFinding[];
109
+ invocationSucceeded: boolean;
110
+ };
111
+ ```
112
+
113
+ P0 or P1 findings route to redesign or fix, then verification, push, and another review. P2-only work routes through a separate verification path and then continues to PR comments without another review.
114
+
115
+ ### CI decision
116
+
117
+ ```typescript
118
+ type CiDecision = {
119
+ status: "green" | "failed" | "pending" | "unavailable";
120
+ relatedFailures: string[];
121
+ unrelatedFailures: string[];
122
+ trackingCommand?: StructuredCommand;
123
+ };
124
+ ```
125
+
126
+ A pending result must include a validated `gh` tracking command. The shell action stops after five minutes. A timeout routes to opportunistic local testing. The workflow checks CI again after that work.
127
+
128
+ ## Implementation sequence
129
+
130
+ ### 1. Typed contracts
131
+
132
+ - Make workflow definitions generic over input and exits.
133
+ - Add source identity, input parser, exit parser, and include declarations.
134
+ - Add direct and dynamic `includeWorkflow()` overloads.
135
+ - Add registry helpers and compile-time contract tests.
136
+ - Keep old workflow definitions valid.
137
+
138
+ ### 2. Composition resolver
139
+
140
+ - Resolve nested direct and dynamic definitions.
141
+ - Preserve parent-relative paths.
142
+ - Build the complete source list.
143
+ - Reject source cycles with the mount chain.
144
+ - Validate child exit nodes and parent include edges.
145
+ - Compile qualified nodes and internal entry and exit transitions.
146
+ - Attach immutable composition metadata to the resolved definition.
147
+
148
+ ### 3. Local execution context
149
+
150
+ - Project each child callback to its mount-local input, outputs, results, and steps.
151
+ - Hide parent and sibling data.
152
+ - Start each re-entry after the latest mount-entry step.
153
+ - Expose only the latest named child result to the parent.
154
+ - Enforce per-invocation and root step limits.
155
+ - Emit include entry and exit events.
156
+
157
+ ### 4. Persistence and resume
158
+
159
+ - Add mounted sources and the resolved digest to state and manifest projections.
160
+ - Add mount metadata to definition snapshots.
161
+ - Compare the complete identity during resume and continuation.
162
+ - Keep old single-source bundles readable.
163
+ - Add trace reconstruction and torn-tail tests for include transitions.
164
+
165
+ ### 5. Viewer grouping
166
+
167
+ - Derive groups from snapshot mount metadata.
168
+ - Keep qualified names in replay and details.
169
+ - Update TypeScript and Rust fixtures together.
170
+
171
+ ### 6. Autoplan
172
+
173
+ - Move the current workflow into the built-in library.
174
+ - Add typed input, `ready`, and `blocked` exits.
175
+ - Accept a previous plan and new evidence.
176
+ - Calculate a plan digest.
177
+ - Return whether the selected plan changed.
178
+ - Keep the current practical, in-scope selection rule.
179
+
180
+ ### 7. Autodoc and autoimplement
181
+
182
+ Add standalone autodoc and build this autoimplement graph:
183
+
184
+ ```text
185
+ find existing plan
186
+ -> missing or unclear -> blocked
187
+ -> documentation current -> implement
188
+ -> documentation missing or stale -> autodoc -> implement
189
+ -> verify
190
+ -> classify issue
191
+ -> redesign -> autoplan -> autodoc -> optional approval -> implement
192
+ -> fix -> verify
193
+ -> publish -> write reviewer command -> run reviewer
194
+
195
+ review result
196
+ -> command error -> rewrite command -> run reviewer
197
+ -> P0/P1 -> classify issue
198
+ -> P2 -> address P2 -> verify P2 -> PR comments
199
+ -> clean -> PR comments
200
+
201
+ PR comments
202
+ -> redesign
203
+ -> fix
204
+ -> CI
205
+
206
+ CI
207
+ -> related design failure -> redesign
208
+ -> related local failure -> fix
209
+ -> pending -> track for at most five minutes
210
+ -> tracking timeout -> run additional useful tests -> check CI
211
+ -> green or allowed unrelated failure -> merge
212
+ -> unavailable or forbidden -> blocked
213
+ ```
214
+
215
+ The structured plan input is optional because the plan can already exist in conversation context or canonical documentation. Its absence never authorizes initial autoplan. Autoimplement blocks when no clear plan exists, skips autodoc for current documentation, and records every evidence-driven revision through autodoc before continuing.
216
+
217
+ The workflow will collect all review rounds in its final output. It will never rerun Pi Reviewer solely because P2 work changed files.
218
+
219
+ ### 8. Monitor repair
220
+
221
+ - Add explicit repair authorization to monitor input.
222
+ - Add `repair` to the check result only when authorization is present.
223
+ - Mount outer `autoplan`, `autodoc`, optional `plan-approval`, and `autoimplement`.
224
+ - Pass the documented outer plan into autoimplement.
225
+ - Return exact replan instructions to autoplan and ask again.
226
+ - Check the target again after a reported repair.
227
+ - Stop on repeated no-progress evidence.
228
+ - Keep ordinary monitor calls observation-only and backward compatible.
229
+
230
+ ### 9. Skills and docs
231
+
232
+ - Update workflow authoring, persistence, monitor, and controller-boundary docs.
233
+ - Align the public monitor and autoimplement skills with the new workflows.
234
+ - Run the normal agent synchronization command in the source repository after skill edits.
235
+ - Record any implementation departure in this plan and the canonical specification.
236
+
237
+ ## Acceptance criteria
238
+
239
+ - Direct imports infer child input and named exits.
240
+ - Invalid direct input mappings fail type checking.
241
+ - Invalid child exit names fail type checking or definition validation.
242
+ - Dynamic references resolve through existing precedence rules.
243
+ - Two mounts of one child do not share invocation state.
244
+ - Standalone autodoc adopts current documents, updates stale documents, and blocks without a selected plan.
245
+ - Autoimplement finds a clear existing plan and never invokes initial autoplan because a plan input is absent.
246
+ - Nested redesign uses a fresh autoplan invocation and passes the revised plan through autodoc.
247
+ - Source cycles fail before the run bundle is created.
248
+ - Included checkpoints, updates, notifications, pause, cancellation, and resume behave like root nodes.
249
+ - Every mounted source and the resolved digest is durable.
250
+ - P0 and P1 work triggers another review round.
251
+ - P2-only work can be addressed without another review round.
252
+ - A failed reviewer command can be corrected and rerun without changing reviewer tools.
253
+ - A pending CI decision supplies an exact command.
254
+ - A CI wait longer than five minutes routes to useful local testing.
255
+ - Monitor mutates only when repair is explicitly authorized.
256
+ - A repaired monitor target is checked again.
257
+ - Repeated repair without changed evidence stops as blocked.
258
+ - Existing workflows and terminal run bundles remain valid.
259
+
260
+ ## Verification
261
+
262
+ Run focused checks while implementing:
263
+
264
+ ```bash
265
+ npx vitest run test/composition.test.ts test/graph.test.ts test/loader.test.ts
266
+ npx vitest run test/engine.test.ts test/run-resume.test.ts test/store.test.ts
267
+ npx vitest run test/builtin-autoplan.test.ts test/builtin-autoimplement.test.ts test/builtin-monitor.test.ts
268
+ ```
269
+
270
+ Run all repository gates before review:
271
+
272
+ ```bash
273
+ npm run check
274
+ npm run test:e2e
275
+ npx slophammer-ts@latest dry .
276
+ npx slophammer-ts@latest check . --only ts.dependency-boundaries-required
277
+ npx -y @simpledoc/simpledoc check
278
+ git diff --check
279
+ ```
280
+
281
+ Run Pi Reviewer against the pushed branch. Fix every P0 and P1 finding and rerun it. P2-only changes do not require another reviewer run unless they expose a new P0 or P1 concern. Check PR comments and CI after review passes.
282
+
283
+ ## Release
284
+
285
+ This adds compatible public APIs to a pre-1.0 package. The planned version is `0.10.0`. Existing `0.9.x` run bundles remain readable, and no published version is rewritten.
286
+
287
+ ## Implementation record
288
+
289
+ The implementation follows the canonical specification with one visible detail: compiled include entry and exit transitions have durable internal step records so resume can reconstruct the active invocation. They do not consume root or child `maxSteps` limits. Viewers label them as entry and named-exit transitions.
290
+
291
+ Direct imports check mapped input and exit names. `includedResult()` recovers the discriminated child result type from a parent output. Dynamic overrides carry a direct contract definition and must match its stable `contractId`, input presence, and named exits.
292
+
293
+ The shipped workflows are registered built-ins:
294
+
295
+ - `autoplan` returns `ready` or `blocked` with plan lineage.
296
+ - `autodoc` records selected plans as a standalone and included built-in.
297
+ - `autoimplement` finds an existing plan, conditionally documents it, and supports evidence-driven redesign, implementation fixes, exact reviewer and CI commands, P0 through P2 history, bounded CI watches, PR comments, merge, and final reporting.
298
+ - `monitor` remains observation-only by default and enables composed repair only through an explicit repair policy.
299
+
300
+ The package and Rust viewer version is `0.10.0`.
301
+
302
+ ## Contract impact
303
+
304
+ - **Session state:** normal workflow messages and tool results only.
305
+ - **Other persistent data:** additive mount, source, digest, review, and CI evidence in existing run bundles.
306
+ - **Pi internals:** none.
307
+ - **Public Pi API:** existing documented extension APIs only.
308
+ - **Public Pi Workflows API:** generic input and exits, direct and dynamic `includeWorkflow()`, and `defineWorkflowRegistry()`.
@@ -32,6 +32,27 @@ temporary directories.
32
32
  sha256-<64 hex>.txt
33
33
  ```
34
34
 
35
+ Human decision records use a separate additive directory next to `runs/` so a waiting run bundle remains immutable:
36
+
37
+ ```text
38
+ ~/.pi/agent/workflows/decisions/
39
+ <decision-id>/
40
+ request.json
41
+ deliveries/<channel>/<attempt-id>.json
42
+ answers/<attempt-id>.json
43
+ resolution.json # atomic accepted-or-cancelled fence
44
+ accepted.json
45
+ cancelled.json # present only when a pending request is cancelled or expires
46
+ settlements/<channel>/<attempt-id>.json
47
+ continuation.json
48
+ ```
49
+
50
+ The request links to the waiting run, node, attempt, workflow source, and canonical request digest. A v2 request stores the canonical subject and a separate normalized operator presentation. Its subject, presentation, revision, choices, and input prompts are bound to the request digest. V2 accepted records and redacted continuation receipts preserve the subject and presentation digests. Final records use no-replace creation and adopt only identical retries. `resolution.json` is the first accepted-or-cancelled fence. It materializes either `accepted.json` or the mutually exclusive `cancelled.json`; a crash can rebuild that detail from the resolution. `continuation.json` binds an accepted answer to one deterministic continuation run. Delivery and settlement records cannot change the accepted answer.
51
+
52
+ Telegram multipart delivery uses additive v2 delivery records for the overall intent, each part, and completion. Part records contain only recipient indexes, part indexes, counts, and content digests. Telegram chat and message IDs remain in the private disposable channel projection and never enter run or decision bundles. An ambiguous part remains unknown and is not retried blindly.
53
+
54
+ A human-decision continuation preserves the parent's original workflow input and replaces the carried checkpoint output with the accepted typed response for routing. Its `humanDecision` state is a redacted receipt. A v2 receipt includes the subject digest, presentation digest, and revision, but not the subject itself. Verified actor, channel, event, and idempotency provenance remains in the private sibling decision records and is not copied into the run bundle. Ordinary checkpoint continuations keep using the answer as the continuation input. Existing bundles without human decision data remain valid. V1 requests and their original digests are never rewritten.
55
+
35
56
  Run ids are `<UTC timestamp>-<workflow slug>-<8 hex chars>`, so lexical order
36
57
  is chronological order.
37
58
 
@@ -142,11 +163,13 @@ Identity and pointers, kept in sync with the state on every snapshot:
142
163
  }
143
164
  ```
144
165
 
145
- `workflowSource` identifies the definition used by the run. User workflow
166
+ `workflowSource` identifies the root definition used by the run. User workflow
146
167
  files use an absolute path and SHA-256 hash. Package-provided workflows use a
147
- stable identity such as `{ "kind": "builtin", "id": "monitor", "revision": "1" }`.
168
+ stable identity such as `{ "kind": "builtin", "id": "monitor", "revision": "4" }`.
148
169
  A built-in identity does not contain an installation path.
149
170
 
171
+ A composed run also records `workflowSources`, sorted by mount path, and `definitionDigest`. Each mounted source has `mountPath`, `workflowName`, and the same file or built-in source identity. The digest is SHA-256 over the resolved definition snapshot.
172
+
150
173
  `paths.artifacts` is declared from bundle creation so a live session-event
151
174
  patch can safely reference a newly written artifact before the next workflow
152
175
  state projection. The directory itself is created only when needed.
@@ -160,8 +183,7 @@ A serializable snapshot of the graph taken at run start
160
183
  (`pi-workflows.definition-snapshot.v1`). Functions such as prompts and
161
184
  validators are not serialized. Each node keeps only its metadata (`nodeType`,
162
185
  `timeoutMs`, `statusDetail`, `expectedOutput`, `summary`, `actionExecution`),
163
- and edges are copied verbatim. The snapshot is what lets viewers draw all
164
- nodes, including ones that have not run yet. It is immutable after run start.
186
+ and edges are copied verbatim. Included nodes also record `mountPath`, `localNodeId`, and internal entry or exit status. The top-level `composition.mounts` list records every mount, entry, named exit, and child step limit. The snapshot is what lets viewers draw all nodes, including ones that have not run yet. It is immutable after run start.
165
187
 
166
188
  ## Resume and repair
167
189
 
@@ -176,10 +198,12 @@ instead of failing. Resume is a named operation with strict rules:
176
198
  `state.traceSeq` and the trace agree again before any new event.
177
199
  3. Completed nodes replay from the projection. The in-flight node reruns with
178
200
  a fresh attempt; a `run_resumed` trace event marks the boundary.
179
- 4. `state.workflowSource` pins the workflow source from run start. File
201
+ 4. `state.workflowSource` pins the root workflow source from run start. File
180
202
  sources require the same hash. Built-in sources require the same catalog
181
- id and revision. Resume refuses a mismatch unless forced, and a forced
182
- resume records the mismatch in the `run_resumed` payload.
203
+ id and revision.
204
+ 5. Composed runs also require the same sorted `workflowSources` and
205
+ `definitionDigest`. A changed or missing child refuses normal resume.
206
+ 6. A forced resume records the identity mismatch in the `run_resumed` payload.
183
207
 
184
208
  Continuation runs (answering a checkpoint) are new bundles, not resumed ones.
185
209
  They link back through `state.parentRunId`, carry the parent's outputs,
@@ -204,6 +228,14 @@ The full run projection (`WorkflowRunState` in
204
228
  "path": "/repo/.pi/workflows/autoimplement.workflow.ts",
205
229
  "hash": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
206
230
  },
231
+ "workflowSources": [
232
+ {
233
+ "mountPath": ["redesign"],
234
+ "workflowName": "autoplan",
235
+ "source": { "kind": "builtin", "id": "autoplan", "revision": "1" }
236
+ }
237
+ ],
238
+ "definitionDigest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
207
239
  "startedAt": "…",
208
240
  "updatedAt": "…",
209
241
  "status": "running",
@@ -215,9 +247,10 @@ The full run projection (`WorkflowRunState` in
215
247
  }
216
248
  ```
217
249
 
218
- - `workflowSource` is the canonical source identity. Resuming a file requires
219
- the same hash. Resuming a built-in requires the same catalog revision. A
220
- mismatch refuses the resume instead of loading another definition.
250
+ - `workflowSource` is the canonical root source identity. Resuming a file requires
251
+ the same hash. Resuming a built-in requires the same catalog revision.
252
+ - `workflowSources` and `definitionDigest` attest the complete composed graph.
253
+ A mismatch refuses resume instead of loading another child definition.
221
254
  - `status` is one of `running`, `waiting`, `completed`, `failed`, `timed_out`,
222
255
  or `cancelled`. A controller host records an abandoned bundle as `failed`
223
256
  with a final `run_interrupted` trace event. Before doing that, recovery checks
@@ -315,6 +348,8 @@ Event catalog and payload contracts:
315
348
  | `agent_prompt_sent` | agent | `prompt` |
316
349
  | `node_finished` | node | `outcome: "ok"`, `durationMs`, `output`, `conversation?`, `action?` |
317
350
  | `node_failed` | node | `outcome`, `durationMs`, `error`, `conversation?`, `action?` |
351
+ | `include_entered` | run | `mountPath`, `workflowName`, `invocation` |
352
+ | `include_exited` | run | `mountPath`, `workflowName`, `invocation`, `exit`, `output` |
318
353
  | `run_paused` | run | _(empty)_ |
319
354
  | `run_resumed` | run | _(empty)_ |
320
355
  | `run_completed` | run | `status`, `finalOutput` |
package/docs/workflows.md CHANGED
@@ -15,8 +15,11 @@ Files are discovered by suffix (`.workflow.ts`, `.workflow.js`, `.workflow.mts`,
15
15
  2. `~/.pi/agent/workflows/` globally
16
16
  3. Workflows built into Pi Workflows
17
17
 
18
- Pi Workflows includes a built-in `monitor` workflow. A project or global file
19
- named `monitor.workflow.ts` replaces it. The package registers each built-in in
18
+ Pi Workflows includes built-in `autoplan`, `autodoc`, `autoimplement`,
19
+ `plan-approval`, and `monitor` workflows. `autoplan` is the current name for the
20
+ planning workflow that was first released as `autodevise`; the old command and
21
+ export are not retained. A project or global file named `monitor.workflow.ts`
22
+ replaces the built-in monitor. The package registers each built-in in
20
23
  a process-local catalog with a stable reference such as `builtin:monitor` and
21
24
  an explicit revision. Built-ins are imported with the engine when a Pi process
22
25
  starts. They are not read from the package directory when a run starts or
@@ -53,15 +56,20 @@ export default defineWorkflow({
53
56
 
54
57
  Top-level fields:
55
58
 
56
- | Field | Type | Notes |
57
- | -------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
58
- | `name` | `string` | Required. Used in run ids and the step contract. `answer`, `cancel`, `list`, `pause`, `resume`, and `status` are reserved for `/workflow` subcommands. |
59
- | `title` | `string` or function | Optional run title, resolved once at start from `{ input, workflowName }`. Async resolution is bounded (30s) and cancellable. |
60
- | `presentationPrompt` | `string` or function | Optional instructions for a normal assistant response after the run. A function receives `{ state, finalOutput, signal }` and may return a prompt or `undefined`. See [Result presentation](#result-presentation). |
61
- | `startAt` | `string` | Required. Id of the first node. |
62
- | `nodes` | `Record<string, node>` | Required, non-empty. Node ids must match `[A-Za-z_][A-Za-z0-9_-]*`. |
63
- | `edges` | `WorkflowEdge[]` | Required. See routing below. |
64
- | `maxSteps` | `number` | Optional loop bound, default 100. The run fails when exceeded. |
59
+ | Field | Type | Notes |
60
+ | -------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
61
+ | `name` | `string` | Required. Used in run ids and the step contract. `answer`, `cancel`, `list`, `pause`, `resume`, and `status` are reserved for `/workflow` subcommands. |
62
+ | `source` | `string` | Optional `import.meta.url` for exact provenance when another TypeScript workflow imports this definition directly. |
63
+ | `contractId` | `string` | Optional stable input-and-exit contract identity. Dynamic overrides must match it. |
64
+ | `input` | `function` | Optional runtime input normalizer and validator. Its return type is the workflow input type. |
65
+ | `title` | `string` or function | Optional run title, resolved once at start from `{ input, workflowName }`. Async resolution is bounded (30s) and cancellable. |
66
+ | `presentationPrompt` | `string` or function | Optional instructions for a normal assistant response after the run. A function receives `{ state, finalOutput, signal }` and may return a prompt or `undefined`. See [Result presentation](#result-presentation). |
67
+ | `startAt` | `string` | Required. Id of the first node. |
68
+ | `nodes` | `Record<string, node>` | Required, non-empty. Node ids must match `[A-Za-z_][A-Za-z0-9_-]*`. |
69
+ | `includes` | `Record<string, include>` | Optional imported or dynamically resolved child workflows. |
70
+ | `exits` | `Record<string, exit>` | Optional named successful terminal nodes used when another workflow includes this workflow. |
71
+ | `edges` | `WorkflowEdge[]` | Required. See routing below. |
72
+ | `maxSteps` | `number` | Optional loop bound, default 100. The run fails when exceeded. |
65
73
 
66
74
  `defineWorkflow` validates the shape eagerly (node ids, edge shapes, function
67
75
  fields) and validates the graph (unknown targets, duplicate outgoing edges,
@@ -266,6 +274,39 @@ checkpoint({
266
274
  });
267
275
  ```
268
276
 
277
+ A typed human decision is an authoring layer over checkpoint:
278
+
279
+ ```typescript
280
+ const choices = defineHumanChoices({
281
+ continue: choice({ label: "Continue" }),
282
+ stop: choice({ label: "Stop" }),
283
+ replan: choice({
284
+ label: "Replan",
285
+ input: textInput({ name: "instructions", prompt: "What should change?" }),
286
+ }),
287
+ });
288
+
289
+ humanDecision({
290
+ audience: "operator",
291
+ choices,
292
+ request: ({ outputs }) => ({
293
+ title: "Approve plan",
294
+ subject: outputs.plan,
295
+ presentation: {
296
+ schema: "pi-workflows.decision-presentation.v1",
297
+ summary: "Review the implementation plan.",
298
+ blocks: [{ kind: "paragraph", text: "The plan is ready for approval." }],
299
+ },
300
+ }),
301
+ });
302
+ ```
303
+
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.
305
+
306
+ 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
+
308
+ See [Human decisions](HUMAN_DECISIONS.md) for channels, recovery, persistence, and plan approval.
309
+
269
310
  ### decision
270
311
 
271
312
  `decision` is sugar over `agent` for constrained choices. It builds the prompt
@@ -314,6 +355,43 @@ A missing case for the resolved value fails the run with a routing error. A
314
355
  node with no outgoing edge (or no matching failure route) ends the run:
315
356
  `completed` on success, `failed`/`timed_out`/`cancelled` otherwise.
316
357
 
358
+ ## Included workflows
359
+
360
+ Use `includeWorkflow()` to mount a standalone workflow under a parent name:
361
+
362
+ ```typescript
363
+ import repair from "./repair.workflow.js";
364
+
365
+ includes: {
366
+ repair: includeWorkflow(repair, {
367
+ input: ({ outputs }) => ({ issue: outputs.check }),
368
+ }),
369
+ },
370
+ ```
371
+
372
+ The child declares named exits:
373
+
374
+ ```typescript
375
+ exits: {
376
+ completed: { from: "finalize", validate: parseCompleted },
377
+ blocked: { from: "blocked", validate: parseBlocked },
378
+ },
379
+ ```
380
+
381
+ Enter through the mount and leave through `<mount>.<exit>`:
382
+
383
+ ```typescript
384
+ { from: "check", to: "repair" }
385
+ { from: "repair.completed", to: "check" }
386
+ { from: "repair.blocked", to: "finish" }
387
+ ```
388
+
389
+ Direct imports check child input and exit names in TypeScript. Use `includedResult(child, outputs.mount)` to recover the child's discriminated exit output without a cast. Dynamic discovered names, built-in references, and file paths are also supported. Every reference resolves before the run starts.
390
+
391
+ Child callbacks receive local input, outputs, results, and steps from their current invocation. Persisted node identities include the mount path. Re-entry starts with empty child-local state. Root and child step limits both apply. Internal entry and exit transitions do not consume those limits.
392
+
393
+ The run records every mounted source and a digest of the resolved graph. Resume refuses a changed child source. Source cycles are rejected before the run starts. See [Workflow composition](WORKFLOW_COMPOSITION.md) for typing, persistence, nesting, and viewer rules.
394
+
317
395
  ## Model workflow control
318
396
 
319
397
  The model sees one `workflow` tool. Its `action` field supports:
@@ -322,7 +400,7 @@ The model sees one `workflow` tool. Its `action` field supports:
322
400
  - `start` with a workflow name or path and structured input.
323
401
  - `status` for the active run or a supplied run ID.
324
402
  - `pause`, `resume`, and `cancel` for the active run.
325
- - `answer` with checkpoint input and an optional run ID.
403
+ - `answer` with ordinary checkpoint input and an optional run ID. Protected `humanDecision()` gates reject this model-facing action.
326
404
  - `update` for a non-completing update from the current agent attempt.
327
405
  - `submit` for the current workflow step contract.
328
406
 
@@ -333,6 +411,14 @@ The normal extension offers all actions. The headless RPC bridge offers only
333
411
  `update` and `submit`, so a workflow child cannot recursively control other
334
412
  runs.
335
413
 
414
+ ### Built-in planning and implementation
415
+
416
+ 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
+
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.
419
+
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.
421
+
336
422
  ### Built-in monitor
337
423
 
338
424
  The built-in `monitor` workflow turns a plain request for repeated checks into
@@ -341,12 +427,17 @@ one looping workflow run. Its input is:
341
427
  ```json
342
428
  {
343
429
  "task": "Check pull request 123",
344
- "stopWhen": "The pull request is merged or closed"
430
+ "stopWhen": "The pull request is merged or closed",
431
+ "repair": {
432
+ "authorized": true,
433
+ "scope": "the current repository"
434
+ }
345
435
  }
346
436
  ```
347
437
 
348
- The first check runs immediately. `everyMinutes` defaults to 30. Each accepted
349
- check must provide one concise report and choose `continue` or `stop`. The
438
+ 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.
439
+
440
+ `everyMinutes` defaults to 30. Each accepted check must provide one concise report and choose `continue`, `repair` when authorized, or `stop`. The
350
441
  runtime queues that report as a workflow notification with `triggerTurn:
351
442
  false`, so it does not cause an assistant reply. A check can also provide
352
443
  independent progress tracks. The regular Pi model running the check observes
@@ -0,0 +1,58 @@
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";
5
+
6
+ export default defineWorkflow({
7
+ name: "approved-plan-example",
8
+ startAt: "design",
9
+ maxSteps: 80,
10
+ 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,
39
+ audience: "operator",
40
+ };
41
+ },
42
+ }),
43
+ },
44
+ 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 }) }),
48
+ },
49
+ 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" },
57
+ ],
58
+ });