@osolmaz/pi-workflows 0.12.0 → 0.13.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 (257) hide show
  1. package/README.md +56 -31
  2. package/dist/builtins/autodoc.workflow.d.ts +4 -4
  3. package/dist/builtins/autoimplement.workflow.d.ts +797 -142
  4. package/dist/builtins/autoimplement.workflow.js +17 -105
  5. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  6. package/dist/builtins/autoplan.workflow.d.ts +126 -13
  7. package/dist/builtins/autoplan.workflow.js +200 -29
  8. package/dist/builtins/autoplan.workflow.js.map +1 -1
  9. package/dist/builtins/catalog.js +7 -5
  10. package/dist/builtins/catalog.js.map +1 -1
  11. package/dist/builtins/index.d.ts +4 -2
  12. package/dist/builtins/index.js +3 -1
  13. package/dist/builtins/index.js.map +1 -1
  14. package/dist/builtins/monitor.workflow.d.ts +56 -23
  15. package/dist/builtins/monitor.workflow.js +508 -329
  16. package/dist/builtins/monitor.workflow.js.map +1 -1
  17. package/dist/builtins/pi-agent-group.d.ts +72 -0
  18. package/dist/builtins/pi-agent-group.js +1087 -0
  19. package/dist/builtins/pi-agent-group.js.map +1 -0
  20. package/dist/builtins/plain-summary.workflow.d.ts +32 -0
  21. package/dist/builtins/plain-summary.workflow.js +134 -0
  22. package/dist/builtins/plain-summary.workflow.js.map +1 -0
  23. package/dist/builtins/plan-approval.workflow.d.ts +39 -5
  24. package/dist/builtins/plan-approval.workflow.js +92 -14
  25. package/dist/builtins/plan-approval.workflow.js.map +1 -1
  26. package/dist/builtins/plan-change.workflow.d.ts +449 -0
  27. package/dist/builtins/plan-change.workflow.js +256 -0
  28. package/dist/builtins/plan-change.workflow.js.map +1 -0
  29. package/dist/builtins/plan-presentation.js +2 -2
  30. package/dist/builtins/plan-presentation.js.map +1 -1
  31. package/dist/builtins/sanity-check.workflow.d.ts +50 -6
  32. package/dist/builtins/sanity-check.workflow.js +150 -28
  33. package/dist/builtins/sanity-check.workflow.js.map +1 -1
  34. package/dist/controllers/effects.d.ts +3 -2
  35. package/dist/controllers/effects.js +8 -1
  36. package/dist/controllers/effects.js.map +1 -1
  37. package/dist/controllers/index.d.ts +1 -1
  38. package/dist/controllers/index.js +1 -1
  39. package/dist/controllers/index.js.map +1 -1
  40. package/dist/controllers/manager.d.ts +1 -0
  41. package/dist/controllers/manager.js +41 -17
  42. package/dist/controllers/manager.js.map +1 -1
  43. package/dist/controllers/sqlite.d.ts +107 -85
  44. package/dist/controllers/sqlite.js +1611 -1486
  45. package/dist/controllers/sqlite.js.map +1 -1
  46. package/dist/controllers/store.d.ts +9 -8
  47. package/dist/controllers/store.js +1 -31
  48. package/dist/controllers/store.js.map +1 -1
  49. package/dist/controllers/types.d.ts +3 -0
  50. package/dist/controllers/workflow-engine-scheduler.d.ts +1 -1
  51. package/dist/controllers/workflow-engine-scheduler.js +3 -5
  52. package/dist/controllers/workflow-engine-scheduler.js.map +1 -1
  53. package/dist/controllers/workflows.d.ts +2 -2
  54. package/dist/controllers/workflows.js +11 -10
  55. package/dist/controllers/workflows.js.map +1 -1
  56. package/dist/extension/controller-host.js +2 -2
  57. package/dist/extension/controller-host.js.map +1 -1
  58. package/dist/extension/decision-channels.d.ts +4 -2
  59. package/dist/extension/decision-channels.js +333 -146
  60. package/dist/extension/decision-channels.js.map +1 -1
  61. package/dist/extension/executor.d.ts +9 -2
  62. package/dist/extension/executor.js +89 -2
  63. package/dist/extension/executor.js.map +1 -1
  64. package/dist/extension/herdr-viewer.d.ts +0 -1
  65. package/dist/extension/herdr-viewer.js +0 -4
  66. package/dist/extension/herdr-viewer.js.map +1 -1
  67. package/dist/extension/index.js +209 -127
  68. package/dist/extension/index.js.map +1 -1
  69. package/dist/extension/recorder.d.ts +3 -10
  70. package/dist/extension/recorder.js +13 -22
  71. package/dist/extension/recorder.js.map +1 -1
  72. package/dist/extension/session-events.d.ts +2 -2
  73. package/dist/extension/step-message.d.ts +7 -2
  74. package/dist/extension/step-message.js +94 -2
  75. package/dist/extension/step-message.js.map +1 -1
  76. package/dist/extension/widget.js +29 -3
  77. package/dist/extension/widget.js.map +1 -1
  78. package/dist/host/rpc-executor.d.ts +3 -0
  79. package/dist/host/rpc-executor.js +2 -0
  80. package/dist/host/rpc-executor.js.map +1 -1
  81. package/dist/host/runner.d.ts +3 -6
  82. package/dist/host/runner.js +83 -32
  83. package/dist/host/runner.js.map +1 -1
  84. package/dist/render/graph-render.js +15 -10
  85. package/dist/render/graph-render.js.map +1 -1
  86. package/dist/state/database.d.ts +35 -0
  87. package/dist/state/database.js +287 -0
  88. package/dist/state/database.js.map +1 -0
  89. package/dist/state/index.d.ts +4 -0
  90. package/dist/state/index.js +5 -0
  91. package/dist/state/index.js.map +1 -0
  92. package/dist/state/json.d.ts +6 -0
  93. package/dist/state/json.js +38 -0
  94. package/dist/state/json.js.map +1 -0
  95. package/dist/state/mutation.d.ts +57 -0
  96. package/dist/state/mutation.js +255 -0
  97. package/dist/state/mutation.js.map +1 -0
  98. package/dist/state/schema.d.ts +6 -0
  99. package/dist/state/schema.js +473 -0
  100. package/dist/state/schema.js.map +1 -0
  101. package/dist/viewer/cli.d.ts +2 -2
  102. package/dist/viewer/cli.js +142 -122
  103. package/dist/viewer/cli.js.map +1 -1
  104. package/dist/viewer/render.d.ts +4 -4
  105. package/dist/viewer/render.js +9 -23
  106. package/dist/viewer/render.js.map +1 -1
  107. package/dist/viewer/session-reducer.d.ts +1 -1
  108. package/dist/viewer/session-reducer.js +1 -1
  109. package/dist/viewer/tui.d.ts +3 -3
  110. package/dist/viewer/tui.js +15 -13
  111. package/dist/viewer/tui.js.map +1 -1
  112. package/dist/viewer/watch.d.ts +2 -5
  113. package/dist/viewer/watch.js +13 -13
  114. package/dist/viewer/watch.js.map +1 -1
  115. package/dist/workflows/catalog.js +7 -2
  116. package/dist/workflows/catalog.js.map +1 -1
  117. package/dist/workflows/composition.js +20 -2
  118. package/dist/workflows/composition.js.map +1 -1
  119. package/dist/workflows/decision-presentation.d.ts +1 -1
  120. package/dist/workflows/decision-presentation.js +51 -38
  121. package/dist/workflows/decision-presentation.js.map +1 -1
  122. package/dist/workflows/definition.d.ts +6 -2
  123. package/dist/workflows/definition.js +17 -0
  124. package/dist/workflows/definition.js.map +1 -1
  125. package/dist/workflows/engine.d.ts +7 -7
  126. package/dist/workflows/engine.js +196 -115
  127. package/dist/workflows/engine.js.map +1 -1
  128. package/dist/workflows/errors.d.ts +16 -3
  129. package/dist/workflows/errors.js +18 -3
  130. package/dist/workflows/errors.js.map +1 -1
  131. package/dist/workflows/human-decision.d.ts +45 -8
  132. package/dist/workflows/human-decision.js +641 -274
  133. package/dist/workflows/human-decision.js.map +1 -1
  134. package/dist/workflows/index.d.ts +5 -6
  135. package/dist/workflows/index.js +4 -5
  136. package/dist/workflows/index.js.map +1 -1
  137. package/dist/workflows/progress.d.ts +1 -0
  138. package/dist/workflows/progress.js +15 -3
  139. package/dist/workflows/progress.js.map +1 -1
  140. package/dist/workflows/schema.js +27 -1
  141. package/dist/workflows/schema.js.map +1 -1
  142. package/dist/workflows/store.d.ts +83 -103
  143. package/dist/workflows/store.js +907 -951
  144. package/dist/workflows/store.js.map +1 -1
  145. package/dist/workflows/types.d.ts +88 -126
  146. package/docs/CONTROLLERS.md +10 -8
  147. package/docs/DEFERRED_TURNS.md +2 -2
  148. package/docs/DESIGN_PHILOSOPHY.md +4 -2
  149. package/docs/HUMAN_DECISIONS.md +36 -47
  150. package/docs/HUMAN_DECISION_PRESENTATIONS.md +14 -24
  151. package/docs/MONITOR.md +179 -237
  152. package/docs/SQLITE_STATE.md +219 -0
  153. package/docs/WORKFLOW_COMPOSITION.md +27 -26
  154. package/docs/WORKFLOW_STEP_MESSAGES.md +20 -18
  155. package/docs/WORKFLOW_UPDATES.md +10 -9
  156. package/docs/development.md +9 -9
  157. package/docs/live-replay-protocol.md +28 -37
  158. package/docs/plans/2026-08-16-workflow-updates-plan.md +1 -1
  159. package/docs/plans/2026-08-19-human-decision-gates-plan.md +1 -1
  160. package/docs/plans/2026-08-21-autoimplement-timeout-fallback-plan.md +1 -1
  161. package/docs/plans/2026-08-21-plan-change-approval-policy-plan.md +322 -0
  162. package/docs/plans/2026-08-21-sanity-check-plan.md +217 -95
  163. package/docs/plans/2026-08-22-goal-finishing-monitor-plan.md +200 -0
  164. package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +701 -0
  165. package/docs/plans/2026-08-23-sqlite-state-plan.md +970 -0
  166. package/docs/plans/replayable-run-bundles-implementation-plan.md +2 -2
  167. package/docs/plans/session-event-replay-implementation-plan.md +1 -1
  168. package/docs/plans/tui-viewer-implementation-plan.md +1 -1
  169. package/docs/session-event-journal.md +63 -434
  170. package/docs/tui-viewer.md +13 -15
  171. package/docs/workflows.md +127 -53
  172. package/examples/workflows/approved-plan.workflow.ts +19 -46
  173. package/examples/workflows/plain-summary.workflow.ts +1 -0
  174. package/herdr-plugin.toml +1 -1
  175. package/package.json +7 -7
  176. package/plugins/herdr/viewer.mjs +1 -11
  177. package/schemas/human-decision-accepted-v1.schema.json +15 -3
  178. package/schemas/human-decision-continuation-v1.schema.json +10 -1
  179. package/schemas/human-decision-delivery-v1.schema.json +8 -0
  180. package/schemas/human-decision-receipt-v1.schema.json +8 -0
  181. package/schemas/human-decision-request-v1.schema.json +24 -4
  182. package/skills/autoimplement/SKILL.md +27 -0
  183. package/skills/autoplan/SKILL.md +5 -4
  184. package/skills/monitor/SKILL.md +91 -135
  185. package/skills/pi-workflows/SKILL.md +9 -8
  186. package/skills/sanity-check/SKILL.md +44 -0
  187. package/src/builtins/autoimplement.workflow.ts +19 -118
  188. package/src/builtins/autoplan.workflow.ts +290 -40
  189. package/src/builtins/catalog.ts +7 -5
  190. package/src/builtins/index.ts +18 -1
  191. package/src/builtins/monitor.workflow.ts +673 -382
  192. package/src/builtins/pi-agent-group.ts +1407 -0
  193. package/src/builtins/plain-summary.workflow.ts +185 -0
  194. package/src/builtins/plan-approval.workflow.ts +157 -24
  195. package/src/builtins/plan-change.workflow.ts +321 -0
  196. package/src/builtins/plan-presentation.ts +2 -2
  197. package/src/builtins/sanity-check.workflow.ts +248 -48
  198. package/src/controllers/effects.ts +7 -0
  199. package/src/controllers/index.ts +0 -6
  200. package/src/controllers/manager.ts +72 -32
  201. package/src/controllers/sqlite.ts +2420 -1948
  202. package/src/controllers/store.ts +17 -38
  203. package/src/controllers/types.ts +3 -0
  204. package/src/controllers/workflow-engine-scheduler.ts +4 -8
  205. package/src/controllers/workflows.ts +45 -21
  206. package/src/extension/controller-host.ts +1 -2
  207. package/src/extension/decision-channels.ts +456 -203
  208. package/src/extension/executor.ts +105 -2
  209. package/src/extension/herdr-viewer.ts +0 -5
  210. package/src/extension/index.ts +251 -151
  211. package/src/extension/recorder.ts +13 -22
  212. package/src/extension/session-events.ts +2 -2
  213. package/src/extension/step-message.ts +106 -4
  214. package/src/extension/widget.ts +32 -5
  215. package/src/host/rpc-executor.ts +4 -0
  216. package/src/host/runner.ts +93 -41
  217. package/src/render/graph-render.ts +12 -6
  218. package/src/state/database.ts +375 -0
  219. package/src/state/index.ts +29 -0
  220. package/src/state/json.ts +42 -0
  221. package/src/state/mutation.ts +435 -0
  222. package/src/state/schema.ts +475 -0
  223. package/src/viewer/cli.ts +151 -144
  224. package/src/viewer/render.ts +19 -30
  225. package/src/viewer/session-reducer.ts +1 -1
  226. package/src/viewer/tui.ts +18 -17
  227. package/src/viewer/watch.ts +13 -17
  228. package/src/workflows/catalog.ts +7 -2
  229. package/src/workflows/composition.ts +21 -3
  230. package/src/workflows/decision-presentation.ts +56 -43
  231. package/src/workflows/definition.ts +36 -3
  232. package/src/workflows/engine.ts +228 -130
  233. package/src/workflows/errors.ts +27 -3
  234. package/src/workflows/human-decision.ts +921 -349
  235. package/src/workflows/index.ts +20 -30
  236. package/src/workflows/progress.ts +18 -3
  237. package/src/workflows/schema.ts +36 -1
  238. package/src/workflows/store.ts +1300 -1163
  239. package/src/workflows/types.ts +100 -137
  240. package/dist/builtins/sanity-check-session.d.ts +0 -17
  241. package/dist/builtins/sanity-check-session.js +0 -168
  242. package/dist/builtins/sanity-check-session.js.map +0 -1
  243. package/dist/workflows/artifacts.d.ts +0 -40
  244. package/dist/workflows/artifacts.js +0 -155
  245. package/dist/workflows/artifacts.js.map +0 -1
  246. package/dist/workflows/migrate-sources.d.ts +0 -42
  247. package/dist/workflows/migrate-sources.js +0 -133
  248. package/dist/workflows/migrate-sources.js.map +0 -1
  249. package/docs/run-bundles.md +0 -481
  250. package/schemas/human-decision-accepted-v2.schema.json +0 -50
  251. package/schemas/human-decision-delivery-v2.schema.json +0 -36
  252. package/schemas/human-decision-receipt-v2.schema.json +0 -39
  253. package/schemas/human-decision-request-v2.schema.json +0 -69
  254. package/schemas/human-decision-resolution-v2.schema.json +0 -27
  255. package/src/builtins/sanity-check-session.ts +0 -205
  256. package/src/workflows/artifacts.ts +0 -188
  257. package/src/workflows/migrate-sources.ts +0 -178
@@ -159,11 +159,11 @@ Mutation policy stays in deterministic effect drivers. Agent workflows return fi
159
159
 
160
160
  ## Child workflows
161
161
 
162
- `ctx.workflows.ensure()` creates or finds a workflow run by a stable request key and input fingerprint. Repeated reconciliations find the same active or completed request. A changed input must use a new key. The controller store reserves and saves each attempt's run ID before the scheduler starts it, so restart recovery can find the bundle.
162
+ `ctx.workflows.ensure()` creates or finds a workflow run by a stable request key and input fingerprint. Repeated reconciliations find the same active or completed request. A changed input must use a new key. An asynchronous child completion validates the reserved request and run IDs through a separate scheduler-completion command; it never reuses the controller claim that launched the child. The controller transaction reserves and saves each attempt's run ID before the scheduler starts it, so recovery can find the run row.
163
163
 
164
- A child run is one immutable attempt. Its existing run bundle remains the execution record. The parent resource points to the current run, and workflow completion enqueues the parent key. A host restart can record an abandoned attempt and create another attempt for the same stable request.
164
+ A child run is one immutable attempt. Its existing SQLite run state remains the execution record. The parent resource points to the current run, and workflow completion enqueues the parent key. A host restart can record an abandoned attempt and create another attempt for the same stable request.
165
165
 
166
- The workflow scheduler records an abandoned running bundle as `failed` with a final `run_interrupted` trace event. The controller store treats that child attempt as `interrupted`, so the next reconciliation starts another immutable attempt. Compute work can run again, while consequential external actions belong in the effect API so recovery observes them before retrying.
166
+ The workflow scheduler records an abandoned running run as `failed` with a final `run_interrupted` event. The controller store treats that child attempt as `interrupted`, so the next reconciliation starts another immutable attempt. Compute work can run again, while consequential external actions belong in the effect API so recovery observes them before retrying.
167
167
 
168
168
  ## Deletion and cleanup
169
169
 
@@ -183,7 +183,7 @@ The Pi extension starts local workers during `session_start` and closes them dur
183
183
 
184
184
  Every reconciliation emits structured records with the controller name, resource key, generation, reconcile ID, outcome and duration, plus the requeue reason. Effect state changes and child workflow links are also recorded. Logs and viewer projections remain secondary to the resource and effect stores.
185
185
 
186
- `pi-workflows controllers` lists resources and their current readiness condition. `pi-workflows controller <controller> <key>` prints one resource together with its effects, child workflows, and recent events. Existing run views continue to read immutable bundles.
186
+ `pi-workflows controllers` lists resources and their current readiness condition. `pi-workflows controller <controller> <key>` prints one resource together with its effects, child workflows, and recent events. Run views read the same database through query-only connections.
187
187
 
188
188
  ## Safety rules
189
189
 
@@ -196,7 +196,7 @@ A production controller must follow these rules:
196
196
  - Reconcile again after each consequential external effect.
197
197
  - Keep model output separate from mutation authority.
198
198
  - Bound worker counts and retry rates. Also bound timeouts and stored payload sizes.
199
- - Redact credentials and private provider responses from logs and run bundles.
199
+ - Redact credentials and private provider responses from logs and SQLite runs.
200
200
 
201
201
  ## Package and Pi integration
202
202
 
@@ -204,11 +204,13 @@ The controller API is exported from `@osolmaz/pi-workflows/controllers`. Control
204
204
 
205
205
  The implementation uses documented Pi extension APIs only. Commands and tools use `registerCommand` and `registerTool`. Session lifecycle uses `session_start` and `session_shutdown`. Workflow prompts use `sendUserMessage`, while status uses `setWidget` and `setStatus`.
206
206
 
207
- The `/controller` command lists and inspects resources, applies specs, requests reconciliation or deletion, and starts or stops local workers. Stopping workers records an active child as interrupted, so starting workers again can create another attempt. Project stores live under `~/.pi/agent/workflows/controllers/projects/<scope>/controller.sqlite`. The scope is a hash of the canonical project directory. `PI_WORKFLOWS_CONTROLLER_DIR` overrides the controller root while preserving project scopes.
207
+ The `/controller` command lists and inspects resources, applies specs, requests reconciliation or deletion, and starts or stops local workers. Stopping workers records an active child as interrupted, so a later worker can create another attempt.
208
208
 
209
- The same store also backs the standalone host (`pi-workflows host`). The host reconciles controllers without a Pi session and claims parked interactive runs from the `workflow_run_queue` table. Conversation children execute in headless `pi --mode rpc` sessions. A Pi session and the host can share one store safely because claims and compare-and-swap writes arbitrate, but run only one set of controller workers at a time to avoid competing work. The host takes an advisory lock against other hosts; the embedded runner in Pi does not take it.
209
+ Controller resources use the canonical [SQLite state](SQLITE_STATE.md) database. `projects` separates repository-local resources by canonical project path. Controller claims use the shared lease generation, token, expiry, and expected resource revision. Effects and child workflows use the shared transactional outbox and deterministic receipts.
210
210
 
211
- Normal workflow prompts, tool calls, and replies remain part of the Pi session. Controller resources, queue rows, and effects live in the controller store. No Pi internal type, private API, or persistent Pi schema changes.
211
+ The same database backs the standalone host (`pi-workflows host`). The host reconciles controllers without a Pi session and claims parked interactive runs from `run_queue`. Conversation children execute in headless `pi --mode rpc` sessions. A Pi session and the host can share the database safely because SQLite transactions and durable leases arbitrate ownership, but only one owner can mutate a resource at a time.
212
+
213
+ Normal workflow prompts, tool calls, and replies remain part of the Pi session. No Pi internal type, private API, or persistent Pi schema changes.
212
214
 
213
215
  ## Exclusions
214
216
 
@@ -246,7 +246,7 @@ Store leases prevent concurrent processes from sending the same resolution. Bran
246
246
 
247
247
  ## Claim transfer
248
248
 
249
- Claim loss stops the old runner's work and all fenced run-bundle writes. The intent stays pending and fallback-ineligible while another runner can resume the run.
249
+ Claim loss stops the old runner's work and all fenced SQLite writes. The intent stays pending and fallback-ineligible while another runner can resume the run.
250
250
 
251
251
  The new runner's next workflow prompt can resolve the intent. If durable state later proves a terminal outcome with no natural successor, terminal handling makes the intent eligible for fallback to its target session.
252
252
 
@@ -281,7 +281,7 @@ This is an alpha hard cutover.
281
281
  - Add `workflow_turn_intents` to the existing database.
282
282
  - Remove the launch-trigger runtime path.
283
283
  - Add no v2 schema, compatibility reader, dual write, alias, or feature flag.
284
- - Keep historical terminal run bundles readable because their contract does not change.
284
+ - Keep historical terminal SQLite runs readable because their contract does not change.
285
285
 
286
286
  ## Conformance
287
287
 
@@ -16,7 +16,7 @@ Shared behavior should usually start as a data format or pure helper used by a w
16
16
 
17
17
  ## Explicit behavior
18
18
 
19
- The graph should show what runs and where it can go. Agent results should be structured and validated. Commands and other side effects should be declared by the workflow author.
19
+ The graph should show what runs and where it can go. Use structured, validated agent results when the graph must inspect fields or choose a route. Use a declared assistant-message result when exact visible text belongs inside the graph. Commands and other side effects should be declared by the workflow author.
20
20
 
21
21
  Avoid hidden polling, implicit retries, automatic command generation, and state changes that the graph cannot explain.
22
22
 
@@ -30,7 +30,9 @@ A notification should not start a model turn unless the workflow explicitly requ
30
30
 
31
31
  Runs should survive interruption and remain safe to resume. Save the run input and every accepted output. Save attempts and events along with enough evidence of side effects for replay and diagnosis.
32
32
 
33
- The trace is the record of what happened. Viewers and state projections should derive their answers from that record instead of creating a second source of truth.
33
+ Immutable SQLite events are the record of what happened. Domain rows are current projections written in the same transaction. Viewers derive their answers from those facts instead of creating another source of truth.
34
+
35
+ Reading shared state never gives mutation authority. Every durable write checks its actor, expected resource revision, and current lease generation when ownership is required. Follow-up work uses deterministic effects and idempotent receipts so partial failure can converge safely.
34
36
 
35
37
  ## Boundaries
36
38
 
@@ -60,6 +60,10 @@ export default defineWorkflow({
60
60
  approve: humanDecision({
61
61
  audience: "operator",
62
62
  choices: planChoices,
63
+ onTimeout: {
64
+ afterMs: 10 * 60_000,
65
+ response: { choice: "continue" },
66
+ },
63
67
  request: ({ outputs }) => ({
64
68
  title: "Approve the implementation plan",
65
69
  subject: outputs.propose,
@@ -106,6 +110,8 @@ type PlanDecision =
106
110
 
107
111
  `humanDecisionEdge()` requires one destination for every choice. A missing or extra case is a TypeScript error. Runtime validation applies the same choice and input contract before an answer can win.
108
112
 
113
+ `onTimeout` is optional. It supplies a positive duration and a response that satisfies the same choice contract. The request stores the resulting absolute deadline and validated response. When no human answer wins before the deadline, the decision recovery owner applies that response with `timeout` provenance. Omitting `onTimeout` keeps an indefinite wait. A request cannot combine `onTimeout` with a separate `expiresAt` value.
114
+
109
115
  ## Checkpoint behavior
110
116
 
111
117
  A human decision still has `nodeType: "checkpoint"`. The helper adds a human decision contract to that checkpoint.
@@ -118,9 +124,7 @@ When the engine reaches the node, it:
118
124
  4. asks the configured channels to deliver it; and
119
125
  5. parks the run in `waiting` state.
120
126
 
121
- When a valid answer is accepted, the continuation keeps the workflow's original input. The human answer becomes the checkpoint node's output in the continuation. Existing checkpoints keep their current behavior, where `/workflow answer` supplies the continuation input.
122
-
123
- This distinction preserves old workflow definitions and run bundles.
127
+ When a valid answer is accepted, the continuation keeps the workflow's original input. The human answer becomes the checkpoint node's output in the continuation. Ordinary checkpoints keep their current behavior, where `/workflow answer` supplies the continuation input.
124
128
 
125
129
  ## Request and response contracts
126
130
 
@@ -136,12 +140,12 @@ A new decision request contains:
136
140
  - the logical audience;
137
141
  - the complete choice contract;
138
142
  - the canonical request digest;
139
- - an optional expiry rule; and
143
+ - an optional absolute deadline and automatic response; and
140
144
  - the creation time.
141
145
 
142
- The presentation is an explicit display allowlist. A channel does not receive the subject and cannot infer operator text from it. The request digest binds the subject, visible presentation, title, revision, choices, and input prompts. Each choice has a stable ID and may have no input or one validated text input contract.
146
+ The presentation is an explicit display allowlist. A channel does not receive the subject and cannot infer operator text from it. The request digest binds the subject, visible presentation, title, revision, choices, input prompts, deadline, and automatic response. Each choice has a stable ID and may have no input or one validated text input contract.
143
147
 
144
- The former `body` form remains available for existing workflow definitions. It creates a v1 request and uses a deterministic readable compatibility formatter. Oversized historical bodies receive a bounded readable prefix and an explicit omission notice with the full body digest and size. V1 request bytes and digests do not change.
148
+ Human decisions use only `pi-workflows.human-decision-request.v1`. The former `body` request and all human-decision `v2` records are invalid. This is an alpha hard cutover: old waiting runs and decision state must be reset rather than migrated or reinterpreted.
145
149
 
146
150
  A submitted response contains:
147
151
 
@@ -210,7 +214,7 @@ pi-workflows keeps credential references in a separate private file. A Telegram
210
214
  }
211
215
  ```
212
216
 
213
- Run `/workflow-channel setup` in Pi TUI to verify and install a profile, `/workflow-channel status` to inspect whether profiles are active, and `/workflow-channel reload` after a private configuration change. Setup asks for the token file path, not the token. It updates mode-`0600` private files and does not copy the token. Token values never enter source files, workflow inputs, run bundles, logs, child environments, or model-visible tool results.
217
+ Run `/workflow-channel setup` in Pi TUI to verify and install a profile, `/workflow-channel status` to inspect whether profiles are active, and `/workflow-channel reload` after a private configuration change. Setup asks for the token file path, not the token. It updates mode-`0600` private files and does not copy the token. Token values never enter source files, workflow inputs, SQLite runs, logs, child environments, or model-visible tool results.
214
218
 
215
219
  The same Unix account can read a local credential file. This design prevents accidental propagation, not a hostile same-account process. A separately owned connector can implement the same channel interface later if stronger isolation becomes necessary.
216
220
 
@@ -250,29 +254,28 @@ A choice without input submits from its button. A text choice such as `replan` w
250
254
  4. the channel verifies the numeric user ID, chat ID, reply message ID, decision ID, and request digest; and
251
255
  5. the exact received text becomes `input.instructions`.
252
256
 
253
- The adapter does not infer a choice from ordinary chat text. Callback payloads contain short opaque IDs because Telegram limits callback data. The private channel store maps each opaque ID to the full decision request.
257
+ The adapter does not infer a choice from ordinary chat text. Callback payloads contain short opaque IDs because Telegram limits callback data. Private local SQLite rows map each opaque ID to the validated decision presentation. Credentials remain outside the database.
254
258
 
255
- Telegram permits one long-polling consumer for a bot profile. Active Pi processes use a shared lease so one process owns polling and the others use the same private channel state. The lease owner can accept a verified reply, but only the Pi session that owns the waiting run creates its continuation. Active sessions inspect the durable accepted-answer fence and recover their own continuation. If no Pi process is running, Telegram delivery and reply collection resume when Pi starts again. Running an always-on service is outside this design.
259
+ Telegram permits one long-polling consumer for a bot profile. Active Pi processes use the shared SQLite lease so one process owns polling and the others observe the same channel state. The lease owner can accept a verified reply, but only the Pi session that owns the waiting run creates its continuation. Active sessions inspect the durable accepted-answer fence and recover their own continuation. If no Pi process is running, Telegram delivery and reply collection resume when Pi starts again. Running an always-on service is outside this design.
256
260
 
257
261
  The Bot API does not provide an idempotency key for `sendMessage`. pi-workflows therefore writes a delivery intent before sending and never blindly retries an ambiguous send. A timed-out send is recorded as `unknown`; Pi remains available and an operator can request another delivery. This avoids automatic duplicate messages while keeping decision acceptance exactly once.
258
262
 
259
263
  ## Durable decision records
260
264
 
261
- Decision records live next to workflow run bundles under the pi-workflows state root. They are additive and linked by run ID. A decision directory contains immutable records for:
265
+ Human decisions use the canonical [SQLite state](SQLITE_STATE.md) database:
262
266
 
263
- - the request;
264
- - channel delivery intents and results;
265
- - answer attempts;
266
- - the atomic accepted-or-cancelled resolution;
267
- - the accepted answer or cancellation detail;
268
- - channel settlement results; and
269
- - the continuation request and result.
267
+ - `human_decisions` stores each immutable request;
268
+ - `human_decision_submissions` records human, policy, channel, and control candidates;
269
+ - `human_decision_resolutions` stores the one accepted-or-cancelled winner;
270
+ - `continuations` links the parent and continuation runs;
271
+ - `effects` records parent settlement, continuation, and presentation settlement work; and
272
+ - channel tables store delivery and settlement receipts.
270
273
 
271
- The resolution record uses a no-replace create. The first valid acceptance, cancellation, or expiry writer wins. Acceptance then materializes the matching accepted-answer detail. Cancellation or expiry materializes cancellation detail. A retry with the same response and idempotency key adopts the existing answer. A conflicting answer receives an `already decided` result.
274
+ A valid human answer, eligible timeout policy, explicit cancellation, or no-default expiry competes for the same resolution primary key. The winning transaction records the immutable resolution, audit event, and required effects together. A retry adopts the existing matching result. A conflicting or late answer receives the durable winner.
272
275
 
273
- The continuation run ID is derived from the accepted decision ID. Recovery adopts an existing matching continuation or creates it once. A crash after answer acceptance cannot run the next workflow step twice. The continuation run carries a redacted receipt with the decision ID, request digest, gate node ID, choice, acceptance time, and answer digest. Actor, channel, event, and idempotency provenance stays only in the private decision records and never enters the run bundle or model context.
276
+ A deadline with a validated default response is timeout-policy acceptance. It cannot become expiry cancellation. No-default expiry can cancel. Automatic policy and continuation creation require the current run owner's token and lease generation. A verified channel can submit a human candidate without gaining run ownership.
274
277
 
275
- SQLite may index pending decisions and channel leases, but immutable decision files remain the source of truth. The index is disposable and rebuildable.
278
+ The continuation run ID is derived from the decision ID. The owner adopts an existing matching continuation or creates it once. The continuation record and redacted receipt carry the resolution provenance, decision ID, request digest, gate node ID, choice, acceptance time, and answer digest. Human actor, channel, event, and idempotency details remain private and do not enter model-visible status output.
276
279
 
277
280
  ## Planning workflow composition
278
281
 
@@ -286,33 +289,19 @@ pi-workflows keeps solution choice, documentation, and implementation in separat
286
289
 
287
290
  `autoimplement` first finds the clear existing plan in its input, the conversation, or referenced canonical documents. It blocks when no clear plan exists. A caller can bypass discovery and autodoc only by supplying both the explicit plan and a `documentation` receipt whose plan digest matches it. A plan without that current-document evidence enters autodoc so the canonical documents are inspected and adopted or updated. The absence of a structured `plan` input never authorizes `autoplan`.
288
291
 
289
- If later implementation, verification, review, comments, or CI evidence invalidates the plan, autoimplement runs `autoplan`, sends the revised plan through `autodoc`, and then resumes implementation. An optional approval policy inserts `plan-approval` after the revised documentation.
292
+ If later implementation, verification, review, comments, or CI evidence invalidates the plan, Autoimplement enters the shared plan-change workflow. That workflow runs Autoplan, Autodoc, plan approval, and bounded exact-text replanning. Monitor uses the same workflow for each new repair plan and passes the selected plan into Autoimplement without a second decision.
290
293
 
291
294
  ## Reusable plan approval workflow
292
295
 
293
- pi-workflows ships a typed `plan-approval` workflow built on `humanDecision()`. Its input contains the documented plan, plan digest, audience, and display summary. It has three named exits:
294
-
295
- - `continue`, with the approval receipt;
296
- - `stop`, with the stop receipt; and
297
- - `replan`, with the exact instructions and receipt.
298
-
299
- A parent can include it without copying prompts or channel handling:
296
+ pi-workflows ships a typed `plan-approval` workflow built on `humanDecision()`. Its policy has three modes:
300
297
 
301
- ```typescript
302
- includes: {
303
- approval: includeWorkflow(planApproval, {
304
- input: ({ outputs }) => ({
305
- plan: outputs.devise.plan,
306
- planDigest: outputs.devise.planDigest,
307
- audience: "operator",
308
- }),
309
- }),
310
- },
311
- ```
298
+ - `auto` asks the configured audience and continues after the configured deadline. It defaults to audience `operator` and 10 minutes.
299
+ - `required` waits for an explicit human answer.
300
+ - `skip` creates no human decision and continues immediately.
312
301
 
313
- A `replan` exit returns to `autoplan` with the previous plan and the exact human instructions as new evidence. The revised plan passes through `autodoc`, receives a new digest, and enters a new approval decision. Step limits bound repeated replanning.
302
+ The workflow has `continue`, `stop`, and exact-text `replan` exits. Continue reports `human`, `timeout`, or `skipped` provenance. Stop and replan always require a human answer.
314
303
 
315
- Monitor repair composes `autoplan`, `autodoc`, optional `plan-approval`, `autoimplement`, and a fresh target check. Autoimplement can mount the same approval workflow after evidence-driven redesign. Existing behavior stays unchanged when no approval policy is present.
304
+ The internal plan-change workflow composes Autoplan, Autodoc, and plan approval once. It owns the replan count, plan digest, and positive revision. Autoimplement and Monitor include this workflow instead of copying approval routes. A plan supplied by the caller or already selected by Monitor bypasses another decision. Only a changed plan digest enters the gate.
316
305
 
317
306
  ## Recovery and cancellation
318
307
 
@@ -325,27 +314,27 @@ Recovery follows these rules:
325
314
  - ambiguous Telegram sends are not retried automatically;
326
315
  - duplicate channel updates are harmless;
327
316
  - stale responses are rejected;
328
- - one accepted response creates one continuation;
329
- - the winning channel dismisses any pending Pi dialog;
317
+ - one human or timeout response creates one continuation;
318
+ - the winning human answer or timeout policy dismisses any pending Pi dialog;
330
319
  - confirmed channel settlement is adopted without another remote call;
331
320
  - failed channel settlement has a bounded retry count and cannot create an unbounded record loop; and
332
321
  - cancellation resolves the owned waiting decision from durable state, even after restart, closes pending views, and prevents a later answer from continuing the run.
333
322
 
334
- A decision with no available channel remains waiting and reports the configuration problem. It does not silently continue or choose a default.
323
+ A required decision with no available channel remains waiting and reports the configuration problem. An automatic decision does not need a channel to apply its saved response after the deadline. A skipped plan policy creates no decision.
335
324
 
336
325
  ## Compatibility
337
326
 
338
- Ordinary checkpoints, their continuation input behavior, and existing run bundles remain unchanged. V2 requests, accepted records, receipts, resolutions, and multipart delivery records are additive. Older viewers ignore them. Updated viewers label a human decision as a checkpoint, show the readable presentation and its fingerprint, and keep the canonical subject separate. Private channel configuration and transport identifiers remain hidden.
327
+ This alpha change updates the current request, accepted-result, receipt, resolution, continuation, and snapshot contracts in place. Old active runs refuse resume through normal source and definition identity checks. There is no compatibility reader, migration, dual path, or new schema generation. Updated viewers label a human decision as a checkpoint, show its deadline and automatic action when present, and keep the canonical subject separate. Private channel configuration and transport identifiers remain hidden.
339
328
 
340
329
  The engine remains independent from Pi and Telegram. Core code owns decision contracts, validation, durable acceptance, and continuation. The Pi extension owns UI and channel lifecycle. The Telegram adapter owns Bot API translation. Workflow definitions own only the question, choices, audience, and routes.
341
330
 
342
331
  ## Contract impact
343
332
 
344
333
  - **Session state:** Pi records normal workflow messages and interactive decision results.
345
- - **Other persistent data:** additive decision records, a rebuildable private channel index, and private channel configuration.
334
+ - **Other persistent data:** decision requests and resolutions can carry a deadline, automatic response, and resolution provenance in the existing decision store. The private channel index remains rebuildable.
346
335
  - **Pi internals:** none.
347
336
  - **Public Pi API:** documented extension lifecycle and UI methods only.
348
- - **Public pi-workflows API:** typed human choices, `humanDecision()`, `humanDecisionEdge()`, the channel interface, and the `plan-approval` workflow.
337
+ - **Public pi-workflows API:** typed human choices, `humanDecision().onTimeout`, `humanDecisionEdge()`, the channel interface, the plan approval policy, and the shared plan-change workflow.
349
338
 
350
339
  ## Verification requirements
351
340
 
@@ -1,8 +1,8 @@
1
1
  # Human decision presentations
2
2
 
3
- This contract is implemented. New human decision requests separate their canonical
4
- subject from the complete readable message shown to an operator. Historical requests
5
- that use `body` remain compatible through a deterministic readable formatter.
3
+ This contract is implemented. Human decision requests separate their canonical
4
+ subject from the complete readable message shown to an operator. Human decisions use
5
+ one v1 contract with no legacy body form or parallel schema version.
6
6
  The implementation plan is in
7
7
  [the human decision presentations plan](plans/2026-08-19-human-decision-presentations-plan.md).
8
8
 
@@ -52,7 +52,7 @@ not see `subject` unless the workflow explicitly copies selected text into
52
52
 
53
53
  ## Request contract
54
54
 
55
- A new request uses `pi-workflows.human-decision-request.v2`. It contains:
55
+ A request uses `pi-workflows.human-decision-request.v1`. It contains:
56
56
 
57
57
  | Field | Required | Meaning |
58
58
  | -------------------- | -------- | ----------------------------------------------- |
@@ -255,25 +255,15 @@ It omits absent sections. It never serializes the plan object. `plan-approval`,
255
255
  `monitor`, `autoplan`, `autodoc`, and `autoimplement` reuse this presenter where
256
256
  they ask a person to approve a plan.
257
257
 
258
- ## Compatibility
258
+ ## Alpha cutover
259
259
 
260
- Current v1 requests remain immutable.
260
+ The human-decision request, accepted result, receipt, delivery, and resolution use one
261
+ v1 schema family. The current structured subject and explicit presentation contract
262
+ replaced the former body request and parallel v2 records in place.
261
263
 
262
- - A v1 string body becomes one readable paragraph at delivery time.
263
- - A v1 object body uses a deterministic compatibility formatter. It converts
264
- stable key order into readable labels and fields with sections and lists.
265
- - An oversized v1 body remains answerable. The formatter keeps a bounded readable
266
- prefix and adds an explicit omission notice with the full body digest and size.
267
- It never hides omitted content behind an ellipsis.
268
- - The compatibility formatter reads only the historical `body`, which was
269
- already the display field. It never reads a new structured subject.
270
- - V1 request bytes and digests do not change.
271
- - Pending and accepted v1 decisions continue to use v1 validation and digest
272
- rules.
273
- - New preferred authoring emits v2. The existing body form remains a deprecated
274
- compatibility overload until a separate removal is approved.
275
-
276
- No migration rewrites run bundles or decision records.
264
+ Old waiting runs and human-decision state are incompatible. The runtime fails with a
265
+ reset instruction instead of reading, migrating, or silently reinterpreting them.
266
+ There is no compatibility reader, dual path, alias, or migration.
277
267
 
278
268
  ## Privacy and security
279
269
 
@@ -311,10 +301,10 @@ npx slophammer-ts@latest check . --only ts.dependency-boundaries-required
311
301
  npx -y @simpledoc/simpledoc check
312
302
  ```
313
303
 
314
- Tests must cover schema validation, canonical digests, readable legacy bodies,
304
+ Tests must cover schema validation, canonical digests, structured-value formatting,
315
305
  plan rendering, Pi and Telegram content parity, unsafe text, Unicode, multipart
316
- delivery, ambiguous sends, recovery, stale answers, viewer output, and proof
317
- that no channel serializes a subject as JSON.
306
+ delivery, ambiguous sends, recovery, stale answers, viewer output, incompatible-state
307
+ failure, and proof that no channel serializes a subject as JSON.
318
308
 
319
309
  ## Boundaries
320
310